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