~ubuntu-branches/ubuntu/trusty/liblas/trusty-proposed

« back to all changes in this revision

Viewing changes to src/transform.cpp

  • Committer: Package Import Robot
  • Author(s): Francesco Paolo Lovergine
  • Date: 2014-01-05 17:00:29 UTC
  • mfrom: (7.1.2 sid)
  • Revision ID: package-import@ubuntu.com-20140105170029-ddtp0j63x5jvck2u
Tags: 1.7.0+dfsg-2
Fixed missing linking of system boost component.
(closes: #733282)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/******************************************************************************
 
2
 * $Id$
 
3
 *
 
4
 * Project:  libLAS - http://liblas.org - A BSD library for LAS format data.
 
5
 * Purpose:  LAS transform implementation for C++ libLAS 
 
6
 * Author:   Howard Butler, hobu.inc@gmail.com
 
7
 *
 
8
 ******************************************************************************
 
9
 * Copyright (c) 2010, Howard Butler
 
10
 *
 
11
 * All rights reserved.
 
12
 * 
 
13
 * Redistribution and use in source and binary forms, with or without 
 
14
 * modification, are permitted provided that the following 
 
15
 * conditions are met:
 
16
 * 
 
17
 *     * Redistributions of source code must retain the above copyright 
 
18
 *       notice, this list of conditions and the following disclaimer.
 
19
 *     * Redistributions in binary form must reproduce the above copyright 
 
20
 *       notice, this list of conditions and the following disclaimer in 
 
21
 *       the documentation and/or other materials provided 
 
22
 *       with the distribution.
 
23
 *     * Neither the name of the Martin Isenburg or Iowa Department 
 
24
 *       of Natural Resources nor the names of its contributors may be 
 
25
 *       used to endorse or promote products derived from this software 
 
26
 *       without specific prior written permission.
 
27
 * 
 
28
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
 
29
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
 
30
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 
 
31
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 
 
32
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, 
 
33
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 
34
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 
 
35
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 
 
36
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 
37
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 
 
38
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 
39
 * OF SUCH DAMAGE.
 
40
 ****************************************************************************/
 
41
 
 
42
#include <liblas/transform.hpp>
 
43
#include <liblas/exception.hpp>
 
44
#include <liblas/header.hpp>
 
45
// boost
 
46
#include <boost/concept_check.hpp>
 
47
#include <boost/tokenizer.hpp>
 
48
#include <boost/lexical_cast.hpp>
 
49
 
 
50
#ifdef _MSC_VER
 
51
# pragma warning(push)
 
52
# pragma warning(disable: 4100) // unreferenced formal param
 
53
#endif
 
54
#include <boost/algorithm/string/erase.hpp>
 
55
#ifdef _MSC_VER
 
56
# pragma warning(pop)
 
57
#endif
 
58
 
 
59
// std
 
60
#include <sstream>
 
61
#include <stdexcept>
 
62
#include <string>
 
63
#include <algorithm>
 
64
 
 
65
#ifdef HAVE_GDAL
 
66
#include <gdal.h>
 
67
#include <ogr_spatialref.h>
 
68
#endif
 
69
 
 
70
typedef boost::tokenizer<boost::char_separator<char> > tokenizer;
 
71
 
 
72
namespace liblas { 
 
73
    
 
74
 
 
75
#ifdef HAVE_GDAL
 
76
    struct OGRSpatialReferenceDeleter
 
77
    {
 
78
       template <typename T>
 
79
       void operator()(T* ptr)
 
80
       {
 
81
           ::OSRDestroySpatialReference(ptr);
 
82
       }
 
83
    };
 
84
 
 
85
    struct OSRTransformDeleter
 
86
    {
 
87
       template <typename T>
 
88
       void operator()(T* ptr)
 
89
       {
 
90
           ::OCTDestroyCoordinateTransformation(ptr);
 
91
       }
 
92
    };
 
93
 
 
94
 
 
95
    struct GDALSourceDeleter
 
96
    {
 
97
       template <typename T>
 
98
       void operator()(T* ptr)
 
99
       {
 
100
           ::GDALClose(ptr);
 
101
       }
 
102
    };
 
103
 
 
104
 
 
105
#endif
 
106
 
 
107
 
 
108
ReprojectionTransform::ReprojectionTransform(const SpatialReference& inSRS, const SpatialReference& outSRS)
 
109
    : m_new_header(0)
 
110
{
 
111
    Initialize(inSRS, outSRS);
 
112
}
 
113
 
 
114
ReprojectionTransform::ReprojectionTransform(
 
115
    const SpatialReference& inSRS, 
 
116
    const SpatialReference& outSRS,
 
117
    const Header* new_header)
 
118
    : m_new_header(new_header)
 
119
{
 
120
    Initialize(inSRS, outSRS);
 
121
}
 
122
 
 
123
void ReprojectionTransform::Initialize(const SpatialReference& inSRS, const SpatialReference& outSRS)
 
124
{
 
125
#ifdef HAVE_GDAL
 
126
    
 
127
    m_in_ref_ptr = ReferencePtr(OSRNewSpatialReference(0), OGRSpatialReferenceDeleter());
 
128
    m_out_ref_ptr = ReferencePtr(OSRNewSpatialReference(0), OGRSpatialReferenceDeleter());
 
129
    
 
130
    int result = OSRSetFromUserInput(m_in_ref_ptr.get(), inSRS.GetWKT(liblas::SpatialReference::eCompoundOK).c_str());
 
131
    if (result != OGRERR_NONE) 
 
132
    {
 
133
        std::ostringstream msg; 
 
134
        msg << "Could not import input spatial reference for ReprojectionTransform:: " 
 
135
            << CPLGetLastErrorMsg() << " code: " << result 
 
136
            << "wkt: '" << inSRS.GetWKT() << "'";
 
137
        throw std::runtime_error(msg.str());
 
138
    }
 
139
    
 
140
    result = OSRSetFromUserInput(m_out_ref_ptr.get(), outSRS.GetWKT(liblas::SpatialReference::eCompoundOK).c_str());
 
141
    if (result != OGRERR_NONE) 
 
142
    {
 
143
        std::ostringstream msg; 
 
144
        msg << "Could not import output spatial reference for ReprojectionTransform:: " 
 
145
            << CPLGetLastErrorMsg() << " code: " << result 
 
146
            << "wkt: '" << outSRS.GetWKT() << "'";
 
147
        std::string message(msg.str());
 
148
        throw std::runtime_error(message);
 
149
    }
 
150
    m_transform_ptr = TransformPtr(OCTNewCoordinateTransformation( m_in_ref_ptr.get(), m_out_ref_ptr.get()), OSRTransformDeleter());
 
151
#else
 
152
 
 
153
    boost::ignore_unused_variable_warning(inSRS);
 
154
    boost::ignore_unused_variable_warning(outSRS);
 
155
#endif
 
156
}
 
157
 
 
158
ReprojectionTransform::~ReprojectionTransform()
 
159
{
 
160
 
 
161
}
 
162
 
 
163
 
 
164
bool ReprojectionTransform::transform(Point& point)
 
165
{
 
166
#ifdef HAVE_GDAL
 
167
    
 
168
    int ret = 0;
 
169
    double x = point.GetX();
 
170
    double y = point.GetY();
 
171
    double z = point.GetZ();
 
172
 
 
173
    ret = OCTTransform(m_transform_ptr.get(), 1, &x, &y, &z);    
 
174
    if (!ret)
 
175
    {
 
176
        std::ostringstream msg; 
 
177
        msg << "Could not project point for ReprojectionTransform::" << CPLGetLastErrorMsg() << ret;
 
178
        throw std::runtime_error(msg.str());
 
179
    }
 
180
    
 
181
    if (this->ModifiesHeader()) 
 
182
    {
 
183
        if (m_new_header)
 
184
            point.SetHeader(m_new_header);
 
185
        else 
 
186
        {
 
187
            // FIXME? 
 
188
        }
 
189
    }
 
190
 
 
191
    point.SetX(x);
 
192
    point.SetY(y);
 
193
    point.SetZ(z);
 
194
    
 
195
    if (detail::compare_distance(point.GetRawX(), (std::numeric_limits<boost::int32_t>::max)()) ||
 
196
        detail::compare_distance(point.GetRawX(), (std::numeric_limits<boost::int32_t>::min)())) {
 
197
        throw std::domain_error("X scale and offset combination is insufficient to represent the data");
 
198
    }
 
199
 
 
200
    if (detail::compare_distance(point.GetRawY(), (std::numeric_limits<boost::int32_t>::max)()) ||
 
201
        detail::compare_distance(point.GetRawY(), (std::numeric_limits<boost::int32_t>::min)())) {
 
202
        throw std::domain_error("Y scale and offset combination is insufficient to represent the data");
 
203
    }    
 
204
 
 
205
    if (detail::compare_distance(point.GetRawZ(), (std::numeric_limits<boost::int32_t>::max)()) ||
 
206
        detail::compare_distance(point.GetRawZ(), (std::numeric_limits<boost::int32_t>::min)())) {
 
207
        throw std::domain_error("Z scale and offset combination is insufficient to represent the data");
 
208
    }        
 
209
 
 
210
    return true;
 
211
#else
 
212
    boost::ignore_unused_variable_warning(point);
 
213
    return true;
 
214
#endif
 
215
}
 
216
 
 
217
 
 
218
 
 
219
TranslationTransform::TranslationTransform(std::string const& expression)
 
220
{
 
221
    if (expression.size() == 0) 
 
222
        throw std::runtime_error("no expression was given to TranslationTransform");
 
223
    
 
224
    boost::char_separator<char> sep_space(" ");
 
225
 
 
226
    tokenizer dimensions(expression, sep_space);
 
227
    for (tokenizer::iterator t = dimensions.begin(); t != dimensions.end(); ++t) {
 
228
        std::string const& s = *t;
 
229
        
 
230
        operation op = GetOperation(s);
 
231
        operations.push_back(op);
 
232
    }
 
233
    
 
234
}
 
235
 
 
236
TranslationTransform::operation TranslationTransform::GetOperation(std::string const& expr)
 
237
{
 
238
 
 
239
    std::string x("x");
 
240
    std::string y("y");
 
241
    std::string z("z");
 
242
    std::string star("*");
 
243
    std::string divide("/");
 
244
    std::string plus("+");
 
245
    std::string minus("-");
 
246
 
 
247
    if (expr.find(x) == std::string::npos &&
 
248
        expr.find(y) == std::string::npos &&
 
249
        expr.find(z) == std::string::npos)
 
250
        throw liblas::invalid_expression("expression is invalid -- use x, y, or z to define a dimension.  No 'x', 'y', or 'z' was found");
 
251
 
 
252
    operation output("X");
 
253
    
 
254
    
 
255
    std::string expression(expr);
 
256
    boost::erase_all(expression, " "); // Get rid of any spaces in the expression chunk
 
257
 
 
258
 
 
259
    std::size_t found_x = expression.find(x);
 
260
    std::size_t found_y = expression.find(y);
 
261
    std::size_t found_z = expression.find(z);
 
262
    std::size_t found_star = expression.find(star);
 
263
    std::size_t found_divide = expression.find(divide);
 
264
    std::size_t found_plus = expression.find(plus);
 
265
    std::size_t found_minus = expression.find(minus);
 
266
    
 
267
    // if they gave something like 'xyz*34' it's invalid
 
268
    if (found_x != std::string::npos &&
 
269
        found_y != std::string::npos &&
 
270
        found_z != std::string::npos)
 
271
        throw liblas::invalid_expression("expression is invalid");
 
272
    
 
273
    std::string::size_type op_pos=std::string::npos;
 
274
    if (found_x != std::string::npos)
 
275
    {
 
276
        output = operation("X");
 
277
        op_pos = expression.find_last_of(x) + 1;
 
278
    }
 
279
 
 
280
    if (found_y != std::string::npos)
 
281
    {
 
282
        output = operation("Y");
 
283
        op_pos = expression.find_last_of(y) + 1;
 
284
    }
 
285
 
 
286
    if (found_z != std::string::npos)
 
287
    {
 
288
        output = operation("Z");
 
289
        op_pos = expression.find_last_of(z) + 1;
 
290
    }
 
291
    
 
292
    if (op_pos == std::string::npos) 
 
293
    {
 
294
        std::ostringstream oss;
 
295
        oss << "Expression '" << expression << "' does not have 'x', 'y', or 'z' to denote fields";
 
296
        throw liblas::invalid_expression(oss.str());
 
297
    }
 
298
    
 
299
    std::string::size_type data_pos = std::string::npos;
 
300
    if (found_star != std::string::npos) 
 
301
    {
 
302
        output.oper = eOPER_MULTIPLY;
 
303
        data_pos = expression.find_last_of(star) + 1;
 
304
    }
 
305
 
 
306
    if (found_divide != std::string::npos) 
 
307
    {
 
308
        output.oper = eOPER_DIVIDE;
 
309
        data_pos = expression.find_last_of(divide) + 1;
 
310
    }
 
311
 
 
312
    if (found_plus != std::string::npos) 
 
313
    {
 
314
        output.oper = eOPER_ADD;
 
315
        data_pos = expression.find_last_of(plus) + 1;
 
316
    }
 
317
    if (found_minus != std::string::npos) 
 
318
    {
 
319
        output.oper = eOPER_SUBTRACT;
 
320
        data_pos = expression.find_last_of(minus) + 1;
 
321
    }
 
322
 
 
323
    if (data_pos == std::string::npos) 
 
324
    {
 
325
        std::ostringstream oss;
 
326
        oss << "Expression '" << expression << "' does not have '*', '/', '+', or '-' to denote operations";
 
327
        throw liblas::invalid_expression(oss.str());
 
328
    }
 
329
    
 
330
    std::string out;
 
331
    out = expression.substr(data_pos, expression.size());
 
332
    
 
333
    double value =  boost::lexical_cast<double>(out);    
 
334
    output.expression = expression;
 
335
    output.value = value;
 
336
    
 
337
    return output;
 
338
            
 
339
}
 
340
bool TranslationTransform::transform(Point& point)
 
341
{
 
342
    for(std::vector<TranslationTransform::operation>::const_iterator op = operations.begin();
 
343
        op != operations.end();
 
344
        op++) 
 
345
    {
 
346
 
 
347
        switch (op->oper) 
 
348
            {
 
349
                case eOPER_MULTIPLY:
 
350
                    if (!op->dimension.compare("X")) 
 
351
                    {
 
352
                        point.SetX(point.GetX() * op->value);
 
353
                    }
 
354
                    if (!op->dimension.compare("Y")) 
 
355
                    {
 
356
                        point.SetY(point.GetY() * op->value);
 
357
                    }
 
358
                    if (!op->dimension.compare("Z")) 
 
359
                    {
 
360
                        point.SetZ(point.GetZ() * op->value);
 
361
                    }
 
362
                    break;
 
363
                case eOPER_DIVIDE:
 
364
                    if (!op->dimension.compare("X")) 
 
365
                    {
 
366
                        point.SetX(point.GetX() / op->value);
 
367
                    }
 
368
                    if (!op->dimension.compare("Y")) 
 
369
                    {
 
370
                        point.SetY(point.GetY() / op->value);
 
371
                    }
 
372
                    if (!op->dimension.compare("Z")) 
 
373
                    {
 
374
                        point.SetZ(point.GetZ() / op->value);
 
375
                    }
 
376
                    break;
 
377
                case eOPER_ADD:
 
378
                    if (!op->dimension.compare("X")) 
 
379
                    {
 
380
                        point.SetX(point.GetX() + op->value);
 
381
                    }
 
382
                    if (!op->dimension.compare("Y")) 
 
383
                    {
 
384
                        point.SetY(point.GetY() + op->value);
 
385
                    }
 
386
                    if (!op->dimension.compare("Z")) 
 
387
                    {
 
388
                        point.SetZ(point.GetZ() + op->value);
 
389
                    }
 
390
 
 
391
                    break;
 
392
                case eOPER_SUBTRACT:
 
393
                    if (!op->dimension.compare("X")) 
 
394
                    {
 
395
                        point.SetX(point.GetX() - op->value);
 
396
                    }
 
397
                    if (!op->dimension.compare("Y")) 
 
398
                    {
 
399
                        point.SetY(point.GetY() - op->value);
 
400
                    }
 
401
                    if (!op->dimension.compare("Z")) 
 
402
                    {
 
403
                        point.SetZ(point.GetZ() - op->value);
 
404
                    }
 
405
                    break;
 
406
 
 
407
                default:
 
408
                    std::ostringstream oss;
 
409
                    oss << "Unhandled expression operation id " << static_cast<boost::int32_t>(op->oper);
 
410
                    throw std::runtime_error(oss.str());
 
411
            }
 
412
 
 
413
    if (detail::compare_distance(point.GetRawX(), (std::numeric_limits<boost::int32_t>::max)()) ||
 
414
        detail::compare_distance(point.GetRawX(), (std::numeric_limits<boost::int32_t>::min)())) {
 
415
        throw std::domain_error("X scale and offset combination of this file is insufficient to represent the data given the expression ");
 
416
    }
 
417
 
 
418
    if (detail::compare_distance(point.GetRawY(), (std::numeric_limits<boost::int32_t>::max)()) ||
 
419
        detail::compare_distance(point.GetRawY(), (std::numeric_limits<boost::int32_t>::min)())) {
 
420
        throw std::domain_error("Y scale and offset combination of this file is insufficient to represent the data given the expression");
 
421
    }    
 
422
 
 
423
    if (detail::compare_distance(point.GetRawZ(), (std::numeric_limits<boost::int32_t>::max)()) ||
 
424
        detail::compare_distance(point.GetRawZ(), (std::numeric_limits<boost::int32_t>::min)())) {
 
425
        throw std::domain_error("Z scale and offset combination of this file is insufficient to represent the data given the expression");
 
426
    }   
 
427
 
 
428
    }
 
429
    return true;
 
430
}
 
431
 
 
432
 
 
433
TranslationTransform::~TranslationTransform()
 
434
{
 
435
}
 
436
 
 
437
#ifdef HAVE_GDAL
 
438
void CPL_STDCALL ColorFetchingTransformGDALErrorHandler(CPLErr eErrClass, int err_no, const char *msg)
 
439
{
 
440
    std::ostringstream oss;
 
441
    
 
442
    if (eErrClass == CE_Failure || eErrClass == CE_Fatal) {
 
443
        oss <<"GDAL Failure number=" << err_no << ": " << msg;
 
444
        throw std::runtime_error(oss.str());
 
445
    } else {
 
446
        return;
 
447
    }
 
448
}
 
449
#endif
 
450
 
 
451
ColorFetchingTransform::ColorFetchingTransform(
 
452
    std::string const& datasource, 
 
453
    std::vector<boost::uint32_t> bands
 
454
)
 
455
    : m_new_header(0)
 
456
    , m_ds(DataSourcePtr())
 
457
    , m_datasource(datasource)
 
458
    , m_bands(bands)
 
459
    , m_scale(0)
 
460
{
 
461
    Initialize();
 
462
}
 
463
 
 
464
ColorFetchingTransform::ColorFetchingTransform(
 
465
    std::string const& datasource, 
 
466
    std::vector<boost::uint32_t> bands,
 
467
    Header const* header
 
468
)
 
469
    : m_new_header(header)
 
470
    , m_ds(DataSourcePtr())
 
471
    , m_datasource(datasource)
 
472
    , m_bands(bands)
 
473
    , m_scale(0)
 
474
{
 
475
    Initialize();
 
476
}
 
477
 
 
478
void ColorFetchingTransform::Initialize()
 
479
{
 
480
#ifdef HAVE_GDAL
 
481
    GDALAllRegister();
 
482
 
 
483
 
 
484
    CPLPopErrorHandler();
 
485
    CPLPushErrorHandler(ColorFetchingTransformGDALErrorHandler);
 
486
 
 
487
    m_ds = DataSourcePtr(GDALOpen( m_datasource.c_str(), GA_ReadOnly ), GDALSourceDeleter());
 
488
 
 
489
    // Assume the first three bands are RGB, and not get any more if the 
 
490
    // user did not supply any bands 
 
491
    if( m_bands.size() == 0 )
 
492
    {
 
493
        for( boost::int32_t i = 0; i < GDALGetRasterCount( m_ds.get() ); i++ )
 
494
        {
 
495
            if (i > 3) break;  
 
496
            m_bands.push_back( i+1 );
 
497
        }
 
498
    }
 
499
 
 
500
    m_forward_transform.assign(0.0);
 
501
    m_inverse_transform.assign(0.0);
 
502
    
 
503
    if (GDALGetGeoTransform( m_ds.get(), &(m_forward_transform.front()) ) != CE_None )
 
504
    {
 
505
        throw std::runtime_error("unable to fetch forward geotransform for raster!");
 
506
    }
 
507
 
 
508
    GDALInvGeoTransform( &(m_forward_transform.front()), &(m_inverse_transform.front()) );
 
509
 
 
510
#endif  
 
511
}
 
512
 
 
513
bool ColorFetchingTransform::transform(Point& point)
 
514
{
 
515
#ifdef HAVE_GDAL
 
516
    
 
517
    boost::int32_t pixel = 0;
 
518
    boost::int32_t line = 0;
 
519
    
 
520
    double x = point.GetX();
 
521
    double y = point.GetY();
 
522
 
 
523
    if (m_new_header) 
 
524
    {
 
525
        point.SetHeader(m_new_header);
 
526
    }
 
527
    
 
528
    pixel = (boost::int32_t) std::floor(
 
529
        m_inverse_transform[0] 
 
530
        + m_inverse_transform[1] * x
 
531
        + m_inverse_transform[2] * y );
 
532
    line = (boost::int32_t) std::floor(
 
533
        m_inverse_transform[3] 
 
534
        + m_inverse_transform[4] * x
 
535
        + m_inverse_transform[5] * y );
 
536
 
 
537
    if( pixel < 0 || line < 0 
 
538
        || pixel >= GDALGetRasterXSize( m_ds.get() )
 
539
        || line  >= GDALGetRasterYSize( m_ds.get() )
 
540
        )
 
541
    {
 
542
        // The x, y is not coincident with this raster, we'll leave whatever
 
543
        // color value might be there alone.
 
544
        return true;
 
545
    }
 
546
    
 
547
    boost::array<double, 2> pix;
 
548
    boost::array<liblas::Color::value_type, 3> color;
 
549
    color.assign(0);
 
550
    
 
551
    for( std::vector<boost::int32_t>::size_type i = 0; 
 
552
         i < m_bands.size(); i++ )
 
553
         {
 
554
             GDALRasterBandH hBand = GDALGetRasterBand( m_ds.get(), m_bands[i] );
 
555
             if (hBand == NULL) 
 
556
             {
 
557
                 continue;
 
558
             }
 
559
             if( GDALRasterIO( hBand, GF_Read, pixel, line, 1, 1, 
 
560
                               &pix[0], 1, 1, GDT_CFloat64, 0, 0) == CE_None )
 
561
             {
 
562
                 color[i] = static_cast<liblas::Color::value_type>(pix[0]);
 
563
                 if (m_scale) {
 
564
                     color[i] = color[i] * static_cast<liblas::Color::value_type>(m_scale);
 
565
                 }
 
566
             }             
 
567
         }
 
568
 
 
569
     point.SetColor(Color(color));
 
570
 
 
571
    return true;
 
572
#else
 
573
    boost::ignore_unused_variable_warning(point);
 
574
    return true;
 
575
#endif
 
576
}
 
577
ColorFetchingTransform::~ColorFetchingTransform()
 
578
{
 
579
#ifdef HAVE_GDAL
 
580
    CPLPopErrorHandler();
 
581
#endif
 
582
}
 
583
 
 
584
} // namespace liblas