1 /******************************************************************************
2 *  LibGHT, software to manage point clouds.
3 *  LibGHT is free and open source software provided by the Government of Canada
4 *  Copyright (c) 2012 Natural Resources Canada
5 *
6 *  Nouri Sabo <nsabo@NRCan.gc.ca>, Natural Resources Canada
7 *  Paul Ramsey <pramsey@opengeo.org>, OpenGeo
8 *
9 ******************************************************************************/
10 
11 #include <libxml/parser.h>
12 #include <libxml/xpath.h>
13 #include "ght_internal.h"
14 #include <math.h>
15 
16 /******************************************************************************
17 *  GhtDimension
18 ******************************************************************************/
19 
20 /** Create an empty dimension */
ght_dimension_new(GhtDimension ** dimension)21 GhtErr ght_dimension_new(GhtDimension **dimension)
22 {
23     GhtDimension *dim;
24     assert(dimension);
25 
26     dim = ght_malloc(sizeof(GhtDimension));
27     memset(dim, 0, sizeof(GhtDimension));
28     dim->scale = 1.0;
29     *dimension = dim;
30     return GHT_OK;
31 }
32 
ght_dimension_free(GhtDimension * dim)33 GhtErr ght_dimension_free(GhtDimension *dim)
34 {
35     if ( dim->name ) ght_free(dim->name);
36     if ( dim->description ) ght_free(dim->description);
37     ght_free(dim);
38     return GHT_OK;
39 }
40 
41 static
ght_dimension_clone(const GhtDimension * dim,GhtDimension ** newdim)42 GhtErr ght_dimension_clone(const GhtDimension *dim, GhtDimension **newdim)
43 {
44     GhtDimension *d = ght_malloc(sizeof(GhtDimension));
45     memcpy(d, dim, sizeof(GhtDimension));
46     if ( dim->name )
47         d->name = ght_strdup(dim->name);
48     if ( dim->description )
49         d->description = ght_strdup(dim->description);
50     return GHT_OK;
51 }
52 
ght_dimension_set_name(GhtDimension * dim,const char * name)53 GhtErr ght_dimension_set_name(GhtDimension *dim, const char *name)
54 {
55     dim->name = ght_strdup(name);
56     return GHT_OK;
57 }
58 
ght_dimension_set_description(GhtDimension * dim,const char * desc)59 GhtErr ght_dimension_set_description(GhtDimension *dim, const char *desc)
60 {
61     dim->description = ght_strdup(desc);
62     return GHT_OK;
63 }
64 
ght_dimension_set_offset(GhtDimension * dim,double offset)65 GhtErr ght_dimension_set_offset(GhtDimension *dim, double offset)
66 {
67     dim->offset = offset;
68     return GHT_OK;
69 }
70 
ght_dimension_set_scale(GhtDimension * dim,double scale)71 GhtErr ght_dimension_set_scale(GhtDimension *dim, double scale)
72 {
73     dim->scale = scale;
74     return GHT_OK;
75 }
76 
ght_dimension_set_type(GhtDimension * dim,GhtType type)77 GhtErr ght_dimension_set_type(GhtDimension *dim, GhtType type)
78 {
79     dim->type = type;
80     return GHT_OK;
81 }
82 
ght_dimension_new_from_parameters(const char * name,const char * desc,GhtType type,double scale,double offset,GhtDimension ** dim)83 GhtErr ght_dimension_new_from_parameters(const char *name, const char *desc, GhtType type, double scale, double offset, GhtDimension **dim)
84 {
85     GhtDimension *d;
86     GHT_TRY(ght_dimension_new(&d));
87     if ( name )
88     {
89         d->name = ght_strdup(name);
90     }
91     else
92     {
93         return GHT_ERROR; /* need a name! */
94     }
95     if ( desc )
96     {
97         d->description = ght_strdup(desc);
98     }
99     else
100     {
101         d->description = ght_strdup("");
102     }
103     d->type = type;
104     d->scale = scale;
105     d->offset = offset;
106     *dim = d;
107     return GHT_OK;
108 }
109 
ght_dimension_get_position(const GhtDimension * dim,uint8_t * position)110 GhtErr ght_dimension_get_position(const GhtDimension *dim, uint8_t *position)
111 {
112     *position = dim->position;
113     return GHT_OK;
114 }
115 
ght_dimension_get_name(const GhtDimension * dim,const char ** name)116 GhtErr ght_dimension_get_name(const GhtDimension *dim, const char **name)
117 {
118     *name = dim->name;
119     return GHT_OK;
120 }
121 
ght_dimension_get_type(const GhtDimension * dim,GhtType * type)122 GhtErr ght_dimension_get_type(const GhtDimension *dim, GhtType *type)
123 {
124     *type = dim->type;
125     return GHT_OK;
126 }
127 
ght_dimension_get_index(const GhtDimension * dim,int * index)128 GhtErr ght_dimension_get_index(const GhtDimension *dim, int *index)
129 {
130     *index = dim->position;
131     return GHT_OK;
132 }
133 
ght_dimension_same(const GhtDimension * dim1,const GhtDimension * dim2,int * same)134 GhtErr ght_dimension_same(const GhtDimension *dim1, const GhtDimension *dim2, int *same)
135 {
136     *same = 0;
137     if ( dim1->position == dim2->position &&
138          strcmp(dim1->name, dim2->name) == 0 &&
139          dim1->type == dim2->type &&
140          fabs(dim1->scale - dim2->scale) < GHT_EPSILON &&
141          fabs(dim1->offset - dim2->offset) < GHT_EPSILON )
142     {
143         *same = 1;
144     }
145     return GHT_OK;
146 }
147 
148 
149 /******************************************************************************
150 *  GhtSchema
151 ******************************************************************************/
152 
ght_schema_same(const GhtSchema * s1,const GhtSchema * s2,int * same)153 GhtErr ght_schema_same(const GhtSchema *s1, const GhtSchema *s2, int *same)
154 {
155     int i;
156     *same = 0;
157     if ( s1->num_dims != s2->num_dims )
158     {
159         return GHT_OK;
160     }
161     for ( i = 0; i < s1->num_dims; i++ )
162     {
163         ght_dimension_same(s1->dims[i], s2->dims[i], same);
164         if ( ! *same )
165             return GHT_OK;
166     }
167     return GHT_OK;
168 }
169 
ght_schema_get_dimension_by_name(const GhtSchema * schema,const char * name,GhtDimension ** dim)170 GhtErr ght_schema_get_dimension_by_name(const GhtSchema *schema, const char *name, GhtDimension **dim)
171 {
172     int i;
173     assert(name);
174     assert(schema);
175     *dim = NULL;
176 
177     for ( i = 0; i < schema->num_dims; i++ )
178     {
179         const char *sname = schema->dims[i]->name;
180         if ( sname && strcasecmp(name, sname) == 0 )
181         {
182             *dim = schema->dims[i];
183             return GHT_OK;
184         }
185     }
186     return GHT_ERROR;
187 }
188 
ght_schema_get_dimension_by_index(const GhtSchema * schema,int i,GhtDimension ** dim)189 GhtErr ght_schema_get_dimension_by_index(const GhtSchema *schema, int i, GhtDimension **dim)
190 {
191     assert(dim);
192     *dim = NULL;
193     if ( i >= 0 && i < schema->num_dims )
194     {
195         *dim = schema->dims[i];
196         return GHT_OK;
197     }
198     return GHT_ERROR;
199 }
200 
ght_schema_new(GhtSchema ** schema)201 GhtErr ght_schema_new(GhtSchema **schema)
202 {
203     static int max_dims = 8;
204     size_t s_size = sizeof(GhtSchema);
205     size_t d_size = sizeof(GhtDimension*) * max_dims;
206     assert(schema);
207     GhtSchema *s = ght_malloc(s_size);
208     memset(s, 0, s_size);
209     s->dims = ght_malloc(d_size);
210     memset(s->dims, 0, sizeof(d_size));
211     s->max_dims = max_dims;
212     *schema = s;
213     return GHT_OK;
214 }
215 
ght_schema_free(GhtSchema * schema)216 GhtErr ght_schema_free(GhtSchema *schema)
217 {
218     int i;
219     assert(schema);
220     for ( i = 0; i < schema->num_dims; i++ )
221     {
222         if ( schema->dims[i] )
223             ght_dimension_free(schema->dims[i]);
224     }
225     ght_free(schema->dims);
226     ght_free(schema);
227     return GHT_OK;
228 }
229 
ght_schema_get_num_dimensions(const GhtSchema * schema,unsigned int * num_dims)230 GhtErr ght_schema_get_num_dimensions(const GhtSchema *schema, unsigned int *num_dims)
231 {
232     assert(schema);
233     *num_dims = schema->num_dims;
234     return GHT_OK;
235 }
236 
ght_schema_add_dimension(GhtSchema * schema,GhtDimension * dim)237 GhtErr ght_schema_add_dimension(GhtSchema *schema, GhtDimension *dim)
238 {
239     int i;
240 
241     assert(schema);
242     assert(dim);
243 
244     if ( ! dim->name ) return GHT_ERROR;
245 
246     for ( i = 0; i < schema->num_dims; i++ )
247     {
248         if ( strcmp(dim->name, schema->dims[i]->name) == 0 )
249         {
250             ght_error("%s: cannot add dimension with a duplicate name", __func__);
251             return GHT_ERROR;
252         }
253     }
254 
255     if ( schema->num_dims == schema->max_dims )
256     {
257         schema->max_dims *= 2;
258         schema->dims = ght_realloc(schema->dims, schema->max_dims * sizeof(GhtDimension*));
259     }
260 
261     dim->position = schema->num_dims;
262     schema->dims[schema->num_dims] = dim;
263     schema->num_dims++;
264 
265     return GHT_OK;
266 }
267 
268 
ght_dimension_from_xml(xmlNodePtr node,GhtDimension ** dimension)269 static GhtErr ght_dimension_from_xml(xmlNodePtr node, GhtDimension **dimension)
270 {
271     xmlNodePtr child;
272     GhtDimension *dim;
273 
274     GHT_TRY(ght_dimension_new(dimension));
275     dim = *dimension;
276 
277     for ( child = node->children; child; child = child->next )
278     {
279         if ( child->type == XML_ELEMENT_NODE )
280         {
281 
282 #define TAG_IS(str) (strcmp(child->name, str) == 0)
283 
284             if ( TAG_IS("name") )
285             {
286                 ght_dimension_set_name(dim, child->children->content);
287             }
288             else if ( TAG_IS("description") )
289             {
290                 ght_dimension_set_description(dim, child->children->content);
291             }
292             else if ( TAG_IS("interpretation") )
293             {
294                 GhtType type;
295                 GHT_TRY(ght_type_from_str(child->children->content, &type));
296                 GHT_TRY(ght_dimension_set_type(dim, type));
297             }
298             else if ( TAG_IS("scale") )
299             {
300                 GHT_TRY(ght_dimension_set_scale(dim, atof(child->children->content)));
301             }
302             else if ( TAG_IS("offset") )
303             {
304                 GHT_TRY(ght_dimension_set_offset(dim, atof(child->children->content)));
305             }
306             else
307             {
308                 /* Unhandled <tag> */
309             }
310         }
311     }
312 
313     return GHT_OK;
314 }
315 
ght_schema_from_xml(xmlDocPtr xml_doc,GhtSchema ** schema)316 static GhtErr ght_schema_from_xml(xmlDocPtr xml_doc, GhtSchema **schema)
317 {
318     static xmlChar *xpath_str = "/pc:PointCloudSchema/pc:dimension";
319     xmlNsPtr xml_ns = NULL;
320     xmlXPathContextPtr xpath_ctx;
321     xmlXPathObjectPtr xpath_obj;
322     xmlNodePtr xml_root = NULL;
323     xmlNodeSetPtr nodes;
324 
325     xml_root = xmlDocGetRootElement(xml_doc);
326 
327     if ( xml_root->ns )
328         xml_ns = xml_root->ns;
329 
330     /* Create xpath evaluation context */
331     xpath_ctx = xmlXPathNewContext(xml_doc);
332     if( ! xpath_ctx )
333     {
334         ght_warn("unable to create new XPath context to read schema XML");
335         return GHT_ERROR;
336     }
337 
338     /* Register the root namespace if there is one */
339     if ( xml_ns )
340         xmlXPathRegisterNs(xpath_ctx, "pc", xml_ns->href);
341 
342     /* Evaluate xpath expression */
343     xpath_obj = xmlXPathEvalExpression(xpath_str, xpath_ctx);
344     if( ! xpath_obj )
345     {
346         xmlXPathFreeContext(xpath_ctx);
347         ght_warn("unable to evaluate xpath expression \"%s\" against schema XML", xpath_str);
348         return GHT_ERROR;
349     }
350 
351     /* Iterate on the dimensions we found */
352     if ( nodes = xpath_obj->nodesetval )
353     {
354         int i;
355         int ndims = nodes->nodeNr;
356 
357         GHT_TRY(ght_schema_new(schema));
358 
359         for ( i = 0; i < ndims; i++ )
360         {
361             /* This is a "dimension" */
362             if( nodes->nodeTab[i]->type == XML_ELEMENT_NODE )
363             {
364                 GhtDimension *dim;
365                 GHT_TRY(ght_dimension_from_xml(nodes->nodeTab[i], &dim));
366                 GHT_TRY(ght_schema_add_dimension(*schema, dim));
367             }
368         }
369     }
370     else
371     {
372         xmlXPathFreeObject(xpath_obj);
373         xmlXPathFreeContext(xpath_ctx);
374         return GHT_ERROR;
375     }
376 
377     xmlXPathFreeObject(xpath_obj);
378     xmlXPathFreeContext(xpath_ctx);
379     return GHT_OK;
380 }
381 
ght_schema_from_xml_str(const char * xml_str,GhtSchema ** schema)382 GhtErr ght_schema_from_xml_str(const char *xml_str, GhtSchema **schema)
383 {
384     const char *xml_ptr = xml_str;
385     size_t xml_size;
386     xmlDocPtr xml_doc;
387     GhtErr result;
388 
389     /* Roll forward to start of XML string */
390     while( (*xml_ptr != '\0') && (*xml_ptr != '<') )
391     {
392         xml_ptr++;
393     }
394 
395     xml_size = strlen(xml_ptr);
396 
397     xmlInitParser();
398     xml_doc = xmlReadMemory(xml_ptr, xml_size, NULL, NULL, 0);
399     if ( ! xml_doc )
400     {
401         ght_warn("unable to parse schema XML");
402         result = GHT_ERROR;
403     }
404     else
405     {
406         result = ght_schema_from_xml(xml_doc, schema);
407         xmlFreeDoc(xml_doc);
408     }
409     xmlCleanupParser();
410 
411     return result;
412 }
413 
ght_schema_clone(const GhtSchema * schema,GhtSchema ** newschema)414 GhtErr ght_schema_clone(const GhtSchema *schema, GhtSchema **newschema)
415 {
416     int i;
417     GhtSchema *s = ght_malloc(sizeof(GhtSchema));
418     s->num_dims = schema->num_dims;
419     s->max_dims = schema->num_dims;
420     s->dims = ght_malloc(s->num_dims * sizeof(GhtDimension*));
421     for ( i = 0; i < s->num_dims; i++ )
422     {
423         ght_dimension_clone(schema->dims[i], &(s->dims[i]));
424     }
425     return GHT_OK;
426 }
427 
ght_schema_to_xml_str(const GhtSchema * schema,char ** xml_str,size_t * xml_str_size)428 GhtErr ght_schema_to_xml_str(const GhtSchema *schema, char **xml_str, size_t *xml_str_size)
429 {
430     int i;
431     stringbuffer_t *sb = ght_ght_stringbuffer_create_with_size(1024);
432     assert(schema);
433     assert(xml_str);
434 
435     ght_stringbuffer_append(sb, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
436     ght_stringbuffer_append(sb, "<pc:PointCloudSchema xmlns:pc=\"http://pointcloud.org/schemas/PC/1.1\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">\n");
437 
438     for ( i = 0; i < schema->num_dims; i++ )
439     {
440         GhtDimension *dim = schema->dims[i];
441         ght_stringbuffer_append(sb, "<pc:dimension>\n");
442         ght_stringbuffer_aprintf(sb, "<pc:position>%d</pc:position>\n", i+1);
443         if ( dim->name )
444         {
445             ght_stringbuffer_aprintf(sb, "<pc:name>%s</pc:name>\n", dim->name);
446         }
447         if ( dim->description )
448         {
449             ght_stringbuffer_aprintf(sb, "<pc:description>%s</pc:description>\n", dim->description);
450         }
451         ght_stringbuffer_aprintf(sb, "<pc:interpretation>%s</pc:interpretation>\n", GhtTypeStrings[dim->type]);
452         ght_stringbuffer_aprintf(sb, "<pc:size>%zu</pc:size>\n", GhtTypeSizes[dim->type]);
453         if ( dim->scale != 1 )
454         {
455             ght_stringbuffer_aprintf(sb, "<pc:scale>%g</pc:scale>\n", dim->scale);
456         }
457         if ( dim->offset != 0 )
458         {
459             ght_stringbuffer_aprintf(sb, "<pc:offset>%g</pc:offset>\n", dim->offset);
460         }
461         ght_stringbuffer_append(sb, "<pc:active>true</pc:active>\n");
462         ght_stringbuffer_append(sb, "</pc:dimension>\n");
463     }
464 
465     ght_stringbuffer_append(sb, "</pc:PointCloudSchema>");
466 
467     *xml_str = ght_stringbuffer_getstringcopy(sb);
468     *xml_str_size = ght_stringbuffer_getlength(sb) + 1;
469     ght_stringbuffer_destroy(sb);
470     return GHT_OK;
471 }
472 
ght_schema_from_xml_file(const char * filename,GhtSchema ** schema)473 GhtErr ght_schema_from_xml_file(const char *filename, GhtSchema **schema)
474 {
475     stringbuffer_t *sb;
476     GhtErr err;
477     FILE *file = NULL;
478     static size_t read_size = 1023;
479     size_t sz;
480     char buf[read_size+1]; /* space for null terminator */
481 
482     file = fopen(filename, "r");
483     if ( ! file )
484     {
485         ght_error("%s: failed to open xml schema file %s for reading", __func__, filename);
486         return GHT_ERROR;
487     }
488 
489     sb = ght_stringbuffer_create();
490 
491     while(1)
492     {
493         sz = fread(buf, read_size, 1, file);
494         buf[read_size] = '\0';
495         ght_stringbuffer_append(sb, buf);
496         if ( sz != read_size )
497             break;
498     }
499 
500     err = ght_schema_from_xml_str(ght_stringbuffer_getstring(sb), schema);
501 
502     ght_stringbuffer_destroy(sb);
503     return err;
504 }
505 
506 
ght_schema_to_xml_file(const GhtSchema * schema,const char * filename)507 GhtErr ght_schema_to_xml_file(const GhtSchema *schema, const char *filename)
508 {
509     size_t xml_size, write_size;
510     char *xml;
511     GhtErr err;
512     FILE *file;
513 
514     file = fopen(filename, "w");
515     if ( ! file )
516     {
517         ght_error("%s: failed to open xml schema file %s for writing", __func__, filename);
518         return GHT_ERROR;
519     }
520 
521     GHT_TRY(ght_schema_to_xml_str(schema, &xml, &xml_size));
522 
523     write_size = fwrite(xml, 1, xml_size, file);
524     if ( write_size != xml_size )
525     {
526         ght_error("%s: failed to write xml schema file", __func__);
527         return GHT_ERROR;
528     }
529 
530     fclose(file);
531 
532     return GHT_OK;
533 }
534 
535 
536