1 
2 /****************************************************************************
3  *
4  * MODULE:       r.ros
5  * AUTHOR(S):    Jianping Xu, Rutgers University, 1993
6  *               Markus Neteler <neteler itc.it>
7  *               Roberto Flor <flor itc.it>, Brad Douglas <rez touchofmadness.com>,
8  *               Glynn Clements <glynn gclements.plus.com>, Jachym Cepicky <jachym les-ejk.cz>
9  * PURPOSE:      Generates rate of spread raster map layers for wildfire modeling
10  *
11  * TODO: (re)move following documentation
12  *
13  * This raster module creates three raster map layers:
14  *	1. Base (perpendicular) rate of spread (ROS);
15  *	2. Maximum (forward) ROS; and
16  * 	3. Direction of the Maximum ROS.
17  * The calculation of the two ROS values for each raster
18  * cell is based on the Fortran code by Pat Andrews (1983)
19  * of the Northern Forest Fire Laboratory, USDA Forest
20  * Service. These three raster map layers are expected to
21  * be the inputs for a separate GRASS raster module
22  * 'r.spread'.
23  *
24  * 'r.ros' can be run in two standard GRASS modes:
25  * interactive and command line. For an interactive run,
26  * type in
27  *	r.ros
28  * and follow the prompts; for a command line mode,
29  * type in
30  * 	r.ros [-v] model=name [moisture_1h=name]
31  *			[moisture_10h=name]
32  *			[moisture_100h=name]
33  *			moisture_live=name [velocity=name]
34  * 			[direction=name] [slope=name]
35  *			[aspect=name] output=name
36  * where:
37  *   Flag:
38  *   Raster Maps:
39  *      model   	1-13: the standard fuel models,
40  *			all other numbers: same as barriers;
41  *  	moisture_1h   	100*moisture_content;
42  *	moisture_10h  	100*moisture_content;
43  *	moisture_100h   100*moisture_content;
44  *	moisture_live   100*moisture_content;
45  *	velocity   	ft/minute;
46  *	direction   	degree;
47  *	slope   	degree;
48  * 	aspect   	degree starting from East, anti-clockwise;
49  *	output
50  *	  for Base ROS	cm/min (technically not ft/min);
51  *  	  for Max ROS	cm/min (technically not ft/min);
52  *	  for Direction	degree.
53  *
54  * Note that the name given as output will be used as
55  * PREFIX for several actual raster maps. For example, if
56  * my_ros is given as the output name, 'r.ros' will
57  * actually produce three raster maps named my_ros.base,
58  * my_ros.max, and my_ros.maxdir, or even my_ros.spotdist
59  * respectively.
60  *
61  * COPYRIGHT:    (C) 2000-2009 by the GRASS Development Team
62  *
63  *               This program is free software under the GNU General Public
64  *               License (>=v2). Read the file COPYING that comes with GRASS
65  *               for details.
66  *
67  *****************************************************************************/
68 
69 #include <stdlib.h>
70 #include <stdio.h>
71 #include <math.h>
72 #include <grass/gis.h>
73 #include <grass/raster.h>
74 #include <grass/glocale.h>
75 #include "local_proto.h"
76 
77 
78 #define DATA(map, r, c)         (map)[(r) * ncols + (c)]
79 
80 /*measurements of the 13 fuel models, input of Rothermel equation (1972) */
81 float WO[4][14] =
82     { {0, 0.034, 0.092, 0.138, 0.230, 0.046, 0.069, 0.052, 0.069, 0.134,
83        0.138, 0.069, 0.184, 0.322},
84 {0, 0, 0.046, 0, 0.184, 0.023, 0.115, 0.086, 0.046, 0.019, 0.092, 0.207,
85  0.644, 1.058},
86 {0, 0, 0.023, 0, 0.092, 0, 0.092, 0.069, 0.115, 0.007, 0.230, 0.253, 0.759,
87  1.288},
88 {0, 0, 0.023, 0, 0.230, 0.092, 0, 0.017, 0, 0, 0.092, 0, 0}
89 };
90 
91 					    /*ovendry fuel loading, lb./ft.^2 */
92 float DELTA[] = { 0, 1.0, 1.0, 2.5, 6.0, 2.0, 2.5, 2.5,
93     0.2, 0.2, 1.0, 1.0, 2.3, 3.0
94 };				/*fuel depth, ft. */
95 float SIGMA[4][14] =
96     { {0, 3500, 3000, 1500, 2000, 2000, 1750, 1750, 2000, 2500, 2000, 1500,
97        1500, 1500},
98 {0, 0, 109, 0, 109, 109, 109, 109, 109, 109, 109, 109, 109, 109},
99 {0, 0, 30, 0, 30, 0, 30, 30, 30, 30, 30, 30, 30, 30},
100 {0, 0, 1500, 0, 1500, 1500, 0, 1500, 0, 0, 1500, 0, 0, 0}
101 };
102 
103 			 /*fuel particale surface-area-to-volume ratio, 1/ft. */
104 float MX[] = { 0, 0.12, 0.15, 0.25, 0.20, 0.20, 0.25, 0.40,
105     0.30, 0.25, 0.25, 0.15, 0.20, 0.25
106 };				/*moisture content of extinction */
107 
108 CELL *map_elev;			/*full array for elevation map layer (for spotting) */
109 int nrows, ncols;
110 struct Cell_head window;
111 
112 
main(int argc,char * argv[])113 int main(int argc, char *argv[])
114 {
115 
116 	/***input of Rothermel equation (1972)***/
117     float h = 8000.0,		/*heat of combustion, BTU/lb. */
118 	rhop = 32,		/*ovendry fuel density, lb./ft.^3 */
119 	ST = 0.0555;		/*fuel total mineral content, lb. minerals/lb. ovendry */
120 
121     float sigma[14];
122 
123 	/***derived parameters of Rothermel equation (1972)***/
124     float R,			/*rate of spread, ft./min.
125 				   R = IR*xi*(1+phiw+phis)/(rhob*epsilon*Qig)  */
126       IR,			/*reaction intensity, BTU/ft.^2/min.
127 				   IR = gamma*wn*h*etaM*etas  */
128       gamma,			/*optimum reation velosity, 1/min.
129 				   gamma = gammamax*(beta/betaop)^A*
130 				   exp(A(1-beta/betaop))  */
131       gammamax,			/*maximum reation velosity, 1/min.
132 				   gammamax = sigma^1.5/(495+0.0594*sigma^1.5)  */
133       betaop,			/*optimum packing ratio
134 				   betaop = 3.348/(sigma^0.8189)  */
135       A,			/*A = 1/(4.77*sigma^0.1-7.27)  */
136       etas = 0.174 / pow(0.01, 0.19),	/*mineral damping coefficient */
137 	xi,			/*propagating flux ratio,
138 				   xi = exp((0.792+0.681*sigma^0.5)(beta+0.1))/
139 				   (192+0.2595*sigma)  */
140 	phiw,			/*wind coefficient,  phiw = C*U^B/(beta/betaop)^E */
141 	C,			/*C = 7.47/exp(0.133*sigma^0.55)  */
142 	B,			/*B = 0.02526*sigma^.054   */
143 	E,			/*E = 0.715/exp(0.000359*sigma)  */
144 	phis,			/*slope coefficient,phis = 5.275/beta^0.3*(tan(theta)^2) */
145 	rhob,			/*ovendry bulk density, lb./ft.^3, rohb = wo/delta */
146 	epsilon[4][14],		/*effective heating number, epsilon = 1/exp(138/sigma) */
147 	Qig[14],		/*heat of preignition, BTU/lb.  Qig = 250+1116*Mf */
148 	beta;			/*packing ratio,  beta = rhob/rhop  */
149 
150 	/***intermediate variables***/
151     float R0,			/*base ROS (w/out wind/slope) */
152       Rdir, sin_fac, cos_fac, Ffactor_all[4][14],	/*in all fuel subclasses by sigma/WO */
153       Ffactor_in_dead[3][14],	/*in dead fuel subclasses by sigma/WO */
154       Ffactor_dead[14],		/*dead fuel weight by sigma/WO */
155       Ffactor_live[14],		/*live fuel weight by sigma/WO */
156       Gfactor_in_dead[3][14],	/*in dead fuel by the 6 classes */
157       G1, G2, G3, G4, G5, wo_dead[14],	/*dead fuel total load */
158       wn_dead,			/*net dead fuel total load */
159       wn_live,			/*net live fuel (total) load */
160       class_sum, moisture[4],	/*moistures of 1-h,10-h,100-h,live fuels */
161       Mf_dead,			/*total moisture of dead fuels */
162       etaM_dead,		/*dead fuel misture damping coefficient */
163       etaM_live,		/*live fuel misture damping coefficient */
164       xmext,			/*live fuel moisture of extinction */
165       phi_ws,			/*wind and slope conbined coefficient */
166       wmfd, fdmois, fined, finel;
167 
168     /*other local variables */
169     int col, row,
170 	spotting,
171 	model, class,
172 	fuel_fd = 0,
173 	mois_1h_fd = 0, mois_10h_fd = 0, mois_100h_fd = 0, mois_live_fd = 0,
174 	vel_fd = 0, dir_fd = 0,
175 	elev_fd = 0, slope_fd = 0, aspect_fd = 0,
176 	base_fd = 0, max_fd = 0, maxdir_fd = 0, spotdist_fd = 0;
177 
178     char *name_base, *name_max, *name_maxdir, *name_spotdist;
179 
180     CELL *fuel,			/*cell buffer for fuel model map layer */
181      *mois_1h,			/*cell buffer for 1-hour fuel moisture map layer */
182      *mois_10h,			/*cell buffer for 10-hour fuel moisture map layer */
183      *mois_100h,		/*cell buffer for 100-hour fuel moisture map layer */
184      *mois_live,		/*cell buffer for live fuel moisture map layer */
185      *vel,			/*cell buffer for wind velocity map layer */
186      *dir,			/*cell buffer for wind direction map layer */
187      *elev = NULL,		/*cell buffer for elevation map layer (for spotting) */
188 	*slope,			/*cell buffer for slope map layer */
189 	*aspect,		/*cell buffer for aspect map layer */
190 	*base,			/*cell buffer for base ROS map layer */
191 	*max,			/*cell buffer for max ROS map layer */
192 	*maxdir,		/*cell buffer for max ROS direction map layer */
193 	*spotdist = NULL;	/*cell buffer for max spotting distance map layer */
194 
195     extern struct Cell_head window;
196 
197     struct
198     {
199 	struct Option *model,
200 	    *mois_1h, *mois_10h, *mois_100h, *mois_live,
201             *vel, *dir, *elev, *slope, *aspect, *base, *max, *maxdir, *spotdist;
202     } parm;
203 
204     /* please, remove before GRASS 7 released */
205     struct GModule *module;
206 
207     /* initialize access to database and create temporary files */
208     G_gisinit(argv[0]);
209 
210     /* Set description */
211     module = G_define_module();
212     G_add_keyword(_("raster"));
213     G_add_keyword(_("fire"));
214     G_add_keyword(_("spread"));
215     G_add_keyword(_("rate of spread"));
216     G_add_keyword(_("hazard"));
217     G_add_keyword(_("model"));
218     module->label = _("Generates rate of spread raster maps.");
219     module->description =
220 	_("Generates three, or four raster map layers showing the base "
221 	  "(perpendicular) rate of spread (ROS), the maximum (forward) ROS, "
222 	  "the direction of the maximum ROS, and optionally the "
223 	  "maximum potential spotting distance for fire spread simulation.");
224 
225     parm.model = G_define_standard_option(G_OPT_R_INPUT);
226     parm.model->key = "model";
227     parm.model->label = _("Raster map containing fuel models");
228     parm.model->description =
229 	_("Name of an "
230 	  "existing raster map layer in the user's current mapset search path containing "
231 	  "the standard fuel models defined by the USDA Forest Service. Valid values "
232 	  "are 1-13; other numbers are recognized as barriers by r.ros.");
233 
234     parm.mois_1h = G_define_standard_option(G_OPT_R_INPUT);
235     parm.mois_1h->key = "moisture_1h";
236     parm.mois_1h->required = NO;
237     parm.mois_1h->label =
238 	_("Raster map containing the 1-hour fuel moisture (%)");
239 	parm.mois_1h->description =
240 	_("Name of an existing raster map layer in "
241 	  "the user's current mapset search path containing the 1-hour (<.25\") "
242 	  "fuel moisture (percentage content multiplied by 100).");
243 
244     parm.mois_10h = G_define_standard_option(G_OPT_R_INPUT);
245     parm.mois_10h->key = "moisture_10h";
246     parm.mois_10h->required = NO;
247     parm.mois_10h->label =
248 	_("Raster map containing the 10-hour fuel moisture (%)");
249     parm.mois_10h->description =
250 	_("Name of an existing raster map layer in the "
251 	  "user's current mapset search path containing the 10-hour (.25-1\") fuel "
252 	  "moisture (percentage content multiplied by 100).");
253 
254     parm.mois_100h = G_define_standard_option(G_OPT_R_INPUT);
255     parm.mois_100h->key = "moisture_100h";
256     parm.mois_100h->required = NO;
257     parm.mois_100h->label =
258 	_("Raster map containing the 100-hour fuel moisture (%)");
259     parm.mois_100h->description =
260 	_("Name of an existing raster map layer in the "
261 	  "user's current mapset search path containing the 100-hour (1-3\") fuel moisture "
262 	  "(percentage content multiplied by 100).");
263 
264     parm.mois_live = G_define_standard_option(G_OPT_R_INPUT);
265     parm.mois_live->key = "moisture_live";
266     parm.mois_live->label =
267 	_("Raster map containing live fuel moisture (%)");
268     parm.mois_live->description =
269 	_("Name of an existing raster map layer in the "
270 	  "user's current mapset search path containing live (herbaceous) fuel "
271 	  "moisture (percentage content multiplied by 100).");
272 
273     parm.vel = G_define_standard_option(G_OPT_R_INPUT);
274     parm.vel->key = "velocity";
275     parm.vel->required = NO;
276     parm.vel->label =
277 	_("Raster map containing midflame wind velocities (ft/min)");
278     parm.vel->description =
279 	_("Name of an existing raster map layer in the user's "
280 	  "current mapset search path containing wind velocities at half of the average "
281 	  "flame height (feet/minute).");
282 
283     parm.dir = G_define_standard_option(G_OPT_R_INPUT);
284     parm.dir->key = "direction";
285     parm.dir->required = NO;
286     parm.dir->label =
287 	_("Name of raster map containing wind directions (degree)");
288     parm.dir->description =
289 	_("Name of an existing raster map "
290 	  "layer in the user's current mapset search path containing wind direction, "
291 	  "clockwise from north (degree).");
292 
293     parm.slope = G_define_standard_option(G_OPT_R_INPUT);
294     parm.slope->key = "slope";
295     parm.slope->required = NO;
296     parm.slope->label =
297 	_("Name of raster map containing slope (degree)");
298     parm.slope->description =
299 	_("Name of an existing raster map layer "
300 	  "in the user's current mapset search path containing "
301 	  "topographic slope (degree).");
302 
303     parm.aspect = G_define_standard_option(G_OPT_R_INPUT);
304     parm.aspect->key = "aspect";
305     parm.aspect->required = NO;
306     parm.aspect->label =
307 	_("Raster map containing aspect (degree, CCW from E)");
308     parm.aspect->description =
309 	_("Name of an existing "
310 	  "raster map layer in the user's current mapset search path containing "
311 	  "topographic aspect, counterclockwise from east (GRASS convention) "
312 	  "in degrees.");
313 
314     parm.elev = G_define_standard_option(G_OPT_R_ELEV);
315     parm.elev->required = NO;
316     parm.elev->label =
317 	_("Raster map containing elevation (m, required for spotting)");
318     parm.elev->description =
319 	_("Name of an existing raster map "
320 	  "layer in the user's current mapset search path containing elevation (meters). "
321 	  "Option is required from spotting distance computation "
322 	  "(when spotting_distance option is provided)");
323 
324     parm.base = G_define_standard_option(G_OPT_R_OUTPUT);
325     parm.base->key = "base_ros";
326     parm.base->required = YES;
327     parm.base->label =
328 	_("Output raster map containing base ROS (cm/min)");
329     parm.base->description =
330 	_("Base (perpendicular) rate of spread (ROS)");
331 
332     parm.max = G_define_standard_option(G_OPT_R_OUTPUT);
333     parm.max->key = "max_ros";
334     parm.max->required = YES;
335     parm.max->label =
336 	_("Output raster map containing maximal ROS (cm/min)");
337     parm.max->description =
338 	_("The maximum (forward) rate of spread (ROS)");
339 
340     parm.maxdir = G_define_standard_option(G_OPT_R_OUTPUT);
341     parm.maxdir->key = "direction_ros";
342     parm.maxdir->required = YES;
343     parm.maxdir->label =
344 	_("Output raster map containing directions of maximal ROS (degree)");
345     parm.maxdir->description =
346 	_("The direction of the maximal (forward) rate of spread (ROS)");
347 
348     parm.spotdist = G_define_standard_option(G_OPT_R_OUTPUT);
349     parm.spotdist->key = "spotting_distance";
350     parm.spotdist->required = NO;
351     parm.spotdist->label =
352 	_("Output raster map containing maximal spotting distance (m)");
353     parm.spotdist->description =
354 	_("The maximal potential spotting distance"
355 	  " (requires elevation raster map to be provided).");
356 
357     /*   Parse command line */
358     if (G_parser(argc, argv))
359 	exit(EXIT_FAILURE);
360 
361     if (parm.spotdist->answer)
362 	spotting = 1;
363     else
364 	spotting = 0;
365 
366     /*  Check if input layers exists in data base  */
367     if (G_find_raster2(parm.model->answer, "") == NULL)
368 	G_fatal_error(_("Raster map <%s> not found"), parm.model->answer);
369 
370     if (!
371 	(parm.mois_1h->answer || parm.mois_10h->answer ||
372 	 parm.mois_100h->answer)) {
373 	G_fatal_error(_("No dead fuel moisture is given. "
374 			"At least one of the 1-h, 10-h, 100-h moisture layers is required."));
375     }
376 
377     if (parm.mois_1h->answer) {
378 	if (G_find_raster2(parm.mois_1h->answer, "") == NULL)
379 	    G_fatal_error(_("Raster map <%s> not found"),
380 			  parm.mois_1h->answer);
381     }
382     if (parm.mois_10h->answer) {
383 	if (G_find_raster2(parm.mois_10h->answer, "") == NULL)
384 	    G_fatal_error(_("Raster map <%s> not found"),
385 			  parm.mois_10h->answer);
386     }
387     if (parm.mois_100h->answer) {
388 	if (G_find_raster2(parm.mois_100h->answer, "") == NULL)
389 	    G_fatal_error(_("Raster map <%s> not found"),
390 			  parm.mois_100h->answer);
391     }
392 
393     if (G_find_raster2(parm.mois_live->answer, "") == NULL)
394 	G_fatal_error(_("Raster map <%s> not found"), parm.mois_live->answer);
395 
396     if (parm.vel->answer && !(parm.dir->answer)) {
397 	G_fatal_error(_("A wind direction layer should be "
398 			"given if the wind velocity layer <%s> has been given"),
399 		      parm.vel->answer);
400     }
401     if (!(parm.vel->answer) && parm.dir->answer) {
402 	G_fatal_error(_("A wind velocity layer should be given "
403 			"if the wind direction layer <%s> has been given"),
404 		      parm.dir->answer);
405     }
406     if (parm.vel->answer) {
407 	if (G_find_raster2(parm.vel->answer, "") == NULL)
408 	    G_fatal_error(_("Raster map <%s> not found"), parm.vel->answer);
409     }
410     if (parm.dir->answer) {
411 	if (G_find_raster2(parm.dir->answer, "") == NULL)
412 	    G_fatal_error(_("Raster map <%s> not found"), parm.dir->answer);
413     }
414 
415     if (parm.slope->answer && !(parm.aspect->answer)) {
416 	G_fatal_error(_("An aspect layer should be given "
417 			"if the slope layer <%s> has been given"),
418 		      parm.slope->answer);
419     }
420     if (!(parm.slope->answer) && parm.aspect->answer) {
421 	G_fatal_error(_("A slope layer should be given "
422 			"if the aspect layer <%s> has been given"),
423 		      parm.aspect->answer);
424     }
425     if (parm.slope->answer) {
426 	if (G_find_raster2(parm.slope->answer, "") == NULL)
427 	    G_fatal_error(_("Raster map <%s> not found"), parm.slope->answer);
428     }
429     if (parm.aspect->answer) {
430 	if (G_find_raster2(parm.aspect->answer, "") == NULL)
431 	    G_fatal_error(_("Raster map <%s> not found"),
432 			  parm.aspect->answer);
433     }
434 
435     if (spotting) {
436 	if (!(parm.elev->answer)) {
437 	    G_fatal_error(_("An elevation layer should be given "
438 			    "if considering spotting"));
439 	}
440 	else {
441 	    if (G_find_raster2(parm.elev->answer, "") == NULL)
442 		G_fatal_error(_("Raster map <%s> not found"),
443 			      parm.elev->answer);
444 	}
445     }
446 
447     /*assign names of the three output ROS layers */
448     name_base = parm.base->answer;
449     name_max = parm.max->answer;
450     name_maxdir = parm.maxdir->answer;
451 
452     /*assign a name to output SPOTTING distance layer */
453     if (spotting) {
454         name_spotdist = parm.spotdist->answer;
455     }
456 
457     /*  Get database window parameters  */
458     G_get_window(&window);
459 
460     /*  find number of rows and columns in window    */
461     nrows = Rast_window_rows();
462     ncols = Rast_window_cols();
463 
464     fuel = Rast_allocate_c_buf();
465     mois_1h = Rast_allocate_c_buf();
466     mois_10h = Rast_allocate_c_buf();
467     mois_100h = Rast_allocate_c_buf();
468     mois_live = Rast_allocate_c_buf();
469     vel = Rast_allocate_c_buf();
470     dir = Rast_allocate_c_buf();
471     slope = Rast_allocate_c_buf();
472     aspect = Rast_allocate_c_buf();
473     base = Rast_allocate_c_buf();
474     max = Rast_allocate_c_buf();
475     maxdir = Rast_allocate_c_buf();
476     if (spotting) {
477 	spotdist = Rast_allocate_c_buf();
478 	elev = Rast_allocate_c_buf();
479 	map_elev = (CELL *) G_calloc(nrows * ncols, sizeof(CELL));
480     }
481 
482     /*  Open input cell layers for reading  */
483 
484     fuel_fd = Rast_open_old(parm.model->answer, "");
485 
486     if (parm.mois_1h->answer)
487 	mois_1h_fd = Rast_open_old(parm.mois_1h->answer, "");
488 
489     if (parm.mois_10h->answer)
490 	mois_10h_fd = Rast_open_old(parm.mois_10h->answer,"");
491 
492     if (parm.mois_100h->answer)
493 	mois_100h_fd = Rast_open_old(parm.mois_100h->answer, "");
494 
495     mois_live_fd = Rast_open_old(parm.mois_live->answer, "");
496 
497     if (parm.vel->answer)
498 	vel_fd = Rast_open_old(parm.vel->answer, "");
499 
500     if (parm.dir->answer)
501 	dir_fd = Rast_open_old(parm.dir->answer, "");
502 
503     if (parm.slope->answer)
504 	slope_fd = Rast_open_old(parm.slope->answer, "");
505 
506     if (parm.aspect->answer)
507 	aspect_fd = Rast_open_old(parm.aspect->answer, "");
508 
509     if (spotting)
510 	elev_fd = Rast_open_old(parm.elev->answer, "");
511 
512     base_fd = Rast_open_c_new(name_base);
513     max_fd = Rast_open_c_new(name_max);
514     maxdir_fd = Rast_open_c_new(name_maxdir);
515     if (spotting)
516 	spotdist_fd = Rast_open_c_new(name_spotdist);
517 
518     /*compute weights, combined wo, and combined sigma */
519     /*wo[model] -- simple sum of WO[class][model] by all fuel subCLASS */
520     /*sigma[model] -- weighted sum of SIGMA[class][model] by all fuel subCLASS       *epsilon[class][model] */
521     for (model = 1; model <= 13; model++) {
522 	class_sum = 0.0;
523 	wo_dead[model] = 0.0;
524 	sigma[model] = 0.0;
525 	for (class = 0; class <= 3; class++) {
526 	    class_sum = class_sum + WO[class][model] * SIGMA[class][model];
527 	    if (SIGMA[class][model] > 0.0) {
528 		epsilon[class][model] = exp(-138.0 / SIGMA[class][model]);
529 	    }
530 	    else {
531 		epsilon[class][model] = 0.0;
532 	    }
533 	}
534 	for (class = 0; class <= 3; class++) {
535 	    Ffactor_all[class][model] =
536 		WO[class][model] * SIGMA[class][model] / class_sum;
537 	    sigma[model] =
538 		sigma[model] +
539 		SIGMA[class][model] * Ffactor_all[class][model];
540 	}
541 	class_sum = 0.0;
542 	for (class = 0; class <= 2; class++) {
543 	    wo_dead[model] = wo_dead[model] + WO[class][model];
544 	    class_sum = class_sum + WO[class][model] * SIGMA[class][model];
545 	}
546 	for (class = 0; class <= 2; class++) {
547 	    Ffactor_in_dead[class][model] =
548 		WO[class][model] * SIGMA[class][model] / class_sum;
549 	}
550 
551 	/* compute G factor for each of the 6 subclasses */
552 	G1 = 0.0;
553 	G2 = 0.0;
554 	G3 = 0.0;
555 	G4 = 0.0;
556 	G5 = 0.0;
557 	for (class = 0; class <= 2; class++) {
558 	    if (SIGMA[class][model] >= 1200)
559 		G1 = G1 + Ffactor_in_dead[class][model];
560 	    if (SIGMA[class][model] < 1200 && SIGMA[class][model] >= 192)
561 		G2 = G2 + Ffactor_in_dead[class][model];
562 	    if (SIGMA[class][model] < 192 && SIGMA[class][model] >= 96)
563 		G3 = G3 + Ffactor_in_dead[class][model];
564 	    if (SIGMA[class][model] < 96 && SIGMA[class][model] >= 48)
565 		G4 = G4 + Ffactor_in_dead[class][model];
566 	    if (SIGMA[class][model] < 48 && SIGMA[class][model] >= 16)
567 		G5 = G5 + Ffactor_in_dead[class][model];
568 	}
569 	for (class = 0; class <= 2; class++) {
570 	    if (SIGMA[class][model] >= 1200)
571 		Gfactor_in_dead[class][model] = G1;
572 	    if (SIGMA[class][model] < 1200 && SIGMA[class][model] >= 192)
573 		Gfactor_in_dead[class][model] = G2;
574 	    if (SIGMA[class][model] < 192 && SIGMA[class][model] >= 96)
575 		Gfactor_in_dead[class][model] = G3;
576 	    if (SIGMA[class][model] < 96 && SIGMA[class][model] >= 48)
577 		Gfactor_in_dead[class][model] = G4;
578 	    if (SIGMA[class][model] < 48 && SIGMA[class][model] >= 16)
579 		Gfactor_in_dead[class][model] = G5;
580 	    if (SIGMA[class][model] < 16)
581 		Gfactor_in_dead[class][model] = 0.0;
582 	}
583 
584 	Ffactor_dead[model] =
585 	    class_sum / (class_sum + WO[3][model] * SIGMA[3][model]);
586 	Ffactor_live[model] = 1 - Ffactor_dead[model];
587     }
588 
589     /*if considering spotting, read elevation map into an array */
590     if (spotting)
591 	for (row = 0; row < nrows; row++) {
592 	    Rast_get_c_row(elev_fd, elev, row);
593 	    for (col = 0; col < ncols; col++)
594 		DATA(map_elev, row, col) = elev[col];
595 	}
596 
597     /*major computation: compute ROSs one cell a time */
598     for (row = 0; row < nrows; row++) {
599 	G_percent(row, nrows, 2);
600 	Rast_get_c_row(fuel_fd, fuel, row);
601 	if (parm.mois_1h->answer)
602 	    Rast_get_c_row(mois_1h_fd, mois_1h, row);
603 	if (parm.mois_10h->answer)
604 	    Rast_get_c_row(mois_10h_fd, mois_10h, row);
605 	if (parm.mois_100h->answer)
606 	    Rast_get_c_row(mois_100h_fd, mois_100h, row);
607 	Rast_get_c_row(mois_live_fd, mois_live, row);
608 	if (parm.vel->answer)
609 	    Rast_get_c_row(vel_fd, vel, row);
610 	if (parm.dir->answer)
611 	    Rast_get_c_row(dir_fd, dir, row);
612 	if (parm.slope->answer)
613 	    Rast_get_c_row(slope_fd, slope, row);
614 	if (parm.aspect->answer)
615 	    Rast_get_c_row(aspect_fd, aspect, row);
616 
617 	/*initialize cell buffers for output map layers */
618 	for (col = 0; col < ncols; col++) {
619 	    base[col] = max[col] = maxdir[col] = 0;
620 	    if (spotting)
621 		spotdist[col] = 0;
622 	}
623 
624 	for (col = 0; col < ncols; col++) {
625 	    /*check if a fuel is within the 13 models,
626 	     *if not, no processing; useful when no data presents*/
627 	    if (fuel[col] < 1 || fuel[col] > 13)
628 		continue;
629 	    if (parm.mois_1h->answer)
630 		moisture[0] = 0.01 * mois_1h[col];
631 	    if (parm.mois_10h->answer)
632 		moisture[1] = 0.01 * mois_10h[col];
633 	    if (parm.mois_100h->answer)
634 		moisture[2] = 0.01 * mois_100h[col];
635 	    moisture[3] = 0.01 * mois_live[col];
636 	    if (parm.aspect->answer)
637 		aspect[col] = (630 - aspect[col]) % 360;
638 
639 	    /* assign some dead fuel moisture if not completely
640 	     *     given based on empirical relationship*/
641 	    if (!(parm.mois_10h->answer || parm.mois_100h->answer)) {
642 		moisture[1] = moisture[0] + 0.01;
643 		moisture[2] = moisture[0] + 0.02;
644 	    }
645 	    if (!(parm.mois_1h->answer || parm.mois_100h->answer)) {
646 		moisture[0] = moisture[1] - 0.01;
647 		moisture[2] = moisture[1] + 0.01;
648 	    }
649 	    if (!(parm.mois_1h->answer || parm.mois_10h->answer)) {
650 		moisture[0] = moisture[2] - 0.02;
651 		moisture[1] = moisture[2] - 0.01;
652 	    }
653 	    if (!(parm.mois_1h->answer) && parm.mois_10h->answer &&
654 		parm.mois_100h->answer)
655 		moisture[0] = moisture[1] - 0.01;
656 	    if (!(parm.mois_10h->answer) && parm.mois_1h->answer &&
657 		parm.mois_100h->answer)
658 		moisture[1] = moisture[0] + 0.01;
659 	    if (!(parm.mois_100h->answer) && parm.mois_1h->answer &&
660 		parm.mois_10h->answer)
661 		moisture[2] = moisture[1] + 0.01;
662 
663 	    /*compute xmext, moisture of extinction of live fuels */
664 	    wmfd = 0.0;
665 	    fined = 0.0;
666 	    if (SIGMA[3][fuel[col]] > 0.0) {
667 		for (class = 0; class <= 2; class++) {
668 		    if (SIGMA[class][fuel[col]] == 0.0)
669 			continue;
670 		    fined =
671 			fined +
672 			WO[class][fuel[col]] * exp(-138.0 /
673 						   SIGMA[class][fuel[col]]);
674 		    wmfd =
675 			wmfd +
676 			WO[class][fuel[col]] * exp(-138.0 /
677 						   SIGMA[class][fuel[col]]) *
678 			moisture[class];
679 		}
680 		fdmois = wmfd / fined;
681 		finel = WO[3][fuel[col]] * exp(-500.0 / SIGMA[3][fuel[col]]);
682 		xmext =
683 		    2.9 * (fined / finel) * (1 - fdmois / MX[fuel[col]]) -
684 		    0.226;
685 	    }
686 	    else
687 		xmext = MX[fuel[col]];
688 	    if (xmext < MX[fuel[col]])
689 		xmext = MX[fuel[col]];
690 
691 	    /*compute other intermediate values */
692 	    Mf_dead = 0.0;
693 	    wn_dead = 0.0;
694 	    class_sum = 0.0;
695 	    for (class = 0; class <= 2; class++) {
696 		Mf_dead =
697 		    Mf_dead +
698 		    moisture[class] * Ffactor_in_dead[class][fuel[col]];
699 		wn_dead =
700 		    wn_dead +
701 		    WO[class][fuel[col]] * Gfactor_in_dead[class][fuel[col]] *
702 		    (1 - ST);
703 		Qig[class] = 250 + 1116 * moisture[class];
704 		class_sum =
705 		    class_sum +
706 		    Ffactor_all[class][fuel[col]] *
707 		    epsilon[class][fuel[col]] * Qig[class];
708 	    }
709 	    etaM_dead =
710 		1.0 - 2.59 * (Mf_dead / MX[fuel[col]]) +
711 		5.11 * (Mf_dead / MX[fuel[col]]) * (Mf_dead / MX[fuel[col]]) -
712 		3.52 * (Mf_dead / MX[fuel[col]]) * (Mf_dead / MX[fuel[col]]) *
713 		(Mf_dead / MX[fuel[col]]);
714 	    if (Mf_dead >= MX[fuel[col]])
715 		etaM_dead = 0.0;
716 	    etaM_live =
717 		1.0 - 2.59 * (moisture[3] / xmext) +
718 		5.11 * (moisture[3] / xmext) * (moisture[3] / xmext) -
719 		3.52 * (moisture[3] / xmext) * (moisture[3] / xmext) *
720 		(moisture[3] / xmext);
721 	    if (moisture[3] >= xmext)
722 		etaM_live = 0.0;
723 	    wn_live = WO[3][fuel[col]] * (1 - ST);
724 	    Qig[3] = 250 + 1116 * moisture[3];
725 	    class_sum =
726 		class_sum +
727 		Ffactor_all[3][fuel[col]] * epsilon[3][fuel[col]] * Qig[3];
728 
729 	    /*final computations */
730 	    rhob = (wo_dead[fuel[col]] + WO[3][fuel[col]]) / DELTA[fuel[col]];
731 	    beta = rhob / rhop;
732 	    betaop = 3.348 / pow(sigma[fuel[col]], 0.8189);
733 	    A = 133 / pow(sigma[fuel[col]], 0.7913);
734 	    gammamax =
735 		pow(sigma[fuel[col]],
736 		    1.5) / (495 + 0.0594 * pow(sigma[fuel[col]], 1.5));
737 	    gamma =
738 		gammamax * pow(beta / betaop,
739 			       A) * exp(A * (1 - beta / betaop));
740 	    xi = exp((0.792 + 0.681 * pow(sigma[fuel[col]], 0.5)) * (beta +
741 								     0.1)) /
742 		(192 + 0.2595 * sigma[fuel[col]]);
743 	    IR = gamma * h * (wn_dead * etaM_dead +
744 			      wn_live * etaM_live) * etas;
745 
746 	    R0 = IR * xi / (rhob * class_sum);
747 
748 	    if (parm.vel->answer && parm.dir->answer) {
749 		C = 7.47 * exp(-0.133 * pow(sigma[fuel[col]], 0.55));
750 		B = 0.02526 * pow(sigma[fuel[col]], 0.54);
751 		E = 0.715 * exp(-0.000359 * sigma[fuel[col]]);
752 		phiw = C * pow((double)vel[col], B) / pow(beta / betaop, E);
753 	    }
754 	    else
755 		phiw = 0.0;
756 
757 	    if (parm.slope->answer && parm.aspect->answer) {
758 		phis =
759 		    5.275 * pow(beta,
760 				-0.3) * tan(M_D2R * slope[col]) *
761 		    tan(M_D2R * slope[col]);
762 	    }
763 	    else
764 		phis = 0.0;
765 
766 	    /*compute the maximum ROS R and its direction,
767 	     *vector adding for the effect of wind and slope*/
768 	    if (parm.dir->answer && parm.aspect->answer) {
769 		sin_fac =
770 		    phiw * sin(M_D2R * dir[col]) +
771 		    phis * sin(M_D2R * aspect[col]);
772 		cos_fac =
773 		    phiw * cos(M_D2R * dir[col]) +
774 		    phis * cos(M_D2R * aspect[col]);
775 		phi_ws = sqrt(sin_fac * sin_fac + cos_fac * cos_fac);
776 		Rdir = atan2(sin_fac, cos_fac) / M_D2R;
777 	    }
778 	    else if (parm.dir->answer && !(parm.aspect->answer)) {
779 		phi_ws = phiw;
780 		Rdir = dir[col];
781 	    }
782 	    else if (!(parm.dir->answer) && parm.aspect->answer) {
783 		phi_ws = phis;
784 		Rdir = (float)aspect[col];
785 	    }
786 	    else {
787 		phi_ws = 0.0;
788 		Rdir = 0.0;
789 	    }
790 	    R = R0 * (1 + phi_ws);
791 	    if (Rdir < 0.0)
792 		Rdir = Rdir + 360;
793 	    /*printf("\nparm.dir->aanswer=%s, parm.aspect->aanswer=%s, phis=%f, phi_ws=%f, aspect[col]=%d,Rdir=%f",parm.dir->answer,parm.aspect->answer,phis,phi_ws,aspect[col],Rdir); */
794 
795 	    /*maximum spotting distance */
796 	    if (spotting)
797 		spotdist[col] =
798 		    spot_dist(fuel[col], R, vel[col], Rdir, row, col);
799 
800 	    /*to avoid 0 R, convert ft./min to cm/min */
801 	    R0 = 30.5 * R0;
802 	    R = 30.5 * R;
803 	    /*4debugging            R0 = R0/30.5/1.1; R = R/30.5/1.1; */
804 
805 	    base[col] = (int)R0;
806 	    max[col] = (int)R;
807 	    maxdir[col] = (int)Rdir;
808 	    /*printf("(%d, %d)\nR0=%.2f, vel=%d, dir=%d, phiw=%.2f, s=%d, as=%d, phis=%.2f, R=%.1f, Rdir=%.0f\n", row, col, R0, vel[col], dir[col], phiw, slope[col], aspect[col], phis, R, Rdir); */
809 	}
810 	Rast_put_row(base_fd, base, CELL_TYPE);
811 	Rast_put_row(max_fd, max, CELL_TYPE);
812 	Rast_put_row(maxdir_fd, maxdir, CELL_TYPE);
813 	if (spotting)
814 	    Rast_put_row(spotdist_fd, spotdist, CELL_TYPE);
815     }
816     G_percent(row, nrows, 2);
817 
818     Rast_close(fuel_fd);
819     if (parm.mois_1h->answer)
820 	Rast_close(mois_1h_fd);
821     if (parm.mois_10h->answer)
822 	Rast_close(mois_10h_fd);
823     if (parm.mois_100h->answer)
824 	Rast_close(mois_100h_fd);
825     Rast_close(mois_live_fd);
826     if (parm.vel->answer)
827 	Rast_close(vel_fd);
828     if (parm.dir->answer)
829 	Rast_close(dir_fd);
830     if (parm.slope->answer)
831 	Rast_close(slope_fd);
832     if (parm.aspect->answer)
833 	Rast_close(aspect_fd);
834     Rast_close(base_fd);
835     Rast_close(max_fd);
836     Rast_close(maxdir_fd);
837     if (spotting) {
838 	Rast_close(spotdist_fd);
839 	G_free(map_elev);
840     }
841 
842     /*
843        for (model = 1; model <= 13; model++) {
844        if (model == 1)
845        printf("\n           Grass and Grass-dominated\n");
846        else if (model == 4)
847        printf("             Chaparral and Shrubfields\n");
848        else if (model == 8)
849        printf("                   Timber Litter\n");
850        else if (model == 11)
851        printf("                   Logging Slash\n");
852        printf("Model %2d   ", model);
853        for (class = 0; class <= 3; class++)
854        printf("%4.0f/%.3f ", SIGMA[class][model], WO[class][model]);
855        printf("  %.1f  %.2f\n", DELTA[model], MX[model]);
856        }
857 
858        printf("\nWeight in All Fuel Subclasses:\n");
859        for (model = 1; model <= 13; model++) {
860        printf("model %2d  ", model);
861        for (class = 0; class <= 3; class++)
862        printf("%.2f  ", Ffactor_all[class][model]);
863        printf("%4.0f/%.3f=%6.0f  model %2d\n", sigma[model], wo_dead[model]+WO[3][model], sigma[model]/(wo_dead[model]+WO[3][model]), model);
864        }
865        printf("\nWeight in Dead Fuel Subclasses, Dead Weight/Live Weight:\n");for (model = 1; model <= 13; model++) {
866        printf("model %2d  ", model);
867        for (class = 0; class <= 2; class++)
868        printf("%.2f  ", Ffactor_in_dead[class][model]);
869        printf("%.2f/%.2f  model %2d\n", Ffactor_dead[model], Ffactor_live[model], model);
870        }
871      */
872 
873     G_done_msg(_("Raster maps <%s>, <%s> and <%s> created."), name_base, name_max, name_maxdir);
874 
875     exit(EXIT_SUCCESS);
876 }
877