1 /***********************************************************************
2  *
3  * MODULE:       v.out.lidar
4  *
5  * AUTHOR(S):    Vaclav Petras
6  *
7  * PURPOSE:      Export LiDAR LAS points
8  *
9  * COPYRIGHT:    (C) 2015 by Vaclav Petras and the GRASS Development Team
10  *
11  *               This program is free software under the
12  *               GNU General Public License (>=v2).
13  *               Read the file COPYING that comes with GRASS
14  *               for details.
15  *
16 ***********************************************************************/
17 
18 #include <stdlib.h>
19 
20 #include <grass/gis.h>
21 #include <grass/colors.h>
22 #include <grass/raster.h>
23 #include <grass/dbmi.h>
24 #include <grass/vector.h>
25 #include <grass/gprojects.h>
26 #include <grass/glocale.h>
27 
28 #include <liblas/capi/liblas.h>
29 
30 #define LAS_FIRST 1
31 #define LAS_MID 2
32 #define LAS_LAST 3
33 
34 struct WriteContext
35 {
36     LASWriterH las_writer;
37     LASPointH las_point;
38     LASColorH las_color;
39     struct Colors *color_table;
40     int layer;
41     int return_layer;
42     int class_layer;
43     int rgb_layer;
44     dbCatValArray *return_column_values;
45     dbCatValArray *n_returns_column_values;
46     dbCatValArray *class_column_values;
47     dbCatValArray *grass_rgb_column_values;
48     dbCatValArray *red_column_values;
49     dbCatValArray *green_column_values;
50     dbCatValArray *blue_column_values;
51 };
52 
53 
54 struct LidarColumnNames
55 {
56     const char *return_n;
57     const char *n_returns;
58     const char *class_n;
59     const char *grass_rgb;
60     const char *red;
61     const char *green;
62     const char *blue;
63 };
64 
65 
66 /*! Open database and store driver and field info
67  *
68  * Use close_database() when you are finished with queries
69  */
open_database(struct Map_info * vector,int field,dbDriver ** driver,struct field_info ** f_info)70 static void open_database(struct Map_info *vector, int field,
71                           dbDriver ** driver, struct field_info **f_info)
72 {
73     struct field_info *f_info_tmp = Vect_get_field(vector, field);
74     if (f_info_tmp == NULL) {
75         /* not ideal message since we don't know the original name of
76          * the field in case of OGR */
77         G_fatal_error(_("Database connection not defined for layer <%d>"),
78                       field);
79     }
80 
81     dbDriver *driver_tmp =
82         db_start_driver_open_database(f_info_tmp->driver,
83                                       f_info_tmp->database);
84     if (driver_tmp == NULL)
85         G_fatal_error("Unable to open database <%s> by driver <%s>",
86                       f_info_tmp->database, f_info_tmp->driver);
87     db_set_error_handler_driver(driver_tmp);
88     *f_info = f_info_tmp;
89     *driver = driver_tmp;
90 }
91 
close_database(dbDriver * driver)92 static void close_database(dbDriver * driver)
93 {
94     db_close_database_shutdown_driver(driver);
95 }
96 
97 /*! Get values in a column
98  *
99  * Checks the type of the column; fails with fatal for non-numeric columns
100  * and warns for floating point columns.
101  *
102  * Use db_CatValArray_free() and G_free() to deallocate the memory.
103  *
104  * \returns cat-value array with column values for each category
105  */
select_integers_from_database(dbDriver * driver,struct field_info * f_info,const char * column,const char * where)106 static dbCatValArray *select_integers_from_database(dbDriver * driver,
107                                                     struct field_info *f_info,
108                                                     const char *column,
109                                                     const char *where)
110 {
111     G_debug(1, "select_integers_from_database: column=%s", column);
112     dbCatValArray *column_values = G_malloc(sizeof(dbCatValArray));
113 
114     /* check if column exists */
115     int ctype = db_column_Ctype(driver, f_info->table, column);
116 
117     if (ctype == -1)
118         G_fatal_error(_("Column <%s> not found in table <%s>"),
119                       column, f_info->table);
120     if (ctype != DB_C_TYPE_INT && ctype != DB_C_TYPE_DOUBLE)
121         G_fatal_error(_("Only numeric column type is supported (column <%s> in table <%s>)"),
122                       column, f_info->table);
123     if (ctype == DB_C_TYPE_DOUBLE)
124         G_warning(_("Double values will be converted to integers (column <%s> in table <%s>)"),
125                       column, f_info->table);
126 
127     db_CatValArray_init(column_values);
128     int nrec =
129         db_select_CatValArray(driver, f_info->table, f_info->key, column,
130                               where, column_values);
131 
132     G_debug(2, "db_select_CatValArray() nrec = %d", nrec);
133     if (nrec < 0)
134         G_fatal_error(_("Unable to select data from table"));
135     return column_values;
136 }
137 
138 /*! Get values in a column
139  *
140  * Checks the type of the column; fails with fatal for non-numeric columns
141  * and warns for floating point columns.
142  *
143  * \returns cat-value array with column values for each category
144  */
select_strings_from_database(dbDriver * driver,struct field_info * f_info,const char * column,const char * where)145 static dbCatValArray *select_strings_from_database(dbDriver * driver,
146                                                    struct field_info *f_info,
147                                                    const char *column,
148                                                    const char *where)
149 {
150     G_debug(1, "select_strings_from_database: column=%s", column);
151     dbCatValArray *column_values = G_malloc(sizeof(dbCatValArray));
152 
153     /* check if column exists */
154     int ctype = db_column_Ctype(driver, f_info->table, column);
155 
156     if (ctype == -1)
157         G_fatal_error(_("Column <%s> not found in table <%s>"),
158                       column, f_info->table);
159     if (ctype != DB_C_TYPE_STRING)
160         G_fatal_error(_("Only numeric column type is supported"));
161 
162     db_CatValArray_init(column_values);
163     int nrec =
164         db_select_CatValArray(driver, f_info->table, f_info->key, column,
165                               where, column_values);
166 
167     G_debug(2, "db_select_CatValArray() nrec = %d", nrec);
168     if (nrec < 0)
169         G_fatal_error(_("Unable to select data from table"));
170     return column_values;
171 }
172 
173 /*! Get integer value in a column for a category
174  *
175  * Floating point numbers are casted to integers.
176  * If the column is not numerical, fatal error is issued.
177  *
178  * \returns The value of the column as an integer
179  */
get_integer_column_value(dbCatValArray * column_values,int cat)180 static int get_integer_column_value(dbCatValArray * column_values, int cat)
181 {
182     int val;
183     dbCatVal *catval;
184 
185     if (db_CatValArray_get_value(column_values, cat, &catval) != DB_OK) {
186         G_fatal_error(_("No record for cat = %d"), cat);
187     }
188     if (catval->isNull) {
189         G_fatal_error(_("NULL value for cat = %d"), cat);
190     }
191 
192     if (column_values->ctype == DB_C_TYPE_INT) {
193         val = catval->val.i;
194     }
195     else if (column_values->ctype == DB_C_TYPE_DOUBLE) {
196         val = catval->val.d;
197     } else {
198         G_fatal_error(_("Column type is not numeric (type = %d, cat = %d"),
199             column_values->ctype, cat);
200     }
201     return val;
202 }
203 
204 /*! Get RGB in a column for a category as three integers
205  *
206  * Expects the column to be a string.
207  */
get_color_column_value(dbCatValArray * cvarr,int cat,int * red,int * green,int * blue)208 static void get_color_column_value(dbCatValArray * cvarr, int cat,
209                                    int *red, int *green, int *blue)
210 {
211     char colorstring[12];       /* RRR:GGG:BBB */
212     dbCatVal *value = NULL;
213 
214     /* read RGB colors from db for current area # */
215     if (cvarr && db_CatValArray_get_value(cvarr, cat, &value) == DB_OK) {
216         sprintf(colorstring, "%s", db_get_string(value->val.s));
217         if (*colorstring != '\0') {
218             G_debug(5, "element: colorstring: %s", colorstring);
219             if (G_str_to_color(colorstring, red, green, blue) == 1) {
220                 G_debug(5, "element: cat %d r:%d g:%d b:%d",
221                         cat, *red, *green, *blue);
222                 /* TODO: handle return code 2 for none? */
223             }
224             else {
225                 G_debug(5, "Invalid color definition '%s' ignored",
226                         colorstring);
227             }
228         }
229         else {
230             G_debug(5, "Invalid color definition '%s' ignored", colorstring);
231         }
232     }
233 }
234 
load_columns(struct WriteContext * write_context,dbDriver * db_driver,struct field_info * f_info,struct LidarColumnNames * columns,const char * where)235 static void load_columns(struct WriteContext *write_context,
236                          dbDriver * db_driver, struct field_info *f_info,
237                          struct LidarColumnNames *columns, const char *where)
238 {
239     if (columns->return_n) {
240         write_context->return_column_values =
241             select_integers_from_database(db_driver, f_info,
242                                           columns->return_n, where);
243     }
244     if (columns->n_returns) {
245         write_context->n_returns_column_values =
246             select_integers_from_database(db_driver, f_info,
247                                           columns->n_returns, where);
248     }
249     if (columns->class_n) {
250         write_context->class_column_values =
251             select_integers_from_database(db_driver, f_info,
252                                           columns->class_n, where);
253     }
254     if (columns->grass_rgb) {
255         write_context->grass_rgb_column_values =
256             select_strings_from_database(db_driver, f_info,
257                                          columns->grass_rgb, where);
258     }
259     if (columns->red) {
260         write_context->red_column_values =
261             select_integers_from_database(db_driver, f_info,
262                                           columns->red, where);
263     }
264     if (columns->green) {
265         write_context->green_column_values =
266             select_integers_from_database(db_driver, f_info,
267                                           columns->green, where);
268     }
269     if (columns->blue) {
270         write_context->blue_column_values =
271             select_integers_from_database(db_driver, f_info,
272                                           columns->blue, where);
273     }
274 }
275 
276 /*! Deallocate the memory for cat-value arrays */
free_columns(struct WriteContext * write_context)277 static void free_columns(struct WriteContext *write_context)
278 {
279     if (write_context->return_column_values) {
280         db_CatValArray_free(write_context->return_column_values);
281         G_free(write_context->return_column_values);
282     }
283     if (write_context->n_returns_column_values) {
284         db_CatValArray_free(write_context->n_returns_column_values);
285         G_free(write_context->n_returns_column_values);
286     }
287     if (write_context->class_column_values) {
288         db_CatValArray_free(write_context->class_column_values);
289         G_free(write_context->class_column_values);
290     }
291     if (write_context->grass_rgb_column_values) {
292         db_CatValArray_free(write_context->grass_rgb_column_values);
293         G_free(write_context->grass_rgb_column_values);
294     }
295     if (write_context->red_column_values) {
296         db_CatValArray_free(write_context->red_column_values);
297         G_free(write_context->red_column_values);
298     }
299     if (write_context->green_column_values) {
300         db_CatValArray_free(write_context->green_column_values);
301         G_free(write_context->green_column_values);
302     }
303     if (write_context->blue_column_values) {
304         db_CatValArray_free(write_context->blue_column_values);
305         G_free(write_context->blue_column_values);
306     }
307 }
308 
309 /*! Set point attributes from the attribute table
310  *
311  * All tables are taken from the context structure. The point and color
312  * are also taken from there.
313  */
set_point_attributes_from_table(struct WriteContext * context,int cat)314 static void set_point_attributes_from_table(struct WriteContext *context,
315                                             int cat)
316 {
317     LASPointH las_point = context->las_point;
318 
319     if (context->return_column_values) {
320         int return_n =
321             get_integer_column_value(context->return_column_values, cat);
322         LASPoint_SetReturnNumber(las_point, return_n);
323     }
324     if (context->n_returns_column_values) {
325         int val =
326             get_integer_column_value(context->n_returns_column_values, cat);
327         LASPoint_SetNumberOfReturns(las_point, val);
328     }
329     if (context->class_column_values) {
330         int val = get_integer_column_value(context->class_column_values, cat);
331 
332         LASPoint_SetClassification(las_point, val);
333     }
334     if (context->grass_rgb_column_values || context->red_column_values ||
335         context->green_column_values || context->blue_column_values) {
336         LASColorH las_color = context->las_color;
337 
338         if (context->grass_rgb_column_values) {
339             int red, green, blue;
340 
341             get_color_column_value(context->grass_rgb_column_values, cat,
342                                    &red, &green, &blue);
343             LASColor_SetRed(las_color, red);
344             LASColor_SetGreen(las_color, green);
345             LASColor_SetBlue(las_color, blue);
346         }
347         if (context->red_column_values) {
348             int val =
349                 get_integer_column_value(context->red_column_values, cat);
350             LASColor_SetRed(las_color, val);
351         }
352         if (context->green_column_values) {
353             int val =
354                 get_integer_column_value(context->green_column_values, cat);
355             LASColor_SetGreen(las_color, val);
356         }
357         if (context->blue_column_values) {
358             int val =
359                 get_integer_column_value(context->blue_column_values, cat);
360             LASColor_SetBlue(las_color, val);
361         }
362         LASPoint_SetColor(las_point, las_color);
363     }
364 }
365 
write_point(struct WriteContext * context,int cat,double x,double y,double z,struct line_cats * cats)366 static void write_point(struct WriteContext *context, int cat, double x,
367                         double y, double z, struct line_cats *cats)
368 {
369     LASPointH las_point = context->las_point;
370 
371     LASPoint_SetX(las_point, x);
372     LASPoint_SetY(las_point, y);
373     LASPoint_SetZ(las_point, z);
374 
375     /* only call when we actually using the attributes */
376     if (context->layer > 0)
377         set_point_attributes_from_table(context, cat);
378     /* after this point cat is used as a short term variable
379      * to store category to retrieve attributes */
380 
381     /* read color table */
382     if (context->color_table) {
383         int red, green, blue;
384         LASColorH las_color = context->las_color;
385         if (Rast_get_c_color(&cat, &red, &green, &blue, context->color_table) == 1) {
386             LASColor_SetRed(las_color, red);
387             LASColor_SetGreen(las_color, green);
388             LASColor_SetBlue(las_color, blue);
389             LASPoint_SetColor(las_point, las_color);
390         }
391         /* TODO: what else, fail, skip or put some defaults? */
392     }
393 
394     if (context->return_layer) {
395         if (!Vect_cat_get(cats, context->return_layer, &cat))
396             return;             /* TODO: is this an error? */
397         if (cat == LAS_FIRST) {
398             LASPoint_SetReturnNumber(las_point, LAS_FIRST);
399             LASPoint_SetNumberOfReturns(las_point, LAS_FIRST);
400         } else if (cat == LAS_LAST) {
401             LASPoint_SetReturnNumber(las_point, LAS_LAST);
402             LASPoint_SetNumberOfReturns(las_point, LAS_LAST);
403         } else {
404             LASPoint_SetReturnNumber(las_point, LAS_MID);
405             LASPoint_SetNumberOfReturns(las_point, LAS_LAST);
406         }
407     }
408     if (context->class_layer) {
409         if (!Vect_cat_get(cats, context->class_layer, &cat))
410             return;             /* TODO: is this an error? */
411         LASPoint_SetClassification(las_point, cat);
412     }
413     if (context->rgb_layer) {
414         LASColorH las_color = context->las_color;
415 
416         /* TODO: defaults for the color are what? */
417         /* TODO: check the range for RGB? */
418         if (context->rgb_layer) {
419             if (!Vect_cat_get(cats, context->rgb_layer, &cat))
420                 return;         /* TODO: is this an error? */
421             /* cat 0 is not valid, so we are adding 1 when storing
422              * now we need to subtract 1 */
423             int rgb = cat - 1;
424             int red = (rgb >> 16) & 0xFF;
425             int green = (rgb >> 8) & 0xFF;
426             int blue = rgb & 0xFF;
427 
428             LASColor_SetRed(las_color, red);
429             LASColor_SetGreen(las_color, green);
430             LASColor_SetBlue(las_color, blue);
431         }                       /* TODO: else all the others? */
432         LASPoint_SetColor(las_point, las_color);
433     }
434 
435     LASError error = LASWriter_WritePoint(context->las_writer, las_point);
436 
437     if (error)
438         G_fatal_error("Failure when writing a point");
439 }
440 
441 
442 /* TODO: these have overlap with vector lib, really needed? */
point_in_region_2d(struct Cell_head * region,double x,double y)443 static int point_in_region_2d(struct Cell_head *region, double x, double y)
444 {
445     if (x > region->east || x < region->west || y < region->south ||
446         y > region->north)
447         return FALSE;
448     return TRUE;
449 }
450 
451 
main(int argc,char ** argv)452 int main(int argc, char **argv)
453 {
454     struct GModule *module;
455     struct Option *map_opt, *foutput_opt;
456     struct Option *field_opt, *cats_opt;
457     struct Option *id_layer_opt;
458     struct Option *return_layer_opt;
459     struct Option *class_layer_opt;
460     struct Option *rgb_layer_opt;
461     struct Option *return_column_opt, *n_returns_column_opt;
462     struct Option *class_column_opt;
463     struct Option *grass_rgb_column_opt;
464     struct Option *red_column_opt, *green_column_opt, *blue_column_opt;
465     struct Option *where_opt;
466     struct Option *las_xyscale_opt, *las_zscale_opt;
467     struct Flag *region_flag, *no_color_table_flag;
468     struct Map_info vinput;
469 
470     G_gisinit(argv[0]);
471 
472     module = G_define_module();
473     G_add_keyword(_("vector"));
474     G_add_keyword(_("export"));
475     G_add_keyword(_("output"));
476     G_add_keyword(_("LIDAR"));
477     G_add_keyword(_("points"));
478     module->label = _("Exports vector points as LAS point cloud");
479     module->description = _("Converts LAS LiDAR point clouds to a GRASS"
480                             " vector map with libLAS");
481 
482     map_opt = G_define_standard_option(G_OPT_V_INPUT);
483 
484     field_opt = G_define_standard_option(G_OPT_V_FIELD_ALL);
485     field_opt->required = NO;
486 
487     foutput_opt = G_define_standard_option(G_OPT_F_OUTPUT);
488 
489     cats_opt = G_define_standard_option(G_OPT_V_CATS);
490     cats_opt->guisection = _("Selection");
491 
492     /* TODO: supported only when attributes are actually used */
493     where_opt = G_define_standard_option(G_OPT_DB_WHERE);
494     where_opt->guisection = _("Selection");
495 
496     id_layer_opt = G_define_standard_option(G_OPT_V_FIELD);
497     id_layer_opt->key = "id_layer";
498     id_layer_opt->label =
499         _("Layer number to store generated point ID as category");
500     id_layer_opt->answer = NULL;
501     id_layer_opt->guisection = _("Categories");
502 
503     return_layer_opt = G_define_standard_option(G_OPT_V_FIELD);
504     return_layer_opt->key = "return_layer";
505     return_layer_opt->label =
506         _("Layer number to store return number as category");
507     return_layer_opt->answer = NULL;
508     return_layer_opt->guisection = _("Categories");
509 
510     class_layer_opt = G_define_standard_option(G_OPT_V_FIELD);
511     class_layer_opt->key = "class_layer";
512     class_layer_opt->label =
513         _("Layer number to store class number as category");
514     class_layer_opt->answer = NULL;
515     class_layer_opt->guisection = _("Categories");
516 
517     rgb_layer_opt = G_define_standard_option(G_OPT_V_FIELD);
518     rgb_layer_opt->key = "rgb_layer";
519     rgb_layer_opt->label =
520         _("Layer number where RGB color is stored as category");
521     rgb_layer_opt->answer = NULL;
522     rgb_layer_opt->guisection = _("Categories");
523 
524     /* TODO: probably replace the option by standardized/expected column names */
525 
526     return_column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
527     return_column_opt->key = "return_column";
528     return_column_opt->label = _("Column with return number");
529     return_column_opt->required = NO;
530     return_column_opt->guisection = _("Columns");
531 
532     n_returns_column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
533     n_returns_column_opt->key = "n_returns_column";
534     n_returns_column_opt->label = _("Column with return number");
535     n_returns_column_opt->required = NO;
536     n_returns_column_opt->guisection = _("Columns");
537 
538     class_column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
539     class_column_opt->key = "class_column";
540     class_column_opt->label = _("Column with return number");
541     class_column_opt->required = NO;
542     class_column_opt->guisection = _("Columns");
543 
544     grass_rgb_column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
545     grass_rgb_column_opt->key = "rgb_column";
546     grass_rgb_column_opt->label = _("RGB color definition column");
547     grass_rgb_column_opt->description = _("Color definition in R:G:B form");
548     grass_rgb_column_opt->required = NO;
549     grass_rgb_column_opt->guisection = _("Columns");
550 
551     red_column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
552     red_column_opt->key = "red_column";
553     red_column_opt->label = _("Column with red color");
554     red_column_opt->required = NO;
555     red_column_opt->guisection = _("Columns");
556 
557     green_column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
558     green_column_opt->key = "green_column";
559     green_column_opt->label = _("Column with green color");
560     green_column_opt->required = NO;
561     green_column_opt->guisection = _("Columns");
562 
563     blue_column_opt = G_define_standard_option(G_OPT_DB_COLUMN);
564     blue_column_opt->key = "blue_column";
565     blue_column_opt->label = _("Column with blue color");
566     blue_column_opt->required = NO;
567     blue_column_opt->guisection = _("Columns");
568 
569     las_xyscale_opt = G_define_option();
570     las_xyscale_opt->key = "las_xyscale";
571     las_xyscale_opt->type = TYPE_DOUBLE;
572     las_xyscale_opt->required = YES;
573     las_xyscale_opt->answer = "0.01";
574     las_xyscale_opt->label = _("Internal scale to apply to X and Y values");
575     las_xyscale_opt->description = _("This scale does not change"
576         " the values itself but only how precisely they are stored,"
577         " for example 0.01 will preserve two decimal places");
578 
579     las_zscale_opt = G_define_option();
580     las_zscale_opt->key = "las_zscale";
581     las_zscale_opt->type = TYPE_DOUBLE;
582     las_zscale_opt->required = YES;
583     las_zscale_opt->answer = "0.01";
584     las_zscale_opt->label = _("Internal scale to apply to z values");
585     las_zscale_opt->description = _("This scale does not change"
586         " the values itself but only how precisely they are stored,"
587         " for example 0.01 will preserve two decimal places");
588 
589     region_flag = G_define_flag();
590     region_flag->key = 'r';
591     region_flag->guisection = _("Selection");
592     region_flag->description = _("Limit export to the current region");
593 
594     no_color_table_flag = G_define_flag();
595     no_color_table_flag->key = 'w';
596     no_color_table_flag->label = _("Ignore color table");
597     no_color_table_flag->description =
598         _("Ignore color table even when set and not other options are present");
599 
600 
601     if (G_parser(argc, argv))
602         exit(EXIT_FAILURE);
603 
604     /* TODO: layer required > 0 with columns */
605 
606     /* TODO: do some check */
607     /*Vect_check_input_output_name(map_opt->answer, voutput_opt->answer,
608        G_FATAL_EXIT);
609      */
610 
611     if (Vect_open_old2(&vinput, map_opt->answer, "", field_opt->answer) < 0)
612         G_fatal_error(_("Unable to open vector map <%s>"), map_opt->answer);
613     int layer = Vect_get_field_number(&vinput, field_opt->answer);
614 
615     struct cat_list *allowed_cats = NULL;
616 
617     if (layer > 0)
618         allowed_cats = Vect_cats_set_constraint(&vinput, layer, NULL,
619                                                 cats_opt->answer);
620 
621     struct line_pnts *line = Vect_new_line_struct();
622     struct line_cats *cats = Vect_new_cats_struct();
623 
624     struct Cell_head comp_region;
625 
626     G_get_window(&comp_region);
627 
628     struct WriteContext write_context;
629 
630     write_context.return_layer = 0;
631     write_context.class_layer = 0;
632     write_context.rgb_layer = 0;
633     if (return_layer_opt->answer)
634         write_context.return_layer = atoi(return_layer_opt->answer);
635     if (class_layer_opt->answer)
636         write_context.class_layer = atoi(class_layer_opt->answer);
637     if (rgb_layer_opt->answer)
638         write_context.rgb_layer = atoi(rgb_layer_opt->answer);
639 
640     /* get GRASS loc proj info */
641     struct Key_Value *proj_info;
642     struct Key_Value *proj_units;
643 
644     /* TODO: should we test for PROJECTION_XY? */
645     proj_info = G_get_projinfo();
646     proj_units = G_get_projunits();
647     char *current_wkt = GPJ_grass_to_wkt(proj_info, proj_units, FALSE, FALSE);
648 
649     G_free_key_value(proj_info);
650     G_free_key_value(proj_units);
651 
652     /* TODO: ignoring errors */
653     LASWriterH las_writer;
654     LASHeaderH las_header = LASHeader_Create();
655     LASSRSH las_srs = LASSRS_Create();
656 
657     LASSRS_SetWKT(las_srs, current_wkt);
658     LASHeader_SetSRS(las_header, las_srs);
659     LASHeader_SetScale(las_header, atof(las_xyscale_opt->answer),
660         atof(las_xyscale_opt->answer), atof(las_zscale_opt->answer));
661     /* TODO: support append mode */
662     int write_mode = 1;
663 
664     las_writer = LASWriter_Create(foutput_opt->answer,
665                                   las_header, write_mode);
666     write_context.las_writer = las_writer;
667 
668     /* to avoid allocation for each point we are writing */
669     write_context.las_point = LASPoint_Create();
670     LASPoint_SetHeader(write_context.las_point, las_header);
671     write_context.las_color = LASColor_Create();
672 
673     write_context.layer = layer;
674     write_context.return_column_values = 0;
675     write_context.n_returns_column_values = 0;
676     write_context.class_column_values = 0;
677     write_context.grass_rgb_column_values = 0;
678     write_context.red_column_values = 0;
679     write_context.green_column_values = 0;
680     write_context.blue_column_values = 0;
681     /* TODO: limit select by the cat values */
682     /* TODO: limit select by 2D/3D region and zrange, possible? */
683 
684     int use_color_attributes = FALSE;
685 
686     if (return_column_opt->answer || n_returns_column_opt->answer
687         || class_column_opt->answer || grass_rgb_column_opt->answer
688         || red_column_opt->answer || green_column_opt->answer
689         || blue_column_opt->answer) {
690         dbDriver *db_driver;
691         struct field_info *f_info;
692 
693         struct LidarColumnNames column_names;
694 
695         column_names.return_n = return_column_opt->answer;
696         column_names.n_returns = n_returns_column_opt->answer;
697         column_names.class_n = class_column_opt->answer;
698         column_names.grass_rgb = grass_rgb_column_opt->answer;
699         column_names.red = red_column_opt->answer;
700         column_names.green = green_column_opt->answer;
701         column_names.blue = blue_column_opt->answer;
702 
703         open_database(&vinput, layer, &db_driver, &f_info);
704         load_columns(&write_context, db_driver, f_info, &column_names,
705                      where_opt->answer);
706         close_database(db_driver);
707 
708         if ( grass_rgb_column_opt->answer || red_column_opt->answer || green_column_opt->answer || blue_column_opt->answer)
709             use_color_attributes = TRUE;
710     }
711 
712     struct Colors color_table;
713     write_context.color_table = 0;
714     if (!use_color_attributes && !no_color_table_flag->answer
715         && !(write_context.rgb_layer)) {
716         int has_colors = Vect_read_colors(Vect_get_name(&vinput),
717                                           Vect_get_mapset(&vinput),
718                                           &color_table);
719         if (has_colors)
720             write_context.color_table = &color_table;
721     }
722 
723     /* some constraints can be set on the map */
724     Vect_set_constraint_type(&vinput, GV_POINT);
725     /* noop for layer=-1 and non-native format, skips lines without cat */
726     Vect_set_constraint_field(&vinput, layer);
727     /* TODO: replace region checks by Vect_set_constraint_region? */
728 
729     int ltype;
730     int cat;
731 
732     while (TRUE) {
733         ltype = Vect_read_next_line(&vinput, line, cats);
734         if (ltype == -1)
735             G_fatal_error(_("Unable to read vector map"));
736         if (ltype == -2)
737             break;              /* end of the map */
738 
739         double x, y, z;
740 
741         Vect_line_get_point(line, 0, &x, &y, &z);
742 
743         /* selections/filters */
744         /* TODO: use region only when actually needed */
745         if (region_flag->answer && !point_in_region_2d(&comp_region, x, y))
746             continue;
747         if (layer > 0 && allowed_cats &&
748             !Vect_cats_in_constraint(cats, layer, allowed_cats))
749             continue;
750 
751         /* TODO: test: skip points without category, unless layer=-1 */
752         /* Use cases:
753          * - all points have category (correct)
754          * - no categories for any point (correct, layer=-1 required)
755          * - some points miss category (not handled)
756          * Here we assume that there is only one set of attributes for one point.
757          * If no layer available, cat contains junk and shouldn't be used.
758 	 *
759 	 * TODO: done
760          */
761 	cat = -1;
762         if (layer > 0) {
763 	    if (allowed_cats) {
764 		int i;
765 
766 		for (i = 0; i < cats->n_cats; i++) {
767 		    if (cats->field[i] == layer &&
768 			Vect_cat_in_cat_list(cats->cat[i], allowed_cats)) {
769 			cat = cats->cat[i];
770 			break;
771 		    }
772 		}
773 	    }
774 	    else {
775 		Vect_cat_get(cats, layer, &cat);
776 	    }
777 	}
778 
779         write_point(&write_context, cat, x, y, z, cats);
780     }
781 
782     /* partially unnecessary as deallocated by the system */
783     Vect_destroy_line_struct(line);
784     Vect_destroy_cats_struct(cats);
785     Vect_close(&vinput);
786 
787     free_columns(&write_context);
788 
789     LASPoint_Destroy(write_context.las_point);
790     LASColor_Destroy(write_context.las_color);
791     LASWriter_Destroy(write_context.las_writer);
792     LASSRS_Destroy(las_srs);
793 
794     return EXIT_SUCCESS;
795 }
796