GRASS GIS 8 Programmer's Manual 8.2.0(2022)-exported
view.c
Go to the documentation of this file.
1
2/*!
3 * \file lib/gis/view.c
4 *
5 * \brief GIS Library - 3D View functions.
6 *
7 * (C) 2001-2014 by the GRASS Development Team
8 *
9 * This program is free software under the GNU General Public License
10 * (>=v2). Read the file COPYING that comes with GRASS for details.
11 *
12 * \author Bill Brown - US Army CERL
13 *
14 * \date 1992-2008
15 */
16
17#include <stdio.h>
18#include <string.h>
19#include <stdlib.h>
20#include <grass/gis.h>
21#include <grass/glocale.h>
22
23
24#define REQ_KEYS 8
25
26static int compare_wind(const struct Cell_head *, const struct Cell_head *);
27static int get_bool(const char *);
28static void pr_winerr(int, const char *);
29static void edge_sort(float sides[4]);
30static int read_old_format(struct G_3dview *, FILE *);
31
32static const int vers_major = 4;
33static const int vers_minor = 1;
34
35static int Suppress_warn = 0;
36
37
38/**
39 * \brief Turns 3D View warnings on and off.
40 *
41 * If Suppress_warn is 0, a warning will be printed if less than 95% of
42 * the window when the view was saved overlaps the current window.
43 *
44 * \param[in] b
45 * \return
46 */
47
49{
50 Suppress_warn = b ? 0 : 1;
51}
52
53
54/**
55 * \brief Sets default for <b>v</b> based on <b>w</b>.
56 *
57 * \param[in,out] v
58 * \param[in] w
59 * \return always returns 1
60 */
61
62int G_get_3dview_defaults(struct G_3dview *v, struct Cell_head *w)
63{
64 if (!v || !w)
65 return (-1);
66
67 v->exag = 1.0;
68 v->fov = 40.0;
69 v->from_to[0][0] = (w->east + w->west) / 2.0;
70 v->from_to[0][1] = w->south - (w->north - w->south);
71 v->from_to[0][2] = w->north - w->south;
72 v->from_to[1][0] = (w->east + w->west) / 2.0;
73 v->from_to[1][1] = (w->north + w->south) / 2.0;
74 v->from_to[1][2] = 0.0;
75
76 v->twist = 0.0;
77 v->mesh_freq = 15;
78 v->poly_freq = 1;
79 v->display_type = 2;
80 v->colorgrid = v->fringe = v->surfonly = v->lightson = v->doavg = 0;
81 v->dozero = v->shading = 1;
82 strcpy(v->bg_col, "black");
83 strcpy(v->grid_col, "white");
84 strcpy(v->other_col, "red");
85 v->ambient = v->shine = 0.3;
86 v->lightcol[0] = v->lightcol[1] = v->lightcol[2] = 0.8;
87 v->lightpos[0] = w->west;
88 v->lightpos[1] = w->north;
89 v->lightpos[2] = (w->east - w->west) / 2.0;
90 v->lightpos[3] = 1.0; /* local source */
91
92 v->vwin.north = w->north;
93 v->vwin.south = w->south;
94 v->vwin.east = w->east;
95 v->vwin.west = w->west;
96 v->vwin.format = w->format;
97 v->vwin.compressed = w->compressed;
98 v->vwin.proj = w->proj;
99 v->vwin.zone = w->zone;
100 v->vwin.ew_res = w->ew_res;
101 v->vwin.ns_res = w->ns_res;
102 v->vwin.cols = w->cols;
103 v->vwin.rows = w->rows;
104
105 return (1);
106
107}
108
109
110/**
111 * \brief Saves info to a 3d.view file.
112 *
113 * The address of a window (struct Cell_head *) may be passed, or if
114 * NULL is passed, the Cell_head structure inside the G_3dview struct
115 * will be used. e.g., if you called <i>G_get_3dview_defaults</i> with
116 * the Cell_head you want saved, the G_3dview returned already contains
117 * the new Cell_head. But if you're using all the keywords, so didn't
118 * need defaults, pass this routine the address of a Cell_head.<br>
119 *
120 * User should call <i>G_get_3dview_defaults</i> before filling a
121 * G_3dview struct to be written if not using all of the optional
122 * keywords.<br>
123 *
124 * These keywords are constant in all 3d.view files:<br>
125 * PGM_ID<br>
126 * <b>cell keywords:</b><br>
127 * north<br>
128 * south<br>
129 * east<br>
130 * west<br>
131 * rows<br>
132 * cols<br>
133 * <b>required keywords:</b><br>
134 * TO_EASTING<br>
135 * TO_NORTHING<br>
136 * TO_HEIGHT<br>
137 * FROM_EASTING<br>
138 * FROM_NORTHING<br>
139 * FROM_HEIGHT<br>
140 * Z_EXAG<br>
141 * FIELD_VIEW<br>
142 * <b>optional keywords:</b> (defaults provided when reading)<br>
143 * TWIST<br>
144 * MESH_FREQ<br>
145 * POLY_RES<br>
146 * DOAVG<br>
147 * DISPLAY_TYPE<br>
148 * DOZERO<br>
149 * COLORGRID<br>
150 * SHADING<br>
151 * FRINGE<br>
152 * BG_COL<br>
153 * GRID_COL<br>
154 * OTHER_COL<br>
155 * LIGHTS_ON<br>
156 * LIGHTPOS<br>
157 * LIGHTCOL<br>
158 * LIGHTAMBIENT<br>
159 * SHINE<br>
160 * SURFACEONLY<br>
161 *
162 * \param[in] fname file name
163 * \param[in] mapset
164 * \param[in] View
165 * \param[in] Win
166 * \return 1 on success
167 * \return -1 on error
168 */
169
170int G_put_3dview(const char *fname, const char *mapset,
171 const struct G_3dview *View, const struct Cell_head *Win)
172{
173 FILE *fp;
174
175 if (NULL == (fp = G_fopen_new("3d.view", fname))) {
176 G_warning(_("Unable to open %s for writing"), fname);
177 return (-1);
178 }
179
180 fprintf(fp, "# %01d.%02d\n", vers_major, vers_minor);
181 fprintf(fp, "PGM_ID: %s\n", View->pgm_id);
182
183 if (Win) {
184 fprintf(fp, "north: %f\n", Win->north);
185 fprintf(fp, "south: %f\n", Win->south);
186 fprintf(fp, "east: %f\n", Win->east);
187 fprintf(fp, "west: %f\n", Win->west);
188 fprintf(fp, "rows: %d\n", Win->rows);
189 fprintf(fp, "cols: %d\n", Win->cols);
190 }
191 else {
192 fprintf(fp, "north: %f\n", View->vwin.north);
193 fprintf(fp, "south: %f\n", View->vwin.south);
194 fprintf(fp, "east: %f\n", View->vwin.east);
195 fprintf(fp, "west: %f\n", View->vwin.west);
196 fprintf(fp, "rows: %d\n", View->vwin.rows);
197 fprintf(fp, "cols: %d\n", View->vwin.cols);
198 }
199
200 fprintf(fp, "TO_EASTING: %f\n", View->from_to[1][0]);
201 fprintf(fp, "TO_NORTHING: %f\n", View->from_to[1][1]);
202 fprintf(fp, "TO_HEIGHT: %f\n", View->from_to[1][2]);
203 fprintf(fp, "FROM_EASTING: %f\n", View->from_to[0][0]);
204 fprintf(fp, "FROM_NORTHING: %f\n", View->from_to[0][1]);
205 fprintf(fp, "FROM_HEIGHT: %f\n", View->from_to[0][2]);
206 fprintf(fp, "Z_EXAG: %f\n", View->exag);
207 fprintf(fp, "TWIST: %f\n", View->twist);
208 fprintf(fp, "FIELD_VIEW: %f\n", View->fov);
209 fprintf(fp, "MESH_FREQ: %d\n", View->mesh_freq);
210 fprintf(fp, "POLY_RES: %d\n", View->poly_freq);
211 fprintf(fp, "DOAVG: %d\n", View->doavg);
212 fprintf(fp, "DISPLAY_TYPE: %d\n", View->display_type);
213 fprintf(fp, "DOZERO: %d\n", View->dozero);
214
215 fprintf(fp, "COLORGRID: %d\n", View->colorgrid); /* 1 = use color */
216 fprintf(fp, "SHADING: %d\n", View->shading);
217 fprintf(fp, "FRINGE: %d\n", View->fringe);
218 fprintf(fp, "BG_COL: %s\n", View->bg_col);
219 fprintf(fp, "GRID_COL: %s\n", View->grid_col);
220 fprintf(fp, "OTHER_COL: %s\n", View->other_col);
221 fprintf(fp, "SURFACEONLY: %d\n", View->surfonly);
222 fprintf(fp, "LIGHTS_ON: %d\n", View->lightson);
223 fprintf(fp, "LIGHTPOS: %f %f %f %f\n", View->lightpos[0],
224 View->lightpos[1], View->lightpos[2], View->lightpos[3]);
225 fprintf(fp, "LIGHTCOL: %f %f %f\n", View->lightcol[0], View->lightcol[1],
226 View->lightcol[2]);
227 fprintf(fp, "LIGHTAMBIENT: %f\n", View->ambient);
228 fprintf(fp, "SHINE: %f\n", View->shine);
229
230 fclose(fp);
231
232 return (1);
233}
234
235
236/**
237 * \brief Gets a 3D View.
238 *
239 * If reading an old format, the window boundaries are not checked
240 * against the current window since boundaries weren't saved.
241 *
242 * \param[in] fname
243 * \param[in] mapset
244 * \param[in,out] View
245 * \return -1 on error
246 * \return 1 on success
247 * \return 2 if <b>fname</b> was written with this version of routine
248 * \return 0 if is older format (through 4.0)
249 */
250
251int G_get_3dview(const char *fname, const char *mapset, struct G_3dview *View)
252{
253 struct Cell_head curwin;
254 FILE *fp;
255 char buffer[80], keystring[24], boo[8], nbuf[128], ebuf[128];
256 int lap, v_maj, v_min, wind_keys = 0, reqkeys = 0;
257 int current = 0; /* current version flag */
258
259 mapset = G_find_file2("3d.view", fname, mapset);
260 if (mapset != NULL) {
261 if (NULL == (fp = G_fopen_old("3d.view", fname, mapset))) {
262 G_warning(_("Unable to open %s for reading"), fname);
263 return (-1);
264 }
265
266 G_get_set_window(&curwin);
267 G_get_3dview_defaults(View, &curwin);
268
269 if (NULL != fgets(buffer, 80, fp)) {
270 if (buffer[0] != '#') { /* old d.3d format */
271 rewind(fp);
272 if (0 <= read_old_format(View, fp))
273 return (0);
274 else
275 return (-1);
276 }
277 else {
278 sscanf(buffer, "#%d.%d\n", &v_maj, &v_min);
279 if (v_maj == vers_major && v_min == vers_minor)
280 current = 1; /* same version */
281 }
282 }
283
284 while (NULL != fgets(buffer, 75, fp)) {
285 if (buffer[0] != '#') {
286
287 sscanf(buffer, "%[^:]:", keystring);
288
289 if (!strcmp(keystring, "PGM_ID")) {
290 sscanf(buffer, "%*s%s", (View->pgm_id));
291 continue;
292 }
293 if (!strcmp(keystring, "north")) {
294 sscanf(buffer, "%*s%lf", &(View->vwin.north));
295 ++wind_keys;
296 continue;
297 }
298 if (!strcmp(keystring, "south")) {
299 sscanf(buffer, "%*s%lf", &(View->vwin.south));
300 ++wind_keys;
301 continue;
302 }
303 if (!strcmp(keystring, "east")) {
304 sscanf(buffer, "%*s%lf", &(View->vwin.east));
305 ++wind_keys;
306 continue;
307 }
308 if (!strcmp(keystring, "west")) {
309 sscanf(buffer, "%*s%lf", &(View->vwin.west));
310 ++wind_keys;
311 continue;
312 }
313 if (!strcmp(keystring, "rows")) {
314 sscanf(buffer, "%*s%d", &(View->vwin.rows));
315 ++wind_keys;
316 continue;
317 }
318 if (!strcmp(keystring, "cols")) {
319 sscanf(buffer, "%*s%d", &(View->vwin.cols));
320 ++wind_keys;
321 continue;
322 }
323 if (!strcmp(keystring, "TO_EASTING")) {
324 sscanf(buffer, "%*s%f", &(View->from_to[1][0]));
325 ++reqkeys;
326 continue;
327 }
328 if (!strcmp(keystring, "TO_NORTHING")) {
329 sscanf(buffer, "%*s%f", &(View->from_to[1][1]));
330 ++reqkeys;
331 continue;
332 }
333 if (!strcmp(keystring, "TO_HEIGHT")) {
334 sscanf(buffer, "%*s%f", &(View->from_to[1][2]));
335 ++reqkeys;
336 continue;
337 }
338 if (!strcmp(keystring, "FROM_EASTING")) {
339 sscanf(buffer, "%*s%f", &(View->from_to[0][0]));
340 ++reqkeys;
341 continue;
342 }
343 if (!strcmp(keystring, "FROM_NORTHING")) {
344 sscanf(buffer, "%*s%f", &(View->from_to[0][1]));
345 ++reqkeys;
346 continue;
347 }
348 if (!strcmp(keystring, "FROM_HEIGHT")) {
349 sscanf(buffer, "%*s%f", &(View->from_to[0][2]));
350 ++reqkeys;
351 continue;
352 }
353 if (!strcmp(keystring, "Z_EXAG")) {
354 sscanf(buffer, "%*s%f", &(View->exag));
355 ++reqkeys;
356 continue;
357 }
358 if (!strcmp(keystring, "MESH_FREQ")) {
359 sscanf(buffer, "%*s%d", &(View->mesh_freq));
360 continue;
361 }
362 if (!strcmp(keystring, "POLY_RES")) {
363 sscanf(buffer, "%*s%d", &(View->poly_freq));
364 continue;
365 }
366 if (!strcmp(keystring, "DOAVG")) {
367 sscanf(buffer, "%*s%d", &(View->doavg));
368 continue;
369 }
370 if (!strcmp(keystring, "FIELD_VIEW")) {
371 sscanf(buffer, "%*s%f", &(View->fov));
372 ++reqkeys;
373 continue;
374 }
375 if (!strcmp(keystring, "TWIST")) {
376 sscanf(buffer, "%*s%f", &(View->twist));
377 continue;
378 }
379 if (!strcmp(keystring, "DISPLAY_TYPE")) {
380 sscanf(buffer, "%*s%d", &View->display_type);
381 continue;
382 }
383 if (!strcmp(keystring, "DOZERO")) {
384 sscanf(buffer, "%*s%s", boo);
385 View->dozero = get_bool(boo);
386 continue;
387 }
388 if (!strcmp(keystring, "COLORGRID")) {
389 sscanf(buffer, "%*s%s", boo);
390 View->colorgrid = get_bool(boo);
391 continue;
392 }
393 if (!strcmp(keystring, "FRINGE")) {
394 sscanf(buffer, "%*s%s", boo);
395 View->fringe = get_bool(boo);
396 continue;
397 }
398 if (!strcmp(keystring, "SHADING")) {
399 sscanf(buffer, "%*s%s", boo);
400 View->shading = get_bool(boo);
401 continue;
402 }
403 if (!strcmp(keystring, "BG_COL")) {
404 sscanf(buffer, "%*s%s", View->bg_col);
405 continue;
406 }
407 if (!strcmp(keystring, "GRID_COL")) {
408 sscanf(buffer, "%*s%s", View->grid_col);
409 continue;
410 }
411 if (!strcmp(keystring, "OTHER_COL")) {
412 sscanf(buffer, "%*s%s", View->other_col);
413 continue;
414 }
415 if (!strcmp(keystring, "SURFACEONLY")) {
416 sscanf(buffer, "%*s%s", boo);
417 View->surfonly = get_bool(boo);
418 continue;
419 }
420 if (!strcmp(keystring, "LIGHTS_ON")) {
421 sscanf(buffer, "%*s%s", boo);
422 View->lightson = get_bool(boo);
423 continue;
424 }
425 if (!strcmp(keystring, "LIGHTPOS")) {
426 sscanf(buffer, "%*s%f%f%f%f", &(View->lightpos[0]),
427 &(View->lightpos[1]), &(View->lightpos[2]),
428 &(View->lightpos[3]));
429 continue;
430 }
431 if (!strcmp(keystring, "LIGHTCOL")) {
432 sscanf(buffer, "%*s%f%f%f", &(View->lightcol[0]),
433 &(View->lightcol[1]), &(View->lightcol[2]));
434 continue;
435 }
436 if (!strcmp(keystring, "LIGHTAMBIENT")) {
437 sscanf(buffer, "%*s%f", &(View->ambient));
438 continue;
439 }
440 if (!strcmp(keystring, "SHINE")) {
441 sscanf(buffer, "%*s%f", &(View->shine));
442 continue;
443 }
444 }
445 }
446
447 fclose(fp);
448
449 if (reqkeys != REQ_KEYS) /* required keys not found */
450 return (-1);
451
452 /* fill rest of View->vwin */
453 if (wind_keys == 6) {
454 View->vwin.ew_res = (View->vwin.east - View->vwin.west) /
455 View->vwin.cols;
456 View->vwin.ns_res = (View->vwin.north - View->vwin.south) /
457 View->vwin.rows;
458 }
459 else
460 return (0); /* older format */
461
462 if (!Suppress_warn) {
463 if (95 > (lap = compare_wind(&(View->vwin), &curwin))) {
464
465 fprintf(stderr, _("GRASS window when view was saved:\n"));
466 G_format_northing(View->vwin.north, nbuf, G_projection());
467 fprintf(stderr, "north: %s\n", nbuf);
468 G_format_northing(View->vwin.south, nbuf, G_projection());
469 fprintf(stderr, "south: %s\n", nbuf);
470 G_format_easting(View->vwin.east, ebuf, G_projection());
471 fprintf(stderr, "east: %s\n", ebuf);
472 G_format_easting(View->vwin.west, ebuf, G_projection());
473 fprintf(stderr, "west: %s\n", ebuf);
474 pr_winerr(lap, fname);
475 }
476 }
477 }
478 else {
479 G_warning(_("Unable to open %s for reading"), fname);
480 return (-1);
481 }
482
483 if (current)
484 return (2);
485
486 return (1);
487}
488
489
490/* returns the percentage of savedwin that overlaps curwin */
491
492static int compare_wind(const struct Cell_head *savedwin,
493 const struct Cell_head *curwin)
494{
495 float e_ings[4], n_ings[4], area_lap, area_saved;
496 int outside = 0;
497
498 if (savedwin->north < curwin->south)
499 outside = 1;
500 if (savedwin->south > curwin->north)
501 outside = 1;
502 if (savedwin->east < curwin->west)
503 outside = 1;
504 if (savedwin->west > curwin->east)
505 outside = 1;
506 if (outside)
507 return (0);
508
509 e_ings[0] = savedwin->west;
510 e_ings[1] = savedwin->east;
511 e_ings[2] = curwin->west;
512 e_ings[3] = curwin->east;
513 edge_sort(e_ings);
514
515 n_ings[0] = savedwin->south;
516 n_ings[1] = savedwin->north;
517 n_ings[2] = curwin->south;
518 n_ings[3] = curwin->north;
519 edge_sort(n_ings);
520
521 area_lap = (e_ings[2] - e_ings[1]) * (n_ings[2] - n_ings[1]);
522 area_saved = (savedwin->east - savedwin->west) *
523 (savedwin->north - savedwin->south);
524
525 return ((int)(area_lap * 100.0 / area_saved));
526}
527
528
529static int get_bool(const char *str)
530{
531 if (str[0] == 'y' || str[0] == 'Y')
532 return (1);
533 if (str[0] == 'n' || str[0] == 'N')
534 return (0);
535
536 return (atoi(str) ? 1 : 0);
537}
538
539
540static void pr_winerr(int vis, /* % of saved window overlapping current window */
541 const char *viewname)
542{
543 switch (vis) {
544 case 0:
545 G_warning(_(" Window saved in \"%s\" is completely outside of current GRASS window."),
546 viewname);
547 break;
548 default:
549 G_warning(_(" Only %d%% of window saved in \"%s\" overlaps with current GRASS window."),
550 vis, viewname);
551 break;
552 }
553}
554
555/*********************************************************************/
556/* sorts 4 floats from lowest to highest */
557
558static void edge_sort(float sides[4])
559{
560 int i, j;
561 float temp;
562
563 for (i = 0; i < 4; ++i) {
564 for (j = i + 1; j < 4; ++j) {
565 if (sides[j] < sides[i]) { /* then swap */
566 temp = sides[i];
567 sides[i] = sides[j];
568 sides[j] = temp;
569 }
570 }
571 }
572}
573
574
575static int read_old_format(struct G_3dview *v, FILE * fp)
576{
577 char buffer[80];
578 int req_keys = 0;
579 double td;
580 char boo[8];
581
582 strcpy((v->pgm_id), "d.3d");
583 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->from_to[1][0])))
584 ++req_keys;
585 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->from_to[1][1])))
586 ++req_keys;
587 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->from_to[1][2])))
588 ++req_keys;
589 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->from_to[0][0])))
590 ++req_keys;
591 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->from_to[0][1])))
592 ++req_keys;
593 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->from_to[0][2])))
594 ++req_keys;
595 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->exag)))
596 ++req_keys;
597 sscanf(fgets(buffer, 80, fp), "%d", &(v->mesh_freq));
598 if (1 == sscanf(fgets(buffer, 80, fp), "%f", &(v->fov)))
599 ++req_keys;
600 if (1 == sscanf(fgets(buffer, 80, fp), "%lf", &td)) { /* resolution */
601 v->vwin.rows = (v->vwin.north - v->vwin.south) / td;
602 v->vwin.cols = (v->vwin.east - v->vwin.west) / td;
603 v->vwin.ew_res = v->vwin.ns_res = td;
604 }
605
606 sscanf(fgets(buffer, 80, fp), "%s", boo); /* linesonly */
607 v->display_type = get_bool(boo) ? 1 : 3;
608 sscanf(fgets(buffer, 80, fp), "%s", boo);
609 v->dozero = get_bool(boo);
610 sscanf(fgets(buffer, 80, fp), "%s", v->grid_col);
611 if (!strcmp(v->grid_col, "color"))
612 v->colorgrid = 1;
613
614 sscanf(fgets(buffer, 80, fp), "%s", v->other_col);
615 sscanf(fgets(buffer, 80, fp), "%s", v->bg_col);
616 sscanf(fgets(buffer, 80, fp), "%s", boo);
617 v->doavg = get_bool(boo);
618
619 if (v->exag) { /* old 3d.view files saved height with no exag */
620 v->from_to[0][2] /= v->exag;
621 v->from_to[1][2] /= v->exag;
622 }
623
624
625 fclose(fp);
626 if (req_keys == REQ_KEYS)
627 return (1);
628 else
629 return (-1);
630}
#define NULL
Definition: ccmath.h:32
double b
const char * G_find_file2(const char *element, const char *name, const char *mapset)
Searches for a file from the mapset search list or in a specified mapset. (look but don't touch)
Definition: find_file.c:249
void G_warning(const char *msg,...)
Print a warning message to stderr.
Definition: gis/error.c:204
FILE * G_fopen_old(const char *element, const char *name, const char *mapset)
Open a database file for reading.
Definition: gis/open.c:253
FILE * G_fopen_new(const char *element, const char *name)
Open a new database file.
Definition: gis/open.c:220
void G_get_set_window(struct Cell_head *window)
Get the current working window (region)
int G_projection(void)
Query cartographic projection.
Definition: proj1.c:32
int G_get_3dview(const char *fname, const char *mapset, struct G_3dview *View)
Gets a 3D View.
Definition: view.c:251
void G_3dview_warning(int b)
Turns 3D View warnings on and off.
Definition: view.c:48
int G_get_3dview_defaults(struct G_3dview *v, struct Cell_head *w)
Sets default for v based on w.
Definition: view.c:62
#define REQ_KEYS
Definition: view.c:24
int G_put_3dview(const char *fname, const char *mapset, const struct G_3dview *View, const struct Cell_head *Win)
Saves info to a 3d.view file.
Definition: view.c:170
void G_format_northing(double north, char *buf, int projection)
Northing to ASCII.
Definition: wind_format.c:29
void G_format_easting(double east, char *buf, int projection)
Easting to ASCII.
Definition: wind_format.c:49