datagenerator.meanderpy
1import numpy as np 2import matplotlib.pyplot as plt 3import scipy.interpolate 4from scipy.spatial import distance 5from scipy import ndimage 6from PIL import Image, ImageDraw 7from skimage import measure 8from skimage import morphology 9 10# from matplotlib.colors import LinearSegmentedColormap 11# import time, sys 12import numba 13import matplotlib.colors as mcolors 14from matplotlib import cm 15from tqdm import trange 16 17 18class Channel: 19 """class for Channel objects""" 20 21 def __init__(self, x, y, z, W, D): 22 """Initialize Channel object 23 24 :param x: x-coordinate of centerline 25 :param y: y-coordinate of centerline 26 :param z: z-coordinate of centerline 27 :param W: channel width 28 :param D: channel depth""" 29 30 self.x = x 31 self.y = y 32 self.z = z 33 self.W = W 34 self.D = D 35 36 37class Cutoff: 38 """class for Cutoff objects""" 39 40 def __init__(self, x, y, z, W, D): 41 """Initialize Cutoff object 42 43 :param x: x-coordinate of centerline 44 :param y: y-coordinate of centerline 45 :param z: z-coordinate of centerline 46 :param W: channel width 47 :param D: channel depth""" 48 49 self.x = x 50 self.y = y 51 self.z = z 52 self.W = W 53 self.D = D 54 55 56class ChannelBelt3D: 57 """class for 3D models of channel belts""" 58 59 def __init__(self, model_type, topo, strat, facies, facies_code, dx, channels): 60 """initialize ChannelBelt3D object 61 62 :param model_type: type of model to be built; can be either 'fluvial' or 'submarine' 63 :param topo: set of topographic surfaces (3D numpy array) 64 :param strat: set of stratigraphic surfaces (3D numpy array) 65 :param facies: facies volume (3D numpy array) 66 :param facies_code: dictionary of facies codes, e.g. {0:'oxbow', 1:'point bar', 2:'levee'} 67 :param dx: gridcell size (m) 68 :param channels: list of channel objects that form 3D model""" 69 70 self.model_type = model_type 71 self.topo = topo 72 self.strat = strat 73 self.facies = facies 74 self.facies_code = facies_code 75 self.dx = dx 76 self.channels = channels 77 78 def plot_xsection(self, xsec, colors, ve): 79 """method for plotting a cross section through a 3D model; also plots map of 80 basal erosional surface and map of final geomorphic surface 81 82 :param xsec: location of cross section along the x-axis (in pixel/ voxel coordinates) 83 :param colors: list of RGB values that define the colors for different facies 84 :param ve: vertical exaggeration 85 :return: handles to the three figures""" 86 87 strat = self.strat 88 dx = self.dx 89 fig1 = plt.figure(figsize=(20, 5)) 90 ax1 = fig1.add_subplot(111) 91 r, c, ts = np.shape(strat) 92 Xv = dx * np.arange(0, r) 93 for i in range(0, ts - 1, 3): 94 X1 = np.concatenate((Xv, Xv[::-1])) 95 Y1 = np.concatenate((strat[:, xsec, i], strat[::-1, xsec, i + 1])) 96 Y2 = np.concatenate((strat[:, xsec, i + 1], strat[::-1, xsec, i + 2])) 97 Y3 = np.concatenate((strat[:, xsec, i + 2], strat[::-1, xsec, i + 3])) 98 if self.model_type == "submarine": 99 ax1.fill( 100 X1, Y1, facecolor=colors[2], linewidth=0.5, edgecolor=[0, 0, 0] 101 ) # oxbow mud 102 ax1.fill( 103 X1, Y2, facecolor=colors[0], linewidth=0.5, edgecolor=[0, 0, 0] 104 ) # point bar sand 105 ax1.fill(X1, Y3, facecolor=colors[1], linewidth=0.5) # levee mud 106 if self.model_type == "fluvial": 107 ax1.fill( 108 X1, Y1, facecolor=colors[0], linewidth=0.5, edgecolor=[0, 0, 0] 109 ) # levee mud 110 ax1.fill( 111 X1, Y2, facecolor=colors[1], linewidth=0.5, edgecolor=[0, 0, 0] 112 ) # oxbow mud 113 ax1.fill(X1, Y3, facecolor=colors[2], linewidth=0.5) # channel sand 114 ax1.set_xlim(0, dx * (r - 1)) 115 ax1.set_aspect(ve, adjustable="datalim") 116 fig2 = plt.figure() 117 ax2 = fig2.add_subplot(111) 118 ax2.contourf(strat[:, :, ts - 1], 100, cmap="viridis") 119 ax2.contour( 120 strat[:, :, ts - 1], 121 100, 122 colors="k", 123 linestyles="solid", 124 linewidths=0.1, 125 alpha=0.4, 126 ) 127 ax2.plot([xsec, xsec], [0, r], "k", linewidth=2) 128 ax2.axis([0, c, 0, r]) 129 ax2.set_aspect("equal", adjustable="box") 130 ax2.set_title("final geomorphic surface") 131 ax2.tick_params( 132 bottom=False, 133 top=False, 134 left=False, 135 right=False, 136 labelbottom=False, 137 labelleft=False, 138 ) 139 fig3 = plt.figure() 140 ax3 = fig3.add_subplot(111) 141 ax3.contourf(strat[:, :, 0], 100, cmap="viridis") 142 ax3.contour( 143 strat[:, :, 0], 144 100, 145 colors="k", 146 linestyles="solid", 147 linewidths=0.1, 148 alpha=0.4, 149 ) 150 ax3.plot([xsec, xsec], [0, r], "k", linewidth=2) 151 ax3.axis([0, c, 0, r]) 152 ax3.set_aspect("equal", adjustable="box") 153 ax3.set_title("basal erosional surface") 154 ax3.tick_params( 155 bottom=False, 156 top=False, 157 left=False, 158 right=False, 159 labelbottom=False, 160 labelleft=False, 161 ) 162 return fig1, fig2, fig3 163 164 165class ChannelBelt: 166 """class for ChannelBelt objects""" 167 168 def __init__(self, channels, cutoffs, cl_times, cutoff_times): 169 """initialize ChannelBelt object 170 171 :param channels: list of Channel objects 172 :param cutoffs: list of Cutoff objects 173 :param cl_times: list of ages of Channel objects (in years) 174 :param cutoff_times: list of ages of Cutoff objects""" 175 176 self.channels = channels 177 self.cutoffs = cutoffs 178 self.cl_times = cl_times 179 self.cutoff_times = cutoff_times 180 181 def migrate( 182 self, 183 nit, 184 saved_ts, 185 deltas, 186 pad, 187 crdist, 188 Cf, 189 kl, 190 kv, 191 dt, 192 dens, 193 t1, 194 t2, 195 t3, 196 aggr_factor, 197 *D 198 ): 199 """method for computing migration rates along channel centerlines and moving the centerlines accordingly 200 201 :param nit: number of iterations 202 :param saved_ts: which time steps will be saved; e.g., if saved_ts = 10, every tenth time step will be saved 203 :param deltas: distance between nodes on centerline 204 :param pad: padding (number of nodepoints along centerline) 205 :param crdist: threshold distance at which cutoffs occur 206 :param Cf: dimensionless Chezy friction factor 207 :param kl: migration rate constant (m/s) 208 :param kv: vertical slope-dependent erosion rate constant (m/s) 209 :param dt: time step (s) 210 :param dens: density of fluid (kg/m3) 211 :param t1: time step when incision starts 212 :param t2: time step when lateral migration starts 213 :param t3: time step when aggradation starts 214 :param aggr_factor: aggradation factor 215 :param D: channel depth (m)""" 216 217 channel = self.channels[ 218 -1 219 ] # first channel is the same as last channel of input 220 x = channel.x 221 y = channel.y 222 z = channel.z 223 W = channel.W 224 if len(D) == 0: 225 D = channel.D 226 else: 227 D = D[0] 228 k = 1.0 # constant in HK equation 229 xc = [] # initialize cutoff coordinates 230 # determine age of last channel: 231 if len(self.cl_times) > 0: 232 last_cl_time = self.cl_times[-1] 233 else: 234 last_cl_time = 0 235 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 236 slope = np.gradient(z) / ds 237 # padding at the beginning can be shorter than padding at the downstream end: 238 pad1 = int(pad / 10.0) 239 if pad1 < 5: 240 pad1 = 5 241 omega = ( 242 -1.0 243 ) # constant in migration rate calculation (Howard and Knutson, 1984) 244 gamma = 2.5 # from Ikeda et al., 1981 and Howard and Knutson, 1984 245 for itn in trange(nit): # main loop 246 # update_progress(itn/nit) 247 x, y = migrate_one_step( 248 x, y, z, W, kl, dt, k, Cf, D, pad, pad1, omega, gamma 249 ) 250 # x, y = migrate_one_step_w_bias(x,y,z,W,kl,dt,k,Cf,D,pad,pad1,omega,gamma) 251 x, y, z, xc, yc, zc = cut_off_cutoffs( 252 x, y, z, s, crdist, deltas 253 ) # find and execute cutoffs 254 x, y, z, dx, dy, dz, ds, s = resample_centerline( 255 x, y, z, deltas 256 ) # resample centerline 257 slope = np.gradient(z) / ds 258 # for itn<=t1, z is unchanged; for itn>t1: 259 if (itn > t1) & (itn <= t2): # incision 260 if np.min(np.abs(slope)) != 0: # if slope is not zero 261 z = z + kv * dens * 9.81 * D * slope * dt 262 else: 263 z = z - kv * dens * 9.81 * D * dt * 0.05 # if slope is zero 264 if (itn > t2) & (itn <= t3): # lateral migration 265 if np.min(np.abs(slope)) != 0: # if slope is not zero 266 # use the median slope to counterbalance incision: 267 z = ( 268 z 269 + kv * dens * 9.81 * D * slope * dt 270 - kv * dens * 9.81 * D * np.median(slope) * dt 271 ) 272 else: 273 z = z # no change in z 274 if itn > t3: # aggradation 275 if np.min(np.abs(slope)) != 0: # if slope is not zero 276 # 'aggr_factor' should be larger than 1 so that this leads to overall aggradation: 277 z = ( 278 z 279 + kv * dens * 9.81 * D * slope * dt 280 - aggr_factor * kv * dens * 9.81 * D * np.mean(slope) * dt 281 ) 282 else: 283 z = z + aggr_factor * dt 284 if len(xc) > 0: # save cutoff data 285 self.cutoff_times.append( 286 last_cl_time + (itn + 1) * dt / (365 * 24 * 60 * 60.0) 287 ) 288 cutoff = Cutoff(xc, yc, zc, W, D) # create cutoff object 289 self.cutoffs.append(cutoff) 290 # saving centerlines: 291 if np.mod(itn, saved_ts) == 0: 292 self.cl_times.append( 293 last_cl_time + (itn + 1) * dt / (365 * 24 * 60 * 60.0) 294 ) 295 channel = Channel(x, y, z, W, D) # create channel object 296 self.channels.append(channel) 297 298 def plot(self, plot_type, pb_age, ob_age, end_time, n_channels): 299 """method for plotting ChannelBelt object 300 301 :param plot_type: can be either 'strat' (for stratigraphic plot), 'morph' (for morphologic plot), or 'age' (for age plot) 302 :param pb_age: age of point bars (in years) at which they get covered by vegetation 303 :param ob_age: age of oxbow lakes (in years) at which they get covered by vegetation 304 :param end_time: age of last channel to be plotted (in years) 305 :param n_channels: total number of channels (used in 'age' plots; can be larger than number of channels being plotted) 306 :return: handle to figure""" 307 308 cot = np.array(self.cutoff_times) 309 sclt = np.array(self.cl_times) 310 if end_time > 0: 311 cot = cot[cot <= end_time] 312 sclt = sclt[sclt <= end_time] 313 times = np.sort(np.hstack((cot, sclt))) 314 times = np.unique(times) 315 order = 0 # variable for ordering objects in plot 316 # set up min and max x and y coordinates of the plot: 317 xmin = np.min(self.channels[0].x) 318 xmax = np.max(self.channels[0].x) 319 ymax = 0 320 for i in range(len(self.channels)): 321 ymax = max(ymax, np.max(np.abs(self.channels[i].y))) 322 ymax = ymax + 2 * self.channels[0].W # add a bit of space on top and bottom 323 ymin = -1 * ymax 324 # size figure so that its size matches the size of the model: 325 fig = plt.figure(figsize=(20, (ymax - ymin) * 20 / (xmax - xmin))) 326 if plot_type == "morph": 327 pb_crit = len(times[times < times[-1] - pb_age]) / float(len(times)) 328 ob_crit = len(times[times < times[-1] - ob_age]) / float(len(times)) 329 green = (106 / 255.0, 159 / 255.0, 67 / 255.0) # vegetation color 330 pb_color = (189 / 255.0, 153 / 255.0, 148 / 255.0) # point bar color 331 ob_color = (15 / 255.0, 58 / 255.0, 65 / 255.0) # oxbow color 332 pb_cmap = make_colormap( 333 [green, green, pb_crit, green, pb_color, 1.0, pb_color] 334 ) # colormap for point bars 335 ob_cmap = make_colormap( 336 [green, green, ob_crit, green, ob_color, 1.0, ob_color] 337 ) # colormap for oxbows 338 plt.fill( 339 [xmin, xmax, xmax, xmin], 340 [ymin, ymin, ymax, ymax], 341 color=(106 / 255.0, 159 / 255.0, 67 / 255.0), 342 ) 343 if plot_type == "age": 344 age_cmap = cm.get_cmap("magma", n_channels) 345 for i in range(0, len(times)): 346 if times[i] in sclt: 347 ind = np.where(sclt == times[i])[0][0] 348 x1 = self.channels[ind].x 349 y1 = self.channels[ind].y 350 W = self.channels[ind].W 351 xm, ym = get_channel_banks(x1, y1, W) 352 if plot_type == "morph": 353 if times[i] > times[-1] - pb_age: 354 plt.fill( 355 xm, 356 ym, 357 facecolor=pb_cmap(i / float(len(times) - 1)), 358 edgecolor="k", 359 linewidth=0.2, 360 ) 361 else: 362 plt.fill(xm, ym, facecolor=pb_cmap(i / float(len(times) - 1))) 363 if plot_type == "strat": 364 order += 1 365 plt.fill( 366 xm, 367 ym, 368 "xkcd:light tan", 369 edgecolor="k", 370 linewidth=0.25, 371 zorder=order, 372 ) 373 if plot_type == "age": 374 order += 1 375 plt.fill( 376 xm, 377 ym, 378 facecolor=age_cmap(i / float(n_channels - 1)), 379 edgecolor="k", 380 linewidth=0.1, 381 zorder=order, 382 ) 383 if times[i] in cot: 384 ind = np.where(cot == times[i])[0][0] 385 for j in range(0, len(self.cutoffs[ind].x)): 386 x1 = self.cutoffs[ind].x[j] 387 y1 = self.cutoffs[ind].y[j] 388 xm, ym = get_channel_banks(x1, y1, self.cutoffs[ind].W) 389 if plot_type == "morph": 390 plt.fill(xm, ym, color=ob_cmap(i / float(len(times) - 1))) 391 if plot_type == "strat": 392 order = order + 1 393 plt.fill( 394 xm, 395 ym, 396 "xkcd:ocean blue", 397 edgecolor="k", 398 linewidth=0.25, 399 zorder=order, 400 ) 401 if plot_type == "age": 402 order += 1 403 plt.fill( 404 xm, 405 ym, 406 "xkcd:sea blue", 407 edgecolor="k", 408 linewidth=0.1, 409 zorder=order, 410 ) 411 412 x1 = self.channels[len(sclt) - 1].x 413 y1 = self.channels[len(sclt) - 1].y 414 xm, ym = get_channel_banks(x1, y1, self.channels[len(sclt) - 1].W) 415 order = order + 1 416 if plot_type == "age": 417 plt.fill( 418 xm, 419 ym, 420 color="xkcd:sea blue", 421 zorder=order, 422 edgecolor="k", 423 linewidth=0.1, 424 ) 425 else: 426 plt.fill( 427 xm, ym, color=(16 / 255.0, 73 / 255.0, 90 / 255.0), zorder=order 428 ) # ,edgecolor='k') 429 plt.axis("equal") 430 plt.xlim(xmin, xmax) 431 plt.ylim(ymin, ymax) 432 return fig 433 434 def create_movie( 435 self, 436 xmin, 437 xmax, 438 plot_type, 439 filename, 440 dirname, 441 pb_age, 442 ob_age, 443 end_time, 444 n_channels, 445 ): 446 """method for creating movie frames (PNG files) that capture the plan-view evolution of a channel belt through time 447 movie has to be assembled from the PNG file after this method is applied 448 449 :param xmin: value of x coodinate on the left side of frame 450 :param xmax: value of x coordinate on right side of frame 451 :param plot_type: plot type; can be either 'strat' (for stratigraphic plot) or 'morph' (for morphologic plot) 452 :param filename: first few characters of the output filenames 453 :param dirname: name of directory where output files should be written 454 :param pb_age: age of point bars (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type') 455 :param ob_age: age of oxbow lakes (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type') 456 :param scale: scaling factor (e.g., 2) that determines how many times larger you want the frame to be, compared to the default scaling of the figure 457 :param end_time: time at which simulation should be stopped 458 :param n_channels: total number of channels + cutoffs for which simulation is run (usually it is len(chb.cutoffs) + len(chb.channels)). Used when plot_type = 'age'""" 459 460 sclt = np.array(self.cl_times) 461 if type(end_time) != list: 462 sclt = sclt[sclt <= end_time] 463 channels = self.channels[: len(sclt)] 464 ymax = 0 465 for i in range(len(channels)): 466 ymax = max(ymax, np.max(np.abs(channels[i].y))) 467 ymax = ymax + 2 * channels[0].W # add a bit of space on top and bottom 468 ymin = -1 * ymax 469 for i in range(0, len(sclt)): 470 fig = self.plot(plot_type, pb_age, ob_age, sclt[i], n_channels) 471 scale = 1 472 fig_height = scale * fig.get_figheight() 473 fig_width = (xmax - xmin) * fig_height / (ymax - ymin) 474 fig.set_figwidth(fig_width) 475 fig.set_figheight(fig_height) 476 fig.gca().set_xlim(xmin, xmax) 477 fig.gca().set_xticks([]) 478 fig.gca().set_yticks([]) 479 plt.plot( 480 [xmin + 200, xmin + 200 + 5000], 481 [ymin + 200, ymin + 200], 482 "k", 483 linewidth=2, 484 ) 485 plt.text(xmin + 200 + 2000, ymin + 200 + 100, "5 km", fontsize=14) 486 fname = dirname + filename + "%03d.png" % (i) 487 fig.savefig(fname, bbox_inches="tight") 488 plt.close() 489 490 def build_3d_model( 491 self, 492 model_type, 493 h_mud, 494 levee_width, 495 h, 496 w, 497 bth, 498 dcr, 499 dx, 500 delta_s, 501 starttime, 502 endtime, 503 xmin, 504 xmax, 505 ymin, 506 ymax, 507 ): 508 """method for building 3D model from set of centerlines (that are part of a ChannelBelt object) 509 510 :param model_type: model type ('fluvial' or 'submarine') 511 :param h_mud: maximum thickness of overbank mud 512 :param levee_width: width of overbank mud 513 :param h: channel depth 514 :param w: channel width 515 :param bth: thickness of channel sand (only used in submarine models) 516 :param dcr: critical channel depth where sand thickness goes to zero (only used in submarine models) 517 :param dx: cell size in x and y directions 518 :param delta_s: sampling distance alogn centerlines 519 :param starttime: age of centerline that will be used as the first centerline in the model 520 :param endtime: age of centerline that will be used as the last centerline in the model 521 :param xmin: minimum x coordinate that defines the model domain; if xmin is set to zero, 522 a plot of the centerlines is generated and the model domain has to be defined by clicking its upper left and lower right corners 523 :param xmax: maximum x coordinate that defines the model domain 524 :param ymin: minimum y coordinate that defines the model domain 525 :param ymax: maximum y coordinate that defines the model domain 526 :return chb_3d: a ChannelBelt3D object 527 :return xmin, xmax, ymin, ymax: x and y coordinates that define the model domain (so that they can be reused later)""" 528 529 sclt = np.array(self.cl_times) 530 ind1 = np.where(sclt >= starttime)[0][0] 531 ind2 = np.where(sclt <= endtime)[0][-1] 532 sclt = sclt[ind1 : ind2 + 1] 533 channels = self.channels[ind1 : ind2 + 1] 534 cot = np.array(self.cutoff_times) 535 if ( 536 (len(cot) > 0) 537 & (len(np.where(cot >= starttime)[0]) > 0) 538 & (len(np.where(cot <= endtime)[0]) > 0) 539 ): 540 cfind1 = np.where(cot >= starttime)[0][0] 541 cfind2 = np.where(cot <= endtime)[0][-1] 542 cot = cot[cfind1 : cfind2 + 1] 543 cutoffs = self.cutoffs[cfind1 : cfind2 + 1] 544 else: 545 cot = [] 546 cutoffs = [] 547 n_steps = len(sclt) # number of events 548 if xmin == 0: # plot centerlines and define model domain 549 plt.figure(figsize=(15, 4)) 550 maxX, minY, maxY = 0, 0, 0 551 for i in range(n_steps): # plot centerlines 552 plt.plot(channels[i].x, channels[i].y, "k") 553 maxX = max(maxX, np.max(channels[i].x)) 554 maxY = max(maxY, np.max(channels[i].y)) 555 minY = min(minY, np.min(channels[i].y)) 556 plt.axis([0, maxX, minY - 10 * w, maxY + 10 * w]) 557 plt.gca().set_aspect("equal", adjustable="box") 558 plt.tight_layout() 559 pts = np.zeros((2, 2)) 560 for i in range(0, 2): 561 pt = np.asarray(plt.ginput(1)) 562 pts[i, :] = pt 563 plt.scatter(pt[0][0], pt[0][1]) 564 plt.plot( 565 [pts[0, 0], pts[1, 0], pts[1, 0], pts[0, 0], pts[0, 0]], 566 [pts[0, 1], pts[0, 1], pts[1, 1], pts[1, 1], pts[0, 1]], 567 "r", 568 ) 569 xmin = min(pts[0, 0], pts[1, 0]) 570 xmax = max(pts[0, 0], pts[1, 0]) 571 ymin = min(pts[0, 1], pts[1, 1]) 572 ymax = max(pts[0, 1], pts[1, 1]) 573 iwidth = int((xmax - xmin) / dx) 574 iheight = int((ymax - ymin) / dx) 575 topo = np.zeros( 576 (iheight, iwidth, 4 * n_steps) 577 ) # array for storing topographic surfaces 578 dists = np.zeros((iheight, iwidth, n_steps)) 579 zmaps = np.zeros((iheight, iwidth, n_steps)) 580 facies = np.zeros((4 * n_steps, 1)) 581 # create initial topography: 582 x1 = np.linspace(0, iwidth - 1, iwidth) 583 y1 = np.linspace(0, iheight - 1, iheight) 584 xv, yv = np.meshgrid(x1, y1) 585 z1 = channels[0].z 586 z1 = z1[(channels[0].x > xmin) & (channels[0].x < xmax)] 587 topoinit = ( 588 z1[0] - ((z1[0] - z1[-1]) / (xmax - xmin)) * xv * dx 589 ) # initial (sloped) topography 590 topo[:, :, 0] = topoinit.copy() 591 surf = topoinit.copy() 592 facies[0] = np.NaN 593 # generate surfaces: 594 channels3D = [] 595 for i in trange(n_steps): 596 x = channels[i].x 597 y = channels[i].y 598 z = channels[i].z 599 cutoff_ind = [] 600 # check if there were cutoffs during the last time step and collect indices in an array: 601 for j in range(len(cot)): 602 if (cot[j] >= sclt[i - 1]) & (cot[j] < sclt[i]): 603 cutoff_ind = np.append(cutoff_ind, j) 604 # create distance map: 605 cl_dist, x_pix, y_pix, z_pix, s_pix, z_map, x1, y1, z1 = dist_map( 606 x, y, z, xmin, xmax, ymin, ymax, dx, delta_s 607 ) 608 if i == 0: 609 cl_dist_prev = cl_dist 610 # erosion: 611 surf = np.minimum(surf, erosion_surface(h, w / dx, cl_dist, z_map)) 612 topo[:, :, 4 * i] = surf # erosional surface 613 dists[:, :, i] = cl_dist 614 zmaps[:, :, i] = z_map 615 facies[4 * i] = np.NaN 616 617 if model_type == "fluvial": 618 pb = point_bar_surface(cl_dist, z_map, h, w / dx) 619 th = np.maximum(surf, pb) - surf 620 th_oxbows = th.copy() 621 # setting sand thickness to zero at cutoff locations: 622 if len(cutoff_ind) > 0: 623 cutoff_dists = 1e10 * np.ones( 624 np.shape(th) 625 ) # initialize cutoff_dists with a large number 626 for j in range(len(cutoff_ind)): 627 cutoff_dist, cfx_pix, cfy_pix = cl_dist_map( 628 cutoffs[int(cutoff_ind[j])].x[0], 629 cutoffs[int(cutoff_ind[j])].y[0], 630 cutoffs[int(cutoff_ind[j])].z[0], 631 xmin, 632 xmax, 633 ymin, 634 ymax, 635 dx, 636 ) 637 cutoff_dists = np.minimum(cutoff_dists, cutoff_dist) 638 th_oxbows[ 639 cutoff_dists >= 0.9 * w / dx 640 ] = 0 # set oxbow fill thickness to zero outside of oxbows 641 th[ 642 cutoff_dists < 0.9 * w / dx 643 ] = 0 # set point bar thickness to zero inside of oxbows 644 else: # no cutoffs 645 th_oxbows = np.zeros(np.shape(th)) 646 th[th < 0] = 0 # eliminate negative th values 647 surf = ( 648 surf + th_oxbows 649 ) # update topographic surface with oxbow deposit thickness 650 topo[:, :, 4 * i + 1] = surf # top of oxbow mud 651 facies[4 * i + 1] = 0 652 surf = surf + th # update topographic surface with sand thickness 653 topo[:, :, 4 * i + 2] = surf # top of sand 654 facies[4 * i + 2] = 1 655 surf = surf + mud_surface( 656 h_mud, levee_width / dx, cl_dist, w / dx, z_map, surf 657 ) # mud/levee deposition 658 topo[:, :, 4 * i + 3] = surf # top of levee 659 facies[4 * i + 3] = 2 660 channels3D.append(Channel(x1 - xmin, y1 - ymin, z1, w, h)) 661 662 if model_type == "submarine": 663 surf = surf + mud_surface( 664 h_mud[i], levee_width / dx, cl_dist, w / dx, z_map, surf 665 ) # mud/levee deposition 666 topo[:, :, 4 * i + 1] = surf # top of levee 667 facies[4 * i + 1] = 2 668 # sand thickness: 669 th, relief = sand_surface(surf, bth, dcr, z_map, h) 670 th[th < 0] = 0 # eliminate negative th values 671 th[cl_dist > 1.0 * w / dx] = 0 # eliminate sand outside of channel 672 th_oxbows = th.copy() 673 # setting sand thickness to zero at cutoff locations: 674 if len(cutoff_ind) > 0: 675 cutoff_dists = 1e10 * np.ones( 676 np.shape(th) 677 ) # initialize cutoff_dists with a large number 678 for j in range(len(cutoff_ind)): 679 cutoff_dist, cfx_pix, cfy_pix = cl_dist_map( 680 cutoffs[int(cutoff_ind[j])].x[0], 681 cutoffs[int(cutoff_ind[j])].y[0], 682 cutoffs[int(cutoff_ind[j])].z[0], 683 xmin, 684 xmax, 685 ymin, 686 ymax, 687 dx, 688 ) 689 cutoff_dists = np.minimum(cutoff_dists, cutoff_dist) 690 th_oxbows[ 691 cutoff_dists >= 0.9 * w / dx 692 ] = 0 # set oxbow fill thickness to zero outside of oxbows 693 th[ 694 cutoff_dists < 0.9 * w / dx 695 ] = 0 # set point bar thickness to zero inside of oxbows 696 # adding back sand near the channel axis (submarine only): 697 # th[cl_dist<0.5*w/dx] = bth*(1 - relief[cl_dist<0.5*w/dx]/dcr) 698 else: # no cutoffs 699 th_oxbows = np.zeros(np.shape(th)) 700 surf = ( 701 surf + th_oxbows 702 ) # update topographic surface with oxbow deposit thickness 703 topo[:, :, 4 * i + 2] = surf # top of oxbow mud 704 facies[4 * i + 2] = 0 705 surf = surf + th # update topographic surface with sand thickness 706 topo[:, :, 4 * i + 3] = surf # top of sand 707 facies[4 * i + 3] = 1 708 channels3D.append(Channel(x1 - xmin, y1 - ymin, z1, w, h)) 709 710 cl_dist_prev = cl_dist.copy() 711 topo = np.concatenate( 712 (np.reshape(topoinit, (iheight, iwidth, 1)), topo), axis=2 713 ) # add initial topography to array 714 strat = topostrat(topo) # create stratigraphic surfaces 715 strat = np.delete( 716 strat, np.arange(4 * n_steps + 1)[1::4], 2 717 ) # get rid of unnecessary stratigraphic surfaces (duplicates) 718 facies = np.delete( 719 facies, np.arange(4 * n_steps)[::4] 720 ) # get rid of unnecessary facies layers (NaNs) 721 if model_type == "fluvial": 722 facies_code = {0: "oxbow", 1: "point bar", 2: "levee"} 723 if model_type == "submarine": 724 facies_code = {0: "oxbow", 1: "channel sand", 2: "levee"} 725 chb_3d = ChannelBelt3D( 726 model_type, topo, strat, facies, facies_code, dx, channels3D 727 ) 728 return chb_3d, xmin, xmax, ymin, ymax # , dists, zmaps 729 730 731def resample_centerline(x, y, z, deltas): 732 """resample centerline so that 'deltas' is roughly constant, using parametric 733 spline representation of curve; note that there is *no* smoothing 734 735 :param x: x-coordinates of centerline 736 :param y: y-coordinates of centerline 737 :param z: z-coordinates of centerline 738 :param deltas: distance between points on centerline 739 :return x: x-coordinates of resampled centerline 740 :return y: y-coordinates of resampled centerline 741 :return z: z-coordinates of resampled centerline 742 :return dx: dx of resampled centerline 743 :return dy: dy of resampled centerline 744 :return dz: dz of resampled centerline 745 :return s: s-coordinates of resampled centerline""" 746 747 dx, dy, dz, ds, s = compute_derivatives(x, y, z) # compute derivatives 748 tck, u = scipy.interpolate.splprep([x, y, z], s=0) 749 unew = np.linspace(0, 1, 1 + int(round(s[-1] / deltas))) # vector for resampling 750 out = scipy.interpolate.splev(unew, tck) # resampling 751 x, y, z = out[0], out[1], out[2] # assign new coordinate values 752 dx, dy, dz, ds, s = compute_derivatives(x, y, z) # recompute derivatives 753 return x, y, z, dx, dy, dz, ds, s 754 755 756def migrate_one_step(x, y, z, W, kl, dt, k, Cf, D, pad, pad1, omega, gamma): 757 """migrate centerline during one time step, using the migration computed as in Howard & Knutson (1984) 758 759 :param x: x-coordinates of centerline 760 :param y: y-coordinates of centerline 761 :param z: z-coordinates of centerline 762 :param W: channel width 763 :param kl: migration rate (or erodibility) constant (m/s) 764 :param dt: duration of time step (s) 765 :param k: constant for calculating the exponent alpha (= 1.0) 766 :param Cf: dimensionless Chezy friction factor 767 :param D: channel depth 768 :param omega: constant in Howard & Knutson equation (= -1.0) 769 :param gamma: constant in Howard & Knutson equation (= 2.5) 770 :return x: new x-coordinates of centerline after migration 771 :return y: new y-coordinates of centerline after migration 772 """ 773 774 ns = len(x) 775 curv = compute_curvature(x, y) 776 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 777 sinuosity = s[-1] / (x[-1] - x[0]) 778 curv = W * curv # dimensionless curvature 779 R0 = ( 780 kl * curv 781 ) # simple linear relationship between curvature and nominal migration rate 782 alpha = k * 2 * Cf / D # exponent for convolution function G 783 R1 = compute_migration_rate(pad, ns, ds, alpha, omega, gamma, R0) 784 R1 = sinuosity ** (-2 / 3.0) * R1 785 # calculate new centerline coordinates: 786 dy_ds = dy[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 787 dx_ds = dx[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 788 # adjust x and y coordinates (this *is* the migration): 789 x[pad1 : ns - pad + 1] = ( 790 x[pad1 : ns - pad + 1] + R1[pad1 : ns - pad + 1] * dy_ds * dt 791 ) 792 y[pad1 : ns - pad + 1] = ( 793 y[pad1 : ns - pad + 1] - R1[pad1 : ns - pad + 1] * dx_ds * dt 794 ) 795 return x, y 796 797 798def migrate_one_step_w_bias(x, y, z, W, kl, dt, k, Cf, D, pad, pad1, omega, gamma): 799 ns = len(x) 800 curv = compute_curvature(x, y) 801 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 802 sinuosity = s[-1] / (x[-1] - x[0]) 803 curv = W * curv # dimensionless curvature 804 R0 = ( 805 kl * curv 806 ) # simple linear relationship between curvature and nominal migration rate 807 alpha = k * 2 * Cf / D # exponent for convolution function G 808 R1 = compute_migration_rate(pad, ns, ds, alpha, omega, gamma, R0) 809 R1 = sinuosity ** (-2 / 3.0) * R1 810 pad = -1 811 # calculate new centerline coordinates: 812 dy_ds = dy[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 813 dx_ds = dx[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 814 tilt_factor = 0.2 815 T = kl * tilt_factor * np.ones(np.shape(x)) 816 angle = 90.0 817 # adjust x and y coordinates (this *is* the migration): 818 x[pad1 : ns - pad + 1] = ( 819 x[pad1 : ns - pad + 1] 820 + R1[pad1 : ns - pad + 1] * dy_ds * dt 821 + T[pad1 : ns - pad + 1] 822 * dy_ds 823 * dt 824 * (np.sin(np.deg2rad(angle)) * dx_ds + np.cos(np.deg2rad(angle)) * dy_ds) 825 ) 826 y[pad1 : ns - pad + 1] = ( 827 y[pad1 : ns - pad + 1] 828 - R1[pad1 : ns - pad + 1] * dx_ds * dt 829 - T[pad1 : ns - pad + 1] 830 * dx_ds 831 * dt 832 * (np.sin(np.deg2rad(angle)) * dx_ds + np.cos(np.deg2rad(angle)) * dy_ds) 833 ) 834 return x, y 835 836 837def generate_initial_channel(W, D, Sl, deltas, pad, n_bends): 838 """generate straight Channel object with some noise added that can serve 839 as input for initializing a ChannelBelt object 840 W - channel width 841 D - channel depth 842 Sl - channel gradient 843 deltas - distance between nodes on centerline 844 pad - padding (number of nodepoints along centerline) 845 n_bends - approximate number of bends to be simulated""" 846 noisy_len = n_bends * 10 * W / 2.0 # length of noisy part of initial centerline 847 pad1 = int( 848 pad / 10.0 849 ) # padding at upstream end can be shorter than padding on downstream end 850 if pad1 < 5: 851 pad1 = 5 852 x = np.linspace( 853 0, noisy_len + (pad + pad1) * deltas, int(noisy_len / deltas + pad + pad1) + 1 854 ) # x coordinate 855 y = 10.0 * ( 856 2 857 * np.random.random_sample( 858 int(noisy_len / deltas) + 1, 859 ) 860 - 1 861 ) 862 y = np.hstack( 863 ( 864 np.zeros( 865 (pad1), 866 ), 867 y, 868 np.zeros( 869 (pad), 870 ), 871 ) 872 ) # y coordinate 873 deltaz = Sl * deltas * (len(x) - 1) 874 z = np.linspace(0, deltaz, len(x))[::-1] # z coordinate 875 return Channel(x, y, z, W, D) 876 877 878@numba.jit(nopython=True) # use Numba to speed up the heaviest computation 879def compute_migration_rate(pad, ns, ds, alpha, omega, gamma, R0): 880 """compute migration rate as weighted sum of upstream curvatures 881 pad - padding (number of nodepoints along centerline) 882 ns - number of points in centerline 883 ds - distances between points in centerline 884 omega - constant in HK model 885 gamma - constant in HK model 886 R0 - nominal migration rate (dimensionless curvature * migration rate constant)""" 887 R1 = np.zeros(ns) # preallocate adjusted channel migration rate 888 pad1 = int( 889 pad / 10.0 890 ) # padding at upstream end can be shorter than padding on downstream end 891 if pad1 < 5: 892 pad1 = 5 893 for i in range(pad1, ns - pad): 894 si2 = np.hstack( 895 (np.array([0]), np.cumsum(ds[i - 1 :: -1])) 896 ) # distance along centerline, backwards from current point 897 G = np.exp(-alpha * si2) # convolution vector 898 R1[i] = omega * R0[i] + gamma * np.sum(R0[i::-1] * G) / np.sum( 899 G 900 ) # main equation 901 return R1 902 903 904def compute_derivatives(x, y, z): 905 """function for computing first derivatives of a curve (centerline) 906 x,y are cartesian coodinates of the curve 907 outputs: 908 dx - first derivative of x coordinate 909 dy - first derivative of y coordinate 910 ds - distances between consecutive points along the curve 911 s - cumulative distance along the curve""" 912 dx = np.gradient(x) # first derivatives 913 dy = np.gradient(y) 914 dz = np.gradient(z) 915 ds = np.sqrt(dx ** 2 + dy ** 2 + dz ** 2) 916 s = np.hstack((0, np.cumsum(ds[1:]))) 917 return dx, dy, dz, ds, s 918 919 920def compute_curvature(x, y): 921 """function for computing first derivatives and curvature of a curve (centerline) 922 x,y are cartesian coodinates of the curve 923 outputs: 924 dx - first derivative of x coordinate 925 dy - first derivative of y coordinate 926 ds - distances between consecutive points along the curve 927 s - cumulative distance along the curve 928 curvature - curvature of the curve (in 1/units of x and y)""" 929 dx = np.gradient(x) # first derivatives 930 dy = np.gradient(y) 931 ddx = np.gradient(dx) # second derivatives 932 ddy = np.gradient(dy) 933 curvature = (dx * ddy - dy * ddx) / ((dx ** 2 + dy ** 2) ** 1.5) 934 return curvature 935 936 937def make_colormap(seq): 938 """Return a LinearSegmentedColormap 939 seq: a sequence of floats and RGB-tuples. The floats should be increasing 940 and in the interval (0,1). 941 [from: https://stackoverflow.com/questions/16834861/create-own-colormap-using-matplotlib-and-plot-color-scale] 942 """ 943 seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3] 944 cdict = {"red": [], "green": [], "blue": []} 945 for i, item in enumerate(seq): 946 if isinstance(item, float): 947 r1, g1, b1 = seq[i - 1] 948 r2, g2, b2 = seq[i + 1] 949 cdict["red"].append([item, r1, r2]) 950 cdict["green"].append([item, g1, g2]) 951 cdict["blue"].append([item, b1, b2]) 952 return mcolors.LinearSegmentedColormap("CustomMap", cdict) 953 954 955def kth_diag_indices(a, k): 956 """function for finding diagonal indices with k offset 957 [from https://stackoverflow.com/questions/10925671/numpy-k-th-diagonal-indices]""" 958 rows, cols = np.diag_indices_from(a) 959 if k < 0: 960 return rows[:k], cols[-k:] 961 elif k > 0: 962 return rows[k:], cols[:-k] 963 else: 964 return rows, cols 965 966 967def find_cutoffs(x, y, crdist, deltas): 968 """function for identifying locations of cutoffs along a centerline 969 and the indices of the segments that will become part of the oxbows 970 x,y - coordinates of centerline 971 crdist - critical cutoff distance 972 deltas - distance between neighboring points along the centerline""" 973 diag_blank_width = int((crdist + 20 * deltas) / deltas) 974 # distance matrix for centerline points: 975 dist = distance.cdist(np.array([x, y]).T, np.array([x, y]).T) 976 dist[ 977 dist > crdist 978 ] = np.NaN # set all values that are larger than the cutoff threshold to NaN 979 # set matrix to NaN along the diagonal zone: 980 for k in range(-diag_blank_width, diag_blank_width + 1): 981 rows, cols = kth_diag_indices(dist, k) 982 dist[rows, cols] = np.NaN 983 i1, i2 = np.where(~np.isnan(dist)) 984 ind1 = i1[np.where(i1 < i2)[0]] # get rid of unnecessary indices 985 ind2 = i2[np.where(i1 < i2)[0]] # get rid of unnecessary indices 986 return ind1, ind2 # return indices of cutoff points and cutoff coordinates 987 988 989def cut_off_cutoffs(x, y, z, s, crdist, deltas): 990 """function for executing cutoffs - removing oxbows from centerline and storing cutoff coordinates 991 x,y - coordinates of centerline 992 crdist - critical cutoff distance 993 deltas - distance between neighboring points along the centerline 994 outputs: 995 x,y,z - updated coordinates of centerline 996 xc, yc, zc - lists with coordinates of cutoff segments""" 997 xc = [] 998 yc = [] 999 zc = [] 1000 ind1, ind2 = find_cutoffs(x, y, crdist, deltas) # initial check for cutoffs 1001 while len(ind1) > 0: 1002 xc.append(x[ind1[0] : ind2[0] + 1]) # x coordinates of cutoff 1003 yc.append(y[ind1[0] : ind2[0] + 1]) # y coordinates of cutoff 1004 zc.append(z[ind1[0] : ind2[0] + 1]) # z coordinates of cutoff 1005 x = np.hstack((x[: ind1[0] + 1], x[ind2[0] :])) # x coordinates after cutoff 1006 y = np.hstack((y[: ind1[0] + 1], y[ind2[0] :])) # y coordinates after cutoff 1007 z = np.hstack((z[: ind1[0] + 1], z[ind2[0] :])) # z coordinates after cutoff 1008 ind1, ind2 = find_cutoffs(x, y, crdist, deltas) 1009 return x, y, z, xc, yc, zc 1010 1011 1012def get_channel_banks(x, y, W): 1013 """function for finding coordinates of channel banks, given a centerline and a channel width 1014 x,y - coordinates of centerline 1015 W - channel width 1016 outputs: 1017 xm, ym - coordinates of channel banks (both left and right banks)""" 1018 x1 = x.copy() 1019 y1 = y.copy() 1020 x2 = x.copy() 1021 y2 = y.copy() 1022 ns = len(x) 1023 dx = np.diff(x) 1024 dy = np.diff(y) 1025 ds = np.sqrt(dx ** 2 + dy ** 2) 1026 x1[:-1] = x[:-1] + 0.5 * W * np.diff(y) / ds 1027 y1[:-1] = y[:-1] - 0.5 * W * np.diff(x) / ds 1028 x2[:-1] = x[:-1] - 0.5 * W * np.diff(y) / ds 1029 y2[:-1] = y[:-1] + 0.5 * W * np.diff(x) / ds 1030 x1[ns - 1] = x[ns - 1] + 0.5 * W * (y[ns - 1] - y[ns - 2]) / ds[ns - 2] 1031 y1[ns - 1] = y[ns - 1] - 0.5 * W * (x[ns - 1] - x[ns - 2]) / ds[ns - 2] 1032 x2[ns - 1] = x[ns - 1] - 0.5 * W * (y[ns - 1] - y[ns - 2]) / ds[ns - 2] 1033 y2[ns - 1] = y[ns - 1] + 0.5 * W * (x[ns - 1] - x[ns - 2]) / ds[ns - 2] 1034 xm = np.hstack((x1, x2[::-1])) 1035 ym = np.hstack((y1, y2[::-1])) 1036 return xm, ym 1037 1038 1039def dist_map(x, y, z, xmin, xmax, ymin, ymax, dx, delta_s): 1040 """function for centerline rasterization and distance map calculation 1041 inputs: 1042 x,y,z - coordinates of centerline 1043 xmin, xmax, ymin, ymax - x and y coordinates that define the area of interest 1044 dx - gridcell size (m) 1045 delta_s - distance between points along centerline (m) 1046 returns: 1047 cl_dist - distance map (distance from centerline) 1048 x_pix, y_pix, z_pix - x,y, and z pixel coordinates of the centerline 1049 s_pix - along-channel distance in pixels 1050 z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) 1051 x, y, z - x,y,z centerline coordinates clipped to the 3D model domain""" 1052 y = y[(x > xmin) & (x < xmax)] 1053 z = z[(x > xmin) & (x < xmax)] 1054 x = x[(x > xmin) & (x < xmax)] 1055 dummy, dy, dz, ds, s = compute_derivatives(x, y, z) 1056 if len(np.where(ds > 2 * delta_s)[0]) > 0: 1057 inds = np.where(ds > 2 * delta_s)[0] 1058 inds = np.hstack((0, inds, len(x))) 1059 lengths = np.diff(inds) 1060 long_segment = np.where(lengths == max(lengths))[0][0] 1061 start_ind = inds[long_segment] + 1 1062 end_ind = inds[long_segment + 1] 1063 if end_ind < len(x): 1064 x = x[start_ind:end_ind] 1065 y = y[start_ind:end_ind] 1066 z = z[start_ind:end_ind] 1067 else: 1068 x = x[start_ind:] 1069 y = y[start_ind:] 1070 z = z[start_ind:] 1071 xdist = xmax - xmin 1072 ydist = ymax - ymin 1073 iwidth = int((xmax - xmin) / dx) 1074 iheight = int((ymax - ymin) / dx) 1075 xratio = iwidth / xdist 1076 # create list with pixel coordinates: 1077 pixels = [] 1078 for i in range(0, len(x)): 1079 px = int(iwidth - (xmax - x[i]) * xratio) 1080 py = int(iheight - (ymax - y[i]) * xratio) 1081 pixels.append((px, py)) 1082 # create image and numpy array: 1083 img = Image.new("RGB", (iwidth, iheight), "white") 1084 draw = ImageDraw.Draw(img) 1085 draw.line(pixels, fill="rgb(0, 0, 0)") # draw centerline as black line 1086 pix = np.array(img) 1087 cl = pix[:, :, 0] 1088 cl[cl == 255] = 1 # set background to 1 (centerline is 0) 1089 y_pix, x_pix = np.where(cl == 0) 1090 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1091 # This next block of code is kind of a hack. Looking for, and eliminating, 'bad' pixels. 1092 img = np.array(img) 1093 img = img[:, :, 0] 1094 img[img == 255] = 1 1095 img1 = morphology.binary_dilation(img, morphology.square(2)).astype(np.uint8) 1096 if len(np.where(img1 == 0)[0]) > 0: 1097 x_pix, y_pix = eliminate_bad_pixels(img, img1) 1098 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1099 img1 = morphology.binary_dilation( 1100 img, np.array([[1, 0, 1], [1, 1, 1]], dtype=np.uint8) 1101 ).astype(np.uint8) 1102 if len(np.where(img1 == 0)[0]) > 0: 1103 x_pix, y_pix = eliminate_bad_pixels(img, img1) 1104 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1105 img1 = morphology.binary_dilation( 1106 img, np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], dtype=np.uint8) 1107 ).astype(np.uint8) 1108 if len(np.where(img1 == 0)[0]) > 0: 1109 x_pix, y_pix = eliminate_bad_pixels(img, img1) 1110 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1111 # redo the distance calculation (because x_pix and y_pix do not always contain all the points in cl): 1112 cl[cl == 0] = 1 1113 cl[y_pix, x_pix] = 0 1114 cl_dist, inds = ndimage.distance_transform_edt(cl, return_indices=True) 1115 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 1116 dx_pix = np.diff(x_pix) 1117 dy_pix = np.diff(y_pix) 1118 ds_pix = np.sqrt(dx_pix ** 2 + dy_pix ** 2) 1119 s_pix = np.hstack((0, np.cumsum(ds_pix))) 1120 f = scipy.interpolate.interp1d(s, z) 1121 snew = s_pix * s[-1] / s_pix[-1] 1122 if snew[-1] > s[-1]: 1123 snew[-1] = s[-1] 1124 snew[snew < s[0]] = s[0] 1125 z_pix = f(snew) 1126 # create z_map: 1127 z_map = np.zeros(np.shape(cl_dist)) 1128 z_map[y_pix, x_pix] = z_pix 1129 xinds = inds[1, :, :] 1130 yinds = inds[0, :, :] 1131 for i in range(0, len(x_pix)): 1132 z_map[(xinds == x_pix[i]) & (yinds == y_pix[i])] = z_pix[i] 1133 return cl_dist, x_pix, y_pix, z_pix, s_pix, z_map, x, y, z 1134 1135 1136def erosion_surface(h, w, cl_dist, z): 1137 """function for creating a parabolic erosional surface 1138 inputs: 1139 h - geomorphic channel depth (m) 1140 w - geomorphic channel width (in pixels, as cl_dist is also given in pixels) 1141 cl_dist - distance map (distance from centerline) 1142 z - reference elevation (m) 1143 returns: 1144 surf - map of the quadratic erosional surface (m) 1145 """ 1146 surf = z + (4 * h / w ** 2) * (cl_dist + w * 0.5) * (cl_dist - w * 0.5) 1147 return surf 1148 1149 1150def point_bar_surface(cl_dist, z, h, w): 1151 """function for creating a Gaussian-based point bar surface 1152 used in 3D fluvial model 1153 inputs: 1154 cl_dist - distance map (distance from centerline) 1155 z - reference elevation (m) 1156 h - channel depth (m) 1157 w - channel width, in pixels, as cl_dist is also given in pixels 1158 returns: 1159 pb - map of the Gaussian surface that can be used to from a point bar deposit (m)""" 1160 pb = z - h * np.exp(-(cl_dist ** 2) / (2 * (w * 0.33) ** 2)) 1161 return pb 1162 1163 1164def sand_surface(surf, bth, dcr, z_map, h): 1165 """function for creating the top horizontal surface sand-rich deposit in the bottom of the channel 1166 used in 3D submarine channel models 1167 inputs: 1168 surf - current geomorphic surface 1169 bth - thickness of sand deposit in axis of channel (m) 1170 dcr - critical channel depth, above which there is no sand deposition (m) 1171 z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) 1172 h - channel depth (m) 1173 returns: 1174 th - thickness map of sand deposit (m) 1175 relief - map of channel relief (m)""" 1176 relief = np.abs(surf - z_map + h) 1177 relief = np.abs(relief - np.amin(relief)) 1178 th = bth * (1 - relief / dcr) # bed thickness inversely related to relief 1179 th[th < 0] = 0.0 # set negative th values to zero 1180 return th, relief 1181 1182 1183def mud_surface(h_mud, levee_width, cl_dist, w, z_map, topo): 1184 """function for creating a map of overbank deposit thickness 1185 inputs: 1186 h_mud - maximum thickness of overbank deposit (m) 1187 levee_width - half-width of overbank deposit (m) 1188 cl_dist - distance map (distance from centerline) 1189 w - channel width (in pixels, as cl_dist is also given in pixels) 1190 z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) 1191 topo - current geomorphic surface 1192 returns: 1193 surf - map of overbank deposit thickness (m)""" 1194 # create a surface that thins linearly away from the channel centerline: 1195 surf1 = (-2 * h_mud / levee_width) * cl_dist + h_mud 1196 surf2 = (2 * h_mud / levee_width) * cl_dist + h_mud 1197 surf = np.minimum(surf1, surf2) 1198 # surface for 'eroding' the central part of the mud layer: 1199 surf3 = h_mud + (4 * 1.5 * h_mud / w ** 2) * (cl_dist + w * 0.5) * ( 1200 cl_dist - w * 0.5 1201 ) 1202 surf = np.minimum(surf, surf3) 1203 surf[surf < 0] = 0 1204 # eliminate negative thicknesses 1205 return surf 1206 1207 1208def topostrat(topo): 1209 """function for converting a stack of geomorphic surfaces into stratigraphic surfaces 1210 inputs: 1211 topo - 3D numpy array of geomorphic surfaces 1212 returns: 1213 strat - 3D numpy array of stratigraphic surfaces 1214 """ 1215 r, c, ts = np.shape(topo) 1216 strat = np.copy(topo) 1217 for i in range(0, ts): 1218 strat[:, :, i] = np.amin(topo[:, :, i:], axis=2) 1219 return strat 1220 1221 1222def cl_dist_map(x, y, z, xmin, xmax, ymin, ymax, dx): 1223 """function for centerline rasterization and distance map calculation (does not return zmap) 1224 used for cutoffs only 1225 inputs: 1226 x,y,z - coordinates of centerline 1227 xmin, xmax, ymin, ymax - x and y coordinates that define the area of interest 1228 dx - gridcell size (m) 1229 returns: 1230 cl_dist - distance map (distance from centerline) 1231 x_pix, y_pix, - x and y pixel coordinates of the centerline 1232 """ 1233 y = y[(x > xmin) & (x < xmax)] 1234 z = z[(x > xmin) & (x < xmax)] 1235 x = x[(x > xmin) & (x < xmax)] 1236 xdist = xmax - xmin 1237 ydist = ymax - ymin 1238 iwidth = int((xmax - xmin) / dx) 1239 iheight = int((ymax - ymin) / dx) 1240 xratio = iwidth / xdist 1241 # create list with pixel coordinates: 1242 pixels = [] 1243 for i in range(0, len(x)): 1244 px = int(iwidth - (xmax - x[i]) * xratio) 1245 py = int(iheight - (ymax - y[i]) * xratio) 1246 pixels.append((px, py)) 1247 # create image and numpy array: 1248 img = Image.new("RGB", (iwidth, iheight), "white") 1249 draw = ImageDraw.Draw(img) 1250 draw.line(pixels, fill="rgb(0, 0, 0)") # draw centerline as black line 1251 pix = np.array(img) 1252 cl = pix[:, :, 0] 1253 cl[cl == 255] = 1 # set background to 1 (centerline is 0) 1254 # calculate Euclidean distance map: 1255 cl_dist, inds = ndimage.distance_transform_edt(cl, return_indices=True) 1256 y_pix, x_pix = np.where(cl == 0) 1257 return cl_dist, x_pix, y_pix 1258 1259 1260def eliminate_bad_pixels(img, img1): 1261 x_ind = np.where(img1 == 0)[1][0] 1262 y_ind = np.where(img1 == 0)[0][0] 1263 img[y_ind : y_ind + 2, x_ind : x_ind + 2] = np.ones( 1264 1, 1265 ).astype(np.uint8) 1266 all_labels = measure.label(img, background=1, connectivity=2) 1267 cl = all_labels.copy() 1268 cl[cl == 2] = 0 1269 cl[cl > 0] = 1 1270 y_pix, x_pix = np.where(cl == 1) 1271 return x_pix, y_pix 1272 1273 1274def order_cl_pixels(x_pix, y_pix): 1275 dist = distance.cdist(np.array([x_pix, y_pix]).T, np.array([x_pix, y_pix]).T) 1276 dist[np.diag_indices_from(dist)] = 100.0 1277 ind = np.argmin(x_pix) # select starting point on left side of image 1278 clinds = [ind] 1279 count = 0 1280 while count < len(x_pix): 1281 t = dist[ind, :].copy() 1282 if len(clinds) > 2: 1283 t[clinds[-2]] = t[clinds[-2]] + 100.0 1284 t[clinds[-3]] = t[clinds[-3]] + 100.0 1285 ind = np.argmin(t) 1286 clinds.append(ind) 1287 count = count + 1 1288 x_pix = x_pix[clinds] 1289 y_pix = y_pix[clinds] 1290 return x_pix, y_pix
19class Channel: 20 """class for Channel objects""" 21 22 def __init__(self, x, y, z, W, D): 23 """Initialize Channel object 24 25 :param x: x-coordinate of centerline 26 :param y: y-coordinate of centerline 27 :param z: z-coordinate of centerline 28 :param W: channel width 29 :param D: channel depth""" 30 31 self.x = x 32 self.y = y 33 self.z = z 34 self.W = W 35 self.D = D
class for Channel objects
22 def __init__(self, x, y, z, W, D): 23 """Initialize Channel object 24 25 :param x: x-coordinate of centerline 26 :param y: y-coordinate of centerline 27 :param z: z-coordinate of centerline 28 :param W: channel width 29 :param D: channel depth""" 30 31 self.x = x 32 self.y = y 33 self.z = z 34 self.W = W 35 self.D = D
Initialize Channel object
Parameters
- x: x-coordinate of centerline
- y: y-coordinate of centerline
- z: z-coordinate of centerline
- W: channel width
- D: channel depth
38class Cutoff: 39 """class for Cutoff objects""" 40 41 def __init__(self, x, y, z, W, D): 42 """Initialize Cutoff object 43 44 :param x: x-coordinate of centerline 45 :param y: y-coordinate of centerline 46 :param z: z-coordinate of centerline 47 :param W: channel width 48 :param D: channel depth""" 49 50 self.x = x 51 self.y = y 52 self.z = z 53 self.W = W 54 self.D = D
class for Cutoff objects
41 def __init__(self, x, y, z, W, D): 42 """Initialize Cutoff object 43 44 :param x: x-coordinate of centerline 45 :param y: y-coordinate of centerline 46 :param z: z-coordinate of centerline 47 :param W: channel width 48 :param D: channel depth""" 49 50 self.x = x 51 self.y = y 52 self.z = z 53 self.W = W 54 self.D = D
Initialize Cutoff object
Parameters
- x: x-coordinate of centerline
- y: y-coordinate of centerline
- z: z-coordinate of centerline
- W: channel width
- D: channel depth
57class ChannelBelt3D: 58 """class for 3D models of channel belts""" 59 60 def __init__(self, model_type, topo, strat, facies, facies_code, dx, channels): 61 """initialize ChannelBelt3D object 62 63 :param model_type: type of model to be built; can be either 'fluvial' or 'submarine' 64 :param topo: set of topographic surfaces (3D numpy array) 65 :param strat: set of stratigraphic surfaces (3D numpy array) 66 :param facies: facies volume (3D numpy array) 67 :param facies_code: dictionary of facies codes, e.g. {0:'oxbow', 1:'point bar', 2:'levee'} 68 :param dx: gridcell size (m) 69 :param channels: list of channel objects that form 3D model""" 70 71 self.model_type = model_type 72 self.topo = topo 73 self.strat = strat 74 self.facies = facies 75 self.facies_code = facies_code 76 self.dx = dx 77 self.channels = channels 78 79 def plot_xsection(self, xsec, colors, ve): 80 """method for plotting a cross section through a 3D model; also plots map of 81 basal erosional surface and map of final geomorphic surface 82 83 :param xsec: location of cross section along the x-axis (in pixel/ voxel coordinates) 84 :param colors: list of RGB values that define the colors for different facies 85 :param ve: vertical exaggeration 86 :return: handles to the three figures""" 87 88 strat = self.strat 89 dx = self.dx 90 fig1 = plt.figure(figsize=(20, 5)) 91 ax1 = fig1.add_subplot(111) 92 r, c, ts = np.shape(strat) 93 Xv = dx * np.arange(0, r) 94 for i in range(0, ts - 1, 3): 95 X1 = np.concatenate((Xv, Xv[::-1])) 96 Y1 = np.concatenate((strat[:, xsec, i], strat[::-1, xsec, i + 1])) 97 Y2 = np.concatenate((strat[:, xsec, i + 1], strat[::-1, xsec, i + 2])) 98 Y3 = np.concatenate((strat[:, xsec, i + 2], strat[::-1, xsec, i + 3])) 99 if self.model_type == "submarine": 100 ax1.fill( 101 X1, Y1, facecolor=colors[2], linewidth=0.5, edgecolor=[0, 0, 0] 102 ) # oxbow mud 103 ax1.fill( 104 X1, Y2, facecolor=colors[0], linewidth=0.5, edgecolor=[0, 0, 0] 105 ) # point bar sand 106 ax1.fill(X1, Y3, facecolor=colors[1], linewidth=0.5) # levee mud 107 if self.model_type == "fluvial": 108 ax1.fill( 109 X1, Y1, facecolor=colors[0], linewidth=0.5, edgecolor=[0, 0, 0] 110 ) # levee mud 111 ax1.fill( 112 X1, Y2, facecolor=colors[1], linewidth=0.5, edgecolor=[0, 0, 0] 113 ) # oxbow mud 114 ax1.fill(X1, Y3, facecolor=colors[2], linewidth=0.5) # channel sand 115 ax1.set_xlim(0, dx * (r - 1)) 116 ax1.set_aspect(ve, adjustable="datalim") 117 fig2 = plt.figure() 118 ax2 = fig2.add_subplot(111) 119 ax2.contourf(strat[:, :, ts - 1], 100, cmap="viridis") 120 ax2.contour( 121 strat[:, :, ts - 1], 122 100, 123 colors="k", 124 linestyles="solid", 125 linewidths=0.1, 126 alpha=0.4, 127 ) 128 ax2.plot([xsec, xsec], [0, r], "k", linewidth=2) 129 ax2.axis([0, c, 0, r]) 130 ax2.set_aspect("equal", adjustable="box") 131 ax2.set_title("final geomorphic surface") 132 ax2.tick_params( 133 bottom=False, 134 top=False, 135 left=False, 136 right=False, 137 labelbottom=False, 138 labelleft=False, 139 ) 140 fig3 = plt.figure() 141 ax3 = fig3.add_subplot(111) 142 ax3.contourf(strat[:, :, 0], 100, cmap="viridis") 143 ax3.contour( 144 strat[:, :, 0], 145 100, 146 colors="k", 147 linestyles="solid", 148 linewidths=0.1, 149 alpha=0.4, 150 ) 151 ax3.plot([xsec, xsec], [0, r], "k", linewidth=2) 152 ax3.axis([0, c, 0, r]) 153 ax3.set_aspect("equal", adjustable="box") 154 ax3.set_title("basal erosional surface") 155 ax3.tick_params( 156 bottom=False, 157 top=False, 158 left=False, 159 right=False, 160 labelbottom=False, 161 labelleft=False, 162 ) 163 return fig1, fig2, fig3
class for 3D models of channel belts
60 def __init__(self, model_type, topo, strat, facies, facies_code, dx, channels): 61 """initialize ChannelBelt3D object 62 63 :param model_type: type of model to be built; can be either 'fluvial' or 'submarine' 64 :param topo: set of topographic surfaces (3D numpy array) 65 :param strat: set of stratigraphic surfaces (3D numpy array) 66 :param facies: facies volume (3D numpy array) 67 :param facies_code: dictionary of facies codes, e.g. {0:'oxbow', 1:'point bar', 2:'levee'} 68 :param dx: gridcell size (m) 69 :param channels: list of channel objects that form 3D model""" 70 71 self.model_type = model_type 72 self.topo = topo 73 self.strat = strat 74 self.facies = facies 75 self.facies_code = facies_code 76 self.dx = dx 77 self.channels = channels
initialize ChannelBelt3D object
Parameters
- model_type: type of model to be built; can be either 'fluvial' or 'submarine'
- topo: set of topographic surfaces (3D numpy array)
- strat: set of stratigraphic surfaces (3D numpy array)
- facies: facies volume (3D numpy array)
- facies_code: dictionary of facies codes, e.g. {0:'oxbow', 1:'point bar', 2: 'levee'}
- dx: gridcell size (m)
- channels: list of channel objects that form 3D model
79 def plot_xsection(self, xsec, colors, ve): 80 """method for plotting a cross section through a 3D model; also plots map of 81 basal erosional surface and map of final geomorphic surface 82 83 :param xsec: location of cross section along the x-axis (in pixel/ voxel coordinates) 84 :param colors: list of RGB values that define the colors for different facies 85 :param ve: vertical exaggeration 86 :return: handles to the three figures""" 87 88 strat = self.strat 89 dx = self.dx 90 fig1 = plt.figure(figsize=(20, 5)) 91 ax1 = fig1.add_subplot(111) 92 r, c, ts = np.shape(strat) 93 Xv = dx * np.arange(0, r) 94 for i in range(0, ts - 1, 3): 95 X1 = np.concatenate((Xv, Xv[::-1])) 96 Y1 = np.concatenate((strat[:, xsec, i], strat[::-1, xsec, i + 1])) 97 Y2 = np.concatenate((strat[:, xsec, i + 1], strat[::-1, xsec, i + 2])) 98 Y3 = np.concatenate((strat[:, xsec, i + 2], strat[::-1, xsec, i + 3])) 99 if self.model_type == "submarine": 100 ax1.fill( 101 X1, Y1, facecolor=colors[2], linewidth=0.5, edgecolor=[0, 0, 0] 102 ) # oxbow mud 103 ax1.fill( 104 X1, Y2, facecolor=colors[0], linewidth=0.5, edgecolor=[0, 0, 0] 105 ) # point bar sand 106 ax1.fill(X1, Y3, facecolor=colors[1], linewidth=0.5) # levee mud 107 if self.model_type == "fluvial": 108 ax1.fill( 109 X1, Y1, facecolor=colors[0], linewidth=0.5, edgecolor=[0, 0, 0] 110 ) # levee mud 111 ax1.fill( 112 X1, Y2, facecolor=colors[1], linewidth=0.5, edgecolor=[0, 0, 0] 113 ) # oxbow mud 114 ax1.fill(X1, Y3, facecolor=colors[2], linewidth=0.5) # channel sand 115 ax1.set_xlim(0, dx * (r - 1)) 116 ax1.set_aspect(ve, adjustable="datalim") 117 fig2 = plt.figure() 118 ax2 = fig2.add_subplot(111) 119 ax2.contourf(strat[:, :, ts - 1], 100, cmap="viridis") 120 ax2.contour( 121 strat[:, :, ts - 1], 122 100, 123 colors="k", 124 linestyles="solid", 125 linewidths=0.1, 126 alpha=0.4, 127 ) 128 ax2.plot([xsec, xsec], [0, r], "k", linewidth=2) 129 ax2.axis([0, c, 0, r]) 130 ax2.set_aspect("equal", adjustable="box") 131 ax2.set_title("final geomorphic surface") 132 ax2.tick_params( 133 bottom=False, 134 top=False, 135 left=False, 136 right=False, 137 labelbottom=False, 138 labelleft=False, 139 ) 140 fig3 = plt.figure() 141 ax3 = fig3.add_subplot(111) 142 ax3.contourf(strat[:, :, 0], 100, cmap="viridis") 143 ax3.contour( 144 strat[:, :, 0], 145 100, 146 colors="k", 147 linestyles="solid", 148 linewidths=0.1, 149 alpha=0.4, 150 ) 151 ax3.plot([xsec, xsec], [0, r], "k", linewidth=2) 152 ax3.axis([0, c, 0, r]) 153 ax3.set_aspect("equal", adjustable="box") 154 ax3.set_title("basal erosional surface") 155 ax3.tick_params( 156 bottom=False, 157 top=False, 158 left=False, 159 right=False, 160 labelbottom=False, 161 labelleft=False, 162 ) 163 return fig1, fig2, fig3
method for plotting a cross section through a 3D model; also plots map of basal erosional surface and map of final geomorphic surface
Parameters
- xsec: location of cross section along the x-axis (in pixel/ voxel coordinates)
- colors: list of RGB values that define the colors for different facies
- ve: vertical exaggeration
Returns
handles to the three figures
166class ChannelBelt: 167 """class for ChannelBelt objects""" 168 169 def __init__(self, channels, cutoffs, cl_times, cutoff_times): 170 """initialize ChannelBelt object 171 172 :param channels: list of Channel objects 173 :param cutoffs: list of Cutoff objects 174 :param cl_times: list of ages of Channel objects (in years) 175 :param cutoff_times: list of ages of Cutoff objects""" 176 177 self.channels = channels 178 self.cutoffs = cutoffs 179 self.cl_times = cl_times 180 self.cutoff_times = cutoff_times 181 182 def migrate( 183 self, 184 nit, 185 saved_ts, 186 deltas, 187 pad, 188 crdist, 189 Cf, 190 kl, 191 kv, 192 dt, 193 dens, 194 t1, 195 t2, 196 t3, 197 aggr_factor, 198 *D 199 ): 200 """method for computing migration rates along channel centerlines and moving the centerlines accordingly 201 202 :param nit: number of iterations 203 :param saved_ts: which time steps will be saved; e.g., if saved_ts = 10, every tenth time step will be saved 204 :param deltas: distance between nodes on centerline 205 :param pad: padding (number of nodepoints along centerline) 206 :param crdist: threshold distance at which cutoffs occur 207 :param Cf: dimensionless Chezy friction factor 208 :param kl: migration rate constant (m/s) 209 :param kv: vertical slope-dependent erosion rate constant (m/s) 210 :param dt: time step (s) 211 :param dens: density of fluid (kg/m3) 212 :param t1: time step when incision starts 213 :param t2: time step when lateral migration starts 214 :param t3: time step when aggradation starts 215 :param aggr_factor: aggradation factor 216 :param D: channel depth (m)""" 217 218 channel = self.channels[ 219 -1 220 ] # first channel is the same as last channel of input 221 x = channel.x 222 y = channel.y 223 z = channel.z 224 W = channel.W 225 if len(D) == 0: 226 D = channel.D 227 else: 228 D = D[0] 229 k = 1.0 # constant in HK equation 230 xc = [] # initialize cutoff coordinates 231 # determine age of last channel: 232 if len(self.cl_times) > 0: 233 last_cl_time = self.cl_times[-1] 234 else: 235 last_cl_time = 0 236 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 237 slope = np.gradient(z) / ds 238 # padding at the beginning can be shorter than padding at the downstream end: 239 pad1 = int(pad / 10.0) 240 if pad1 < 5: 241 pad1 = 5 242 omega = ( 243 -1.0 244 ) # constant in migration rate calculation (Howard and Knutson, 1984) 245 gamma = 2.5 # from Ikeda et al., 1981 and Howard and Knutson, 1984 246 for itn in trange(nit): # main loop 247 # update_progress(itn/nit) 248 x, y = migrate_one_step( 249 x, y, z, W, kl, dt, k, Cf, D, pad, pad1, omega, gamma 250 ) 251 # x, y = migrate_one_step_w_bias(x,y,z,W,kl,dt,k,Cf,D,pad,pad1,omega,gamma) 252 x, y, z, xc, yc, zc = cut_off_cutoffs( 253 x, y, z, s, crdist, deltas 254 ) # find and execute cutoffs 255 x, y, z, dx, dy, dz, ds, s = resample_centerline( 256 x, y, z, deltas 257 ) # resample centerline 258 slope = np.gradient(z) / ds 259 # for itn<=t1, z is unchanged; for itn>t1: 260 if (itn > t1) & (itn <= t2): # incision 261 if np.min(np.abs(slope)) != 0: # if slope is not zero 262 z = z + kv * dens * 9.81 * D * slope * dt 263 else: 264 z = z - kv * dens * 9.81 * D * dt * 0.05 # if slope is zero 265 if (itn > t2) & (itn <= t3): # lateral migration 266 if np.min(np.abs(slope)) != 0: # if slope is not zero 267 # use the median slope to counterbalance incision: 268 z = ( 269 z 270 + kv * dens * 9.81 * D * slope * dt 271 - kv * dens * 9.81 * D * np.median(slope) * dt 272 ) 273 else: 274 z = z # no change in z 275 if itn > t3: # aggradation 276 if np.min(np.abs(slope)) != 0: # if slope is not zero 277 # 'aggr_factor' should be larger than 1 so that this leads to overall aggradation: 278 z = ( 279 z 280 + kv * dens * 9.81 * D * slope * dt 281 - aggr_factor * kv * dens * 9.81 * D * np.mean(slope) * dt 282 ) 283 else: 284 z = z + aggr_factor * dt 285 if len(xc) > 0: # save cutoff data 286 self.cutoff_times.append( 287 last_cl_time + (itn + 1) * dt / (365 * 24 * 60 * 60.0) 288 ) 289 cutoff = Cutoff(xc, yc, zc, W, D) # create cutoff object 290 self.cutoffs.append(cutoff) 291 # saving centerlines: 292 if np.mod(itn, saved_ts) == 0: 293 self.cl_times.append( 294 last_cl_time + (itn + 1) * dt / (365 * 24 * 60 * 60.0) 295 ) 296 channel = Channel(x, y, z, W, D) # create channel object 297 self.channels.append(channel) 298 299 def plot(self, plot_type, pb_age, ob_age, end_time, n_channels): 300 """method for plotting ChannelBelt object 301 302 :param plot_type: can be either 'strat' (for stratigraphic plot), 'morph' (for morphologic plot), or 'age' (for age plot) 303 :param pb_age: age of point bars (in years) at which they get covered by vegetation 304 :param ob_age: age of oxbow lakes (in years) at which they get covered by vegetation 305 :param end_time: age of last channel to be plotted (in years) 306 :param n_channels: total number of channels (used in 'age' plots; can be larger than number of channels being plotted) 307 :return: handle to figure""" 308 309 cot = np.array(self.cutoff_times) 310 sclt = np.array(self.cl_times) 311 if end_time > 0: 312 cot = cot[cot <= end_time] 313 sclt = sclt[sclt <= end_time] 314 times = np.sort(np.hstack((cot, sclt))) 315 times = np.unique(times) 316 order = 0 # variable for ordering objects in plot 317 # set up min and max x and y coordinates of the plot: 318 xmin = np.min(self.channels[0].x) 319 xmax = np.max(self.channels[0].x) 320 ymax = 0 321 for i in range(len(self.channels)): 322 ymax = max(ymax, np.max(np.abs(self.channels[i].y))) 323 ymax = ymax + 2 * self.channels[0].W # add a bit of space on top and bottom 324 ymin = -1 * ymax 325 # size figure so that its size matches the size of the model: 326 fig = plt.figure(figsize=(20, (ymax - ymin) * 20 / (xmax - xmin))) 327 if plot_type == "morph": 328 pb_crit = len(times[times < times[-1] - pb_age]) / float(len(times)) 329 ob_crit = len(times[times < times[-1] - ob_age]) / float(len(times)) 330 green = (106 / 255.0, 159 / 255.0, 67 / 255.0) # vegetation color 331 pb_color = (189 / 255.0, 153 / 255.0, 148 / 255.0) # point bar color 332 ob_color = (15 / 255.0, 58 / 255.0, 65 / 255.0) # oxbow color 333 pb_cmap = make_colormap( 334 [green, green, pb_crit, green, pb_color, 1.0, pb_color] 335 ) # colormap for point bars 336 ob_cmap = make_colormap( 337 [green, green, ob_crit, green, ob_color, 1.0, ob_color] 338 ) # colormap for oxbows 339 plt.fill( 340 [xmin, xmax, xmax, xmin], 341 [ymin, ymin, ymax, ymax], 342 color=(106 / 255.0, 159 / 255.0, 67 / 255.0), 343 ) 344 if plot_type == "age": 345 age_cmap = cm.get_cmap("magma", n_channels) 346 for i in range(0, len(times)): 347 if times[i] in sclt: 348 ind = np.where(sclt == times[i])[0][0] 349 x1 = self.channels[ind].x 350 y1 = self.channels[ind].y 351 W = self.channels[ind].W 352 xm, ym = get_channel_banks(x1, y1, W) 353 if plot_type == "morph": 354 if times[i] > times[-1] - pb_age: 355 plt.fill( 356 xm, 357 ym, 358 facecolor=pb_cmap(i / float(len(times) - 1)), 359 edgecolor="k", 360 linewidth=0.2, 361 ) 362 else: 363 plt.fill(xm, ym, facecolor=pb_cmap(i / float(len(times) - 1))) 364 if plot_type == "strat": 365 order += 1 366 plt.fill( 367 xm, 368 ym, 369 "xkcd:light tan", 370 edgecolor="k", 371 linewidth=0.25, 372 zorder=order, 373 ) 374 if plot_type == "age": 375 order += 1 376 plt.fill( 377 xm, 378 ym, 379 facecolor=age_cmap(i / float(n_channels - 1)), 380 edgecolor="k", 381 linewidth=0.1, 382 zorder=order, 383 ) 384 if times[i] in cot: 385 ind = np.where(cot == times[i])[0][0] 386 for j in range(0, len(self.cutoffs[ind].x)): 387 x1 = self.cutoffs[ind].x[j] 388 y1 = self.cutoffs[ind].y[j] 389 xm, ym = get_channel_banks(x1, y1, self.cutoffs[ind].W) 390 if plot_type == "morph": 391 plt.fill(xm, ym, color=ob_cmap(i / float(len(times) - 1))) 392 if plot_type == "strat": 393 order = order + 1 394 plt.fill( 395 xm, 396 ym, 397 "xkcd:ocean blue", 398 edgecolor="k", 399 linewidth=0.25, 400 zorder=order, 401 ) 402 if plot_type == "age": 403 order += 1 404 plt.fill( 405 xm, 406 ym, 407 "xkcd:sea blue", 408 edgecolor="k", 409 linewidth=0.1, 410 zorder=order, 411 ) 412 413 x1 = self.channels[len(sclt) - 1].x 414 y1 = self.channels[len(sclt) - 1].y 415 xm, ym = get_channel_banks(x1, y1, self.channels[len(sclt) - 1].W) 416 order = order + 1 417 if plot_type == "age": 418 plt.fill( 419 xm, 420 ym, 421 color="xkcd:sea blue", 422 zorder=order, 423 edgecolor="k", 424 linewidth=0.1, 425 ) 426 else: 427 plt.fill( 428 xm, ym, color=(16 / 255.0, 73 / 255.0, 90 / 255.0), zorder=order 429 ) # ,edgecolor='k') 430 plt.axis("equal") 431 plt.xlim(xmin, xmax) 432 plt.ylim(ymin, ymax) 433 return fig 434 435 def create_movie( 436 self, 437 xmin, 438 xmax, 439 plot_type, 440 filename, 441 dirname, 442 pb_age, 443 ob_age, 444 end_time, 445 n_channels, 446 ): 447 """method for creating movie frames (PNG files) that capture the plan-view evolution of a channel belt through time 448 movie has to be assembled from the PNG file after this method is applied 449 450 :param xmin: value of x coodinate on the left side of frame 451 :param xmax: value of x coordinate on right side of frame 452 :param plot_type: plot type; can be either 'strat' (for stratigraphic plot) or 'morph' (for morphologic plot) 453 :param filename: first few characters of the output filenames 454 :param dirname: name of directory where output files should be written 455 :param pb_age: age of point bars (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type') 456 :param ob_age: age of oxbow lakes (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type') 457 :param scale: scaling factor (e.g., 2) that determines how many times larger you want the frame to be, compared to the default scaling of the figure 458 :param end_time: time at which simulation should be stopped 459 :param n_channels: total number of channels + cutoffs for which simulation is run (usually it is len(chb.cutoffs) + len(chb.channels)). Used when plot_type = 'age'""" 460 461 sclt = np.array(self.cl_times) 462 if type(end_time) != list: 463 sclt = sclt[sclt <= end_time] 464 channels = self.channels[: len(sclt)] 465 ymax = 0 466 for i in range(len(channels)): 467 ymax = max(ymax, np.max(np.abs(channels[i].y))) 468 ymax = ymax + 2 * channels[0].W # add a bit of space on top and bottom 469 ymin = -1 * ymax 470 for i in range(0, len(sclt)): 471 fig = self.plot(plot_type, pb_age, ob_age, sclt[i], n_channels) 472 scale = 1 473 fig_height = scale * fig.get_figheight() 474 fig_width = (xmax - xmin) * fig_height / (ymax - ymin) 475 fig.set_figwidth(fig_width) 476 fig.set_figheight(fig_height) 477 fig.gca().set_xlim(xmin, xmax) 478 fig.gca().set_xticks([]) 479 fig.gca().set_yticks([]) 480 plt.plot( 481 [xmin + 200, xmin + 200 + 5000], 482 [ymin + 200, ymin + 200], 483 "k", 484 linewidth=2, 485 ) 486 plt.text(xmin + 200 + 2000, ymin + 200 + 100, "5 km", fontsize=14) 487 fname = dirname + filename + "%03d.png" % (i) 488 fig.savefig(fname, bbox_inches="tight") 489 plt.close() 490 491 def build_3d_model( 492 self, 493 model_type, 494 h_mud, 495 levee_width, 496 h, 497 w, 498 bth, 499 dcr, 500 dx, 501 delta_s, 502 starttime, 503 endtime, 504 xmin, 505 xmax, 506 ymin, 507 ymax, 508 ): 509 """method for building 3D model from set of centerlines (that are part of a ChannelBelt object) 510 511 :param model_type: model type ('fluvial' or 'submarine') 512 :param h_mud: maximum thickness of overbank mud 513 :param levee_width: width of overbank mud 514 :param h: channel depth 515 :param w: channel width 516 :param bth: thickness of channel sand (only used in submarine models) 517 :param dcr: critical channel depth where sand thickness goes to zero (only used in submarine models) 518 :param dx: cell size in x and y directions 519 :param delta_s: sampling distance alogn centerlines 520 :param starttime: age of centerline that will be used as the first centerline in the model 521 :param endtime: age of centerline that will be used as the last centerline in the model 522 :param xmin: minimum x coordinate that defines the model domain; if xmin is set to zero, 523 a plot of the centerlines is generated and the model domain has to be defined by clicking its upper left and lower right corners 524 :param xmax: maximum x coordinate that defines the model domain 525 :param ymin: minimum y coordinate that defines the model domain 526 :param ymax: maximum y coordinate that defines the model domain 527 :return chb_3d: a ChannelBelt3D object 528 :return xmin, xmax, ymin, ymax: x and y coordinates that define the model domain (so that they can be reused later)""" 529 530 sclt = np.array(self.cl_times) 531 ind1 = np.where(sclt >= starttime)[0][0] 532 ind2 = np.where(sclt <= endtime)[0][-1] 533 sclt = sclt[ind1 : ind2 + 1] 534 channels = self.channels[ind1 : ind2 + 1] 535 cot = np.array(self.cutoff_times) 536 if ( 537 (len(cot) > 0) 538 & (len(np.where(cot >= starttime)[0]) > 0) 539 & (len(np.where(cot <= endtime)[0]) > 0) 540 ): 541 cfind1 = np.where(cot >= starttime)[0][0] 542 cfind2 = np.where(cot <= endtime)[0][-1] 543 cot = cot[cfind1 : cfind2 + 1] 544 cutoffs = self.cutoffs[cfind1 : cfind2 + 1] 545 else: 546 cot = [] 547 cutoffs = [] 548 n_steps = len(sclt) # number of events 549 if xmin == 0: # plot centerlines and define model domain 550 plt.figure(figsize=(15, 4)) 551 maxX, minY, maxY = 0, 0, 0 552 for i in range(n_steps): # plot centerlines 553 plt.plot(channels[i].x, channels[i].y, "k") 554 maxX = max(maxX, np.max(channels[i].x)) 555 maxY = max(maxY, np.max(channels[i].y)) 556 minY = min(minY, np.min(channels[i].y)) 557 plt.axis([0, maxX, minY - 10 * w, maxY + 10 * w]) 558 plt.gca().set_aspect("equal", adjustable="box") 559 plt.tight_layout() 560 pts = np.zeros((2, 2)) 561 for i in range(0, 2): 562 pt = np.asarray(plt.ginput(1)) 563 pts[i, :] = pt 564 plt.scatter(pt[0][0], pt[0][1]) 565 plt.plot( 566 [pts[0, 0], pts[1, 0], pts[1, 0], pts[0, 0], pts[0, 0]], 567 [pts[0, 1], pts[0, 1], pts[1, 1], pts[1, 1], pts[0, 1]], 568 "r", 569 ) 570 xmin = min(pts[0, 0], pts[1, 0]) 571 xmax = max(pts[0, 0], pts[1, 0]) 572 ymin = min(pts[0, 1], pts[1, 1]) 573 ymax = max(pts[0, 1], pts[1, 1]) 574 iwidth = int((xmax - xmin) / dx) 575 iheight = int((ymax - ymin) / dx) 576 topo = np.zeros( 577 (iheight, iwidth, 4 * n_steps) 578 ) # array for storing topographic surfaces 579 dists = np.zeros((iheight, iwidth, n_steps)) 580 zmaps = np.zeros((iheight, iwidth, n_steps)) 581 facies = np.zeros((4 * n_steps, 1)) 582 # create initial topography: 583 x1 = np.linspace(0, iwidth - 1, iwidth) 584 y1 = np.linspace(0, iheight - 1, iheight) 585 xv, yv = np.meshgrid(x1, y1) 586 z1 = channels[0].z 587 z1 = z1[(channels[0].x > xmin) & (channels[0].x < xmax)] 588 topoinit = ( 589 z1[0] - ((z1[0] - z1[-1]) / (xmax - xmin)) * xv * dx 590 ) # initial (sloped) topography 591 topo[:, :, 0] = topoinit.copy() 592 surf = topoinit.copy() 593 facies[0] = np.NaN 594 # generate surfaces: 595 channels3D = [] 596 for i in trange(n_steps): 597 x = channels[i].x 598 y = channels[i].y 599 z = channels[i].z 600 cutoff_ind = [] 601 # check if there were cutoffs during the last time step and collect indices in an array: 602 for j in range(len(cot)): 603 if (cot[j] >= sclt[i - 1]) & (cot[j] < sclt[i]): 604 cutoff_ind = np.append(cutoff_ind, j) 605 # create distance map: 606 cl_dist, x_pix, y_pix, z_pix, s_pix, z_map, x1, y1, z1 = dist_map( 607 x, y, z, xmin, xmax, ymin, ymax, dx, delta_s 608 ) 609 if i == 0: 610 cl_dist_prev = cl_dist 611 # erosion: 612 surf = np.minimum(surf, erosion_surface(h, w / dx, cl_dist, z_map)) 613 topo[:, :, 4 * i] = surf # erosional surface 614 dists[:, :, i] = cl_dist 615 zmaps[:, :, i] = z_map 616 facies[4 * i] = np.NaN 617 618 if model_type == "fluvial": 619 pb = point_bar_surface(cl_dist, z_map, h, w / dx) 620 th = np.maximum(surf, pb) - surf 621 th_oxbows = th.copy() 622 # setting sand thickness to zero at cutoff locations: 623 if len(cutoff_ind) > 0: 624 cutoff_dists = 1e10 * np.ones( 625 np.shape(th) 626 ) # initialize cutoff_dists with a large number 627 for j in range(len(cutoff_ind)): 628 cutoff_dist, cfx_pix, cfy_pix = cl_dist_map( 629 cutoffs[int(cutoff_ind[j])].x[0], 630 cutoffs[int(cutoff_ind[j])].y[0], 631 cutoffs[int(cutoff_ind[j])].z[0], 632 xmin, 633 xmax, 634 ymin, 635 ymax, 636 dx, 637 ) 638 cutoff_dists = np.minimum(cutoff_dists, cutoff_dist) 639 th_oxbows[ 640 cutoff_dists >= 0.9 * w / dx 641 ] = 0 # set oxbow fill thickness to zero outside of oxbows 642 th[ 643 cutoff_dists < 0.9 * w / dx 644 ] = 0 # set point bar thickness to zero inside of oxbows 645 else: # no cutoffs 646 th_oxbows = np.zeros(np.shape(th)) 647 th[th < 0] = 0 # eliminate negative th values 648 surf = ( 649 surf + th_oxbows 650 ) # update topographic surface with oxbow deposit thickness 651 topo[:, :, 4 * i + 1] = surf # top of oxbow mud 652 facies[4 * i + 1] = 0 653 surf = surf + th # update topographic surface with sand thickness 654 topo[:, :, 4 * i + 2] = surf # top of sand 655 facies[4 * i + 2] = 1 656 surf = surf + mud_surface( 657 h_mud, levee_width / dx, cl_dist, w / dx, z_map, surf 658 ) # mud/levee deposition 659 topo[:, :, 4 * i + 3] = surf # top of levee 660 facies[4 * i + 3] = 2 661 channels3D.append(Channel(x1 - xmin, y1 - ymin, z1, w, h)) 662 663 if model_type == "submarine": 664 surf = surf + mud_surface( 665 h_mud[i], levee_width / dx, cl_dist, w / dx, z_map, surf 666 ) # mud/levee deposition 667 topo[:, :, 4 * i + 1] = surf # top of levee 668 facies[4 * i + 1] = 2 669 # sand thickness: 670 th, relief = sand_surface(surf, bth, dcr, z_map, h) 671 th[th < 0] = 0 # eliminate negative th values 672 th[cl_dist > 1.0 * w / dx] = 0 # eliminate sand outside of channel 673 th_oxbows = th.copy() 674 # setting sand thickness to zero at cutoff locations: 675 if len(cutoff_ind) > 0: 676 cutoff_dists = 1e10 * np.ones( 677 np.shape(th) 678 ) # initialize cutoff_dists with a large number 679 for j in range(len(cutoff_ind)): 680 cutoff_dist, cfx_pix, cfy_pix = cl_dist_map( 681 cutoffs[int(cutoff_ind[j])].x[0], 682 cutoffs[int(cutoff_ind[j])].y[0], 683 cutoffs[int(cutoff_ind[j])].z[0], 684 xmin, 685 xmax, 686 ymin, 687 ymax, 688 dx, 689 ) 690 cutoff_dists = np.minimum(cutoff_dists, cutoff_dist) 691 th_oxbows[ 692 cutoff_dists >= 0.9 * w / dx 693 ] = 0 # set oxbow fill thickness to zero outside of oxbows 694 th[ 695 cutoff_dists < 0.9 * w / dx 696 ] = 0 # set point bar thickness to zero inside of oxbows 697 # adding back sand near the channel axis (submarine only): 698 # th[cl_dist<0.5*w/dx] = bth*(1 - relief[cl_dist<0.5*w/dx]/dcr) 699 else: # no cutoffs 700 th_oxbows = np.zeros(np.shape(th)) 701 surf = ( 702 surf + th_oxbows 703 ) # update topographic surface with oxbow deposit thickness 704 topo[:, :, 4 * i + 2] = surf # top of oxbow mud 705 facies[4 * i + 2] = 0 706 surf = surf + th # update topographic surface with sand thickness 707 topo[:, :, 4 * i + 3] = surf # top of sand 708 facies[4 * i + 3] = 1 709 channels3D.append(Channel(x1 - xmin, y1 - ymin, z1, w, h)) 710 711 cl_dist_prev = cl_dist.copy() 712 topo = np.concatenate( 713 (np.reshape(topoinit, (iheight, iwidth, 1)), topo), axis=2 714 ) # add initial topography to array 715 strat = topostrat(topo) # create stratigraphic surfaces 716 strat = np.delete( 717 strat, np.arange(4 * n_steps + 1)[1::4], 2 718 ) # get rid of unnecessary stratigraphic surfaces (duplicates) 719 facies = np.delete( 720 facies, np.arange(4 * n_steps)[::4] 721 ) # get rid of unnecessary facies layers (NaNs) 722 if model_type == "fluvial": 723 facies_code = {0: "oxbow", 1: "point bar", 2: "levee"} 724 if model_type == "submarine": 725 facies_code = {0: "oxbow", 1: "channel sand", 2: "levee"} 726 chb_3d = ChannelBelt3D( 727 model_type, topo, strat, facies, facies_code, dx, channels3D 728 ) 729 return chb_3d, xmin, xmax, ymin, ymax # , dists, zmaps
class for ChannelBelt objects
169 def __init__(self, channels, cutoffs, cl_times, cutoff_times): 170 """initialize ChannelBelt object 171 172 :param channels: list of Channel objects 173 :param cutoffs: list of Cutoff objects 174 :param cl_times: list of ages of Channel objects (in years) 175 :param cutoff_times: list of ages of Cutoff objects""" 176 177 self.channels = channels 178 self.cutoffs = cutoffs 179 self.cl_times = cl_times 180 self.cutoff_times = cutoff_times
initialize ChannelBelt object
Parameters
- channels: list of Channel objects
- cutoffs: list of Cutoff objects
- cl_times: list of ages of Channel objects (in years)
- cutoff_times: list of ages of Cutoff objects
182 def migrate( 183 self, 184 nit, 185 saved_ts, 186 deltas, 187 pad, 188 crdist, 189 Cf, 190 kl, 191 kv, 192 dt, 193 dens, 194 t1, 195 t2, 196 t3, 197 aggr_factor, 198 *D 199 ): 200 """method for computing migration rates along channel centerlines and moving the centerlines accordingly 201 202 :param nit: number of iterations 203 :param saved_ts: which time steps will be saved; e.g., if saved_ts = 10, every tenth time step will be saved 204 :param deltas: distance between nodes on centerline 205 :param pad: padding (number of nodepoints along centerline) 206 :param crdist: threshold distance at which cutoffs occur 207 :param Cf: dimensionless Chezy friction factor 208 :param kl: migration rate constant (m/s) 209 :param kv: vertical slope-dependent erosion rate constant (m/s) 210 :param dt: time step (s) 211 :param dens: density of fluid (kg/m3) 212 :param t1: time step when incision starts 213 :param t2: time step when lateral migration starts 214 :param t3: time step when aggradation starts 215 :param aggr_factor: aggradation factor 216 :param D: channel depth (m)""" 217 218 channel = self.channels[ 219 -1 220 ] # first channel is the same as last channel of input 221 x = channel.x 222 y = channel.y 223 z = channel.z 224 W = channel.W 225 if len(D) == 0: 226 D = channel.D 227 else: 228 D = D[0] 229 k = 1.0 # constant in HK equation 230 xc = [] # initialize cutoff coordinates 231 # determine age of last channel: 232 if len(self.cl_times) > 0: 233 last_cl_time = self.cl_times[-1] 234 else: 235 last_cl_time = 0 236 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 237 slope = np.gradient(z) / ds 238 # padding at the beginning can be shorter than padding at the downstream end: 239 pad1 = int(pad / 10.0) 240 if pad1 < 5: 241 pad1 = 5 242 omega = ( 243 -1.0 244 ) # constant in migration rate calculation (Howard and Knutson, 1984) 245 gamma = 2.5 # from Ikeda et al., 1981 and Howard and Knutson, 1984 246 for itn in trange(nit): # main loop 247 # update_progress(itn/nit) 248 x, y = migrate_one_step( 249 x, y, z, W, kl, dt, k, Cf, D, pad, pad1, omega, gamma 250 ) 251 # x, y = migrate_one_step_w_bias(x,y,z,W,kl,dt,k,Cf,D,pad,pad1,omega,gamma) 252 x, y, z, xc, yc, zc = cut_off_cutoffs( 253 x, y, z, s, crdist, deltas 254 ) # find and execute cutoffs 255 x, y, z, dx, dy, dz, ds, s = resample_centerline( 256 x, y, z, deltas 257 ) # resample centerline 258 slope = np.gradient(z) / ds 259 # for itn<=t1, z is unchanged; for itn>t1: 260 if (itn > t1) & (itn <= t2): # incision 261 if np.min(np.abs(slope)) != 0: # if slope is not zero 262 z = z + kv * dens * 9.81 * D * slope * dt 263 else: 264 z = z - kv * dens * 9.81 * D * dt * 0.05 # if slope is zero 265 if (itn > t2) & (itn <= t3): # lateral migration 266 if np.min(np.abs(slope)) != 0: # if slope is not zero 267 # use the median slope to counterbalance incision: 268 z = ( 269 z 270 + kv * dens * 9.81 * D * slope * dt 271 - kv * dens * 9.81 * D * np.median(slope) * dt 272 ) 273 else: 274 z = z # no change in z 275 if itn > t3: # aggradation 276 if np.min(np.abs(slope)) != 0: # if slope is not zero 277 # 'aggr_factor' should be larger than 1 so that this leads to overall aggradation: 278 z = ( 279 z 280 + kv * dens * 9.81 * D * slope * dt 281 - aggr_factor * kv * dens * 9.81 * D * np.mean(slope) * dt 282 ) 283 else: 284 z = z + aggr_factor * dt 285 if len(xc) > 0: # save cutoff data 286 self.cutoff_times.append( 287 last_cl_time + (itn + 1) * dt / (365 * 24 * 60 * 60.0) 288 ) 289 cutoff = Cutoff(xc, yc, zc, W, D) # create cutoff object 290 self.cutoffs.append(cutoff) 291 # saving centerlines: 292 if np.mod(itn, saved_ts) == 0: 293 self.cl_times.append( 294 last_cl_time + (itn + 1) * dt / (365 * 24 * 60 * 60.0) 295 ) 296 channel = Channel(x, y, z, W, D) # create channel object 297 self.channels.append(channel)
method for computing migration rates along channel centerlines and moving the centerlines accordingly
Parameters
- nit: number of iterations
- saved_ts: which time steps will be saved; e.g., if saved_ts = 10, every tenth time step will be saved
- deltas: distance between nodes on centerline
- pad: padding (number of nodepoints along centerline)
- crdist: threshold distance at which cutoffs occur
- Cf: dimensionless Chezy friction factor
- kl: migration rate constant (m/s)
- kv: vertical slope-dependent erosion rate constant (m/s)
- dt: time step (s)
- dens: density of fluid (kg/m3)
- t1: time step when incision starts
- t2: time step when lateral migration starts
- t3: time step when aggradation starts
- aggr_factor: aggradation factor
- D: channel depth (m)
299 def plot(self, plot_type, pb_age, ob_age, end_time, n_channels): 300 """method for plotting ChannelBelt object 301 302 :param plot_type: can be either 'strat' (for stratigraphic plot), 'morph' (for morphologic plot), or 'age' (for age plot) 303 :param pb_age: age of point bars (in years) at which they get covered by vegetation 304 :param ob_age: age of oxbow lakes (in years) at which they get covered by vegetation 305 :param end_time: age of last channel to be plotted (in years) 306 :param n_channels: total number of channels (used in 'age' plots; can be larger than number of channels being plotted) 307 :return: handle to figure""" 308 309 cot = np.array(self.cutoff_times) 310 sclt = np.array(self.cl_times) 311 if end_time > 0: 312 cot = cot[cot <= end_time] 313 sclt = sclt[sclt <= end_time] 314 times = np.sort(np.hstack((cot, sclt))) 315 times = np.unique(times) 316 order = 0 # variable for ordering objects in plot 317 # set up min and max x and y coordinates of the plot: 318 xmin = np.min(self.channels[0].x) 319 xmax = np.max(self.channels[0].x) 320 ymax = 0 321 for i in range(len(self.channels)): 322 ymax = max(ymax, np.max(np.abs(self.channels[i].y))) 323 ymax = ymax + 2 * self.channels[0].W # add a bit of space on top and bottom 324 ymin = -1 * ymax 325 # size figure so that its size matches the size of the model: 326 fig = plt.figure(figsize=(20, (ymax - ymin) * 20 / (xmax - xmin))) 327 if plot_type == "morph": 328 pb_crit = len(times[times < times[-1] - pb_age]) / float(len(times)) 329 ob_crit = len(times[times < times[-1] - ob_age]) / float(len(times)) 330 green = (106 / 255.0, 159 / 255.0, 67 / 255.0) # vegetation color 331 pb_color = (189 / 255.0, 153 / 255.0, 148 / 255.0) # point bar color 332 ob_color = (15 / 255.0, 58 / 255.0, 65 / 255.0) # oxbow color 333 pb_cmap = make_colormap( 334 [green, green, pb_crit, green, pb_color, 1.0, pb_color] 335 ) # colormap for point bars 336 ob_cmap = make_colormap( 337 [green, green, ob_crit, green, ob_color, 1.0, ob_color] 338 ) # colormap for oxbows 339 plt.fill( 340 [xmin, xmax, xmax, xmin], 341 [ymin, ymin, ymax, ymax], 342 color=(106 / 255.0, 159 / 255.0, 67 / 255.0), 343 ) 344 if plot_type == "age": 345 age_cmap = cm.get_cmap("magma", n_channels) 346 for i in range(0, len(times)): 347 if times[i] in sclt: 348 ind = np.where(sclt == times[i])[0][0] 349 x1 = self.channels[ind].x 350 y1 = self.channels[ind].y 351 W = self.channels[ind].W 352 xm, ym = get_channel_banks(x1, y1, W) 353 if plot_type == "morph": 354 if times[i] > times[-1] - pb_age: 355 plt.fill( 356 xm, 357 ym, 358 facecolor=pb_cmap(i / float(len(times) - 1)), 359 edgecolor="k", 360 linewidth=0.2, 361 ) 362 else: 363 plt.fill(xm, ym, facecolor=pb_cmap(i / float(len(times) - 1))) 364 if plot_type == "strat": 365 order += 1 366 plt.fill( 367 xm, 368 ym, 369 "xkcd:light tan", 370 edgecolor="k", 371 linewidth=0.25, 372 zorder=order, 373 ) 374 if plot_type == "age": 375 order += 1 376 plt.fill( 377 xm, 378 ym, 379 facecolor=age_cmap(i / float(n_channels - 1)), 380 edgecolor="k", 381 linewidth=0.1, 382 zorder=order, 383 ) 384 if times[i] in cot: 385 ind = np.where(cot == times[i])[0][0] 386 for j in range(0, len(self.cutoffs[ind].x)): 387 x1 = self.cutoffs[ind].x[j] 388 y1 = self.cutoffs[ind].y[j] 389 xm, ym = get_channel_banks(x1, y1, self.cutoffs[ind].W) 390 if plot_type == "morph": 391 plt.fill(xm, ym, color=ob_cmap(i / float(len(times) - 1))) 392 if plot_type == "strat": 393 order = order + 1 394 plt.fill( 395 xm, 396 ym, 397 "xkcd:ocean blue", 398 edgecolor="k", 399 linewidth=0.25, 400 zorder=order, 401 ) 402 if plot_type == "age": 403 order += 1 404 plt.fill( 405 xm, 406 ym, 407 "xkcd:sea blue", 408 edgecolor="k", 409 linewidth=0.1, 410 zorder=order, 411 ) 412 413 x1 = self.channels[len(sclt) - 1].x 414 y1 = self.channels[len(sclt) - 1].y 415 xm, ym = get_channel_banks(x1, y1, self.channels[len(sclt) - 1].W) 416 order = order + 1 417 if plot_type == "age": 418 plt.fill( 419 xm, 420 ym, 421 color="xkcd:sea blue", 422 zorder=order, 423 edgecolor="k", 424 linewidth=0.1, 425 ) 426 else: 427 plt.fill( 428 xm, ym, color=(16 / 255.0, 73 / 255.0, 90 / 255.0), zorder=order 429 ) # ,edgecolor='k') 430 plt.axis("equal") 431 plt.xlim(xmin, xmax) 432 plt.ylim(ymin, ymax) 433 return fig
method for plotting ChannelBelt object
Parameters
- plot_type: can be either 'strat' (for stratigraphic plot), 'morph' (for morphologic plot), or 'age' (for age plot)
- pb_age: age of point bars (in years) at which they get covered by vegetation
- ob_age: age of oxbow lakes (in years) at which they get covered by vegetation
- end_time: age of last channel to be plotted (in years)
- n_channels: total number of channels (used in 'age' plots; can be larger than number of channels being plotted)
Returns
handle to figure
435 def create_movie( 436 self, 437 xmin, 438 xmax, 439 plot_type, 440 filename, 441 dirname, 442 pb_age, 443 ob_age, 444 end_time, 445 n_channels, 446 ): 447 """method for creating movie frames (PNG files) that capture the plan-view evolution of a channel belt through time 448 movie has to be assembled from the PNG file after this method is applied 449 450 :param xmin: value of x coodinate on the left side of frame 451 :param xmax: value of x coordinate on right side of frame 452 :param plot_type: plot type; can be either 'strat' (for stratigraphic plot) or 'morph' (for morphologic plot) 453 :param filename: first few characters of the output filenames 454 :param dirname: name of directory where output files should be written 455 :param pb_age: age of point bars (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type') 456 :param ob_age: age of oxbow lakes (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type') 457 :param scale: scaling factor (e.g., 2) that determines how many times larger you want the frame to be, compared to the default scaling of the figure 458 :param end_time: time at which simulation should be stopped 459 :param n_channels: total number of channels + cutoffs for which simulation is run (usually it is len(chb.cutoffs) + len(chb.channels)). Used when plot_type = 'age'""" 460 461 sclt = np.array(self.cl_times) 462 if type(end_time) != list: 463 sclt = sclt[sclt <= end_time] 464 channels = self.channels[: len(sclt)] 465 ymax = 0 466 for i in range(len(channels)): 467 ymax = max(ymax, np.max(np.abs(channels[i].y))) 468 ymax = ymax + 2 * channels[0].W # add a bit of space on top and bottom 469 ymin = -1 * ymax 470 for i in range(0, len(sclt)): 471 fig = self.plot(plot_type, pb_age, ob_age, sclt[i], n_channels) 472 scale = 1 473 fig_height = scale * fig.get_figheight() 474 fig_width = (xmax - xmin) * fig_height / (ymax - ymin) 475 fig.set_figwidth(fig_width) 476 fig.set_figheight(fig_height) 477 fig.gca().set_xlim(xmin, xmax) 478 fig.gca().set_xticks([]) 479 fig.gca().set_yticks([]) 480 plt.plot( 481 [xmin + 200, xmin + 200 + 5000], 482 [ymin + 200, ymin + 200], 483 "k", 484 linewidth=2, 485 ) 486 plt.text(xmin + 200 + 2000, ymin + 200 + 100, "5 km", fontsize=14) 487 fname = dirname + filename + "%03d.png" % (i) 488 fig.savefig(fname, bbox_inches="tight") 489 plt.close()
method for creating movie frames (PNG files) that capture the plan-view evolution of a channel belt through time movie has to be assembled from the PNG file after this method is applied
Parameters
- xmin: value of x coodinate on the left side of frame
- xmax: value of x coordinate on right side of frame
- plot_type: plot type; can be either 'strat' (for stratigraphic plot) or 'morph' (for morphologic plot)
- filename: first few characters of the output filenames
- dirname: name of directory where output files should be written
- pb_age: age of point bars (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type')
- ob_age: age of oxbow lakes (in years) at which they get covered by vegetation (if the 'morph' option is used for 'plot_type')
- scale: scaling factor (e.g., 2) that determines how many times larger you want the frame to be, compared to the default scaling of the figure
- end_time: time at which simulation should be stopped
- n_channels: total number of channels + cutoffs for which simulation is run (usually it is len(chb.cutoffs) + len(chb.channels)). Used when plot_type = 'age'
491 def build_3d_model( 492 self, 493 model_type, 494 h_mud, 495 levee_width, 496 h, 497 w, 498 bth, 499 dcr, 500 dx, 501 delta_s, 502 starttime, 503 endtime, 504 xmin, 505 xmax, 506 ymin, 507 ymax, 508 ): 509 """method for building 3D model from set of centerlines (that are part of a ChannelBelt object) 510 511 :param model_type: model type ('fluvial' or 'submarine') 512 :param h_mud: maximum thickness of overbank mud 513 :param levee_width: width of overbank mud 514 :param h: channel depth 515 :param w: channel width 516 :param bth: thickness of channel sand (only used in submarine models) 517 :param dcr: critical channel depth where sand thickness goes to zero (only used in submarine models) 518 :param dx: cell size in x and y directions 519 :param delta_s: sampling distance alogn centerlines 520 :param starttime: age of centerline that will be used as the first centerline in the model 521 :param endtime: age of centerline that will be used as the last centerline in the model 522 :param xmin: minimum x coordinate that defines the model domain; if xmin is set to zero, 523 a plot of the centerlines is generated and the model domain has to be defined by clicking its upper left and lower right corners 524 :param xmax: maximum x coordinate that defines the model domain 525 :param ymin: minimum y coordinate that defines the model domain 526 :param ymax: maximum y coordinate that defines the model domain 527 :return chb_3d: a ChannelBelt3D object 528 :return xmin, xmax, ymin, ymax: x and y coordinates that define the model domain (so that they can be reused later)""" 529 530 sclt = np.array(self.cl_times) 531 ind1 = np.where(sclt >= starttime)[0][0] 532 ind2 = np.where(sclt <= endtime)[0][-1] 533 sclt = sclt[ind1 : ind2 + 1] 534 channels = self.channels[ind1 : ind2 + 1] 535 cot = np.array(self.cutoff_times) 536 if ( 537 (len(cot) > 0) 538 & (len(np.where(cot >= starttime)[0]) > 0) 539 & (len(np.where(cot <= endtime)[0]) > 0) 540 ): 541 cfind1 = np.where(cot >= starttime)[0][0] 542 cfind2 = np.where(cot <= endtime)[0][-1] 543 cot = cot[cfind1 : cfind2 + 1] 544 cutoffs = self.cutoffs[cfind1 : cfind2 + 1] 545 else: 546 cot = [] 547 cutoffs = [] 548 n_steps = len(sclt) # number of events 549 if xmin == 0: # plot centerlines and define model domain 550 plt.figure(figsize=(15, 4)) 551 maxX, minY, maxY = 0, 0, 0 552 for i in range(n_steps): # plot centerlines 553 plt.plot(channels[i].x, channels[i].y, "k") 554 maxX = max(maxX, np.max(channels[i].x)) 555 maxY = max(maxY, np.max(channels[i].y)) 556 minY = min(minY, np.min(channels[i].y)) 557 plt.axis([0, maxX, minY - 10 * w, maxY + 10 * w]) 558 plt.gca().set_aspect("equal", adjustable="box") 559 plt.tight_layout() 560 pts = np.zeros((2, 2)) 561 for i in range(0, 2): 562 pt = np.asarray(plt.ginput(1)) 563 pts[i, :] = pt 564 plt.scatter(pt[0][0], pt[0][1]) 565 plt.plot( 566 [pts[0, 0], pts[1, 0], pts[1, 0], pts[0, 0], pts[0, 0]], 567 [pts[0, 1], pts[0, 1], pts[1, 1], pts[1, 1], pts[0, 1]], 568 "r", 569 ) 570 xmin = min(pts[0, 0], pts[1, 0]) 571 xmax = max(pts[0, 0], pts[1, 0]) 572 ymin = min(pts[0, 1], pts[1, 1]) 573 ymax = max(pts[0, 1], pts[1, 1]) 574 iwidth = int((xmax - xmin) / dx) 575 iheight = int((ymax - ymin) / dx) 576 topo = np.zeros( 577 (iheight, iwidth, 4 * n_steps) 578 ) # array for storing topographic surfaces 579 dists = np.zeros((iheight, iwidth, n_steps)) 580 zmaps = np.zeros((iheight, iwidth, n_steps)) 581 facies = np.zeros((4 * n_steps, 1)) 582 # create initial topography: 583 x1 = np.linspace(0, iwidth - 1, iwidth) 584 y1 = np.linspace(0, iheight - 1, iheight) 585 xv, yv = np.meshgrid(x1, y1) 586 z1 = channels[0].z 587 z1 = z1[(channels[0].x > xmin) & (channels[0].x < xmax)] 588 topoinit = ( 589 z1[0] - ((z1[0] - z1[-1]) / (xmax - xmin)) * xv * dx 590 ) # initial (sloped) topography 591 topo[:, :, 0] = topoinit.copy() 592 surf = topoinit.copy() 593 facies[0] = np.NaN 594 # generate surfaces: 595 channels3D = [] 596 for i in trange(n_steps): 597 x = channels[i].x 598 y = channels[i].y 599 z = channels[i].z 600 cutoff_ind = [] 601 # check if there were cutoffs during the last time step and collect indices in an array: 602 for j in range(len(cot)): 603 if (cot[j] >= sclt[i - 1]) & (cot[j] < sclt[i]): 604 cutoff_ind = np.append(cutoff_ind, j) 605 # create distance map: 606 cl_dist, x_pix, y_pix, z_pix, s_pix, z_map, x1, y1, z1 = dist_map( 607 x, y, z, xmin, xmax, ymin, ymax, dx, delta_s 608 ) 609 if i == 0: 610 cl_dist_prev = cl_dist 611 # erosion: 612 surf = np.minimum(surf, erosion_surface(h, w / dx, cl_dist, z_map)) 613 topo[:, :, 4 * i] = surf # erosional surface 614 dists[:, :, i] = cl_dist 615 zmaps[:, :, i] = z_map 616 facies[4 * i] = np.NaN 617 618 if model_type == "fluvial": 619 pb = point_bar_surface(cl_dist, z_map, h, w / dx) 620 th = np.maximum(surf, pb) - surf 621 th_oxbows = th.copy() 622 # setting sand thickness to zero at cutoff locations: 623 if len(cutoff_ind) > 0: 624 cutoff_dists = 1e10 * np.ones( 625 np.shape(th) 626 ) # initialize cutoff_dists with a large number 627 for j in range(len(cutoff_ind)): 628 cutoff_dist, cfx_pix, cfy_pix = cl_dist_map( 629 cutoffs[int(cutoff_ind[j])].x[0], 630 cutoffs[int(cutoff_ind[j])].y[0], 631 cutoffs[int(cutoff_ind[j])].z[0], 632 xmin, 633 xmax, 634 ymin, 635 ymax, 636 dx, 637 ) 638 cutoff_dists = np.minimum(cutoff_dists, cutoff_dist) 639 th_oxbows[ 640 cutoff_dists >= 0.9 * w / dx 641 ] = 0 # set oxbow fill thickness to zero outside of oxbows 642 th[ 643 cutoff_dists < 0.9 * w / dx 644 ] = 0 # set point bar thickness to zero inside of oxbows 645 else: # no cutoffs 646 th_oxbows = np.zeros(np.shape(th)) 647 th[th < 0] = 0 # eliminate negative th values 648 surf = ( 649 surf + th_oxbows 650 ) # update topographic surface with oxbow deposit thickness 651 topo[:, :, 4 * i + 1] = surf # top of oxbow mud 652 facies[4 * i + 1] = 0 653 surf = surf + th # update topographic surface with sand thickness 654 topo[:, :, 4 * i + 2] = surf # top of sand 655 facies[4 * i + 2] = 1 656 surf = surf + mud_surface( 657 h_mud, levee_width / dx, cl_dist, w / dx, z_map, surf 658 ) # mud/levee deposition 659 topo[:, :, 4 * i + 3] = surf # top of levee 660 facies[4 * i + 3] = 2 661 channels3D.append(Channel(x1 - xmin, y1 - ymin, z1, w, h)) 662 663 if model_type == "submarine": 664 surf = surf + mud_surface( 665 h_mud[i], levee_width / dx, cl_dist, w / dx, z_map, surf 666 ) # mud/levee deposition 667 topo[:, :, 4 * i + 1] = surf # top of levee 668 facies[4 * i + 1] = 2 669 # sand thickness: 670 th, relief = sand_surface(surf, bth, dcr, z_map, h) 671 th[th < 0] = 0 # eliminate negative th values 672 th[cl_dist > 1.0 * w / dx] = 0 # eliminate sand outside of channel 673 th_oxbows = th.copy() 674 # setting sand thickness to zero at cutoff locations: 675 if len(cutoff_ind) > 0: 676 cutoff_dists = 1e10 * np.ones( 677 np.shape(th) 678 ) # initialize cutoff_dists with a large number 679 for j in range(len(cutoff_ind)): 680 cutoff_dist, cfx_pix, cfy_pix = cl_dist_map( 681 cutoffs[int(cutoff_ind[j])].x[0], 682 cutoffs[int(cutoff_ind[j])].y[0], 683 cutoffs[int(cutoff_ind[j])].z[0], 684 xmin, 685 xmax, 686 ymin, 687 ymax, 688 dx, 689 ) 690 cutoff_dists = np.minimum(cutoff_dists, cutoff_dist) 691 th_oxbows[ 692 cutoff_dists >= 0.9 * w / dx 693 ] = 0 # set oxbow fill thickness to zero outside of oxbows 694 th[ 695 cutoff_dists < 0.9 * w / dx 696 ] = 0 # set point bar thickness to zero inside of oxbows 697 # adding back sand near the channel axis (submarine only): 698 # th[cl_dist<0.5*w/dx] = bth*(1 - relief[cl_dist<0.5*w/dx]/dcr) 699 else: # no cutoffs 700 th_oxbows = np.zeros(np.shape(th)) 701 surf = ( 702 surf + th_oxbows 703 ) # update topographic surface with oxbow deposit thickness 704 topo[:, :, 4 * i + 2] = surf # top of oxbow mud 705 facies[4 * i + 2] = 0 706 surf = surf + th # update topographic surface with sand thickness 707 topo[:, :, 4 * i + 3] = surf # top of sand 708 facies[4 * i + 3] = 1 709 channels3D.append(Channel(x1 - xmin, y1 - ymin, z1, w, h)) 710 711 cl_dist_prev = cl_dist.copy() 712 topo = np.concatenate( 713 (np.reshape(topoinit, (iheight, iwidth, 1)), topo), axis=2 714 ) # add initial topography to array 715 strat = topostrat(topo) # create stratigraphic surfaces 716 strat = np.delete( 717 strat, np.arange(4 * n_steps + 1)[1::4], 2 718 ) # get rid of unnecessary stratigraphic surfaces (duplicates) 719 facies = np.delete( 720 facies, np.arange(4 * n_steps)[::4] 721 ) # get rid of unnecessary facies layers (NaNs) 722 if model_type == "fluvial": 723 facies_code = {0: "oxbow", 1: "point bar", 2: "levee"} 724 if model_type == "submarine": 725 facies_code = {0: "oxbow", 1: "channel sand", 2: "levee"} 726 chb_3d = ChannelBelt3D( 727 model_type, topo, strat, facies, facies_code, dx, channels3D 728 ) 729 return chb_3d, xmin, xmax, ymin, ymax # , dists, zmaps
method for building 3D model from set of centerlines (that are part of a ChannelBelt object)
Parameters
- model_type: model type ('fluvial' or 'submarine')
- h_mud: maximum thickness of overbank mud
- levee_width: width of overbank mud
- h: channel depth
- w: channel width
- bth: thickness of channel sand (only used in submarine models)
- dcr: critical channel depth where sand thickness goes to zero (only used in submarine models)
- dx: cell size in x and y directions
- delta_s: sampling distance alogn centerlines
- starttime: age of centerline that will be used as the first centerline in the model
- endtime: age of centerline that will be used as the last centerline in the model
- xmin: minimum x coordinate that defines the model domain; if xmin is set to zero, a plot of the centerlines is generated and the model domain has to be defined by clicking its upper left and lower right corners
- xmax: maximum x coordinate that defines the model domain
- ymin: minimum y coordinate that defines the model domain
- ymax: maximum y coordinate that defines the model domain
Returns
a ChannelBelt3D object
Returns
x and y coordinates that define the model domain (so that they can be reused later)
732def resample_centerline(x, y, z, deltas): 733 """resample centerline so that 'deltas' is roughly constant, using parametric 734 spline representation of curve; note that there is *no* smoothing 735 736 :param x: x-coordinates of centerline 737 :param y: y-coordinates of centerline 738 :param z: z-coordinates of centerline 739 :param deltas: distance between points on centerline 740 :return x: x-coordinates of resampled centerline 741 :return y: y-coordinates of resampled centerline 742 :return z: z-coordinates of resampled centerline 743 :return dx: dx of resampled centerline 744 :return dy: dy of resampled centerline 745 :return dz: dz of resampled centerline 746 :return s: s-coordinates of resampled centerline""" 747 748 dx, dy, dz, ds, s = compute_derivatives(x, y, z) # compute derivatives 749 tck, u = scipy.interpolate.splprep([x, y, z], s=0) 750 unew = np.linspace(0, 1, 1 + int(round(s[-1] / deltas))) # vector for resampling 751 out = scipy.interpolate.splev(unew, tck) # resampling 752 x, y, z = out[0], out[1], out[2] # assign new coordinate values 753 dx, dy, dz, ds, s = compute_derivatives(x, y, z) # recompute derivatives 754 return x, y, z, dx, dy, dz, ds, s
resample centerline so that 'deltas' is roughly constant, using parametric spline representation of curve; note that there is no smoothing
Parameters
- x: x-coordinates of centerline
- y: y-coordinates of centerline
- z: z-coordinates of centerline
- deltas: distance between points on centerline
Returns
x-coordinates of resampled centerline
Returns
y-coordinates of resampled centerline
Returns
z-coordinates of resampled centerline
Returns
dx of resampled centerline
Returns
dy of resampled centerline
Returns
dz of resampled centerline
Returns
s-coordinates of resampled centerline
757def migrate_one_step(x, y, z, W, kl, dt, k, Cf, D, pad, pad1, omega, gamma): 758 """migrate centerline during one time step, using the migration computed as in Howard & Knutson (1984) 759 760 :param x: x-coordinates of centerline 761 :param y: y-coordinates of centerline 762 :param z: z-coordinates of centerline 763 :param W: channel width 764 :param kl: migration rate (or erodibility) constant (m/s) 765 :param dt: duration of time step (s) 766 :param k: constant for calculating the exponent alpha (= 1.0) 767 :param Cf: dimensionless Chezy friction factor 768 :param D: channel depth 769 :param omega: constant in Howard & Knutson equation (= -1.0) 770 :param gamma: constant in Howard & Knutson equation (= 2.5) 771 :return x: new x-coordinates of centerline after migration 772 :return y: new y-coordinates of centerline after migration 773 """ 774 775 ns = len(x) 776 curv = compute_curvature(x, y) 777 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 778 sinuosity = s[-1] / (x[-1] - x[0]) 779 curv = W * curv # dimensionless curvature 780 R0 = ( 781 kl * curv 782 ) # simple linear relationship between curvature and nominal migration rate 783 alpha = k * 2 * Cf / D # exponent for convolution function G 784 R1 = compute_migration_rate(pad, ns, ds, alpha, omega, gamma, R0) 785 R1 = sinuosity ** (-2 / 3.0) * R1 786 # calculate new centerline coordinates: 787 dy_ds = dy[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 788 dx_ds = dx[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 789 # adjust x and y coordinates (this *is* the migration): 790 x[pad1 : ns - pad + 1] = ( 791 x[pad1 : ns - pad + 1] + R1[pad1 : ns - pad + 1] * dy_ds * dt 792 ) 793 y[pad1 : ns - pad + 1] = ( 794 y[pad1 : ns - pad + 1] - R1[pad1 : ns - pad + 1] * dx_ds * dt 795 ) 796 return x, y
migrate centerline during one time step, using the migration computed as in Howard & Knutson (1984)
Parameters
- x: x-coordinates of centerline
- y: y-coordinates of centerline
- z: z-coordinates of centerline
- W: channel width
- kl: migration rate (or erodibility) constant (m/s)
- dt: duration of time step (s)
- k: constant for calculating the exponent alpha (= 1.0)
- Cf: dimensionless Chezy friction factor
- D: channel depth
- omega: constant in Howard & Knutson equation (= -1.0)
- gamma: constant in Howard & Knutson equation (= 2.5)
Returns
new x-coordinates of centerline after migration
Returns
new y-coordinates of centerline after migration
799def migrate_one_step_w_bias(x, y, z, W, kl, dt, k, Cf, D, pad, pad1, omega, gamma): 800 ns = len(x) 801 curv = compute_curvature(x, y) 802 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 803 sinuosity = s[-1] / (x[-1] - x[0]) 804 curv = W * curv # dimensionless curvature 805 R0 = ( 806 kl * curv 807 ) # simple linear relationship between curvature and nominal migration rate 808 alpha = k * 2 * Cf / D # exponent for convolution function G 809 R1 = compute_migration_rate(pad, ns, ds, alpha, omega, gamma, R0) 810 R1 = sinuosity ** (-2 / 3.0) * R1 811 pad = -1 812 # calculate new centerline coordinates: 813 dy_ds = dy[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 814 dx_ds = dx[pad1 : ns - pad + 1] / ds[pad1 : ns - pad + 1] 815 tilt_factor = 0.2 816 T = kl * tilt_factor * np.ones(np.shape(x)) 817 angle = 90.0 818 # adjust x and y coordinates (this *is* the migration): 819 x[pad1 : ns - pad + 1] = ( 820 x[pad1 : ns - pad + 1] 821 + R1[pad1 : ns - pad + 1] * dy_ds * dt 822 + T[pad1 : ns - pad + 1] 823 * dy_ds 824 * dt 825 * (np.sin(np.deg2rad(angle)) * dx_ds + np.cos(np.deg2rad(angle)) * dy_ds) 826 ) 827 y[pad1 : ns - pad + 1] = ( 828 y[pad1 : ns - pad + 1] 829 - R1[pad1 : ns - pad + 1] * dx_ds * dt 830 - T[pad1 : ns - pad + 1] 831 * dx_ds 832 * dt 833 * (np.sin(np.deg2rad(angle)) * dx_ds + np.cos(np.deg2rad(angle)) * dy_ds) 834 ) 835 return x, y
838def generate_initial_channel(W, D, Sl, deltas, pad, n_bends): 839 """generate straight Channel object with some noise added that can serve 840 as input for initializing a ChannelBelt object 841 W - channel width 842 D - channel depth 843 Sl - channel gradient 844 deltas - distance between nodes on centerline 845 pad - padding (number of nodepoints along centerline) 846 n_bends - approximate number of bends to be simulated""" 847 noisy_len = n_bends * 10 * W / 2.0 # length of noisy part of initial centerline 848 pad1 = int( 849 pad / 10.0 850 ) # padding at upstream end can be shorter than padding on downstream end 851 if pad1 < 5: 852 pad1 = 5 853 x = np.linspace( 854 0, noisy_len + (pad + pad1) * deltas, int(noisy_len / deltas + pad + pad1) + 1 855 ) # x coordinate 856 y = 10.0 * ( 857 2 858 * np.random.random_sample( 859 int(noisy_len / deltas) + 1, 860 ) 861 - 1 862 ) 863 y = np.hstack( 864 ( 865 np.zeros( 866 (pad1), 867 ), 868 y, 869 np.zeros( 870 (pad), 871 ), 872 ) 873 ) # y coordinate 874 deltaz = Sl * deltas * (len(x) - 1) 875 z = np.linspace(0, deltaz, len(x))[::-1] # z coordinate 876 return Channel(x, y, z, W, D)
generate straight Channel object with some noise added that can serve as input for initializing a ChannelBelt object W - channel width D - channel depth Sl - channel gradient deltas - distance between nodes on centerline pad - padding (number of nodepoints along centerline) n_bends - approximate number of bends to be simulated
879@numba.jit(nopython=True) # use Numba to speed up the heaviest computation 880def compute_migration_rate(pad, ns, ds, alpha, omega, gamma, R0): 881 """compute migration rate as weighted sum of upstream curvatures 882 pad - padding (number of nodepoints along centerline) 883 ns - number of points in centerline 884 ds - distances between points in centerline 885 omega - constant in HK model 886 gamma - constant in HK model 887 R0 - nominal migration rate (dimensionless curvature * migration rate constant)""" 888 R1 = np.zeros(ns) # preallocate adjusted channel migration rate 889 pad1 = int( 890 pad / 10.0 891 ) # padding at upstream end can be shorter than padding on downstream end 892 if pad1 < 5: 893 pad1 = 5 894 for i in range(pad1, ns - pad): 895 si2 = np.hstack( 896 (np.array([0]), np.cumsum(ds[i - 1 :: -1])) 897 ) # distance along centerline, backwards from current point 898 G = np.exp(-alpha * si2) # convolution vector 899 R1[i] = omega * R0[i] + gamma * np.sum(R0[i::-1] * G) / np.sum( 900 G 901 ) # main equation 902 return R1
compute migration rate as weighted sum of upstream curvatures pad - padding (number of nodepoints along centerline) ns - number of points in centerline ds - distances between points in centerline omega - constant in HK model gamma - constant in HK model R0 - nominal migration rate (dimensionless curvature * migration rate constant)
905def compute_derivatives(x, y, z): 906 """function for computing first derivatives of a curve (centerline) 907 x,y are cartesian coodinates of the curve 908 outputs: 909 dx - first derivative of x coordinate 910 dy - first derivative of y coordinate 911 ds - distances between consecutive points along the curve 912 s - cumulative distance along the curve""" 913 dx = np.gradient(x) # first derivatives 914 dy = np.gradient(y) 915 dz = np.gradient(z) 916 ds = np.sqrt(dx ** 2 + dy ** 2 + dz ** 2) 917 s = np.hstack((0, np.cumsum(ds[1:]))) 918 return dx, dy, dz, ds, s
function for computing first derivatives of a curve (centerline) x,y are cartesian coodinates of the curve outputs: dx - first derivative of x coordinate dy - first derivative of y coordinate ds - distances between consecutive points along the curve s - cumulative distance along the curve
921def compute_curvature(x, y): 922 """function for computing first derivatives and curvature of a curve (centerline) 923 x,y are cartesian coodinates of the curve 924 outputs: 925 dx - first derivative of x coordinate 926 dy - first derivative of y coordinate 927 ds - distances between consecutive points along the curve 928 s - cumulative distance along the curve 929 curvature - curvature of the curve (in 1/units of x and y)""" 930 dx = np.gradient(x) # first derivatives 931 dy = np.gradient(y) 932 ddx = np.gradient(dx) # second derivatives 933 ddy = np.gradient(dy) 934 curvature = (dx * ddy - dy * ddx) / ((dx ** 2 + dy ** 2) ** 1.5) 935 return curvature
function for computing first derivatives and curvature of a curve (centerline) x,y are cartesian coodinates of the curve outputs: dx - first derivative of x coordinate dy - first derivative of y coordinate ds - distances between consecutive points along the curve s - cumulative distance along the curve curvature - curvature of the curve (in 1/units of x and y)
938def make_colormap(seq): 939 """Return a LinearSegmentedColormap 940 seq: a sequence of floats and RGB-tuples. The floats should be increasing 941 and in the interval (0,1). 942 [from: https://stackoverflow.com/questions/16834861/create-own-colormap-using-matplotlib-and-plot-color-scale] 943 """ 944 seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3] 945 cdict = {"red": [], "green": [], "blue": []} 946 for i, item in enumerate(seq): 947 if isinstance(item, float): 948 r1, g1, b1 = seq[i - 1] 949 r2, g2, b2 = seq[i + 1] 950 cdict["red"].append([item, r1, r2]) 951 cdict["green"].append([item, g1, g2]) 952 cdict["blue"].append([item, b1, b2]) 953 return mcolors.LinearSegmentedColormap("CustomMap", cdict)
Return a LinearSegmentedColormap seq: a sequence of floats and RGB-tuples. The floats should be increasing and in the interval (0,1). [from: https://stackoverflow.com/questions/16834861/create-own-colormap-using-matplotlib-and-plot-color-scale]
956def kth_diag_indices(a, k): 957 """function for finding diagonal indices with k offset 958 [from https://stackoverflow.com/questions/10925671/numpy-k-th-diagonal-indices]""" 959 rows, cols = np.diag_indices_from(a) 960 if k < 0: 961 return rows[:k], cols[-k:] 962 elif k > 0: 963 return rows[k:], cols[:-k] 964 else: 965 return rows, cols
function for finding diagonal indices with k offset [from https://stackoverflow.com/questions/10925671/numpy-k-th-diagonal-indices]
968def find_cutoffs(x, y, crdist, deltas): 969 """function for identifying locations of cutoffs along a centerline 970 and the indices of the segments that will become part of the oxbows 971 x,y - coordinates of centerline 972 crdist - critical cutoff distance 973 deltas - distance between neighboring points along the centerline""" 974 diag_blank_width = int((crdist + 20 * deltas) / deltas) 975 # distance matrix for centerline points: 976 dist = distance.cdist(np.array([x, y]).T, np.array([x, y]).T) 977 dist[ 978 dist > crdist 979 ] = np.NaN # set all values that are larger than the cutoff threshold to NaN 980 # set matrix to NaN along the diagonal zone: 981 for k in range(-diag_blank_width, diag_blank_width + 1): 982 rows, cols = kth_diag_indices(dist, k) 983 dist[rows, cols] = np.NaN 984 i1, i2 = np.where(~np.isnan(dist)) 985 ind1 = i1[np.where(i1 < i2)[0]] # get rid of unnecessary indices 986 ind2 = i2[np.where(i1 < i2)[0]] # get rid of unnecessary indices 987 return ind1, ind2 # return indices of cutoff points and cutoff coordinates
function for identifying locations of cutoffs along a centerline and the indices of the segments that will become part of the oxbows x,y - coordinates of centerline crdist - critical cutoff distance deltas - distance between neighboring points along the centerline
990def cut_off_cutoffs(x, y, z, s, crdist, deltas): 991 """function for executing cutoffs - removing oxbows from centerline and storing cutoff coordinates 992 x,y - coordinates of centerline 993 crdist - critical cutoff distance 994 deltas - distance between neighboring points along the centerline 995 outputs: 996 x,y,z - updated coordinates of centerline 997 xc, yc, zc - lists with coordinates of cutoff segments""" 998 xc = [] 999 yc = [] 1000 zc = [] 1001 ind1, ind2 = find_cutoffs(x, y, crdist, deltas) # initial check for cutoffs 1002 while len(ind1) > 0: 1003 xc.append(x[ind1[0] : ind2[0] + 1]) # x coordinates of cutoff 1004 yc.append(y[ind1[0] : ind2[0] + 1]) # y coordinates of cutoff 1005 zc.append(z[ind1[0] : ind2[0] + 1]) # z coordinates of cutoff 1006 x = np.hstack((x[: ind1[0] + 1], x[ind2[0] :])) # x coordinates after cutoff 1007 y = np.hstack((y[: ind1[0] + 1], y[ind2[0] :])) # y coordinates after cutoff 1008 z = np.hstack((z[: ind1[0] + 1], z[ind2[0] :])) # z coordinates after cutoff 1009 ind1, ind2 = find_cutoffs(x, y, crdist, deltas) 1010 return x, y, z, xc, yc, zc
function for executing cutoffs - removing oxbows from centerline and storing cutoff coordinates x,y - coordinates of centerline crdist - critical cutoff distance deltas - distance between neighboring points along the centerline outputs: x,y,z - updated coordinates of centerline xc, yc, zc - lists with coordinates of cutoff segments
1013def get_channel_banks(x, y, W): 1014 """function for finding coordinates of channel banks, given a centerline and a channel width 1015 x,y - coordinates of centerline 1016 W - channel width 1017 outputs: 1018 xm, ym - coordinates of channel banks (both left and right banks)""" 1019 x1 = x.copy() 1020 y1 = y.copy() 1021 x2 = x.copy() 1022 y2 = y.copy() 1023 ns = len(x) 1024 dx = np.diff(x) 1025 dy = np.diff(y) 1026 ds = np.sqrt(dx ** 2 + dy ** 2) 1027 x1[:-1] = x[:-1] + 0.5 * W * np.diff(y) / ds 1028 y1[:-1] = y[:-1] - 0.5 * W * np.diff(x) / ds 1029 x2[:-1] = x[:-1] - 0.5 * W * np.diff(y) / ds 1030 y2[:-1] = y[:-1] + 0.5 * W * np.diff(x) / ds 1031 x1[ns - 1] = x[ns - 1] + 0.5 * W * (y[ns - 1] - y[ns - 2]) / ds[ns - 2] 1032 y1[ns - 1] = y[ns - 1] - 0.5 * W * (x[ns - 1] - x[ns - 2]) / ds[ns - 2] 1033 x2[ns - 1] = x[ns - 1] - 0.5 * W * (y[ns - 1] - y[ns - 2]) / ds[ns - 2] 1034 y2[ns - 1] = y[ns - 1] + 0.5 * W * (x[ns - 1] - x[ns - 2]) / ds[ns - 2] 1035 xm = np.hstack((x1, x2[::-1])) 1036 ym = np.hstack((y1, y2[::-1])) 1037 return xm, ym
function for finding coordinates of channel banks, given a centerline and a channel width x,y - coordinates of centerline W - channel width outputs: xm, ym - coordinates of channel banks (both left and right banks)
1040def dist_map(x, y, z, xmin, xmax, ymin, ymax, dx, delta_s): 1041 """function for centerline rasterization and distance map calculation 1042 inputs: 1043 x,y,z - coordinates of centerline 1044 xmin, xmax, ymin, ymax - x and y coordinates that define the area of interest 1045 dx - gridcell size (m) 1046 delta_s - distance between points along centerline (m) 1047 returns: 1048 cl_dist - distance map (distance from centerline) 1049 x_pix, y_pix, z_pix - x,y, and z pixel coordinates of the centerline 1050 s_pix - along-channel distance in pixels 1051 z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) 1052 x, y, z - x,y,z centerline coordinates clipped to the 3D model domain""" 1053 y = y[(x > xmin) & (x < xmax)] 1054 z = z[(x > xmin) & (x < xmax)] 1055 x = x[(x > xmin) & (x < xmax)] 1056 dummy, dy, dz, ds, s = compute_derivatives(x, y, z) 1057 if len(np.where(ds > 2 * delta_s)[0]) > 0: 1058 inds = np.where(ds > 2 * delta_s)[0] 1059 inds = np.hstack((0, inds, len(x))) 1060 lengths = np.diff(inds) 1061 long_segment = np.where(lengths == max(lengths))[0][0] 1062 start_ind = inds[long_segment] + 1 1063 end_ind = inds[long_segment + 1] 1064 if end_ind < len(x): 1065 x = x[start_ind:end_ind] 1066 y = y[start_ind:end_ind] 1067 z = z[start_ind:end_ind] 1068 else: 1069 x = x[start_ind:] 1070 y = y[start_ind:] 1071 z = z[start_ind:] 1072 xdist = xmax - xmin 1073 ydist = ymax - ymin 1074 iwidth = int((xmax - xmin) / dx) 1075 iheight = int((ymax - ymin) / dx) 1076 xratio = iwidth / xdist 1077 # create list with pixel coordinates: 1078 pixels = [] 1079 for i in range(0, len(x)): 1080 px = int(iwidth - (xmax - x[i]) * xratio) 1081 py = int(iheight - (ymax - y[i]) * xratio) 1082 pixels.append((px, py)) 1083 # create image and numpy array: 1084 img = Image.new("RGB", (iwidth, iheight), "white") 1085 draw = ImageDraw.Draw(img) 1086 draw.line(pixels, fill="rgb(0, 0, 0)") # draw centerline as black line 1087 pix = np.array(img) 1088 cl = pix[:, :, 0] 1089 cl[cl == 255] = 1 # set background to 1 (centerline is 0) 1090 y_pix, x_pix = np.where(cl == 0) 1091 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1092 # This next block of code is kind of a hack. Looking for, and eliminating, 'bad' pixels. 1093 img = np.array(img) 1094 img = img[:, :, 0] 1095 img[img == 255] = 1 1096 img1 = morphology.binary_dilation(img, morphology.square(2)).astype(np.uint8) 1097 if len(np.where(img1 == 0)[0]) > 0: 1098 x_pix, y_pix = eliminate_bad_pixels(img, img1) 1099 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1100 img1 = morphology.binary_dilation( 1101 img, np.array([[1, 0, 1], [1, 1, 1]], dtype=np.uint8) 1102 ).astype(np.uint8) 1103 if len(np.where(img1 == 0)[0]) > 0: 1104 x_pix, y_pix = eliminate_bad_pixels(img, img1) 1105 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1106 img1 = morphology.binary_dilation( 1107 img, np.array([[1, 0, 1], [0, 1, 0], [1, 0, 1]], dtype=np.uint8) 1108 ).astype(np.uint8) 1109 if len(np.where(img1 == 0)[0]) > 0: 1110 x_pix, y_pix = eliminate_bad_pixels(img, img1) 1111 x_pix, y_pix = order_cl_pixels(x_pix, y_pix) 1112 # redo the distance calculation (because x_pix and y_pix do not always contain all the points in cl): 1113 cl[cl == 0] = 1 1114 cl[y_pix, x_pix] = 0 1115 cl_dist, inds = ndimage.distance_transform_edt(cl, return_indices=True) 1116 dx, dy, dz, ds, s = compute_derivatives(x, y, z) 1117 dx_pix = np.diff(x_pix) 1118 dy_pix = np.diff(y_pix) 1119 ds_pix = np.sqrt(dx_pix ** 2 + dy_pix ** 2) 1120 s_pix = np.hstack((0, np.cumsum(ds_pix))) 1121 f = scipy.interpolate.interp1d(s, z) 1122 snew = s_pix * s[-1] / s_pix[-1] 1123 if snew[-1] > s[-1]: 1124 snew[-1] = s[-1] 1125 snew[snew < s[0]] = s[0] 1126 z_pix = f(snew) 1127 # create z_map: 1128 z_map = np.zeros(np.shape(cl_dist)) 1129 z_map[y_pix, x_pix] = z_pix 1130 xinds = inds[1, :, :] 1131 yinds = inds[0, :, :] 1132 for i in range(0, len(x_pix)): 1133 z_map[(xinds == x_pix[i]) & (yinds == y_pix[i])] = z_pix[i] 1134 return cl_dist, x_pix, y_pix, z_pix, s_pix, z_map, x, y, z
function for centerline rasterization and distance map calculation inputs: x,y,z - coordinates of centerline xmin, xmax, ymin, ymax - x and y coordinates that define the area of interest dx - gridcell size (m) delta_s - distance between points along centerline (m) returns: cl_dist - distance map (distance from centerline) x_pix, y_pix, z_pix - x,y, and z pixel coordinates of the centerline s_pix - along-channel distance in pixels z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) x, y, z - x,y,z centerline coordinates clipped to the 3D model domain
1137def erosion_surface(h, w, cl_dist, z): 1138 """function for creating a parabolic erosional surface 1139 inputs: 1140 h - geomorphic channel depth (m) 1141 w - geomorphic channel width (in pixels, as cl_dist is also given in pixels) 1142 cl_dist - distance map (distance from centerline) 1143 z - reference elevation (m) 1144 returns: 1145 surf - map of the quadratic erosional surface (m) 1146 """ 1147 surf = z + (4 * h / w ** 2) * (cl_dist + w * 0.5) * (cl_dist - w * 0.5) 1148 return surf
function for creating a parabolic erosional surface inputs: h - geomorphic channel depth (m) w - geomorphic channel width (in pixels, as cl_dist is also given in pixels) cl_dist - distance map (distance from centerline) z - reference elevation (m) returns: surf - map of the quadratic erosional surface (m)
1151def point_bar_surface(cl_dist, z, h, w): 1152 """function for creating a Gaussian-based point bar surface 1153 used in 3D fluvial model 1154 inputs: 1155 cl_dist - distance map (distance from centerline) 1156 z - reference elevation (m) 1157 h - channel depth (m) 1158 w - channel width, in pixels, as cl_dist is also given in pixels 1159 returns: 1160 pb - map of the Gaussian surface that can be used to from a point bar deposit (m)""" 1161 pb = z - h * np.exp(-(cl_dist ** 2) / (2 * (w * 0.33) ** 2)) 1162 return pb
function for creating a Gaussian-based point bar surface used in 3D fluvial model inputs: cl_dist - distance map (distance from centerline) z - reference elevation (m) h - channel depth (m) w - channel width, in pixels, as cl_dist is also given in pixels returns: pb - map of the Gaussian surface that can be used to from a point bar deposit (m)
1165def sand_surface(surf, bth, dcr, z_map, h): 1166 """function for creating the top horizontal surface sand-rich deposit in the bottom of the channel 1167 used in 3D submarine channel models 1168 inputs: 1169 surf - current geomorphic surface 1170 bth - thickness of sand deposit in axis of channel (m) 1171 dcr - critical channel depth, above which there is no sand deposition (m) 1172 z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) 1173 h - channel depth (m) 1174 returns: 1175 th - thickness map of sand deposit (m) 1176 relief - map of channel relief (m)""" 1177 relief = np.abs(surf - z_map + h) 1178 relief = np.abs(relief - np.amin(relief)) 1179 th = bth * (1 - relief / dcr) # bed thickness inversely related to relief 1180 th[th < 0] = 0.0 # set negative th values to zero 1181 return th, relief
function for creating the top horizontal surface sand-rich deposit in the bottom of the channel used in 3D submarine channel models inputs: surf - current geomorphic surface bth - thickness of sand deposit in axis of channel (m) dcr - critical channel depth, above which there is no sand deposition (m) z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) h - channel depth (m) returns: th - thickness map of sand deposit (m) relief - map of channel relief (m)
1184def mud_surface(h_mud, levee_width, cl_dist, w, z_map, topo): 1185 """function for creating a map of overbank deposit thickness 1186 inputs: 1187 h_mud - maximum thickness of overbank deposit (m) 1188 levee_width - half-width of overbank deposit (m) 1189 cl_dist - distance map (distance from centerline) 1190 w - channel width (in pixels, as cl_dist is also given in pixels) 1191 z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) 1192 topo - current geomorphic surface 1193 returns: 1194 surf - map of overbank deposit thickness (m)""" 1195 # create a surface that thins linearly away from the channel centerline: 1196 surf1 = (-2 * h_mud / levee_width) * cl_dist + h_mud 1197 surf2 = (2 * h_mud / levee_width) * cl_dist + h_mud 1198 surf = np.minimum(surf1, surf2) 1199 # surface for 'eroding' the central part of the mud layer: 1200 surf3 = h_mud + (4 * 1.5 * h_mud / w ** 2) * (cl_dist + w * 0.5) * ( 1201 cl_dist - w * 0.5 1202 ) 1203 surf = np.minimum(surf, surf3) 1204 surf[surf < 0] = 0 1205 # eliminate negative thicknesses 1206 return surf
function for creating a map of overbank deposit thickness inputs: h_mud - maximum thickness of overbank deposit (m) levee_width - half-width of overbank deposit (m) cl_dist - distance map (distance from centerline) w - channel width (in pixels, as cl_dist is also given in pixels) z_map - map of reference channel thalweg elevation (elevation of closest point along centerline) topo - current geomorphic surface returns: surf - map of overbank deposit thickness (m)
1209def topostrat(topo): 1210 """function for converting a stack of geomorphic surfaces into stratigraphic surfaces 1211 inputs: 1212 topo - 3D numpy array of geomorphic surfaces 1213 returns: 1214 strat - 3D numpy array of stratigraphic surfaces 1215 """ 1216 r, c, ts = np.shape(topo) 1217 strat = np.copy(topo) 1218 for i in range(0, ts): 1219 strat[:, :, i] = np.amin(topo[:, :, i:], axis=2) 1220 return strat
function for converting a stack of geomorphic surfaces into stratigraphic surfaces inputs: topo - 3D numpy array of geomorphic surfaces returns: strat - 3D numpy array of stratigraphic surfaces
1223def cl_dist_map(x, y, z, xmin, xmax, ymin, ymax, dx): 1224 """function for centerline rasterization and distance map calculation (does not return zmap) 1225 used for cutoffs only 1226 inputs: 1227 x,y,z - coordinates of centerline 1228 xmin, xmax, ymin, ymax - x and y coordinates that define the area of interest 1229 dx - gridcell size (m) 1230 returns: 1231 cl_dist - distance map (distance from centerline) 1232 x_pix, y_pix, - x and y pixel coordinates of the centerline 1233 """ 1234 y = y[(x > xmin) & (x < xmax)] 1235 z = z[(x > xmin) & (x < xmax)] 1236 x = x[(x > xmin) & (x < xmax)] 1237 xdist = xmax - xmin 1238 ydist = ymax - ymin 1239 iwidth = int((xmax - xmin) / dx) 1240 iheight = int((ymax - ymin) / dx) 1241 xratio = iwidth / xdist 1242 # create list with pixel coordinates: 1243 pixels = [] 1244 for i in range(0, len(x)): 1245 px = int(iwidth - (xmax - x[i]) * xratio) 1246 py = int(iheight - (ymax - y[i]) * xratio) 1247 pixels.append((px, py)) 1248 # create image and numpy array: 1249 img = Image.new("RGB", (iwidth, iheight), "white") 1250 draw = ImageDraw.Draw(img) 1251 draw.line(pixels, fill="rgb(0, 0, 0)") # draw centerline as black line 1252 pix = np.array(img) 1253 cl = pix[:, :, 0] 1254 cl[cl == 255] = 1 # set background to 1 (centerline is 0) 1255 # calculate Euclidean distance map: 1256 cl_dist, inds = ndimage.distance_transform_edt(cl, return_indices=True) 1257 y_pix, x_pix = np.where(cl == 0) 1258 return cl_dist, x_pix, y_pix
function for centerline rasterization and distance map calculation (does not return zmap) used for cutoffs only inputs: x,y,z - coordinates of centerline xmin, xmax, ymin, ymax - x and y coordinates that define the area of interest dx - gridcell size (m) returns: cl_dist - distance map (distance from centerline) x_pix, y_pix, - x and y pixel coordinates of the centerline
1261def eliminate_bad_pixels(img, img1): 1262 x_ind = np.where(img1 == 0)[1][0] 1263 y_ind = np.where(img1 == 0)[0][0] 1264 img[y_ind : y_ind + 2, x_ind : x_ind + 2] = np.ones( 1265 1, 1266 ).astype(np.uint8) 1267 all_labels = measure.label(img, background=1, connectivity=2) 1268 cl = all_labels.copy() 1269 cl[cl == 2] = 0 1270 cl[cl > 0] = 1 1271 y_pix, x_pix = np.where(cl == 1) 1272 return x_pix, y_pix
1275def order_cl_pixels(x_pix, y_pix): 1276 dist = distance.cdist(np.array([x_pix, y_pix]).T, np.array([x_pix, y_pix]).T) 1277 dist[np.diag_indices_from(dist)] = 100.0 1278 ind = np.argmin(x_pix) # select starting point on left side of image 1279 clinds = [ind] 1280 count = 0 1281 while count < len(x_pix): 1282 t = dist[ind, :].copy() 1283 if len(clinds) > 2: 1284 t[clinds[-2]] = t[clinds[-2]] + 100.0 1285 t[clinds[-3]] = t[clinds[-3]] + 100.0 1286 ind = np.argmin(t) 1287 clinds.append(ind) 1288 count = count + 1 1289 x_pix = x_pix[clinds] 1290 y_pix = y_pix[clinds] 1291 return x_pix, y_pix