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 "ght_internal.h"
12 #include <float.h>
13 
14 /******************************************************************************
15 *  GhtNodeList
16 ******************************************************************************/
17 
18 /** New, empty, nodelist */
19 GhtErr
ght_nodelist_new(int capacity,GhtNodeList ** nodelist)20 ght_nodelist_new(int capacity, GhtNodeList **nodelist)
21 {
22     GhtNodeList *nl;
23     assert(nodelist);
24     nl = ght_malloc(sizeof(GhtNodeList));
25     if ( ! nl ) return GHT_ERROR;
26     memset(nl, 0, sizeof(GhtNodeList));
27     nl->nodes = NULL;
28     nl->num_nodes = 0;
29     nl->max_nodes = capacity;
30     if ( capacity )
31     {
32         nl->nodes = ght_malloc(sizeof(GhtNode*)*capacity);
33     }
34     *nodelist = nl;
35     return GHT_OK;
36 }
37 
38 GhtErr
ght_nodelist_get_num_nodes(const GhtNodeList * nodelist,int * num_nodes)39 ght_nodelist_get_num_nodes(const GhtNodeList *nodelist, int *num_nodes)
40 {
41     assert(nodelist);
42     *num_nodes = nodelist->num_nodes;
43     return GHT_OK;
44 }
45 
46 GhtErr
ght_nodelist_get_node(const GhtNodeList * nodelist,int index,GhtNode ** node)47 ght_nodelist_get_node(const GhtNodeList *nodelist, int index, GhtNode **node)
48 {
49     assert(nodelist);
50     assert(nodelist->num_nodes > index);
51     assert(index >= 0);
52 
53     *node = nodelist->nodes[index];
54     return GHT_OK;
55 }
56 
57 /** Free just the containing structure, leaving the nodes */
58 GhtErr
ght_nodelist_free_shallow(GhtNodeList * nl)59 ght_nodelist_free_shallow(GhtNodeList *nl)
60 {
61     if ( nl->nodes )
62         ght_free(nl->nodes);
63 
64     ght_free(nl);
65 }
66 
67 /** Free all the nodes, then the containing stuff */
68 GhtErr
ght_nodelist_free_deep(GhtNodeList * nl)69 ght_nodelist_free_deep(GhtNodeList *nl)
70 {
71     int i;
72     if ( nl->nodes )
73     {
74         for ( i = 0; i < nl->num_nodes; i++ )
75         {
76             if ( nl->nodes[i] )
77                 ght_node_free(nl->nodes[i]);
78         }
79     }
80     return ght_nodelist_free_shallow(nl);
81 }
82 
83 /** Add node, adding memory space as necessary. */
84 GhtErr
ght_nodelist_add_node(GhtNodeList * nl,GhtNode * node)85 ght_nodelist_add_node(GhtNodeList *nl, GhtNode *node)
86 {
87 
88     /* First time, initialize */
89     if ( nl->max_nodes == 0 )
90     {
91         nl->nodes = ght_malloc(sizeof(GhtNode*) * 8);
92         nl->max_nodes = 8;
93     }
94 
95     /* Node list is full, so expand it */
96     if ( nl->num_nodes == nl->max_nodes )
97     {
98         nl->max_nodes *= 2;
99         nl->nodes = ght_realloc(nl->nodes, sizeof(GhtNode*) * nl->max_nodes);
100         if ( ! nl->nodes ) return GHT_ERROR;
101     }
102 
103     /* Something wrong with memory? */
104     if ( ! nl->nodes ) return GHT_ERROR;
105 
106     /* Add the node to list */
107     nl->nodes[nl->num_nodes] = node;
108     nl->num_nodes++;
109     return GHT_OK;
110 }
111 
112 
113 
114 /******************************************************************************
115 *  GhtNode
116 ******************************************************************************/
117 
118 /** Some nodelist utility functions */
119 static int
ght_node_is_leaf(const GhtNode * node)120 ght_node_is_leaf(const GhtNode *node)
121 {
122     return (! node->children) || (node->children->num_nodes == 0);
123 }
124 
125 static int
ght_node_num_children(const GhtNode * node)126 ght_node_num_children(const GhtNode *node)
127 {
128     if ( ! node->children)
129         return 0;
130     return node->children->num_nodes;
131 }
132 
133 static GhtErr
ght_node_new(GhtNode ** node)134 ght_node_new(GhtNode **node)
135 {
136     GhtNode *n = ght_malloc(sizeof(GhtNode));
137     if ( ! n ) return GHT_ERROR;
138     memset(n, 0, sizeof(GhtNode));
139     n->children = NULL;
140     n->attributes = NULL;
141     n->hash = NULL;
142     *node = n;
143     return GHT_OK;
144 }
145 
146 GhtErr
ght_node_set_hash(GhtNode * node,GhtHash * hash)147 ght_node_set_hash(GhtNode *node, GhtHash *hash)
148 {
149     if ( node->hash )
150         ght_free(node->hash);
151     node->hash = hash;
152     return GHT_OK;
153 }
154 
155 
156 GhtErr
ght_node_get_attributes(const GhtNode * node,GhtAttribute ** attr)157 ght_node_get_attributes(const GhtNode *node, GhtAttribute **attr)
158 {
159     if ( node->attributes )
160     {
161         *attr = node->attributes;
162         return GHT_OK;
163     }
164     *attr = NULL;
165     return GHT_ERROR;
166 }
167 
168 GhtErr
ght_node_get_coordinate(const GhtNode * node,GhtCoordinate * coord)169 ght_node_get_coordinate(const GhtNode *node, GhtCoordinate *coord)
170 {
171     if ( ! node->hash )
172     {
173         return GHT_ERROR;
174     }
175     return ght_coordinate_from_hash(node->hash, coord);
176 }
177 
178 /** Create new node, taking ownership of hash parameter */
179 GhtErr
ght_node_new_from_hash(GhtHash * hash,GhtNode ** node)180 ght_node_new_from_hash(GhtHash *hash, GhtNode **node)
181 {
182     GHT_TRY(ght_node_new(node));
183     GHT_TRY(ght_node_set_hash(*node, ght_strdup(hash)));
184     return GHT_OK;
185 }
186 
187 /** Create new node, taking ownership of hash parameter */
188 GhtErr
ght_node_new_from_coordinate(const GhtCoordinate * coord,unsigned int resolution,GhtNode ** node)189 ght_node_new_from_coordinate(const GhtCoordinate *coord, unsigned int resolution, GhtNode **node)
190 {
191     GhtHash *hash;
192     assert(node != NULL);
193     assert(coord != NULL);
194     GHT_TRY(ght_hash_from_coordinate(coord, resolution, &hash));
195     GHT_TRY(ght_node_new(node));
196     GHT_TRY(ght_node_set_hash(*node, hash));
197     return GHT_OK;
198 }
199 
200 static GhtErr
ght_node_add_child(GhtNode * parent,GhtNode * child)201 ght_node_add_child(GhtNode *parent, GhtNode *child)
202 {
203     if ( ! parent->children )
204     {
205         ght_nodelist_new(1, &(parent->children));
206     }
207     return ght_nodelist_add_node(parent->children, child);
208 }
209 
210 static GhtErr
ght_node_transfer_attributes(GhtNode * node_from,GhtNode * node_to)211 ght_node_transfer_attributes(GhtNode *node_from, GhtNode *node_to)
212 {
213     size_t sz;
214 
215     /* Nothing to transfer */
216     if ( ! node_from->attributes )
217         return GHT_OK;
218 
219     /* Uh oh, something is already there */
220     if ( node_to->attributes )
221         return GHT_ERROR;
222 
223     node_to->attributes = node_from->attributes;
224     node_from->attributes = NULL;
225 
226     return GHT_OK;
227 }
228 
229 /**
230 * Recursive function, walk down from parent node, looking for
231 * appropriate insertion point for node_to_insert. If duplicates,
232 * and duplicate leaf, insert as hash-less "attribute only" node.
233 * ["abcdefg", "abcdeff", "abcdddd", "abbbeee"] becomes
234 * "ab"->["c"->["d"->["ddd","ef"->["g","f"]]],"b"]
235 */
236 GhtErr
ght_node_insert_node(GhtNode * node,GhtNode * node_to_insert,GhtDuplicates duplicates)237 ght_node_insert_node(GhtNode *node, GhtNode *node_to_insert, GhtDuplicates duplicates)
238 {
239     GhtHash *node_leaf, *node_to_insert_leaf;
240     GhtErr err;
241     GhtHashMatch matchtype;
242 
243     /* NULL hash implies this node is a faux node for duplicate points */
244     if ( ! node->hash )
245         return GHT_INCOMPLETE;
246 
247     /* matchtype in (GHT_NONE, GHT_GLOBAL, GHT_SAME, GHT_CHILD, GHT_SPLIT) */
248     /* NONE and GLOBAL come back with GHT_ERROR, so we don't handle them yet */
249     GHT_TRY(ght_hash_leaf_parts(node->hash, node_to_insert->hash, GHT_MAX_HASH_LENGTH,
250                                 &matchtype, &node_leaf, &node_to_insert_leaf));
251 
252     /* Insert node is child of node, either explicitly, or implicitly for */
253     /* the "" hash which serves as a master parent */
254     /* "abcdef" is a GHT_CHILD or "abc", and gets added as "def" */
255     if ( matchtype == GHT_CHILD || matchtype == GHT_GLOBAL )
256     {
257         int i;
258         ght_node_set_hash(node_to_insert, ght_strdup(node_to_insert_leaf));
259         for ( i = 0; i < ght_node_num_children(node); i++ )
260         {
261             err = ght_node_insert_node(node->children->nodes[i], node_to_insert, duplicates);
262             /* Node added to one of the children */
263             if ( err == GHT_OK ) return GHT_OK;
264         }
265         /* Node didn't fit any of the children, so add it at this level */
266         return ght_node_add_child(node, node_to_insert);
267     }
268 
269     if ( matchtype == GHT_SAME )
270     {
271         /* New node is duplicate of this node. We insert an */
272         /* empty node (no hash) underneath, to hang attributes off of */
273         /* and use this node as the parent */
274         if ( duplicates )
275         {
276             /* If this is the first duplicate, add a copy of the parent */
277             /* To serve as a proxy leaf for this value */
278             if ( ( ! node->children ) || ( node->children->num_nodes == 0 ) )
279             {
280                 GhtNode *parent_leaf;
281                 GHT_TRY(ght_node_new(&parent_leaf));
282                 GHT_TRY(ght_node_transfer_attributes(node, parent_leaf));
283                 GHT_TRY(ght_node_add_child(node, parent_leaf));
284             }
285 
286             /* Add the new node under the parent, stripping the hash */
287             ght_free(node_to_insert->hash);
288             node_to_insert->hash = NULL;
289             GHT_TRY(ght_node_add_child(node, node_to_insert));
290 
291             return GHT_OK;
292         }
293         else
294         {
295             /* For now, we just skip duplicates. */
296             /* In future, average / median the duplicates onto parent here? */
297             return GHT_OK;
298         }
299     }
300 
301     /* "abcdef" and "abcghi" need to GHT_SPLIT, into "abc"->["def", "ghi"] */
302     if ( matchtype == GHT_SPLIT )
303     {
304         /* We need a new node to hold that part of the parent that is not shared */
305         GhtNode *another_node_to_insert;
306         GHT_TRY(ght_node_new_from_hash(node_leaf, &another_node_to_insert));
307         /* Move attributes to the new child */
308         GHT_TRY(ght_node_transfer_attributes(node, another_node_to_insert));
309 
310         /* Any children of the parent need to move down the tree with the unique part of the hash */
311         if ( node->children )
312         {
313             another_node_to_insert->children = node->children;
314             node->children = NULL;
315         }
316         /* Null-terminate parent hash at end of shared part */
317         *node_leaf = '\0';
318         /* Pull the non-shared part of insert node hash to the front */
319         memmove(node_to_insert->hash, node_to_insert_leaf, strlen(node_to_insert_leaf)+1);
320         /* Add the unique portion of the parent to the parent */
321         GHT_TRY(ght_node_add_child(node, another_node_to_insert));
322         /* Add the unique portion of the insert node to the parent */
323         GHT_TRY(ght_node_add_child(node, node_to_insert));
324         /* Done! */
325         return GHT_OK;
326     }
327 
328     /* Don't get here */
329     return GHT_ERROR;
330 
331 }
332 
333 
334 GhtErr
ght_node_to_string(GhtNode * node,stringbuffer_t * sb,int level)335 ght_node_to_string(GhtNode *node, stringbuffer_t *sb, int level)
336 {
337     int i = 0;
338 
339     /* Print hash */
340     if ( node->hash )
341         ght_stringbuffer_aprintf(sb, "%*s%s", 2*level, "", node->hash);
342     else
343         ght_stringbuffer_aprintf(sb, "%*s%s", 2*level, "", "[hash-is-null]");
344 
345     /* Print attributes */
346     if ( node->attributes )
347     {
348         GhtAttribute *attr = node->attributes;
349         ght_stringbuffer_append(sb, "  ");
350         while ( attr )
351         {
352             ght_attribute_to_string(attr, sb);
353             if ( attr->next ) ght_stringbuffer_append(sb, ":");
354             attr = attr->next;
355         }
356     }
357     ght_stringbuffer_append(sb, "\n");
358 
359     /* Recurse into children */
360     for ( i = 0; i < ght_node_num_children(node); i++ )
361     {
362         GHT_TRY(ght_node_to_string(node->children->nodes[i], sb, level + 1));
363     }
364     return GHT_OK;
365 }
366 
367 
368 GhtErr
ght_node_count_leaves(const GhtNode * node,int * count)369 ght_node_count_leaves(const GhtNode *node, int *count)
370 {
371     int i;
372     GhtErr err;
373 
374     /* No-op on empty */
375     if ( ! node ) return GHT_OK;
376 
377     if ( ght_node_is_leaf(node) )
378     {
379         *count += 1;
380         return GHT_OK;
381     }
382     for ( i = 0; i < ght_node_num_children(node); i++ )
383     {
384         GHT_TRY(ght_node_count_leaves(node->children->nodes[i], count));
385     }
386     return GHT_OK;
387 }
388 
389 GhtErr
ght_node_free(GhtNode * node)390 ght_node_free(GhtNode *node)
391 {
392     int i;
393     const int deep = 1;
394     assert(node != NULL);
395 
396     if ( node->attributes )
397         GHT_TRY(ght_attribute_free(node->attributes));
398 
399     if ( node->children )
400         GHT_TRY(ght_nodelist_free_deep(node->children));
401 
402     if ( node->hash )
403         GHT_TRY(ght_hash_free(node->hash));
404 
405     ght_free(node);
406 }
407 
408 
409 GhtErr
ght_node_add_attribute(GhtNode * node,GhtAttribute * attribute)410 ght_node_add_attribute(GhtNode *node, GhtAttribute *attribute)
411 {
412     GhtAttribute *attr = node->attributes;
413 
414     /* First one! */
415     if ( ! node->attributes )
416     {
417         node->attributes = attribute;
418         return GHT_OK;
419     }
420 
421     while ( attr->next )
422     {
423         attr = attr->next;
424     }
425 
426     attr->next = attribute;
427     return GHT_OK;
428 }
429 
430 GhtErr
ght_node_count_attributes(const GhtNode * node,uint8_t * count)431 ght_node_count_attributes(const GhtNode *node, uint8_t *count)
432 {
433     uint8_t c = 0;
434     GhtAttribute *attr = node->attributes;
435     while ( attr )
436     {
437         c++;
438         attr = attr->next;
439     }
440     *count = c;
441     return GHT_OK;
442 }
443 
444 GhtErr
ght_node_delete_attribute(GhtNode * node,const GhtDimension * dim)445 ght_node_delete_attribute(GhtNode *node, const GhtDimension *dim)
446 {
447     GhtAttribute *attr = node->attributes;
448     GhtAttribute *attr_prev;
449 
450     /* No attributes, noop */
451     if ( ! attr )
452         return GHT_OK;
453 
454     /* Roll forward until we find a match */
455     while( attr->dim != dim )
456     {
457         if ( attr->next )
458         {
459             attr_prev = attr;
460             attr = attr->next;
461         }
462         /* No attributes matches dimension */
463         else
464         {
465             return GHT_ERROR;
466         }
467     }
468 
469     /* Handle first attribute in list specially */
470     if ( attr == node->attributes )
471     {
472         if ( attr->next )
473             node->attributes = attr->next;
474         else
475             node->attributes = NULL;
476     }
477     else
478     {
479         attr_prev->next = attr->next;
480     }
481 
482     ght_free(attr);
483     return GHT_OK;
484 }
485 
486 /*
487 * Recursive compaction routine. Pulls attribute up to the highest node such that
488 * all children share the attribute value.
489 */
490 static GhtErr
ght_node_compact_attribute_with_delta(GhtNode * node,const GhtDimension * dim,double delta,GhtAttribute * compacted_attribute)491 ght_node_compact_attribute_with_delta(GhtNode *node,
492                                       const GhtDimension *dim,
493                                       double delta,
494                                       GhtAttribute *compacted_attribute)
495 {
496     int i;
497 
498     /* This is an internal node, see if all the children share a value in this dimension */
499     if ( node->children && node->children->num_nodes > 0 )
500     {
501         double minval = DBL_MAX;
502         double maxval = -1 * DBL_MAX;
503         double totval = 0.0;
504         int node_count = 0;
505 
506         /* Figure out the range of values for this dimension in child nodes */
507         for ( i = 0; i < node->children->num_nodes; i++ )
508         {
509             GhtAttribute attr;
510             GhtErr err;
511             err = ght_node_compact_attribute_with_delta(node->children->nodes[i], dim, delta, &attr);
512             if ( err == GHT_OK )
513             {
514                 double d;
515                 GHT_TRY(ght_attribute_get_value(&attr, &d));
516                 (d < minval) ? (minval = d) : 0;
517                 (d > maxval) ? (maxval = d) : 0;
518                 totval += d;
519                 node_count++;
520             }
521             else
522             {
523                 continue;
524             }
525         }
526 
527         /* If the range is narrow, and we got values from all our children, compact them */
528         if ( (maxval-minval) < delta && node_count == node->children->num_nodes )
529         {
530             double val = (minval+maxval)/2.0;
531             GhtAttribute *myattr;
532             for ( i = 0; i < node->children->num_nodes; i++ )
533             {
534                 ght_node_delete_attribute(node->children->nodes[i], dim);
535             }
536             ght_attribute_new_from_double(dim, val, &myattr);
537             memcpy(compacted_attribute, myattr, sizeof(GhtAttribute));
538             ght_node_add_attribute(node, myattr);
539             return GHT_OK;
540         }
541         return GHT_ERROR;
542     }
543     /* This is a leaf node, send the attribute value up to the caller */
544     else
545     {
546         if ( ! node->attributes ) return GHT_ERROR;
547         return ght_attribute_get_by_dimension(node->attributes, dim, compacted_attribute);
548     }
549 }
550 
551 GhtErr
ght_node_compact_attribute(GhtNode * node,const GhtDimension * dim,GhtAttribute * attr)552 ght_node_compact_attribute(GhtNode *node, const GhtDimension *dim, GhtAttribute *attr)
553 {
554     return ght_node_compact_attribute_with_delta(node, dim, 10e-8, attr);
555 }
556 
557 /**
558 * Recursive node serialization:
559 * - length of GhtHash
560 * - GhtHash (no null terminator)
561 * - number of GhtAttributes
562 * - GhtAttribute[]
563 * - number of child GhtNodes
564 * - GhtNode[]
565 */
566 GhtErr
ght_node_write(const GhtNode * node,GhtWriter * writer)567 ght_node_write(const GhtNode *node, GhtWriter *writer)
568 {
569     uint8_t attrcount = 0;
570     uint8_t childcount = 0;
571     GhtAttribute *attr = node->attributes;
572 
573     /* Write the hash */
574     GHT_TRY(ght_hash_write(node->hash, writer));
575 
576     /* Write the attributes */
577     GHT_TRY(ght_node_count_attributes(node, &attrcount));
578     ght_write(writer, &attrcount, 1);
579     while( attr )
580     {
581         ght_attribute_write(attr, writer);
582         attr = attr->next;
583     }
584 
585     /* Write the children */
586     if ( node->children )
587         childcount = node->children->num_nodes;
588 
589     ght_write(writer, &childcount, 1);
590     if ( childcount )
591     {
592         int i;
593         for ( i = 0; i < node->children->num_nodes; i++ )
594         {
595             ght_node_write(node->children->nodes[i], writer);
596         }
597     }
598     return GHT_OK;
599 }
600 
601 /**
602 * Recursive node deserialization
603 */
604 GhtErr
ght_node_read(GhtReader * reader,GhtNode ** node)605 ght_node_read(GhtReader *reader, GhtNode **node)
606 {
607     int i;
608     uint8_t attrcount;
609     uint8_t childcount;
610     GhtHash *hash = NULL;
611     GhtNode *n = NULL;
612     GhtAttribute *attr = NULL;
613 
614     /* Read the hash string */
615     ght_hash_read(reader, &hash);
616     if ( hash )
617     {
618         GHT_TRY(ght_node_new_from_hash(hash, &n));
619     }
620     else
621     {
622         GHT_TRY(ght_node_new(&n));
623     }
624 
625     /* Read the attributes */
626     ght_read(reader, &attrcount, 1);
627     while ( attrcount )
628     {
629         GHT_TRY(ght_attribute_read(reader, &attr));
630         GHT_TRY(ght_node_add_attribute(n, attr));
631         attrcount--;
632     }
633 
634     /* Read the children */
635     ght_read(reader, &childcount, 1);
636     /* Set up an exactly sized node list to hold the children */
637     if ( childcount > 0 )
638     {
639         GHT_TRY(ght_nodelist_new(childcount, &(n->children)));
640     }
641     for ( i = 0; i < childcount; i++ )
642     {
643         GhtNode *nc = NULL;
644         GHT_TRY(ght_node_read(reader, &nc));
645         if ( nc )
646         {
647             GHT_TRY(ght_node_add_child(n, nc));
648         }
649     }
650 
651     *node = n;
652     return GHT_OK;
653 }
654 
655 /* Recursively build a nodelist from a tree of GhtNodes */
656 GhtErr
ght_node_to_nodelist(const GhtNode * node,GhtNodeList * nodelist,GhtAttribute * attr,GhtHash * hash)657 ght_node_to_nodelist(const GhtNode *node, GhtNodeList *nodelist, GhtAttribute *attr, GhtHash *hash)
658 {
659     static int hash_array_len = GHT_MAX_HASH_LENGTH + 1;
660     GhtHash h[hash_array_len];
661     GhtAttribute *a;
662 
663     /* Add our part of the hash to the incoming part */
664     memset(h, 0, hash_array_len);
665     strncpy(h, hash, hash_array_len);
666     if ( node->hash )
667     {
668         strcat(h, node->hash);
669     }
670 
671     /* Make a copy of all the incoming attributes */
672     GHT_TRY(ght_attribute_union(node->attributes, attr, &a));
673 
674     /* Recurse down to leaf nodes, copying attributes and passing them down */
675     if ( node->children && node->children->num_nodes > 0 )
676     {
677         int i;
678         for ( i = 0; i < node->children->num_nodes; i++ )
679         {
680             GHT_TRY(ght_node_to_nodelist(node->children->nodes[i], nodelist, a, h));
681         }
682         ght_attribute_free(a);
683     }
684     /* This is a leaf node, create a new node and add to list */
685     else
686     {
687         GhtNode *n;
688         GHT_TRY(ght_node_new_from_hash(h, &n));
689         GHT_TRY(ght_node_add_attribute(n, a));
690         GHT_TRY(ght_nodelist_add_node(nodelist, n));
691     }
692 
693 
694     return GHT_OK;
695 }
696 
697 /* Recursively build a nodelist from a tree of GhtNodes */
698 GhtErr
ght_node_get_extent(const GhtNode * node,const GhtHash * hash,GhtArea * area)699 ght_node_get_extent(const GhtNode *node, const GhtHash *hash, GhtArea *area)
700 {
701     static int hash_array_len = GHT_MAX_HASH_LENGTH + 1;
702     GhtHash h[hash_array_len];
703     GhtCoordinate coord;
704 
705     /* Add our part of the hash to the incoming part */
706     memset(h, 0, hash_array_len);
707     strncpy(h, hash, hash_array_len);
708     if ( node->hash )
709         strcat(h, node->hash);
710 
711     if ( node->children && node->children->num_nodes > 0 )
712     {
713         int i;
714         for ( i = 0; i < node->children->num_nodes; i++ )
715         {
716             if ( node->children->nodes[i] && node->children->nodes[i]->hash )
717             {
718                 ght_node_get_extent(node->children->nodes[i], h, area);
719             }
720         }
721     }
722     else
723     {
724         ght_coordinate_from_hash(h, &coord);
725         if ( coord.x < area->x.min ) area->x.min = coord.x;
726         if ( coord.x > area->x.max ) area->x.max = coord.x;
727         if ( coord.y < area->y.min ) area->y.min = coord.y;
728         if ( coord.y > area->y.max ) area->y.max = coord.y;
729     }
730 
731     return GHT_OK;
732 }
733 
734 GhtErr
ght_node_filter_by_attribute(const GhtNode * node,const GhtFilter * filter,GhtNode ** filtered_node)735 ght_node_filter_by_attribute(const GhtNode *node, const GhtFilter *filter, GhtNode **filtered_node)
736 {
737     int i;
738     double val;
739     int keep = 1;
740     GhtAttribute *attr;
741     GhtNode *node_copy = NULL;
742 
743     /* Our default position is nothing is getting returned */
744     *filtered_node = NULL;
745 
746     /* No-op on an empty input */
747     if ( ! node )
748         return GHT_OK;
749 
750     attr = node->attributes;
751     while ( attr )
752     {
753         /* Only filter on the attribute of interest */
754         if ( attr->dim != filter->dim )
755         {
756             attr = attr->next;
757         }
758         else
759         {
760             GHT_TRY(ght_attribute_get_value(attr, &val));
761             switch ( filter->mode )
762             {
763                 case GHT_GREATER_THAN:
764                     keep = (val > filter->range.min);
765                     break;
766                 case GHT_LESS_THAN:
767                     keep = (val < filter->range.max);
768                     break;
769                 case GHT_BETWEEN:
770                     keep = ((val <= filter->range.max) && (val >= filter->range.min));
771                     break;
772                 case GHT_EQUAL:
773                     keep = (val == filter->range.min);
774                     break;
775                 default:
776                     ght_error("%s: invalid GhtFilterMode (%d)", __func__, filter->mode);
777             }
778             break;
779         }
780     }
781 
782     /* We found a relevant attribute, and it failed the filter test. */
783     /* So, this node (and all it's children) can be excluded! */
784     if ( ! keep )
785     {
786         return GHT_OK;
787     }
788 
789     /* Also take copies of any children that pass the filter */
790     if ( node->children )
791     {
792         for ( i = 0; i < node->children->num_nodes; i++ )
793         {
794             GhtNode *child_copy;
795             GHT_TRY(ght_node_filter_by_attribute(node->children->nodes[i], filter, &child_copy));
796             /* Child survived the filtering */
797             if ( child_copy )
798             {
799                 if ( ! node_copy )
800                 {
801                     GHT_TRY(ght_node_new(&node_copy));
802                     GHT_TRY(ght_hash_clone(node->hash, &(node_copy->hash)));
803                     GHT_TRY(ght_attribute_clone(node->attributes, &(node_copy->attributes)));
804                 }
805                 GHT_TRY(ght_node_add_child(node_copy, child_copy));
806             }
807         }
808     }
809     else
810     {
811         GHT_TRY(ght_node_new(&node_copy));
812         GHT_TRY(ght_hash_clone(node->hash, &(node_copy->hash)));
813         GHT_TRY(ght_attribute_clone(node->attributes, &(node_copy->attributes)));
814     }
815 
816     /* Done, return the structure */
817     *filtered_node = node_copy;
818     return GHT_OK;
819 }