source: palm/trunk/SCRIPTS/palm_csd_files/palm_csd_canopy_generator.py @ 4793

Last change on this file since 4793 was 4793, checked in by maronga, 3 years ago

allow for overhanging vegetation in palm_csd

File size: 29.6 KB
Line 
1#!/usr/bin/env python3
2# -*- coding: utf-8 -*-
3#--------------------------------------------------------------------------------#
4# This file is part of the PALM model system.
5#
6# PALM is free software: you can redistribute it and/or modify it under the terms
7# of the GNU General Public License as published by the Free Software Foundation,
8# either version 3 of the License, or (at your option) any later version.
9#
10# PALM is distributed in the hope that it will be useful, but WITHOUT ANY
11# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along with
15# PALM. If not, see <http://www.gnu.org/licenses/>.
16#
17# Copyright 1997-2020  Leibniz Universitaet Hannover
18#--------------------------------------------------------------------------------#
19#
20# Current revisions:
21# -----------------
22#
23#
24# Former revisions:
25# -----------------
26# $Id: palm_csd_canopy_generator.py 3773 2019-03-01 08:56:57Z maronga $
27# Bugfix: do not allow BAD < 0, revised treatment of tree trunks
28#
29# 3773 2019-03-01 08:56:57Z maronga
30# Added support for tree shape
31#
32# 3773 2019-03-01 08:56:57Z maronga
33# Bugfix: conversion to integer required for indexing
34#
35# 3668 2019-01-14 12:49:24Z maronga
36# Various improvements and bugfixes
37#
38# 3629 2018-12-13 12:18:54Z maronga
39# Initial revision
40#
41# Description:
42# ------------
43# Canopy generator routines for creating 3D leaf and basal area densities for
44# single trees and tree patches
45#
46# @Author Bjoern Maronga (maronga@muk.uni-hannover.de)
47#------------------------------------------------------------------------------#
48import numpy as np
49import math
50import scipy.integrate as integrate
51import scipy.ndimage as nd
52
53def generate_single_tree_lad(x,y,dz,max_tree_height,max_patch_height,tree_type,tree_height,tree_dia,trunk_dia,tree_lai,season,fill):
54
55
56#  Step 1: Create arrays for storing the data
57   max_canopy_height = max( max_tree_height,max_patch_height )
58 
59   zlad = np.arange(0,math.floor(max_canopy_height/dz)*dz+2*dz,dz)
60   zlad[1:] = zlad[1:] - 0.5 * dz
61
62   lad = np.ones((len(zlad),len(y),len(x)))
63   lad[:,:,:] = fill
64
65   bad = np.ones((len(zlad),len(y),len(x)))
66   bad[:,:,:] = fill
67   
68   ids = np.ones((len(zlad),len(y),len(x)))
69   ids[:,:,:] = fill
70 
71   types = np.ones((len(zlad),len(y),len(x)))
72   types[:,:,:] = np.byte(-127) 
73
74#  Calculating the number of trees in the arrays and a boolean array storing the location of trees which is used for convenience in the following loop environment   
75   number_of_trees_array = np.where( (tree_type.flatten() != fill) | (tree_dia.flatten() != fill) | (trunk_dia.flatten() != fill),1.0,fill)
76   number_of_trees = len( number_of_trees_array[number_of_trees_array == 1.0] )
77   dx = x[1] - x[0]
78       
79   valid_pixels = np.where( (tree_type[:,:] != fill) | (tree_dia[:,:] != fill) | (trunk_dia[:,:] != fill),True,False)
80   
81#  For each tree, create a small 3d array containing the LAD field for the individual tree
82   print("Start generating " +  str(number_of_trees) + " trees...")
83   tree_id_counter = 0
84   if (number_of_trees > 0):
85      for i in range(0,len(x)-1):
86         for j in range(0,len(y)-1):
87            if valid_pixels[j,i]:
88               tree_id_counter = tree_id_counter + 1
89#               print("   Processing tree No " +  str(tree_id_counter) + " ...", end="")
90               lad_loc, bad_loc, x_loc, y_loc, z_loc, status = process_single_tree(dx,dz,tree_type[j,i],fill,tree_height[j,i],tree_lai[j,i],tree_dia[j,i],trunk_dia[j,i],season)
91               if ( np.any(lad_loc) != fill ):
92 
93#                 Calculate the position of the local 3d tree array within the full domain in order to achieve correct mapping and cutting off at the edges of the full domain
94                  lad_loc_nx = int(len(x_loc) / 2)
95                  lad_loc_ny = int(len(y_loc) / 2)
96                  lad_loc_nz = int(len(z_loc))
97                             
98                  odd_x = int(len(x_loc) % 2)
99                  odd_y = int(len(y_loc) % 2)
100               
101                  ind_l_x  = max(0,(i-lad_loc_nx))
102                  ind_l_y  = max(0,(j-lad_loc_ny))
103                  ind_r_x  = min(len(x)-1,i+lad_loc_nx-1+odd_x)
104                  ind_r_y  = min(len(y)-1,j+lad_loc_ny-1+odd_y)
105               
106                  out_l_x = ind_l_x - (i-lad_loc_nx)
107                  out_l_y = ind_l_y - (j-lad_loc_ny)
108                  out_r_x = len(x_loc)-1 + ind_r_x - (i+lad_loc_nx-1+odd_x)
109                  out_r_y = len(y_loc)-1 + ind_r_y - (j+lad_loc_ny-1+odd_y)               
110   
111
112                  lad[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1] = np.where(lad_loc[0:lad_loc_nz,out_l_y:out_r_y+1,out_l_x:out_r_x+1] != fill,lad_loc[0:lad_loc_nz,out_l_y:out_r_y+1,out_l_x:out_r_x+1],lad[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1]) 
113                  bad[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1] = np.where(bad_loc[0:lad_loc_nz,out_l_y:out_r_y+1,out_l_x:out_r_x+1] != fill,bad_loc[0:lad_loc_nz,out_l_y:out_r_y+1,out_l_x:out_r_x+1],bad[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1]) 
114                  ids[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1] = np.where(lad_loc[0:lad_loc_nz,out_l_y:out_r_y+1,out_l_x:out_r_x+1] != fill,tree_id_counter,ids[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1]) 
115                  types[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1] = np.where(lad_loc[0:lad_loc_nz,out_l_y:out_r_y+1,out_l_x:out_r_x+1] != fill,np.byte(tree_type[j,i]),types[0:lad_loc_nz,ind_l_y:ind_r_y+1,ind_l_x:ind_r_x+1])                   
116
117
118#                  if ( status == 0 ):
119#                     status_char = " ok."
120#                  else:
121#                     status_char = " skipped."
122#                  print(status_char)
123
124               del lad_loc, x_loc, y_loc, z_loc, status
125   return lad, bad, ids, types, zlad
126
127
128def process_single_tree(dx,dz,tree_type,tree_shape,tree_height,tree_lai,tree_dia,trunk_dia,season):
129
130#  Set some parameters
131   sphere_extinction = 0.6
132   cone_extinction = 0.2
133   ml_n_low = 0.5
134   ml_n_high = 6.0
135   
136#  Populate look up table for tree species and their properties
137#  #1 Tree shapes were manually lookep up.
138#  #2 Crown h/w ratio - missing
139#  #3 Crown diameter based on Berlin tree statistics
140#  #4 Tree height based on Berlin tree statistics
141#  #5 Tree LAI summer - missing
142#  #6 Tree LAI winter - missing
143#  #7 Height of lad maximum - missing
144#  #8 Ratio LAD/BAD - missing
145#  #9 Trunk diameter at breast height from Berlin
146   default_trees = []
147                                                #1   #2   #3   #4    #5   #6   #7   #8     #9
148   default_trees.append(tree("Default",         1.0, 1.0, 4.0, 12.0, 3.0, 0.8, 0.6, 0.025, 0.35)) 
149   default_trees.append(tree("Abies",           3.0, 1.0, 4.0, 12.0, 3.0, 0.8, 0.6, 0.025, 0.80)) 
150   default_trees.append(tree("Acer",            1.0, 1.0, 7.0, 12.0, 3.0, 0.8, 0.6, 0.025, 0.80))
151   default_trees.append(tree("Aesculus",        1.0, 1.0, 7.0, 12.0, 3.0, 0.8, 0.6, 0.025, 1.00))
152   default_trees.append(tree("Ailanthus",       1.0, 1.0, 8.5, 13.5, 3.0, 0.8, 0.6, 0.025, 1.30))
153   default_trees.append(tree("Alnus",           3.0, 1.0, 6.0, 16.0, 3.0, 0.8, 0.6, 0.025, 1.20))
154   default_trees.append(tree("Amelanchier",     1.0, 1.0, 3.0,  4.0, 3.0, 0.8, 0.6, 0.025, 1.20))
155   default_trees.append(tree("Betula",          1.0, 1.0, 6.0, 14.0, 3.0, 0.8, 0.6, 0.025, 0.30))
156   default_trees.append(tree("Buxus",           1.0, 1.0, 4.0,  4.0, 3.0, 0.8, 0.6, 0.025, 0.90))
157   default_trees.append(tree("Calocedrus",      3.0, 1.0, 5.0, 10.0, 3.0, 0.8, 0.6, 0.025, 0.50))
158   default_trees.append(tree("Caragana",        1.0, 1.0, 3.5,  6.0, 3.0, 0.8, 0.6, 0.025, 0.90))
159   default_trees.append(tree("Carpinus",        1.0, 1.0, 6.0, 10.0, 3.0, 0.8, 0.6, 0.025, 0.70))
160   default_trees.append(tree("Carya",           1.0, 1.0, 5.0, 17.0, 3.0, 0.8, 0.6, 0.025, 0.80))
161   default_trees.append(tree("Castanea",        1.0, 1.0, 4.5,  7.0, 3.0, 0.8, 0.6, 0.025, 0.80))
162   default_trees.append(tree("Catalpa",         1.0, 1.0, 5.5,  6.5, 3.0, 0.8, 0.6, 0.025, 0.70))
163   default_trees.append(tree("Cedrus",          1.0, 1.0, 8.0, 13.0, 3.0, 0.8, 0.6, 0.025, 0.80))
164   default_trees.append(tree("Celtis",          1.0, 1.0, 6.0,  9.0, 3.0, 0.8, 0.6, 0.025, 0.80))
165   default_trees.append(tree("Cercidiphyllum",  1.0, 1.0, 3.0,  6.5, 3.0, 0.8, 0.6, 0.025, 0.80))
166   default_trees.append(tree("Cercis",          1.0, 1.0, 2.5,  7.5, 3.0, 0.8, 0.6, 0.025, 0.90))
167   default_trees.append(tree("Chamaecyparis",   5.0, 1.0, 3.5,  9.0, 3.0, 0.8, 0.6, 0.025, 0.70))
168   default_trees.append(tree("Cladrastis",      1.0, 1.0, 5.0, 10.0, 3.0, 0.8, 0.6, 0.025, 0.80))
169   default_trees.append(tree("Cornus",          1.0, 1.0, 4.5,  6.5, 3.0, 0.8, 0.6, 0.025, 1.20))
170   default_trees.append(tree("Corylus",         1.0, 1.0, 5.0,  9.0, 3.0, 0.8, 0.6, 0.025, 0.40))
171   default_trees.append(tree("Cotinus",         1.0, 1.0, 4.0,  4.0, 3.0, 0.8, 0.6, 0.025, 0.70))
172   default_trees.append(tree("Crataegus",       3.0, 1.0, 3.5,  6.0, 3.0, 0.8, 0.6, 0.025, 1.40))
173   default_trees.append(tree("Cryptomeria",     3.0, 1.0, 5.0, 10.0, 3.0, 0.8, 0.6, 0.025, 0.50))
174   default_trees.append(tree("Cupressocyparis", 3.0, 1.0, 3.0,  8.0, 3.0, 0.8, 0.6, 0.025, 0.40))
175   default_trees.append(tree("Cupressus",       3.0, 1.0, 5.0,  7.0, 3.0, 0.8, 0.6, 0.025, 0.40))
176   default_trees.append(tree("Cydonia",         1.0, 1.0, 2.0,  3.0, 3.0, 0.8, 0.6, 0.025, 0.90))
177   default_trees.append(tree("Davidia",         1.0, 1.0,10.0, 14.0, 3.0, 0.8, 0.6, 0.025, 0.40))
178   default_trees.append(tree("Elaeagnus",       1.0, 1.0, 6.5,  6.0, 3.0, 0.8, 0.6, 0.025, 1.20))
179   default_trees.append(tree("Euodia",          1.0, 1.0, 4.5,  6.0, 3.0, 0.8, 0.6, 0.025, 0.90))
180   default_trees.append(tree("Euonymus",        1.0, 1.0, 4.5,  6.0, 3.0, 0.8, 0.6, 0.025, 0.60))
181   default_trees.append(tree("Fagus",           1.0, 1.0,10.0, 12.5, 3.0, 0.8, 0.6, 0.025, 0.50))
182   default_trees.append(tree("Fraxinus",        1.0, 1.0, 5.5, 10.5, 3.0, 0.8, 0.6, 0.025, 1.60))
183   default_trees.append(tree("Ginkgo",          3.0, 1.0, 4.0,  8.5, 3.0, 0.8, 0.6, 0.025, 0.80))
184   default_trees.append(tree("Gleditsia",       1.0, 1.0, 6.5, 10.5, 3.0, 0.8, 0.6, 0.025, 0.60))
185   default_trees.append(tree("Gymnocladus",     1.0, 1.0, 5.5, 10.0, 3.0, 0.8, 0.6, 0.025, 0.80))
186   default_trees.append(tree("Hippophae",       1.0, 1.0, 9.5,  8.5, 3.0, 0.8, 0.6, 0.025, 0.80))
187   default_trees.append(tree("Ilex",            1.0, 1.0, 4.0,  7.5, 3.0, 0.8, 0.6, 0.025, 0.80))
188   default_trees.append(tree("Juglans",         1.0, 1.0, 7.0,  9.0, 3.0, 0.8, 0.6, 0.025, 0.50))
189   default_trees.append(tree("Juniperus",       5.0, 1.0, 3.0,  7.0, 3.0, 0.8, 0.6, 0.025, 0.90))
190   default_trees.append(tree("Koelreuteria",    1.0, 1.0, 3.5,  5.5, 3.0, 0.8, 0.6, 0.025, 0.50))
191   default_trees.append(tree("Laburnum",        1.0, 1.0, 3.0,  6.0, 3.0, 0.8, 0.6, 0.025, 0.60))
192   default_trees.append(tree("Larix",           3.0, 1.0, 7.0, 16.5, 3.0, 0.8, 0.6, 0.025, 0.60))
193   default_trees.append(tree("Ligustrum",       1.0, 1.0, 3.0,  6.0, 3.0, 0.8, 0.6, 0.025, 1.10))
194   default_trees.append(tree("Liquidambar",     3.0, 1.0, 3.0,  7.0, 3.0, 0.8, 0.6, 0.025, 0.30))
195   default_trees.append(tree("Liriodendron",    3.0, 1.0, 4.5,  9.5, 3.0, 0.8, 0.6, 0.025, 0.50))
196   default_trees.append(tree("Lonicera",        1.0, 1.0, 7.0,  9.0, 3.0, 0.8, 0.6, 0.025, 0.70))
197   default_trees.append(tree("Magnolia",        1.0, 1.0, 3.0,  5.0, 3.0, 0.8, 0.6, 0.025, 0.60))
198   default_trees.append(tree("Malus",           1.0, 1.0, 4.5,  5.0, 3.0, 0.8, 0.6, 0.025, 0.30))
199   default_trees.append(tree("Metasequoia",     5.0, 1.0, 4.5, 12.0, 3.0, 0.8, 0.6, 0.025, 0.50))
200   default_trees.append(tree("Morus",           1.0, 1.0, 7.5, 11.5, 3.0, 0.8, 0.6, 0.025, 1.00))
201   default_trees.append(tree("Ostrya",          1.0, 1.0, 2.0,  6.0, 3.0, 0.8, 0.6, 0.025, 1.00))
202   default_trees.append(tree("Parrotia",        1.0, 1.0, 7.0,  7.0, 3.0, 0.8, 0.6, 0.025, 0.30))
203   default_trees.append(tree("Paulownia",       1.0, 1.0, 4.0,  8.0, 3.0, 0.8, 0.6, 0.025, 0.40))
204   default_trees.append(tree("Phellodendron",   1.0, 1.0,13.5, 13.5, 3.0, 0.8, 0.6, 0.025, 0.50))
205   default_trees.append(tree("Picea",           3.0, 1.0, 3.0, 13.0, 3.0, 0.8, 0.6, 0.025, 0.90))
206   default_trees.append(tree("Pinus",           3.0, 1.0, 6.0, 16.0, 3.0, 0.8, 0.6, 0.025, 0.80))
207   default_trees.append(tree("Platanus",        1.0, 1.0,10.0, 14.5, 3.0, 0.8, 0.6, 0.025, 1.10))
208   default_trees.append(tree("Populus",         1.0, 1.0, 9.0, 20.0, 3.0, 0.8, 0.6, 0.025, 1.40))
209   default_trees.append(tree("Prunus",          1.0, 1.0, 5.0,  7.0, 3.0, 0.8, 0.6, 0.025, 1.60))
210   default_trees.append(tree("Pseudotsuga",     3.0, 1.0, 6.0, 17.5, 3.0, 0.8, 0.6, 0.025, 0.70))
211   default_trees.append(tree("Ptelea",          1.0, 1.0, 5.0,  4.0, 3.0, 0.8, 0.6, 0.025, 1.10))
212   default_trees.append(tree("Pterocaria",      1.0, 1.0,10.0, 12.0, 3.0, 0.8, 0.6, 0.025, 0.50))
213   default_trees.append(tree("Pterocarya",      1.0, 1.0,11.5, 14.5, 3.0, 0.8, 0.6, 0.025, 1.60))
214   default_trees.append(tree("Pyrus",           3.0, 1.0, 3.0,  6.0, 3.0, 0.8, 0.6, 0.025, 1.80))
215   default_trees.append(tree("Quercus",         1.0, 1.0, 8.0, 14.0, 3.1, 0.1, 0.6, 0.025, 0.40)) #
216   default_trees.append(tree("Rhamnus",         1.0, 1.0, 4.5,  4.5, 3.0, 0.8, 0.6, 0.025, 1.30))
217   default_trees.append(tree("Rhus",            1.0, 1.0, 7.0,  5.5, 3.0, 0.8, 0.6, 0.025, 0.50))
218   default_trees.append(tree("Robinia",         1.0, 1.0, 4.5, 13.5, 3.0, 0.8, 0.6, 0.025, 0.50))
219   default_trees.append(tree("Salix",           1.0, 1.0, 7.0, 14.0, 3.0, 0.8, 0.6, 0.025, 1.10))
220   default_trees.append(tree("Sambucus",        1.0, 1.0, 8.0,  6.0, 3.0, 0.8, 0.6, 0.025, 1.40))
221   default_trees.append(tree("Sasa",            1.0, 1.0,10.0, 25.0, 3.0, 0.8, 0.6, 0.025, 0.60))
222   default_trees.append(tree("Sequoiadendron",  5.0, 1.0, 5.5, 10.5, 3.0, 0.8, 0.6, 0.025, 1.60))
223   default_trees.append(tree("Sophora",         1.0, 1.0, 7.5, 10.0, 3.0, 0.8, 0.6, 0.025, 1.40))
224   default_trees.append(tree("Sorbus",          1.0, 1.0, 4.0,  7.0, 3.0, 0.8, 0.6, 0.025, 1.10))
225   default_trees.append(tree("Syringa",         1.0, 1.0, 4.5,  5.0, 3.0, 0.8, 0.6, 0.025, 0.60))
226   default_trees.append(tree("Tamarix",         1.0, 1.0, 6.0,  7.0, 3.0, 0.8, 0.6, 0.025, 0.50))
227   default_trees.append(tree("Taxodium",        5.0, 1.0, 6.0, 16.5, 3.0, 0.8, 0.6, 0.025, 0.60))
228   default_trees.append(tree("Taxus",           2.0, 1.0, 5.0,  7.5, 3.0, 0.8, 0.6, 0.025, 1.50))
229   default_trees.append(tree("Thuja",           3.0, 1.0, 3.5,  9.0, 3.0, 0.8, 0.6, 0.025, 0.70))
230   default_trees.append(tree("Tilia",           3.0, 1.0, 7.0, 12.5, 3.0, 0.8, 0.6, 0.025, 0.70))
231   default_trees.append(tree("Tsuga",           3.0, 1.0, 6.0, 10.5, 3.0, 0.8, 0.6, 0.025, 1.10))
232   default_trees.append(tree("Ulmus",           1.0, 1.0, 7.5, 14.0, 3.0, 0.8, 0.6, 0.025, 0.80))
233   default_trees.append(tree("Zelkova",         1.0, 1.0, 4.0,  5.5, 3.0, 0.8, 0.6, 0.025, 1.20))
234   default_trees.append(tree("Zenobia",         1.0, 1.0, 5.0,  5.0, 3.0, 0.8, 0.6, 0.025, 0.40))
235   
236#  Define fill values
237   fillvalues = {
238   "tree_data": float(-9999.0),
239   "tree_type": np.byte(-127)
240   }
241   
242   
243#  Check for missing data in the input and set default values if needed
244   if ( tree_type == fillvalues["tree_type"] ):
245      tree_type = int(0)
246   else:
247      tree_type = int(tree_type)
248
249   if ( tree_shape == fillvalues["tree_data"] ):
250      tree_shape = default_trees[tree_type].shape
251
252   if ( tree_height == fillvalues["tree_data"] ):
253      tree_height = default_trees[tree_type].height
254     
255   if ( tree_lai == fillvalues["tree_data"] ):
256      if (season == "summer"):
257         tree_lai = default_trees[tree_type].lai_summer
258
259      else:
260         tree_lai = default_trees[tree_type].lai_winter     
261
262   if ( tree_dia == fillvalues["tree_data"] ):
263      tree_dia = default_trees[tree_type].diameter
264     
265   if ( trunk_dia == fillvalues["tree_data"] ):
266      trunk_dia = default_trees[tree_type].dbh   
267 
268 
269#  Assign values that are not defined as user input from lookup table
270   tree_ratio          = default_trees[tree_type].ratio
271   lad_max_height      = default_trees[tree_type].lad_max_height
272   bad_scale           = default_trees[tree_type].bad_scale
273
274   #print("Tree input parameters:")
275   #print("----------------------")
276   #print("type:           " + str(default_trees[tree_type].species) )
277   #print("height:         " + str(tree_height))
278   #print("lai:            " + str(tree_lai))
279   #print("crown diameter: " + str(tree_dia))
280   #print("trunk diameter: " + str(trunk_dia))
281   #print("shape: " + str(tree_shape))
282   #print("height/width: " + str(tree_ratio))
283
284#  Calculate crown height and height of the crown center
285   crown_height   = tree_ratio * tree_dia
286   if ( crown_height > tree_height ):
287      crown_height = tree_height
288
289   crown_center   = tree_height - crown_height * 0.5
290
291
292#  Calculate height of maximum LAD
293   z_lad_max = lad_max_height * tree_height
294   
295#  Calculate the maximum LAD after Lalic and Mihailovic (2004)
296   lad_max_part_1 = integrate.quad(lambda z: ( ( tree_height - z_lad_max ) / ( tree_height - z ) ) ** (ml_n_high) * np.exp( ml_n_high * (1.0 - ( tree_height - z_lad_max ) / ( tree_height - z ) ) ), 0.0, z_lad_max)
297   lad_max_part_2 = integrate.quad(lambda z: ( ( tree_height - z_lad_max ) / ( tree_height - z ) ) ** (ml_n_low) * np.exp( ml_n_low * (1.0 - ( tree_height - z_lad_max ) / ( tree_height - z ) ) ), z_lad_max, tree_height)
298   
299   lad_max = tree_lai / (lad_max_part_1[0] + lad_max_part_2[0])
300   
301   
302#  Define position of tree and its output domain
303   nx = int(tree_dia / dx) + 2
304   nz = int(tree_height / dz) + 2
305   
306
307 
308#  Add one grid point if diameter is an odd value   
309   if ( (tree_dia % 2.0) != 0.0 ):
310      nx = nx + 1
311
312#  Create local domain of the tree's LAD
313   x = np.arange(0,nx*dx,dx)
314   x[:] = x[:] - 0.5 * dx
315   y = x
316
317   z = np.arange(0,nz*dz,dz)
318   z[1:] = z[1:] - 0.5 * dz
319
320#  Define center of the tree position inside the local LAD domain
321   tree_location_x = x[int(nx/2)]
322   tree_location_y = y[int(nx/2)]
323
324
325#  Calculate LAD profile after Lalic and Mihailovic (2004). Will be later used for normalization
326   lad_profile     =  np.arange(0,nz,1.0)
327   lad_profile[:]  = 0.0
328
329   for k in range(1,nz-1):
330       if ( (z[k] > 0.0) & (z[k] < z_lad_max) ):
331          n = ml_n_high
332       else:
333          n = ml_n_low
334     
335       lad_profile[k] =  lad_max * ( ( tree_height - z_lad_max ) / ( tree_height - z[k] ) ) ** (n) * np.exp( n * (1.0 - ( tree_height - z_lad_max ) / ( tree_height - z[k] ) ) )
336
337#  Create lad array and populate according to the specific tree shape. This is still experimental
338   lad_loc = np.ones((nz,nx,nx))
339   lad_loc[:,:,:] = fillvalues["tree_data"]
340   bad_loc = np.copy(lad_loc)
341
342
343#  For very small trees, no LAD is calculated
344   if ( tree_height <=  (0.5*dz) ):
345        print("    Shallow tree found. Action: ignore.")
346        return lad_loc, bad_loc, x, y, z, 1
347   
348   
349#  Branch for spheres and ellipsoids. A symmetric LAD sphere is created assuming an LAD extinction towards the center of the tree, representing the effect of sunlight extinction and thus
350#  less leaf mass inside the tree crown. Extinction coefficients are experimental.
351   if ( tree_shape == 1 ):
352      for i in range(0,nx-1):
353         for j in range(0,nx-1):
354            for k in range(0,nz):
355               r_test = np.sqrt( (x[i] - tree_location_x)**(2)/(tree_dia * 0.5)**(2) + (y[j] - tree_location_y)**(2)/(tree_dia * 0.5)**2 + (z[k] - crown_center)**(2)/(crown_height * 0.5)**(2))
356               if ( r_test <= 1.0 ):
357                  lad_loc[k,j,i] = lad_max * np.exp( - sphere_extinction * (1.0 - r_test) ) 
358               else:
359                  lad_loc[k,j,i] = fillvalues["tree_data"]
360
361            if ( np.any( lad_loc[:,j,i] != fillvalues["tree_data"]) ):
362               lad_loc[0,j,i] = 0.0
363
364
365#  Branch for cylinder shapes
366   if ( tree_shape == 2 ):
367      k_min = int((crown_center - crown_height * 0.5) / dz)
368      k_max = int((crown_center + crown_height * 0.5) / dz)
369      for i in range(0,nx-1):
370         for j in range(0,nx-1):
371            for k in range(k_min,k_max):
372               r_test = np.sqrt( (x[i] - tree_location_x)**(2)/(tree_dia * 0.5)**(2) + (y[j] - tree_location_y)**(2)/(tree_dia * 0.5)**(2))
373               if ( r_test <= 1.0 ):
374                  r_test3 = np.sqrt( (z[k] - crown_center)**(2)/(crown_height * 0.5)**(2))
375                  lad_loc[k,j,i] = lad_max * np.exp ( - sphere_extinction * (1.0 - max(r_test,r_test3) ) )                 
376               else:
377                  lad_loc[k,j,i] = fillvalues["tree_data"]
378
379            if ( np.any( lad_loc[:,j,i] != fillvalues["tree_data"]) ):
380               lad_loc[0,j,i] = 0.0
381
382#  Branch for cone shapes
383   if ( tree_shape == 3 ):
384      k_min = int((crown_center - crown_height * 0.5) / dz)
385      k_max = int((crown_center + crown_height * 0.5) / dz)
386      for i in range(0,nx-1):
387         for j in range(0,nx-1):
388            for k in range(k_min,k_max):
389               k_rel = k - k_min
390               r_test = (x[i] - tree_location_x)**(2) + (y[j] - tree_location_y)**(2) - ( (tree_dia * 0.5)**(2) / crown_height**(2) ) * ( z[k_rel] - crown_height)**(2)
391               if ( r_test <= 0.0 ):
392                  r_test2 = np.sqrt( (x[i] - tree_location_x)**(2)/(tree_dia * 0.5)**(2) + (y[j] - tree_location_y)**(2)/(tree_dia * 0.5)**(2))
393                  r_test3 = np.sqrt( (z[k] - crown_center)**(2)/(crown_height * 0.5)**(2))
394                  lad_loc[k,j,i] = lad_max * np.exp ( - cone_extinction * (1.0 - max((r_test+1.0),r_test2,r_test3)) )         
395               else:
396                  lad_loc[k,j,i] = fillvalues["tree_data"]
397
398            if ( np.any( lad_loc[:,j,i] != fillvalues["tree_data"]) ):
399               lad_loc[0,j,i] = 0.0
400 
401#  Branch for inverted cone shapes. TODO: what is r_test2 and r_test3 used for? Debugging needed!
402   if ( tree_shape == 4 ):
403      k_min = int((crown_center - crown_height * 0.5) / dz)
404      k_max = int((crown_center + crown_height * 0.5) / dz)
405      for i in range(0,nx-1):
406         for j in range(0,nx-1):
407            for k in range(k_min,k_max):
408               k_rel = k_max - k
409               r_test = (x[i] - tree_location_x)**(2) + (y[j] - tree_location_y)**(2) - ( (tree_dia * 0.5)**(2) / crown_height**(2) ) * ( z[k_rel] - crown_height)**(2)
410               if ( r_test <= 0.0 ):
411                  r_test2 = np.sqrt( (x[i] - tree_location_x)**(2)/(tree_dia * 0.5)**(2) + (y[j] - tree_location_y)**(2)/(tree_dia * 0.5)**(2))
412                  r_test3 = np.sqrt( (z[k] - crown_center)**(2)/(crown_height * 0.5)**(2))
413                  lad_loc[k,j,i] = lad_max * np.exp ( - cone_extinction * ( - r_test ) )         
414               else:
415                  lad_loc[k,j,i] = fillvalues["tree_data"]
416
417            if ( np.any( lad_loc[:,j,i] != fillvalues["tree_data"]) ):
418               lad_loc[0,j,i] = 0.0
419 
420 
421#  Branch for paraboloid shapes
422   if ( tree_shape == 5 ):
423      k_min = int((crown_center - crown_height * 0.5) / dz)
424      k_max = int((crown_center + crown_height * 0.5) / dz)
425      for i in range(0,nx-1):
426         for j in range(0,nx-1):
427            for k in range(k_min,k_max):
428               k_rel = k - k_min
429               r_test = ((x[i] - tree_location_x)**(2) + (y[j] - tree_location_y)**(2)) * crown_height / (tree_dia * 0.5)**(2) - z[k_rel]
430               if ( r_test <= 0.0 ):
431                   lad_loc[k,j,i] = lad_max * np.exp ( - cone_extinction * (- r_test) )               
432               else:
433                  lad_loc[k,j,i] = fillvalues["tree_data"]
434
435            if ( np.any( lad_loc[:,j,i] != fillvalues["tree_data"]) ):
436               lad_loc[0,j,i] = 0.0
437 
438 
439 
440#  Branch for inverted paraboloid shapes
441   if ( tree_shape == 6 ):
442      k_min = int((crown_center - crown_height * 0.5) / dz)
443      k_max = int((crown_center + crown_height * 0.5) / dz)
444      for i in range(0,nx-1):
445         for j in range(0,nx-1):
446            for k in range(k_min,k_max):
447               k_rel = k_max - k
448               r_test = ((x[i] - tree_location_x)**(2) + (y[j] - tree_location_y)**(2)) * crown_height / (tree_dia * 0.5)**(2) - z[k_rel]
449               if ( r_test <= 0.0 ):
450                   lad_loc[k,j,i] = lad_max * np.exp ( - cone_extinction * (- r_test) )               
451               else:
452                  lad_loc[k,j,i] = fillvalues["tree_data"]
453
454            if ( np.any( lad_loc[:,j,i] != fillvalues["tree_data"]) ):
455               lad_loc[0,j,i] = 0.0 
456 
457 
458#  Normalize the LAD profile so that the vertically integrated Lalic and Mihailovic (2004) is reproduced by the LAD array. Deactivated for now.
459   #for i in range(0,nx-1):
460      #for j in range(0,nx-1):
461         #lad_clean = np.where(lad_loc[:,j,i] == fillvalues["tree_data"],0.0,lad_loc[:,j,i])
462         #lai_from_int = integrate.simps (lad_clean, z)
463         #print(lai_from_int)
464         #for k in range(0,nz):
465            #if ( np.any(lad_loc[k,j,i] > 0.0) ): 
466               #lad_loc[k,j,i] = np.where((lad_loc[k,j,i] != fillvalues["tree_data"]),lad_loc[k,j,i] / lai_from_int * lad_profile[k],lad_loc[k,j,i])
467
468
469#  Create BAD array and populate. TODO: revise as low LAD inside the foliage does not result in low BAD values.
470   bad_loc = np.where(lad_loc != fillvalues["tree_data"],(1.0 - (lad_loc/(max(lad_loc.flatten())+0.01)))*0.1,lad_loc)
471   
472
473#  Overwrite grid cells that are occupied by the tree trunk
474   radius = trunk_dia * 0.5
475   for i in range(0,nx-1):
476      for j in range(0,nx-1):
477         for k in range(0,nz):
478            if ( z[k] <= crown_center ):
479               r_test = np.sqrt( (x[i] - tree_location_x)**(2) + (y[j] - tree_location_y)**(2)  )
480               if ( r_test == 0.0 ):
481                  if ( trunk_dia <= dx ):
482                     bad_loc[k,j,i] = radius**2 * 3.14159265359
483                  else:
484                     # WORKAROUND: divide remaining circle area over the 8 surrounding valid_pixels
485                     bad_loc[k,j-1:j+2,i-1:i+2] = radius**2 * 3.14159265359 / 8.0
486                     # for the central pixel fill the pixel
487                     bad_loc[k,j,i] = dx**2
488               #elif ( r_test <= radius ):
489                  # TODO: calculate circle segment of grid points cut by the grid
490 
491
492   return lad_loc, bad_loc, x, y, z, 0
493
494
495def process_patch(dz,patch_height,vegetation_type,max_height_lad,patch_lai,alpha,beta):
496   
497#  Define fill values
498   fillvalues = {
499   "tree_data": float(-9999.0),
500   "tree_id":   int(-9999),   
501   "tree_type": np.byte(-127), 
502   "pch_index": int(-9999)
503   }
504
505   
506   phdz = patch_height[:,:] / dz
507   pch_index = np.where( (patch_height[:,:] != fillvalues["tree_data"]),phdz.astype(int)+1,int(-1))   
508   pch_index = np.where( (pch_index[:,:] == 0) ,fillvalues["pch_index"],pch_index[:,:])
509   pch_index = np.where( (pch_index[:,:] == -1) ,0,pch_index[:,:])
510
511   max_canopy_height = max(max(patch_height.flatten()),max_height_lad)
512
513   z = np.arange(0,math.floor(max_canopy_height/dz)*dz+2*dz,dz)
514   
515   z[1:] = z[1:] - 0.5 * dz
516
517   nz = len(z)
518   ny = len(patch_height[:,0])
519   nx = len(patch_height[0,:])
520
521   pre_lad = np.ones((nz))
522   pre_lad[:] = 0.0
523   lad_loc = np.ones( (nz,ny,nx) )
524   lad_loc[:,:,:] = fillvalues["tree_data"]
525   
526   for i in range(0,nx-1):
527      for j in range(0,ny-1):
528          int_bpdf = 0.0
529          if ( (patch_height[j,i] != fillvalues["tree_data"]) & (patch_height[j,i] >= (0.5*dz)) ):
530             for k in range(1,pch_index[j,i]):
531                int_bpdf = int_bpdf + ( ( ( z[k] / patch_height[j,i] )**( alpha - 1 ) ) * ( ( 1.0 - ( z[k] / patch_height[j,i] ) )**(beta - 1 ) ) * ( dz / patch_height[j,i] ) )
532
533             for k in range(1,pch_index[j,i]):
534                pre_lad[k] =  patch_lai[j,i] * ( ( ( dz*k / patch_height[j,i] )**( alpha - 1.0 ) ) * ( ( 1.0 - ( dz*k / patch_height[j,i] ) )**(beta - 1.0 ) ) / int_bpdf ) / patch_height[j,i]
535
536             lad_loc[0,j,i] = pre_lad[0]
537             
538             for k in range(0,pch_index[j,i]):           
539                lad_loc[k,j,i] = 0.5 * ( pre_lad[k-1] + pre_lad[k] )
540
541
542   patch_id_2d = np.where(lad_loc[0,:,:] != fillvalues["tree_data"], 1, 0)
543   patch_id = np.where(lad_loc != fillvalues["tree_data"], 1, 0)
544   patch_type = np.where(lad_loc != fillvalues["tree_data"], np.byte(1), np.byte(-127))
545
546   num_ids = nd.label(patch_id_2d, output=patch_id_2d)
547 
548   for k in range(0,nz):
549   
550      vegetation_type_int = vegetation_type.astype(int)
551      patch_id[k,:,:]   = np.where((patch_id_2d != 0) & (lad_loc[k,:,:] != fillvalues["tree_data"]), patch_id_2d, fillvalues["tree_id"])
552      patch_type[k,:,:] = np.where((patch_id_2d != 0) & (lad_loc[k,:,:] != fillvalues["tree_data"]), np.byte(100+vegetation_type), fillvalues["tree_type"])
553
554   return lad_loc, patch_id, patch_type, nz, 0
555
556
557# CLASS TREE
558#
559# Default tree geometrical parameters:
560#
561# species: name of the tree type
562#
563# shape: defines the general shape of the tree and can be one of the following types:
564# 1.0 sphere or ellipsoid
565# 2.0 cylinder
566# 3.0 cone
567# 4.0 inverted cone
568# 5.0 paraboloid (rounded cone)
569# 6.0 inverted paraboloid (invertes rounded cone)
570
571# ratio:  ratio of maximum crown height to the maximum crown diameter
572# diameter: default crown diameter (m)
573# height:   default total height of the tree including trunk (m)
574# lai_summer: default leaf area index fully leafed
575# lai_winter: default winter-teim leaf area index
576# lad_max: default maximum leaf area density (m2/m3)
577# lad_max_height: default height where the leaf area density is maximum relative to total tree height
578# bad_scale: ratio of basal area in the crown area to the leaf area
579# dbh: default trunk diameter at breast height (1.4 m) (m)
580#
581class tree:
582    def __init__(self, species, shape, ratio, diameter, height, lai_summer, lai_winter, lad_max_height, bad_scale, dbh): 
583       self.species = species
584       self.shape = shape
585       self.ratio = ratio
586       self.diameter = diameter
587       self.height = height
588       self.lai_summer = lai_summer
589       self.lai_winter = lai_winter
590       self.lad_max_height = lad_max_height
591       self.bad_scale = bad_scale
592       self.dbh = dbh   
593   
Note: See TracBrowser for help on using the repository browser.