Coverage Report

Created: 2026-01-22 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/grids.cpp
Line
Count
Source
1
/******************************************************************************
2
 * Project:  PROJ
3
 * Purpose:  Grid management
4
 * Author:   Even Rouault, <even.rouault at spatialys.com>
5
 *
6
 ******************************************************************************
7
 * Copyright (c) 2000, Frank Warmerdam <warmerdam@pobox.com>
8
 * Copyright (c) 2019, Even Rouault, <even.rouault at spatialys.com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 *****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
#define LRU11_DO_NOT_DEFINE_OUT_OF_CLASS_METHODS
33
34
#include "grids.hpp"
35
#include "filemanager.hpp"
36
#include "proj/internal/internal.hpp"
37
#include "proj/internal/lru_cache.hpp"
38
#include "proj_internal.h"
39
40
#ifdef TIFF_ENABLED
41
#include "tiffio.h"
42
#endif
43
44
#include <algorithm>
45
#include <cmath>
46
#include <cstdint>
47
#include <cstring>
48
#include <limits>
49
50
NS_PROJ_START
51
52
using namespace internal;
53
54
/************************************************************************/
55
/*                             swap_words()                             */
56
/*                                                                      */
57
/*      Convert the byte order of the given word(s) in place.           */
58
/************************************************************************/
59
60
static const int byte_order_test = 1;
61
#define IS_LSB                                                                 \
62
0
    (1 == (reinterpret_cast<const unsigned char *>(&byte_order_test))[0])
63
64
static void swap_words(void *dataIn, size_t word_size, size_t word_count)
65
66
0
{
67
0
    unsigned char *data = static_cast<unsigned char *>(dataIn);
68
0
    for (size_t word = 0; word < word_count; word++) {
69
0
        for (size_t i = 0; i < word_size / 2; i++) {
70
0
            unsigned char t;
71
72
0
            t = data[i];
73
0
            data[i] = data[word_size - i - 1];
74
0
            data[word_size - i - 1] = t;
75
0
        }
76
77
0
        data += word_size;
78
0
    }
79
0
}
80
81
// ---------------------------------------------------------------------------
82
83
342
void ExtentAndRes::computeInvRes() {
84
342
    invResX = 1.0 / resX;
85
342
    invResY = 1.0 / resY;
86
342
}
87
88
// ---------------------------------------------------------------------------
89
90
0
bool ExtentAndRes::fullWorldLongitude() const {
91
0
    return isGeographic && east - west + resX >= 2 * M_PI - 1e-10;
92
0
}
93
94
// ---------------------------------------------------------------------------
95
96
0
bool ExtentAndRes::contains(const ExtentAndRes &other) const {
97
0
    return other.west >= west && other.east <= east && other.south >= south &&
98
0
           other.north <= north;
99
0
}
100
101
// ---------------------------------------------------------------------------
102
103
0
bool ExtentAndRes::intersects(const ExtentAndRes &other) const {
104
0
    return other.west < east && west <= other.west && other.south < north &&
105
0
           south <= other.north;
106
0
}
107
108
// ---------------------------------------------------------------------------
109
110
Grid::Grid(const std::string &nameIn, int widthIn, int heightIn,
111
           const ExtentAndRes &extentIn)
112
342
    : m_name(nameIn), m_width(widthIn), m_height(heightIn), m_extent(extentIn) {
113
342
}
114
115
// ---------------------------------------------------------------------------
116
117
342
Grid::~Grid() = default;
118
119
// ---------------------------------------------------------------------------
120
121
VerticalShiftGrid::VerticalShiftGrid(const std::string &nameIn, int widthIn,
122
                                     int heightIn, const ExtentAndRes &extentIn)
123
25
    : Grid(nameIn, widthIn, heightIn, extentIn) {}
124
125
// ---------------------------------------------------------------------------
126
127
25
VerticalShiftGrid::~VerticalShiftGrid() = default;
128
129
// ---------------------------------------------------------------------------
130
131
342
static ExtentAndRes globalExtent() {
132
342
    ExtentAndRes extent;
133
342
    extent.isGeographic = true;
134
342
    extent.west = -M_PI;
135
342
    extent.south = -M_PI / 2;
136
342
    extent.east = M_PI;
137
342
    extent.north = M_PI / 2;
138
342
    extent.resX = M_PI;
139
342
    extent.resY = M_PI / 2;
140
342
    extent.computeInvRes();
141
342
    return extent;
142
342
}
143
144
// ---------------------------------------------------------------------------
145
146
static const std::string emptyString;
147
148
class NullVerticalShiftGrid : public VerticalShiftGrid {
149
150
  public:
151
25
    NullVerticalShiftGrid() : VerticalShiftGrid("null", 3, 3, globalExtent()) {}
152
153
0
    bool isNullGrid() const override { return true; }
154
    bool valueAt(int, int, float &out) const override;
155
0
    bool isNodata(float, double) const override { return false; }
156
0
    void reassign_context(PJ_CONTEXT *) override {}
157
0
    bool hasChanged() const override { return false; }
158
0
    const std::string &metadataItem(const std::string &, int) const override {
159
0
        return emptyString;
160
0
    }
161
};
162
163
// ---------------------------------------------------------------------------
164
165
0
bool NullVerticalShiftGrid::valueAt(int, int, float &out) const {
166
0
    out = 0.0f;
167
0
    return true;
168
0
}
169
170
// ---------------------------------------------------------------------------
171
172
class FloatLineCache {
173
174
  private:
175
    typedef uint64_t Key;
176
    lru11::Cache<Key, std::vector<float>, lru11::NullLock> cache_;
177
178
  public:
179
0
    explicit FloatLineCache(size_t maxSize) : cache_(maxSize) {}
180
    void insert(uint32_t subgridIdx, uint32_t lineNumber,
181
                const std::vector<float> &data);
182
    const std::vector<float> *get(uint32_t subgridIdx, uint32_t lineNumber);
183
};
184
185
// ---------------------------------------------------------------------------
186
187
void FloatLineCache::insert(uint32_t subgridIdx, uint32_t lineNumber,
188
0
                            const std::vector<float> &data) {
189
0
    cache_.insert((static_cast<uint64_t>(subgridIdx) << 32) | lineNumber, data);
190
0
}
191
192
// ---------------------------------------------------------------------------
193
194
const std::vector<float> *FloatLineCache::get(uint32_t subgridIdx,
195
0
                                              uint32_t lineNumber) {
196
0
    return cache_.getPtr((static_cast<uint64_t>(subgridIdx) << 32) |
197
0
                         lineNumber);
198
0
}
199
200
// ---------------------------------------------------------------------------
201
202
class GTXVerticalShiftGrid : public VerticalShiftGrid {
203
    PJ_CONTEXT *m_ctx;
204
    std::unique_ptr<File> m_fp;
205
    std::unique_ptr<FloatLineCache> m_cache;
206
    mutable std::vector<float> m_buffer{};
207
208
    GTXVerticalShiftGrid(const GTXVerticalShiftGrid &) = delete;
209
    GTXVerticalShiftGrid &operator=(const GTXVerticalShiftGrid &) = delete;
210
211
  public:
212
    explicit GTXVerticalShiftGrid(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp,
213
                                  const std::string &nameIn, int widthIn,
214
                                  int heightIn, const ExtentAndRes &extentIn,
215
                                  std::unique_ptr<FloatLineCache> &&cache)
216
0
        : VerticalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
217
0
          m_fp(std::move(fp)), m_cache(std::move(cache)) {}
218
219
    ~GTXVerticalShiftGrid() override;
220
221
    bool valueAt(int x, int y, float &out) const override;
222
    bool isNodata(float val, double multiplier) const override;
223
224
0
    const std::string &metadataItem(const std::string &, int) const override {
225
0
        return emptyString;
226
0
    }
227
228
    static GTXVerticalShiftGrid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
229
                                      const std::string &name);
230
231
0
    void reassign_context(PJ_CONTEXT *ctx) override {
232
0
        m_ctx = ctx;
233
0
        m_fp->reassign_context(ctx);
234
0
    }
235
236
0
    bool hasChanged() const override { return m_fp->hasChanged(); }
237
};
238
239
// ---------------------------------------------------------------------------
240
241
0
GTXVerticalShiftGrid::~GTXVerticalShiftGrid() = default;
242
243
// ---------------------------------------------------------------------------
244
245
GTXVerticalShiftGrid *GTXVerticalShiftGrid::open(PJ_CONTEXT *ctx,
246
                                                 std::unique_ptr<File> fp,
247
0
                                                 const std::string &name) {
248
0
    unsigned char header[40];
249
250
    /* -------------------------------------------------------------------- */
251
    /*      Read the header.                                                */
252
    /* -------------------------------------------------------------------- */
253
0
    if (fp->read(header, sizeof(header)) != sizeof(header)) {
254
0
        pj_log(ctx, PJ_LOG_ERROR, _("Cannot read grid header"));
255
0
        proj_context_errno_set(ctx,
256
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
257
0
        return nullptr;
258
0
    }
259
260
    /* -------------------------------------------------------------------- */
261
    /*      Regularize fields of interest and extract.                      */
262
    /* -------------------------------------------------------------------- */
263
0
    if (IS_LSB) {
264
0
        swap_words(header + 0, 8, 4);
265
0
        swap_words(header + 32, 4, 2);
266
0
    }
267
268
0
    double xorigin, yorigin, xstep, ystep;
269
0
    int rows, columns;
270
271
0
    memcpy(&yorigin, header + 0, 8);
272
0
    memcpy(&xorigin, header + 8, 8);
273
0
    memcpy(&ystep, header + 16, 8);
274
0
    memcpy(&xstep, header + 24, 8);
275
276
0
    memcpy(&rows, header + 32, 4);
277
0
    memcpy(&columns, header + 36, 4);
278
279
0
    if (columns <= 0 || rows <= 0 || xorigin < -360 || xorigin > 360 ||
280
0
        yorigin < -90 || yorigin > 90) {
281
0
        pj_log(ctx, PJ_LOG_ERROR,
282
0
               _("gtx file header has invalid extents, corrupt?"));
283
0
        proj_context_errno_set(ctx,
284
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
285
0
        return nullptr;
286
0
    }
287
288
    /* some GTX files come in 0-360 and we shift them back into the
289
       expected -180 to 180 range if possible.  This does not solve
290
       problems with grids spanning the dateline. */
291
0
    if (xorigin >= 180.0)
292
0
        xorigin -= 360.0;
293
294
0
    if (xorigin >= 0.0 && xorigin + xstep * columns > 180.0) {
295
0
        pj_log(ctx, PJ_LOG_DEBUG,
296
0
               "This GTX spans the dateline!  This will cause problems.");
297
0
    }
298
299
0
    ExtentAndRes extent;
300
0
    extent.isGeographic = true;
301
0
    extent.west = xorigin * DEG_TO_RAD;
302
0
    extent.south = yorigin * DEG_TO_RAD;
303
0
    extent.resX = xstep * DEG_TO_RAD;
304
0
    extent.resY = ystep * DEG_TO_RAD;
305
0
    extent.east = (xorigin + xstep * (columns - 1)) * DEG_TO_RAD;
306
0
    extent.north = (yorigin + ystep * (rows - 1)) * DEG_TO_RAD;
307
0
    extent.computeInvRes();
308
309
    // Cache up to 1 megapixel per GTX file
310
0
    const int maxLinesInCache = 1024 * 1024 / columns;
311
0
    auto cache = std::make_unique<FloatLineCache>(maxLinesInCache);
312
0
    return new GTXVerticalShiftGrid(ctx, std::move(fp), name, columns, rows,
313
0
                                    extent, std::move(cache));
314
0
}
315
316
// ---------------------------------------------------------------------------
317
318
0
bool GTXVerticalShiftGrid::valueAt(int x, int y, float &out) const {
319
0
    assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
320
321
0
    const std::vector<float> *pBuffer = m_cache->get(0, y);
322
0
    if (pBuffer == nullptr) {
323
0
        try {
324
0
            m_buffer.resize(m_width);
325
0
        } catch (const std::exception &e) {
326
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
327
0
            return false;
328
0
        }
329
330
0
        const size_t nLineSizeInBytes = sizeof(float) * m_width;
331
0
        m_fp->seek(40 + nLineSizeInBytes * static_cast<unsigned long long>(y));
332
0
        if (m_fp->read(&m_buffer[0], nLineSizeInBytes) != nLineSizeInBytes) {
333
0
            proj_context_errno_set(
334
0
                m_ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
335
0
            return false;
336
0
        }
337
338
0
        if (IS_LSB) {
339
0
            swap_words(&m_buffer[0], sizeof(float), m_width);
340
0
        }
341
342
0
        out = m_buffer[x];
343
0
        try {
344
0
            m_cache->insert(0, y, m_buffer);
345
0
        } catch (const std::exception &e) {
346
            // Should normally not happen
347
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
348
0
        }
349
0
    } else {
350
0
        out = (*pBuffer)[x];
351
0
    }
352
353
0
    return true;
354
0
}
355
356
// ---------------------------------------------------------------------------
357
358
0
bool GTXVerticalShiftGrid::isNodata(float val, double multiplier) const {
359
    /* nodata?  */
360
    /* GTX official nodata value if  -88.88880f, but some grids also */
361
    /* use other  big values for nodata (e.g naptrans2008.gtx has */
362
    /* nodata values like -2147479936), so test them too */
363
0
    return val * multiplier > 1000 || val * multiplier < -1000 ||
364
0
           val == -88.88880f;
365
0
}
366
367
// ---------------------------------------------------------------------------
368
369
25
VerticalShiftGridSet::VerticalShiftGridSet() = default;
370
371
// ---------------------------------------------------------------------------
372
373
25
VerticalShiftGridSet::~VerticalShiftGridSet() = default;
374
375
// ---------------------------------------------------------------------------
376
377
395
static bool IsTIFF(size_t header_size, const unsigned char *header) {
378
    // Test combinations of signature for ClassicTIFF/BigTIFF little/big endian
379
395
    return header_size >= 4 && (((header[0] == 'I' && header[1] == 'I') ||
380
41
                                 (header[0] == 'M' && header[1] == 'M')) &&
381
0
                                ((header[2] == 0x2A && header[3] == 0) ||
382
0
                                 (header[3] == 0x2A && header[2] == 0) ||
383
0
                                 (header[2] == 0x2B && header[3] == 0) ||
384
0
                                 (header[3] == 0x2B && header[2] == 0)));
385
395
}
386
387
#ifdef TIFF_ENABLED
388
389
// ---------------------------------------------------------------------------
390
391
enum class TIFFDataType { Int16, UInt16, Int32, UInt32, Float32, Float64 };
392
393
// ---------------------------------------------------------------------------
394
395
constexpr uint16_t TIFFTAG_GEOPIXELSCALE = 33550;
396
constexpr uint16_t TIFFTAG_GEOTIEPOINTS = 33922;
397
constexpr uint16_t TIFFTAG_GEOTRANSMATRIX = 34264;
398
constexpr uint16_t TIFFTAG_GEOKEYDIRECTORY = 34735;
399
constexpr uint16_t TIFFTAG_GEODOUBLEPARAMS = 34736;
400
constexpr uint16_t TIFFTAG_GEOASCIIPARAMS = 34737;
401
#ifndef TIFFTAG_GDAL_METADATA
402
// Starting with libtiff > 4.1.0, those symbolic names are #define in tiff.h
403
constexpr uint16_t TIFFTAG_GDAL_METADATA = 42112;
404
constexpr uint16_t TIFFTAG_GDAL_NODATA = 42113;
405
#endif
406
407
// ---------------------------------------------------------------------------
408
409
class BlockCache {
410
  public:
411
    void insert(uint32_t ifdIdx, uint32_t blockNumber,
412
                const std::vector<unsigned char> &data);
413
    const std::vector<unsigned char> *get(uint32_t ifdIdx,
414
                                          uint32_t blockNumber);
415
416
  private:
417
    typedef uint64_t Key;
418
419
    static constexpr int NUM_BLOCKS_AT_CROSSING_TILES = 4;
420
    static constexpr int MAX_SAMPLE_COUNT = 3;
421
    lru11::Cache<Key, std::vector<unsigned char>, lru11::NullLock> cache_{
422
        NUM_BLOCKS_AT_CROSSING_TILES * MAX_SAMPLE_COUNT};
423
};
424
425
// ---------------------------------------------------------------------------
426
427
void BlockCache::insert(uint32_t ifdIdx, uint32_t blockNumber,
428
0
                        const std::vector<unsigned char> &data) {
429
0
    cache_.insert((static_cast<uint64_t>(ifdIdx) << 32) | blockNumber, data);
430
0
}
431
432
// ---------------------------------------------------------------------------
433
434
const std::vector<unsigned char> *BlockCache::get(uint32_t ifdIdx,
435
0
                                                  uint32_t blockNumber) {
436
0
    return cache_.getPtr((static_cast<uint64_t>(ifdIdx) << 32) | blockNumber);
437
0
}
438
439
// ---------------------------------------------------------------------------
440
441
class GTiffGrid : public Grid {
442
    PJ_CONTEXT *m_ctx;   // owned by the belonging GTiffDataset
443
    TIFF *m_hTIFF;       // owned by the belonging GTiffDataset
444
    BlockCache &m_cache; // owned by the belonging GTiffDataset
445
    File *m_fp;          // owned by the belonging GTiffDataset
446
    uint32_t m_ifdIdx;
447
    TIFFDataType m_dt;
448
    uint16_t m_samplesPerPixel;
449
    uint16_t m_planarConfig; // set to -1 if m_samplesPerPixel == 1
450
    bool m_bottomUp;
451
    toff_t m_dirOffset;
452
    bool m_tiled;
453
    uint32_t m_blockWidth = 0;
454
    uint32_t m_blockHeight = 0;
455
    mutable std::vector<unsigned char> m_buffer{};
456
    mutable uint32_t m_bufferBlockId = std::numeric_limits<uint32_t>::max();
457
    unsigned m_blocksPerRow = 0;
458
    unsigned m_blocksPerCol = 0;
459
    unsigned m_blocks = 0;
460
    std::vector<double> m_adfOffset{};
461
    std::vector<double> m_adfScale{};
462
    std::map<std::pair<int, std::string>, std::string> m_metadata{};
463
    bool m_hasNodata = false;
464
    bool m_blockIs256Pixel = false;
465
    bool m_isSingleBlock = false;
466
    float m_noData = 0.0f;
467
    uint32_t m_subfileType = 0;
468
469
    GTiffGrid(const GTiffGrid &) = delete;
470
    GTiffGrid &operator=(const GTiffGrid &) = delete;
471
472
    template <class T>
473
    float readValue(const std::vector<unsigned char> &buffer,
474
                    uint32_t offsetInBlock, uint16_t sample) const;
475
476
  public:
477
    GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache, File *fp,
478
              uint32_t ifdIdx, const std::string &nameIn, int widthIn,
479
              int heightIn, const ExtentAndRes &extentIn, TIFFDataType dtIn,
480
              uint16_t samplesPerPixelIn, uint16_t planarConfig,
481
              bool bottomUpIn);
482
483
    ~GTiffGrid() override;
484
485
0
    uint16_t samplesPerPixel() const { return m_samplesPerPixel; }
486
487
    bool valueAt(uint16_t sample, int x, int y, float &out) const;
488
489
    bool valuesAt(int x_start, int y_start, int x_count, int y_count,
490
                  int sample_count, const int *sample_idx, float *out,
491
                  bool &nodataFound) const;
492
493
    bool isNodata(float val) const;
494
495
    const std::string &metadataItem(const std::string &key,
496
                                    int sample = -1) const override;
497
498
0
    uint32_t subfileType() const { return m_subfileType; }
499
500
0
    void reassign_context(PJ_CONTEXT *ctx) { m_ctx = ctx; }
501
502
0
    bool hasChanged() const override { return m_fp->hasChanged(); }
503
};
504
505
// ---------------------------------------------------------------------------
506
507
GTiffGrid::GTiffGrid(PJ_CONTEXT *ctx, TIFF *hTIFF, BlockCache &cache, File *fp,
508
                     uint32_t ifdIdx, const std::string &nameIn, int widthIn,
509
                     int heightIn, const ExtentAndRes &extentIn,
510
                     TIFFDataType dtIn, uint16_t samplesPerPixelIn,
511
                     uint16_t planarConfig, bool bottomUpIn)
512
0
    : Grid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_hTIFF(hTIFF),
513
0
      m_cache(cache), m_fp(fp), m_ifdIdx(ifdIdx), m_dt(dtIn),
514
0
      m_samplesPerPixel(samplesPerPixelIn),
515
0
      m_planarConfig(samplesPerPixelIn == 1 ? static_cast<uint16_t>(-1)
516
0
                                            : planarConfig),
517
0
      m_bottomUp(bottomUpIn), m_dirOffset(TIFFCurrentDirOffset(hTIFF)),
518
0
      m_tiled(TIFFIsTiled(hTIFF) != 0) {
519
520
0
    if (m_tiled) {
521
0
        TIFFGetField(m_hTIFF, TIFFTAG_TILEWIDTH, &m_blockWidth);
522
0
        TIFFGetField(m_hTIFF, TIFFTAG_TILELENGTH, &m_blockHeight);
523
0
    } else {
524
0
        m_blockWidth = widthIn;
525
0
        TIFFGetField(m_hTIFF, TIFFTAG_ROWSPERSTRIP, &m_blockHeight);
526
0
        if (m_blockHeight > static_cast<unsigned>(m_height))
527
0
            m_blockHeight = m_height;
528
0
    }
529
530
0
    m_blockIs256Pixel = (m_blockWidth == 256) && (m_blockHeight == 256);
531
0
    m_isSingleBlock = (m_blockWidth == static_cast<uint32_t>(m_width)) &&
532
0
                      (m_blockHeight == static_cast<uint32_t>(m_height));
533
534
0
    TIFFGetField(m_hTIFF, TIFFTAG_SUBFILETYPE, &m_subfileType);
535
536
0
    m_blocksPerRow = (m_width + m_blockWidth - 1) / m_blockWidth;
537
0
    m_blocksPerCol = (m_height + m_blockHeight - 1) / m_blockHeight;
538
0
    m_blocks = m_blocksPerRow * m_blocksPerCol;
539
540
0
    const char *text = nullptr;
541
    // Poor-man XML parsing of TIFFTAG_GDAL_METADATA tag. Hopefully good
542
    // enough for our purposes.
543
0
    if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_METADATA, &text)) {
544
0
        const char *ptr = text;
545
0
        while (true) {
546
0
            ptr = strstr(ptr, "<Item ");
547
0
            if (ptr == nullptr)
548
0
                break;
549
0
            const char *endTag = strchr(ptr, '>');
550
0
            if (endTag == nullptr)
551
0
                break;
552
0
            const char *endValue = strchr(endTag, '<');
553
0
            if (endValue == nullptr)
554
0
                break;
555
556
0
            std::string tag;
557
0
            tag.append(ptr, endTag - ptr);
558
559
0
            std::string value;
560
0
            value.append(endTag + 1, endValue - (endTag + 1));
561
562
0
            std::string gridName;
563
0
            auto namePos = tag.find("name=\"");
564
0
            if (namePos == std::string::npos)
565
0
                break;
566
0
            {
567
0
                namePos += strlen("name=\"");
568
0
                const auto endQuote = tag.find('"', namePos);
569
0
                if (endQuote == std::string::npos)
570
0
                    break;
571
0
                gridName = tag.substr(namePos, endQuote - namePos);
572
0
            }
573
574
0
            const auto samplePos = tag.find("sample=\"");
575
0
            int sample = -1;
576
0
            if (samplePos != std::string::npos) {
577
0
                sample = atoi(tag.c_str() + samplePos + strlen("sample=\""));
578
0
            }
579
580
0
            m_metadata[std::pair<int, std::string>(sample, gridName)] = value;
581
582
0
            auto rolePos = tag.find("role=\"");
583
0
            if (rolePos != std::string::npos) {
584
0
                rolePos += strlen("role=\"");
585
0
                const auto endQuote = tag.find('"', rolePos);
586
0
                if (endQuote == std::string::npos)
587
0
                    break;
588
0
                const auto role = tag.substr(rolePos, endQuote - rolePos);
589
0
                if (role == "offset") {
590
0
                    if (sample >= 0 &&
591
0
                        static_cast<unsigned>(sample) <= m_samplesPerPixel) {
592
0
                        try {
593
0
                            if (m_adfOffset.empty()) {
594
0
                                m_adfOffset.resize(m_samplesPerPixel);
595
0
                                m_adfScale.resize(m_samplesPerPixel, 1);
596
0
                            }
597
0
                            m_adfOffset[sample] = c_locale_stod(value);
598
0
                        } catch (const std::exception &) {
599
0
                        }
600
0
                    }
601
0
                } else if (role == "scale") {
602
0
                    if (sample >= 0 &&
603
0
                        static_cast<unsigned>(sample) <= m_samplesPerPixel) {
604
0
                        try {
605
0
                            if (m_adfOffset.empty()) {
606
0
                                m_adfOffset.resize(m_samplesPerPixel);
607
0
                                m_adfScale.resize(m_samplesPerPixel, 1);
608
0
                            }
609
0
                            m_adfScale[sample] = c_locale_stod(value);
610
0
                        } catch (const std::exception &) {
611
0
                        }
612
0
                    }
613
0
                }
614
0
            }
615
616
0
            ptr = endValue + 1;
617
0
        }
618
0
    }
619
620
0
    if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_NODATA, &text)) {
621
0
        try {
622
0
            m_noData = static_cast<float>(c_locale_stod(text));
623
0
            m_hasNodata = true;
624
0
        } catch (const std::exception &) {
625
0
        }
626
0
    }
627
628
0
    auto oIter = m_metadata.find(std::pair<int, std::string>(-1, "grid_name"));
629
0
    if (oIter != m_metadata.end()) {
630
0
        m_name += ", " + oIter->second;
631
0
    }
632
0
}
633
634
// ---------------------------------------------------------------------------
635
636
0
GTiffGrid::~GTiffGrid() = default;
637
638
// ---------------------------------------------------------------------------
639
640
template <class T>
641
float GTiffGrid::readValue(const std::vector<unsigned char> &buffer,
642
0
                           uint32_t offsetInBlock, uint16_t sample) const {
643
0
    const auto ptr = reinterpret_cast<const T *>(buffer.data());
644
0
    assert(offsetInBlock < buffer.size() / sizeof(T));
645
0
    const auto val = ptr[offsetInBlock];
646
0
    if ((!m_hasNodata || static_cast<float>(val) != m_noData) &&
647
0
        sample < m_adfScale.size()) {
648
0
        double scale = m_adfScale[sample];
649
0
        double offset = m_adfOffset[sample];
650
0
        return static_cast<float>(val * scale + offset);
651
0
    } else {
652
0
        return static_cast<float>(val);
653
0
    }
654
0
}
Unexecuted instantiation: float osgeo::proj::GTiffGrid::readValue<short>(std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, unsigned int, unsigned short) const
Unexecuted instantiation: float osgeo::proj::GTiffGrid::readValue<unsigned short>(std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, unsigned int, unsigned short) const
Unexecuted instantiation: float osgeo::proj::GTiffGrid::readValue<int>(std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, unsigned int, unsigned short) const
Unexecuted instantiation: float osgeo::proj::GTiffGrid::readValue<unsigned int>(std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, unsigned int, unsigned short) const
Unexecuted instantiation: float osgeo::proj::GTiffGrid::readValue<float>(std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, unsigned int, unsigned short) const
Unexecuted instantiation: float osgeo::proj::GTiffGrid::readValue<double>(std::__1::vector<unsigned char, std::__1::allocator<unsigned char> > const&, unsigned int, unsigned short) const
655
656
// ---------------------------------------------------------------------------
657
658
bool GTiffGrid::valueAt(uint16_t sample, int x, int yFromBottom,
659
0
                        float &out) const {
660
0
    assert(x >= 0 && yFromBottom >= 0 && x < m_width && yFromBottom < m_height);
661
0
    assert(sample < m_samplesPerPixel);
662
663
    // All non-TIFF grids have the first rows in the file being the one
664
    // corresponding to the southern-most row. In GeoTIFF, the convention is
665
    // *generally* different (when m_bottomUp == false), TIFF being an
666
    // image-oriented image. If m_bottomUp == true, then we had GeoTIFF hints
667
    // that the first row of the image is the southern-most.
668
0
    const int yTIFF = m_bottomUp ? yFromBottom : m_height - 1 - yFromBottom;
669
670
0
    int blockXOff;
671
0
    int blockYOff;
672
0
    uint32_t blockId;
673
674
0
    if (m_blockIs256Pixel) {
675
0
        const int blockX = x / 256;
676
0
        blockXOff = x % 256;
677
0
        const int blockY = yTIFF / 256;
678
0
        blockYOff = yTIFF % 256;
679
0
        blockId = blockY * m_blocksPerRow + blockX;
680
0
    } else if (m_isSingleBlock) {
681
0
        blockXOff = x;
682
0
        blockYOff = yTIFF;
683
0
        blockId = 0;
684
0
    } else {
685
0
        const int blockX = x / m_blockWidth;
686
0
        blockXOff = x % m_blockWidth;
687
0
        const int blockY = yTIFF / m_blockHeight;
688
0
        blockYOff = yTIFF % m_blockHeight;
689
0
        blockId = blockY * m_blocksPerRow + blockX;
690
0
    }
691
692
0
    if (m_planarConfig == PLANARCONFIG_SEPARATE) {
693
0
        blockId += sample * m_blocks;
694
0
    }
695
696
0
    const std::vector<unsigned char> *pBuffer =
697
0
        blockId == m_bufferBlockId ? &m_buffer : m_cache.get(m_ifdIdx, blockId);
698
0
    if (pBuffer == nullptr) {
699
0
        if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset &&
700
0
            !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) {
701
0
            return false;
702
0
        }
703
0
        if (m_buffer.empty()) {
704
0
            const auto blockSize = static_cast<size_t>(
705
0
                m_tiled ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF));
706
0
            try {
707
0
                m_buffer.resize(blockSize);
708
0
            } catch (const std::exception &e) {
709
0
                pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
710
0
                return false;
711
0
            }
712
0
        }
713
714
0
        if (m_tiled) {
715
0
            if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(),
716
0
                                    m_buffer.size()) == -1) {
717
0
                return false;
718
0
            }
719
0
        } else {
720
0
            if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(),
721
0
                                     m_buffer.size()) == -1) {
722
0
                return false;
723
0
            }
724
0
        }
725
726
0
        pBuffer = &m_buffer;
727
0
        try {
728
0
            m_cache.insert(m_ifdIdx, blockId, m_buffer);
729
0
            m_bufferBlockId = blockId;
730
0
        } catch (const std::exception &e) {
731
            // Should normally not happen
732
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
733
0
        }
734
0
    }
735
736
0
    uint32_t offsetInBlock;
737
0
    if (m_blockIs256Pixel)
738
0
        offsetInBlock = blockXOff + blockYOff * 256U;
739
0
    else
740
0
        offsetInBlock = blockXOff + blockYOff * m_blockWidth;
741
0
    if (m_planarConfig == PLANARCONFIG_CONTIG)
742
0
        offsetInBlock = offsetInBlock * m_samplesPerPixel + sample;
743
744
0
    switch (m_dt) {
745
0
    case TIFFDataType::Int16:
746
0
        out = readValue<short>(*pBuffer, offsetInBlock, sample);
747
0
        break;
748
749
0
    case TIFFDataType::UInt16:
750
0
        out = readValue<unsigned short>(*pBuffer, offsetInBlock, sample);
751
0
        break;
752
753
0
    case TIFFDataType::Int32:
754
0
        out = readValue<int>(*pBuffer, offsetInBlock, sample);
755
0
        break;
756
757
0
    case TIFFDataType::UInt32:
758
0
        out = readValue<unsigned int>(*pBuffer, offsetInBlock, sample);
759
0
        break;
760
761
0
    case TIFFDataType::Float32:
762
0
        out = readValue<float>(*pBuffer, offsetInBlock, sample);
763
0
        break;
764
765
0
    case TIFFDataType::Float64:
766
0
        out = readValue<double>(*pBuffer, offsetInBlock, sample);
767
0
        break;
768
0
    }
769
770
0
    return true;
771
0
}
772
773
// ---------------------------------------------------------------------------
774
775
bool GTiffGrid::valuesAt(int x_start, int y_start, int x_count, int y_count,
776
                         int sample_count, const int *sample_idx, float *out,
777
0
                         bool &nodataFound) const {
778
0
    const auto getTIFFRow = [this](int y) {
779
0
        return m_bottomUp ? y : m_height - 1 - y;
780
0
    };
781
0
    nodataFound = false;
782
0
    if (m_blockIs256Pixel && m_planarConfig == PLANARCONFIG_CONTIG &&
783
0
        m_dt == TIFFDataType::Float32 &&
784
0
        (x_start / 256) == (x_start + x_count - 1) / 256 &&
785
0
        getTIFFRow(y_start) / 256 == getTIFFRow(y_start + y_count - 1) / 256 &&
786
0
        !m_hasNodata && m_adfScale.empty() &&
787
0
        (sample_count == 1 ||
788
0
         (sample_count == 2 && sample_idx[1] == sample_idx[0] + 1) ||
789
0
         (sample_count == 3 && sample_idx[1] == sample_idx[0] + 1 &&
790
0
          sample_idx[2] == sample_idx[0] + 2))) {
791
0
        const int yTIFF = m_bottomUp ? y_start : m_height - (y_start + y_count);
792
0
        int blockXOff;
793
0
        int blockYOff;
794
0
        uint32_t blockId;
795
0
        const int blockX = x_start / 256;
796
0
        blockXOff = x_start % 256;
797
0
        const int blockY = yTIFF / 256;
798
0
        blockYOff = yTIFF % 256;
799
0
        blockId = blockY * m_blocksPerRow + blockX;
800
801
0
        const std::vector<unsigned char> *pBuffer =
802
0
            blockId == m_bufferBlockId ? &m_buffer
803
0
                                       : m_cache.get(m_ifdIdx, blockId);
804
0
        if (pBuffer == nullptr) {
805
0
            if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset &&
806
0
                !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) {
807
0
                return false;
808
0
            }
809
0
            if (m_buffer.empty()) {
810
0
                const auto blockSize =
811
0
                    static_cast<size_t>(m_tiled ? TIFFTileSize64(m_hTIFF)
812
0
                                                : TIFFStripSize64(m_hTIFF));
813
0
                try {
814
0
                    m_buffer.resize(blockSize);
815
0
                } catch (const std::exception &e) {
816
0
                    pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
817
0
                    return false;
818
0
                }
819
0
            }
820
821
0
            if (m_tiled) {
822
0
                if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(),
823
0
                                        m_buffer.size()) == -1) {
824
0
                    return false;
825
0
                }
826
0
            } else {
827
0
                if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(),
828
0
                                         m_buffer.size()) == -1) {
829
0
                    return false;
830
0
                }
831
0
            }
832
833
0
            pBuffer = &m_buffer;
834
0
            try {
835
0
                m_cache.insert(m_ifdIdx, blockId, m_buffer);
836
0
                m_bufferBlockId = blockId;
837
0
            } catch (const std::exception &e) {
838
                // Should normally not happen
839
0
                pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
840
0
            }
841
0
        }
842
843
0
        uint32_t offsetInBlockStart = blockXOff + blockYOff * 256U;
844
845
0
        if (sample_count == m_samplesPerPixel) {
846
0
            const int sample_count_mul_x_count = sample_count * x_count;
847
0
            for (int y = 0; y < y_count; ++y) {
848
0
                uint32_t offsetInBlock =
849
0
                    (offsetInBlockStart +
850
0
                     256 * (m_bottomUp ? y : y_count - 1 - y)) *
851
0
                        m_samplesPerPixel +
852
0
                    sample_idx[0];
853
0
                memcpy(out,
854
0
                       reinterpret_cast<const float *>(pBuffer->data()) +
855
0
                           offsetInBlock,
856
0
                       sample_count_mul_x_count * sizeof(float));
857
0
                out += sample_count_mul_x_count;
858
0
            }
859
0
        } else {
860
0
            switch (sample_count) {
861
0
            case 1:
862
0
                for (int y = 0; y < y_count; ++y) {
863
0
                    uint32_t offsetInBlock =
864
0
                        (offsetInBlockStart +
865
0
                         256 * (m_bottomUp ? y : y_count - 1 - y)) *
866
0
                            m_samplesPerPixel +
867
0
                        sample_idx[0];
868
0
                    const float *in_ptr =
869
0
                        reinterpret_cast<const float *>(pBuffer->data()) +
870
0
                        offsetInBlock;
871
0
                    for (int x = 0; x < x_count; ++x) {
872
0
                        memcpy(out, in_ptr, sample_count * sizeof(float));
873
0
                        in_ptr += m_samplesPerPixel;
874
0
                        out += sample_count;
875
0
                    }
876
0
                }
877
0
                break;
878
0
            case 2:
879
0
                for (int y = 0; y < y_count; ++y) {
880
0
                    uint32_t offsetInBlock =
881
0
                        (offsetInBlockStart +
882
0
                         256 * (m_bottomUp ? y : y_count - 1 - y)) *
883
0
                            m_samplesPerPixel +
884
0
                        sample_idx[0];
885
0
                    const float *in_ptr =
886
0
                        reinterpret_cast<const float *>(pBuffer->data()) +
887
0
                        offsetInBlock;
888
0
                    for (int x = 0; x < x_count; ++x) {
889
0
                        memcpy(out, in_ptr, sample_count * sizeof(float));
890
0
                        in_ptr += m_samplesPerPixel;
891
0
                        out += sample_count;
892
0
                    }
893
0
                }
894
0
                break;
895
0
            case 3:
896
0
                for (int y = 0; y < y_count; ++y) {
897
0
                    uint32_t offsetInBlock =
898
0
                        (offsetInBlockStart +
899
0
                         256 * (m_bottomUp ? y : y_count - 1 - y)) *
900
0
                            m_samplesPerPixel +
901
0
                        sample_idx[0];
902
0
                    const float *in_ptr =
903
0
                        reinterpret_cast<const float *>(pBuffer->data()) +
904
0
                        offsetInBlock;
905
0
                    for (int x = 0; x < x_count; ++x) {
906
0
                        memcpy(out, in_ptr, sample_count * sizeof(float));
907
0
                        in_ptr += m_samplesPerPixel;
908
0
                        out += sample_count;
909
0
                    }
910
0
                }
911
0
                break;
912
0
            }
913
0
        }
914
0
        return true;
915
0
    }
916
917
0
    for (int y = y_start; y < y_start + y_count; ++y) {
918
0
        for (int x = x_start; x < x_start + x_count; ++x) {
919
0
            for (int isample = 0; isample < sample_count; ++isample) {
920
0
                if (!valueAt(static_cast<uint16_t>(sample_idx[isample]), x, y,
921
0
                             *out))
922
0
                    return false;
923
0
                if (isNodata(*out)) {
924
0
                    nodataFound = true;
925
0
                }
926
0
                ++out;
927
0
            }
928
0
        }
929
0
    }
930
0
    return true;
931
0
}
932
933
// ---------------------------------------------------------------------------
934
935
0
bool GTiffGrid::isNodata(float val) const {
936
0
    return (m_hasNodata && val == m_noData) || std::isnan(val);
937
0
}
938
939
// ---------------------------------------------------------------------------
940
941
const std::string &GTiffGrid::metadataItem(const std::string &key,
942
0
                                           int sample) const {
943
0
    auto iter = m_metadata.find(std::pair<int, std::string>(sample, key));
944
0
    if (iter == m_metadata.end()) {
945
0
        return emptyString;
946
0
    }
947
0
    return iter->second;
948
0
}
949
950
// ---------------------------------------------------------------------------
951
952
class GTiffDataset {
953
    PJ_CONTEXT *m_ctx;
954
    std::unique_ptr<File> m_fp;
955
    TIFF *m_hTIFF = nullptr;
956
    bool m_hasNextGrid = false;
957
    uint32_t m_ifdIdx = 0;
958
    toff_t m_nextDirOffset = 0;
959
    std::string m_filename{};
960
    BlockCache m_cache{};
961
962
    GTiffDataset(const GTiffDataset &) = delete;
963
    GTiffDataset &operator=(const GTiffDataset &) = delete;
964
965
    // libtiff I/O routines
966
0
    static tsize_t tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) {
967
0
        GTiffDataset *self = static_cast<GTiffDataset *>(fd);
968
0
        return self->m_fp->read(buf, size);
969
0
    }
970
971
0
    static tsize_t tiffWriteProc(thandle_t, tdata_t, tsize_t) {
972
0
        assert(false);
973
0
        return 0;
974
0
    }
975
976
0
    static toff_t tiffSeekProc(thandle_t fd, toff_t off, int whence) {
977
0
        GTiffDataset *self = static_cast<GTiffDataset *>(fd);
978
0
        if (self->m_fp->seek(off, whence))
979
0
            return static_cast<toff_t>(self->m_fp->tell());
980
0
        else
981
0
            return static_cast<toff_t>(-1);
982
0
    }
983
984
0
    static int tiffCloseProc(thandle_t) {
985
        // done in destructor
986
0
        return 0;
987
0
    }
988
989
0
    static toff_t tiffSizeProc(thandle_t fd) {
990
0
        GTiffDataset *self = static_cast<GTiffDataset *>(fd);
991
0
        const auto old_off = self->m_fp->tell();
992
0
        self->m_fp->seek(0, SEEK_END);
993
0
        const auto file_size = static_cast<toff_t>(self->m_fp->tell());
994
0
        self->m_fp->seek(old_off);
995
0
        return file_size;
996
0
    }
997
998
0
    static int tiffMapProc(thandle_t, tdata_t *, toff_t *) { return (0); }
999
1000
0
    static void tiffUnmapProc(thandle_t, tdata_t, toff_t) {}
1001
1002
  public:
1003
    GTiffDataset(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
1004
0
        : m_ctx(ctx), m_fp(std::move(fp)) {}
1005
    virtual ~GTiffDataset();
1006
1007
    bool openTIFF(const std::string &filename);
1008
1009
    std::unique_ptr<GTiffGrid> nextGrid();
1010
1011
0
    void reassign_context(PJ_CONTEXT *ctx) {
1012
0
        m_ctx = ctx;
1013
0
        m_fp->reassign_context(ctx);
1014
0
    }
1015
};
1016
1017
// ---------------------------------------------------------------------------
1018
1019
0
GTiffDataset::~GTiffDataset() {
1020
0
    if (m_hTIFF)
1021
0
        TIFFClose(m_hTIFF);
1022
0
}
1023
1024
// ---------------------------------------------------------------------------
1025
class OneTimeTIFFTagInit {
1026
1027
    static TIFFExtendProc ParentExtender;
1028
1029
    // Function called by libtiff when initializing a TIFF directory
1030
0
    static void GTiffTagExtender(TIFF *tif) {
1031
0
        static const TIFFFieldInfo xtiffFieldInfo[] = {
1032
            // GeoTIFF tags
1033
0
            {TIFFTAG_GEOPIXELSCALE, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1034
0
             TRUE, const_cast<char *>("GeoPixelScale")},
1035
0
            {TIFFTAG_GEOTIEPOINTS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1036
0
             TRUE, const_cast<char *>("GeoTiePoints")},
1037
0
            {TIFFTAG_GEOTRANSMATRIX, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1038
0
             TRUE, const_cast<char *>("GeoTransformationMatrix")},
1039
1040
0
            {TIFFTAG_GEOKEYDIRECTORY, -1, -1, TIFF_SHORT, FIELD_CUSTOM, TRUE,
1041
0
             TRUE, const_cast<char *>("GeoKeyDirectory")},
1042
0
            {TIFFTAG_GEODOUBLEPARAMS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1043
0
             TRUE, const_cast<char *>("GeoDoubleParams")},
1044
0
            {TIFFTAG_GEOASCIIPARAMS, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
1045
0
             FALSE, const_cast<char *>("GeoASCIIParams")},
1046
1047
            // GDAL tags
1048
0
            {TIFFTAG_GDAL_METADATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
1049
0
             FALSE, const_cast<char *>("GDALMetadata")},
1050
0
            {TIFFTAG_GDAL_NODATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE,
1051
0
             const_cast<char *>("GDALNoDataValue")},
1052
1053
0
        };
1054
1055
0
        if (ParentExtender)
1056
0
            (*ParentExtender)(tif);
1057
1058
0
        TIFFMergeFieldInfo(tif, xtiffFieldInfo,
1059
0
                           sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0]));
1060
0
    }
1061
1062
  public:
1063
0
    OneTimeTIFFTagInit() {
1064
0
        assert(ParentExtender == nullptr);
1065
        // Install our TIFF tag extender
1066
0
        ParentExtender = TIFFSetTagExtender(GTiffTagExtender);
1067
0
    }
1068
};
1069
1070
TIFFExtendProc OneTimeTIFFTagInit::ParentExtender = nullptr;
1071
1072
// ---------------------------------------------------------------------------
1073
1074
0
bool GTiffDataset::openTIFF(const std::string &filename) {
1075
0
    static OneTimeTIFFTagInit oneTimeTIFFTagInit;
1076
0
    m_hTIFF =
1077
0
        TIFFClientOpen(filename.c_str(), "r", static_cast<thandle_t>(this),
1078
0
                       GTiffDataset::tiffReadProc, GTiffDataset::tiffWriteProc,
1079
0
                       GTiffDataset::tiffSeekProc, GTiffDataset::tiffCloseProc,
1080
0
                       GTiffDataset::tiffSizeProc, GTiffDataset::tiffMapProc,
1081
0
                       GTiffDataset::tiffUnmapProc);
1082
1083
0
    m_filename = filename;
1084
0
    m_hasNextGrid = true;
1085
0
    return m_hTIFF != nullptr;
1086
0
}
1087
// ---------------------------------------------------------------------------
1088
1089
0
std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
1090
0
    if (!m_hasNextGrid)
1091
0
        return nullptr;
1092
0
    if (m_nextDirOffset) {
1093
0
        TIFFSetSubDirectory(m_hTIFF, m_nextDirOffset);
1094
0
    }
1095
1096
0
    uint32_t width = 0;
1097
0
    uint32_t height = 0;
1098
0
    TIFFGetField(m_hTIFF, TIFFTAG_IMAGEWIDTH, &width);
1099
0
    TIFFGetField(m_hTIFF, TIFFTAG_IMAGELENGTH, &height);
1100
0
    if (width == 0 || height == 0 || width > INT_MAX || height > INT_MAX) {
1101
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid image size"));
1102
0
        return nullptr;
1103
0
    }
1104
1105
0
    uint16_t samplesPerPixel = 0;
1106
0
    if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) {
1107
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SamplesPerPixel tag"));
1108
0
        return nullptr;
1109
0
    }
1110
0
    if (samplesPerPixel == 0) {
1111
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid SamplesPerPixel value"));
1112
0
        return nullptr;
1113
0
    }
1114
1115
0
    uint16_t bitsPerSample = 0;
1116
0
    if (!TIFFGetField(m_hTIFF, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) {
1117
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing BitsPerSample tag"));
1118
0
        return nullptr;
1119
0
    }
1120
1121
0
    uint16_t planarConfig = 0;
1122
0
    if (!TIFFGetField(m_hTIFF, TIFFTAG_PLANARCONFIG, &planarConfig)) {
1123
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing PlanarConfig tag"));
1124
0
        return nullptr;
1125
0
    }
1126
1127
0
    uint16_t sampleFormat = 0;
1128
0
    if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) {
1129
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SampleFormat tag"));
1130
0
        return nullptr;
1131
0
    }
1132
1133
0
    TIFFDataType dt;
1134
0
    if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 16)
1135
0
        dt = TIFFDataType::Int16;
1136
0
    else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 16)
1137
0
        dt = TIFFDataType::UInt16;
1138
0
    else if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 32)
1139
0
        dt = TIFFDataType::Int32;
1140
0
    else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 32)
1141
0
        dt = TIFFDataType::UInt32;
1142
0
    else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 32)
1143
0
        dt = TIFFDataType::Float32;
1144
0
    else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 64)
1145
0
        dt = TIFFDataType::Float64;
1146
0
    else {
1147
0
        pj_log(m_ctx, PJ_LOG_ERROR,
1148
0
               _("Unsupported combination of SampleFormat "
1149
0
                 "and BitsPerSample values"));
1150
0
        return nullptr;
1151
0
    }
1152
1153
0
    uint16_t photometric = PHOTOMETRIC_MINISBLACK;
1154
0
    if (!TIFFGetField(m_hTIFF, TIFFTAG_PHOTOMETRIC, &photometric))
1155
0
        photometric = PHOTOMETRIC_MINISBLACK;
1156
0
    if (photometric != PHOTOMETRIC_MINISBLACK) {
1157
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported Photometric value"));
1158
0
        return nullptr;
1159
0
    }
1160
1161
0
    uint16_t compression = COMPRESSION_NONE;
1162
0
    if (!TIFFGetField(m_hTIFF, TIFFTAG_COMPRESSION, &compression))
1163
0
        compression = COMPRESSION_NONE;
1164
1165
0
    if (compression != COMPRESSION_NONE &&
1166
0
        !TIFFIsCODECConfigured(compression)) {
1167
0
        pj_log(m_ctx, PJ_LOG_ERROR,
1168
0
               _("Cannot open TIFF file due to missing codec."));
1169
0
        return nullptr;
1170
0
    }
1171
    // We really don't want to try dealing with old-JPEG images
1172
0
    if (compression == COMPRESSION_OJPEG) {
1173
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported compression method."));
1174
0
        return nullptr;
1175
0
    }
1176
1177
0
    const auto blockSize = TIFFIsTiled(m_hTIFF) ? TIFFTileSize64(m_hTIFF)
1178
0
                                                : TIFFStripSize64(m_hTIFF);
1179
0
    if (blockSize == 0 || blockSize > 64 * 1024 * 2014) {
1180
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported block size."));
1181
0
        return nullptr;
1182
0
    }
1183
1184
0
    unsigned short count = 0;
1185
0
    unsigned short *geokeys = nullptr;
1186
0
    bool pixelIsArea = false;
1187
1188
0
    ExtentAndRes extent;
1189
0
    extent.isGeographic = true;
1190
1191
0
    if (!TIFFGetField(m_hTIFF, TIFFTAG_GEOKEYDIRECTORY, &count, &geokeys)) {
1192
0
        pj_log(m_ctx, PJ_LOG_TRACE, "No GeoKeys tag");
1193
0
    } else {
1194
0
        if (count < 4 || (count % 4) != 0) {
1195
0
            pj_log(m_ctx, PJ_LOG_ERROR,
1196
0
                   _("Wrong number of values in GeoKeys tag"));
1197
0
            return nullptr;
1198
0
        }
1199
1200
0
        if (geokeys[0] != 1) {
1201
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported GeoTIFF major version"));
1202
0
            return nullptr;
1203
0
        }
1204
        // We only know that we support GeoTIFF 1.0 and 1.1 at that time
1205
0
        if (geokeys[1] != 1 || geokeys[2] > 1) {
1206
0
            pj_log(m_ctx, PJ_LOG_TRACE, "GeoTIFF %d.%d possibly not handled",
1207
0
                   geokeys[1], geokeys[2]);
1208
0
        }
1209
1210
0
        for (unsigned int i = 4; i + 3 < count; i += 4) {
1211
0
            constexpr unsigned short GTModelTypeGeoKey = 1024;
1212
0
            constexpr unsigned short ModelTypeProjected = 1;
1213
0
            constexpr unsigned short ModelTypeGeographic = 2;
1214
1215
0
            constexpr unsigned short GTRasterTypeGeoKey = 1025;
1216
0
            constexpr unsigned short RasterPixelIsArea = 1;
1217
            // constexpr unsigned short RasterPixelIsPoint = 2;
1218
1219
0
            if (geokeys[i] == GTModelTypeGeoKey) {
1220
0
                if (geokeys[i + 3] == ModelTypeProjected) {
1221
0
                    extent.isGeographic = false;
1222
0
                } else if (geokeys[i + 3] != ModelTypeGeographic) {
1223
0
                    pj_log(m_ctx, PJ_LOG_ERROR,
1224
0
                           _("Only GTModelTypeGeoKey = "
1225
0
                             "ModelTypeGeographic or ModelTypeProjected are "
1226
0
                             "supported"));
1227
0
                    return nullptr;
1228
0
                }
1229
0
            } else if (geokeys[i] == GTRasterTypeGeoKey) {
1230
0
                if (geokeys[i + 3] == RasterPixelIsArea) {
1231
0
                    pixelIsArea = true;
1232
0
                }
1233
0
            }
1234
0
        }
1235
0
    }
1236
1237
0
    double hRes = 0;
1238
0
    double vRes = 0;
1239
0
    double west = 0;
1240
0
    double north = 0;
1241
1242
0
    double *matrix = nullptr;
1243
0
    if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTRANSMATRIX, &count, &matrix) &&
1244
0
        count == 16) {
1245
        // If using GDAL to produce a bottom-up georeferencing, it will produce
1246
        // a GeoTransformationMatrix, since negative values in GeoPixelScale
1247
        // have historically been implementation bugs.
1248
0
        if (matrix[1] != 0 || matrix[4] != 0) {
1249
0
            pj_log(m_ctx, PJ_LOG_ERROR,
1250
0
                   _("Rotational terms not supported in "
1251
0
                     "GeoTransformationMatrix tag"));
1252
0
            return nullptr;
1253
0
        }
1254
1255
0
        west = matrix[3];
1256
0
        hRes = matrix[0];
1257
0
        north = matrix[7];
1258
0
        vRes = -matrix[5]; // negation to simulate GeoPixelScale convention
1259
0
    } else {
1260
0
        double *geopixelscale = nullptr;
1261
0
        if (TIFFGetField(m_hTIFF, TIFFTAG_GEOPIXELSCALE, &count,
1262
0
                         &geopixelscale) != 1) {
1263
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoPixelScale tag"));
1264
0
            return nullptr;
1265
0
        }
1266
0
        if (count != 3) {
1267
0
            pj_log(m_ctx, PJ_LOG_ERROR,
1268
0
                   _("Wrong number of values in GeoPixelScale tag"));
1269
0
            return nullptr;
1270
0
        }
1271
0
        hRes = geopixelscale[0];
1272
0
        vRes = geopixelscale[1];
1273
1274
0
        double *geotiepoints = nullptr;
1275
0
        if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, &count,
1276
0
                         &geotiepoints) != 1) {
1277
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoTiePoints tag"));
1278
0
            return nullptr;
1279
0
        }
1280
0
        if (count != 6) {
1281
0
            pj_log(m_ctx, PJ_LOG_ERROR,
1282
0
                   _("Wrong number of values in GeoTiePoints tag"));
1283
0
            return nullptr;
1284
0
        }
1285
1286
0
        west = geotiepoints[3] - geotiepoints[0] * hRes;
1287
0
        north = geotiepoints[4] + geotiepoints[1] * vRes;
1288
0
    }
1289
1290
0
    if (pixelIsArea) {
1291
0
        west += 0.5 * hRes;
1292
0
        north -= 0.5 * vRes;
1293
0
    }
1294
1295
0
    const double mulFactor = extent.isGeographic ? DEG_TO_RAD : 1;
1296
0
    extent.west = west * mulFactor;
1297
0
    extent.north = north * mulFactor;
1298
0
    extent.resX = hRes * mulFactor;
1299
0
    extent.resY = fabs(vRes) * mulFactor;
1300
0
    extent.east = (west + hRes * (width - 1)) * mulFactor;
1301
0
    extent.south = (north - vRes * (height - 1)) * mulFactor;
1302
0
    extent.computeInvRes();
1303
1304
0
    if (vRes < 0) {
1305
0
        std::swap(extent.north, extent.south);
1306
0
    }
1307
1308
0
    if (!((!extent.isGeographic ||
1309
0
           (fabs(extent.west) <= 4 * M_PI && fabs(extent.east) <= 4 * M_PI &&
1310
0
            fabs(extent.north) <= M_PI + 1e-5 &&
1311
0
            fabs(extent.south) <= M_PI + 1e-5)) &&
1312
0
          extent.west < extent.east && extent.south < extent.north &&
1313
0
          extent.resX > 1e-10 && extent.resY > 1e-10)) {
1314
0
        pj_log(m_ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
1315
0
               m_filename.c_str());
1316
0
        return nullptr;
1317
0
    }
1318
1319
0
    auto ret = std::unique_ptr<GTiffGrid>(new GTiffGrid(
1320
0
        m_ctx, m_hTIFF, m_cache, m_fp.get(), m_ifdIdx, m_filename, width,
1321
0
        height, extent, dt, samplesPerPixel, planarConfig, vRes < 0));
1322
0
    m_ifdIdx++;
1323
0
    m_hasNextGrid = TIFFReadDirectory(m_hTIFF) != 0;
1324
0
    m_nextDirOffset = TIFFCurrentDirOffset(m_hTIFF);
1325
1326
    // If the TIFF file contains multiple grids, append the index of the grid
1327
    // in the grid name to help debugging.
1328
0
    if (m_ifdIdx >= 2 || m_hasNextGrid) {
1329
0
        ret->m_name += " (index ";
1330
0
        ret->m_name += std::to_string(m_ifdIdx); // 1-based
1331
0
        ret->m_name += ')';
1332
0
    }
1333
1334
0
    return ret;
1335
0
}
1336
1337
// ---------------------------------------------------------------------------
1338
1339
class GTiffVGridShiftSet : public VerticalShiftGridSet {
1340
1341
    std::unique_ptr<GTiffDataset> m_GTiffDataset;
1342
1343
    GTiffVGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
1344
0
        : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {}
1345
1346
  public:
1347
    ~GTiffVGridShiftSet() override;
1348
1349
    static std::unique_ptr<GTiffVGridShiftSet>
1350
    open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1351
         const std::string &filename);
1352
1353
0
    void reassign_context(PJ_CONTEXT *ctx) override {
1354
0
        VerticalShiftGridSet::reassign_context(ctx);
1355
0
        if (m_GTiffDataset) {
1356
0
            m_GTiffDataset->reassign_context(ctx);
1357
0
        }
1358
0
    }
1359
1360
0
    bool reopen(PJ_CONTEXT *ctx) override {
1361
0
        pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
1362
0
               m_name.c_str());
1363
0
        m_grids.clear();
1364
0
        m_GTiffDataset.reset();
1365
0
        auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
1366
0
        if (!fp) {
1367
0
            return false;
1368
0
        }
1369
0
        auto newGS = open(ctx, std::move(fp), m_name);
1370
0
        if (newGS) {
1371
0
            m_grids = std::move(newGS->m_grids);
1372
0
            m_GTiffDataset = std::move(newGS->m_GTiffDataset);
1373
0
        }
1374
0
        return !m_grids.empty();
1375
0
    }
1376
};
1377
1378
// ---------------------------------------------------------------------------
1379
1380
template <class GridType, class GenericGridType>
1381
static void
1382
insertIntoHierarchy(PJ_CONTEXT *ctx, std::unique_ptr<GridType> &&grid,
1383
                    const std::string &gridName, const std::string &parentName,
1384
                    std::vector<std::unique_ptr<GenericGridType>> &topGrids,
1385
0
                    std::map<std::string, GridType *> &mapGrids) {
1386
0
    const auto &extent = grid->extentAndRes();
1387
1388
    // If we have one or both of grid_name and parent_grid_name, try to use
1389
    // the names to recreate the hierarchy
1390
0
    if (!gridName.empty()) {
1391
0
        if (mapGrids.find(gridName) != mapGrids.end()) {
1392
0
            pj_log(ctx, PJ_LOG_DEBUG, "Several grids called %s found!",
1393
0
                   gridName.c_str());
1394
0
        }
1395
0
        mapGrids[gridName] = grid.get();
1396
0
    }
1397
1398
0
    if (!parentName.empty()) {
1399
0
        auto iter = mapGrids.find(parentName);
1400
0
        if (iter == mapGrids.end()) {
1401
0
            pj_log(ctx, PJ_LOG_DEBUG,
1402
0
                   "Grid %s refers to non-existing parent %s. "
1403
0
                   "Using bounding-box method.",
1404
0
                   gridName.c_str(), parentName.c_str());
1405
0
        } else {
1406
0
            if (iter->second->extentAndRes().contains(extent)) {
1407
0
                iter->second->m_children.emplace_back(std::move(grid));
1408
0
                return;
1409
0
            } else {
1410
0
                pj_log(ctx, PJ_LOG_DEBUG,
1411
0
                       "Grid %s refers to parent %s, but its extent is "
1412
0
                       "not included in it. Using bounding-box method.",
1413
0
                       gridName.c_str(), parentName.c_str());
1414
0
            }
1415
0
        }
1416
0
    } else if (!gridName.empty()) {
1417
0
        topGrids.emplace_back(std::move(grid));
1418
0
        return;
1419
0
    }
1420
1421
0
    const std::string &type = grid->metadataItem("TYPE");
1422
1423
    // Fallback to analyzing spatial extents
1424
0
    for (const auto &candidateParent : topGrids) {
1425
0
        if (!type.empty() && candidateParent->metadataItem("TYPE") != type) {
1426
0
            continue;
1427
0
        }
1428
1429
0
        const auto &candidateParentExtent = candidateParent->extentAndRes();
1430
0
        if (candidateParentExtent.contains(extent)) {
1431
0
            static_cast<GridType *>(candidateParent.get())
1432
0
                ->insertGrid(ctx, std::move(grid));
1433
0
            return;
1434
0
        } else if (candidateParentExtent.intersects(extent)) {
1435
0
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
1436
0
        }
1437
0
    }
1438
1439
0
    topGrids.emplace_back(std::move(grid));
1440
0
}
Unexecuted instantiation: grids.cpp:void osgeo::proj::insertIntoHierarchy<osgeo::proj::GTiffVGrid, osgeo::proj::VerticalShiftGrid>(pj_ctx*, std::__1::unique_ptr<osgeo::proj::GTiffVGrid, std::__1::default_delete<osgeo::proj::GTiffVGrid> >&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::unique_ptr<osgeo::proj::VerticalShiftGrid, std::__1::default_delete<osgeo::proj::VerticalShiftGrid> >, std::__1::allocator<std::__1::unique_ptr<osgeo::proj::VerticalShiftGrid, std::__1::default_delete<osgeo::proj::VerticalShiftGrid> > > >&, std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, osgeo::proj::GTiffVGrid*, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, osgeo::proj::GTiffVGrid*> > >&)
Unexecuted instantiation: grids.cpp:void osgeo::proj::insertIntoHierarchy<osgeo::proj::GTiffHGrid, osgeo::proj::HorizontalShiftGrid>(pj_ctx*, std::__1::unique_ptr<osgeo::proj::GTiffHGrid, std::__1::default_delete<osgeo::proj::GTiffHGrid> >&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::unique_ptr<osgeo::proj::HorizontalShiftGrid, std::__1::default_delete<osgeo::proj::HorizontalShiftGrid> >, std::__1::allocator<std::__1::unique_ptr<osgeo::proj::HorizontalShiftGrid, std::__1::default_delete<osgeo::proj::HorizontalShiftGrid> > > >&, std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, osgeo::proj::GTiffHGrid*, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, osgeo::proj::GTiffHGrid*> > >&)
Unexecuted instantiation: grids.cpp:void osgeo::proj::insertIntoHierarchy<osgeo::proj::GTiffGenericGrid, osgeo::proj::GenericShiftGrid>(pj_ctx*, std::__1::unique_ptr<osgeo::proj::GTiffGenericGrid, std::__1::default_delete<osgeo::proj::GTiffGenericGrid> >&&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, std::__1::vector<std::__1::unique_ptr<osgeo::proj::GenericShiftGrid, std::__1::default_delete<osgeo::proj::GenericShiftGrid> >, std::__1::allocator<std::__1::unique_ptr<osgeo::proj::GenericShiftGrid, std::__1::default_delete<osgeo::proj::GenericShiftGrid> > > >&, std::__1::map<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, osgeo::proj::GTiffGenericGrid*, std::__1::less<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const, osgeo::proj::GTiffGenericGrid*> > >&)
1441
1442
// ---------------------------------------------------------------------------
1443
1444
class GTiffVGrid : public VerticalShiftGrid {
1445
    friend void insertIntoHierarchy<GTiffVGrid, VerticalShiftGrid>(
1446
        PJ_CONTEXT *ctx, std::unique_ptr<GTiffVGrid> &&grid,
1447
        const std::string &gridName, const std::string &parentName,
1448
        std::vector<std::unique_ptr<VerticalShiftGrid>> &topGrids,
1449
        std::map<std::string, GTiffVGrid *> &mapGrids);
1450
1451
    std::unique_ptr<GTiffGrid> m_grid;
1452
    uint16_t m_idxSample;
1453
1454
  public:
1455
    GTiffVGrid(std::unique_ptr<GTiffGrid> &&grid, uint16_t idxSample);
1456
1457
    ~GTiffVGrid() override;
1458
1459
0
    bool valueAt(int x, int y, float &out) const override {
1460
0
        return m_grid->valueAt(m_idxSample, x, y, out);
1461
0
    }
1462
1463
0
    bool isNodata(float val, double /* multiplier */) const override {
1464
0
        return m_grid->isNodata(val);
1465
0
    }
1466
1467
    const std::string &metadataItem(const std::string &key,
1468
0
                                    int sample = -1) const override {
1469
0
        return m_grid->metadataItem(key, sample);
1470
0
    }
1471
1472
    void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffVGrid> &&subgrid);
1473
1474
0
    void reassign_context(PJ_CONTEXT *ctx) override {
1475
0
        m_grid->reassign_context(ctx);
1476
0
    }
1477
1478
0
    bool hasChanged() const override { return m_grid->hasChanged(); }
1479
};
1480
1481
// ---------------------------------------------------------------------------
1482
1483
0
GTiffVGridShiftSet::~GTiffVGridShiftSet() = default;
1484
1485
// ---------------------------------------------------------------------------
1486
1487
GTiffVGrid::GTiffVGrid(std::unique_ptr<GTiffGrid> &&grid, uint16_t idxSample)
1488
0
    : VerticalShiftGrid(grid->name(), grid->width(), grid->height(),
1489
0
                        grid->extentAndRes()),
1490
0
      m_grid(std::move(grid)), m_idxSample(idxSample) {}
1491
1492
// ---------------------------------------------------------------------------
1493
1494
0
GTiffVGrid::~GTiffVGrid() = default;
1495
1496
// ---------------------------------------------------------------------------
1497
1498
void GTiffVGrid::insertGrid(PJ_CONTEXT *ctx,
1499
0
                            std::unique_ptr<GTiffVGrid> &&subgrid) {
1500
0
    bool gridInserted = false;
1501
0
    const auto &extent = subgrid->extentAndRes();
1502
0
    for (const auto &candidateParent : m_children) {
1503
0
        const auto &candidateParentExtent = candidateParent->extentAndRes();
1504
0
        if (candidateParentExtent.contains(extent)) {
1505
0
            static_cast<GTiffVGrid *>(candidateParent.get())
1506
0
                ->insertGrid(ctx, std::move(subgrid));
1507
0
            gridInserted = true;
1508
0
            break;
1509
0
        } else if (candidateParentExtent.intersects(extent)) {
1510
0
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
1511
0
        }
1512
0
    }
1513
0
    if (!gridInserted) {
1514
0
        m_children.emplace_back(std::move(subgrid));
1515
0
    }
1516
0
}
1517
1518
// ---------------------------------------------------------------------------
1519
1520
std::unique_ptr<GTiffVGridShiftSet>
1521
GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1522
0
                         const std::string &filename) {
1523
0
    auto set = std::unique_ptr<GTiffVGridShiftSet>(
1524
0
        new GTiffVGridShiftSet(ctx, std::move(fp)));
1525
0
    set->m_name = filename;
1526
0
    set->m_format = "gtiff";
1527
0
    if (!set->m_GTiffDataset->openTIFF(filename)) {
1528
0
        return nullptr;
1529
0
    }
1530
0
    uint16_t idxSample = 0;
1531
1532
0
    std::map<std::string, GTiffVGrid *> mapGrids;
1533
0
    for (int ifd = 0;; ++ifd) {
1534
0
        auto grid = set->m_GTiffDataset->nextGrid();
1535
0
        if (!grid) {
1536
0
            if (ifd == 0) {
1537
0
                return nullptr;
1538
0
            }
1539
0
            break;
1540
0
        }
1541
1542
0
        const auto subfileType = grid->subfileType();
1543
0
        if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
1544
0
            if (ifd == 0) {
1545
0
                pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
1546
0
                return nullptr;
1547
0
            } else {
1548
0
                pj_log(ctx, PJ_LOG_DEBUG,
1549
0
                       "Ignoring IFD %d as it has a unsupported subfileType",
1550
0
                       ifd);
1551
0
                continue;
1552
0
            }
1553
0
        }
1554
1555
        // Identify the index of the vertical correction
1556
0
        bool foundDescriptionForAtLeastOneSample = false;
1557
0
        bool foundDescriptionForShift = false;
1558
0
        for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
1559
0
            const auto &desc = grid->metadataItem("DESCRIPTION", i);
1560
0
            if (!desc.empty()) {
1561
0
                foundDescriptionForAtLeastOneSample = true;
1562
0
            }
1563
0
            if (desc == "geoid_undulation" || desc == "vertical_offset" ||
1564
0
                desc == "hydroid_height" ||
1565
0
                desc == "ellipsoidal_height_offset") {
1566
0
                idxSample = static_cast<uint16_t>(i);
1567
0
                foundDescriptionForShift = true;
1568
0
            }
1569
0
        }
1570
1571
0
        if (foundDescriptionForAtLeastOneSample) {
1572
0
            if (!foundDescriptionForShift) {
1573
0
                if (ifd > 0) {
1574
                    // Assuming that extra IFD without our channel of interest
1575
                    // can be ignored
1576
                    // One could imagine to put the accuracy values in separate
1577
                    // IFD for example
1578
0
                    pj_log(ctx, PJ_LOG_DEBUG,
1579
0
                           "Ignoring IFD %d as it has no "
1580
0
                           "geoid_undulation/vertical_offset/hydroid_height/"
1581
0
                           "ellipsoidal_height_offset channel",
1582
0
                           ifd);
1583
0
                    continue;
1584
0
                } else {
1585
0
                    pj_log(ctx, PJ_LOG_DEBUG,
1586
0
                           "IFD 0 has channel descriptions, but no "
1587
0
                           "geoid_undulation/vertical_offset/hydroid_height/"
1588
0
                           "ellipsoidal_height_offset channel");
1589
0
                    return nullptr;
1590
0
                }
1591
0
            }
1592
0
        }
1593
1594
0
        if (idxSample >= grid->samplesPerPixel()) {
1595
0
            pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index"));
1596
0
            return nullptr;
1597
0
        }
1598
1599
0
        const std::string &gridName = grid->metadataItem("grid_name");
1600
0
        const std::string &parentName = grid->metadataItem("parent_grid_name");
1601
1602
0
        auto vgrid = std::make_unique<GTiffVGrid>(std::move(grid), idxSample);
1603
1604
0
        insertIntoHierarchy(ctx, std::move(vgrid), gridName, parentName,
1605
0
                            set->m_grids, mapGrids);
1606
0
    }
1607
0
    return set;
1608
0
}
1609
#endif // TIFF_ENABLED
1610
1611
// ---------------------------------------------------------------------------
1612
1613
std::unique_ptr<VerticalShiftGridSet>
1614
9.12k
VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
1615
9.12k
    if (filename == "null") {
1616
25
        auto set =
1617
25
            std::unique_ptr<VerticalShiftGridSet>(new VerticalShiftGridSet());
1618
25
        set->m_name = filename;
1619
25
        set->m_format = "null";
1620
25
        set->m_grids.push_back(std::unique_ptr<NullVerticalShiftGrid>(
1621
25
            new NullVerticalShiftGrid()));
1622
25
        return set;
1623
25
    }
1624
1625
9.10k
    auto fp = FileManager::open_resource_file(ctx, filename.c_str());
1626
9.10k
    if (!fp) {
1627
8.99k
        return nullptr;
1628
8.99k
    }
1629
109
    const auto &actualName(fp->name());
1630
109
    if (ends_with(actualName, "gtx") || ends_with(actualName, "GTX")) {
1631
0
        auto grid = GTXVerticalShiftGrid::open(ctx, std::move(fp), actualName);
1632
0
        if (!grid) {
1633
0
            return nullptr;
1634
0
        }
1635
0
        auto set =
1636
0
            std::unique_ptr<VerticalShiftGridSet>(new VerticalShiftGridSet());
1637
0
        set->m_name = actualName;
1638
0
        set->m_format = "gtx";
1639
0
        set->m_grids.push_back(std::unique_ptr<VerticalShiftGrid>(grid));
1640
0
        return set;
1641
0
    }
1642
1643
    /* -------------------------------------------------------------------- */
1644
    /*      Load a header, to determine the file type.                      */
1645
    /* -------------------------------------------------------------------- */
1646
109
    unsigned char header[4];
1647
109
    size_t header_size = fp->read(header, sizeof(header));
1648
109
    if (header_size != sizeof(header)) {
1649
105
        return nullptr;
1650
105
    }
1651
4
    fp->seek(0);
1652
1653
4
    if (IsTIFF(header_size, header)) {
1654
0
#ifdef TIFF_ENABLED
1655
0
        auto set = std::unique_ptr<VerticalShiftGridSet>(
1656
0
            GTiffVGridShiftSet::open(ctx, std::move(fp), actualName));
1657
0
        if (!set)
1658
0
            proj_context_errno_set(
1659
0
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
1660
0
        return set;
1661
#else
1662
        pj_log(ctx, PJ_LOG_ERROR,
1663
               _("TIFF grid, but TIFF support disabled in this build"));
1664
        return nullptr;
1665
#endif
1666
0
    }
1667
1668
4
    pj_log(ctx, PJ_LOG_ERROR,
1669
4
           "Unrecognized vertical grid format for filename '%s'",
1670
4
           filename.c_str());
1671
4
    return nullptr;
1672
4
}
1673
1674
// ---------------------------------------------------------------------------
1675
1676
0
bool VerticalShiftGridSet::reopen(PJ_CONTEXT *ctx) {
1677
0
    pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
1678
0
           m_name.c_str());
1679
0
    auto newGS = open(ctx, m_name);
1680
0
    m_grids.clear();
1681
0
    if (newGS) {
1682
0
        m_grids = std::move(newGS->m_grids);
1683
0
    }
1684
0
    return !m_grids.empty();
1685
0
}
1686
1687
// ---------------------------------------------------------------------------
1688
1689
static bool isPointInExtent(double x, double y, const ExtentAndRes &extent,
1690
0
                            double eps = 0) {
1691
0
    if (!(y + eps >= extent.south && y - eps <= extent.north))
1692
0
        return false;
1693
0
    if (extent.fullWorldLongitude())
1694
0
        return true;
1695
0
    if (extent.isGeographic) {
1696
0
        if (x + eps < extent.west)
1697
0
            x += 2 * M_PI;
1698
0
        else if (x - eps > extent.east)
1699
0
            x -= 2 * M_PI;
1700
0
    }
1701
0
    if (!(x + eps >= extent.west && x - eps <= extent.east))
1702
0
        return false;
1703
0
    return true;
1704
0
}
1705
1706
// ---------------------------------------------------------------------------
1707
1708
const VerticalShiftGrid *VerticalShiftGrid::gridAt(double longitude,
1709
0
                                                   double lat) const {
1710
0
    for (const auto &child : m_children) {
1711
0
        const auto &extentChild = child->extentAndRes();
1712
0
        if (isPointInExtent(longitude, lat, extentChild)) {
1713
0
            return child->gridAt(longitude, lat);
1714
0
        }
1715
0
    }
1716
0
    return this;
1717
0
}
1718
// ---------------------------------------------------------------------------
1719
1720
const VerticalShiftGrid *VerticalShiftGridSet::gridAt(double longitude,
1721
0
                                                      double lat) const {
1722
0
    for (const auto &grid : m_grids) {
1723
0
        if (grid->isNullGrid()) {
1724
0
            return grid.get();
1725
0
        }
1726
0
        const auto &extent = grid->extentAndRes();
1727
0
        if (isPointInExtent(longitude, lat, extent)) {
1728
0
            return grid->gridAt(longitude, lat);
1729
0
        }
1730
0
    }
1731
0
    return nullptr;
1732
0
}
1733
1734
// ---------------------------------------------------------------------------
1735
1736
0
void VerticalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
1737
0
    for (const auto &grid : m_grids) {
1738
0
        grid->reassign_context(ctx);
1739
0
    }
1740
0
}
1741
1742
// ---------------------------------------------------------------------------
1743
1744
HorizontalShiftGrid::HorizontalShiftGrid(const std::string &nameIn, int widthIn,
1745
                                         int heightIn,
1746
                                         const ExtentAndRes &extentIn)
1747
178
    : Grid(nameIn, widthIn, heightIn, extentIn) {}
1748
1749
// ---------------------------------------------------------------------------
1750
1751
178
HorizontalShiftGrid::~HorizontalShiftGrid() = default;
1752
1753
// ---------------------------------------------------------------------------
1754
1755
178
HorizontalShiftGridSet::HorizontalShiftGridSet() = default;
1756
1757
// ---------------------------------------------------------------------------
1758
1759
178
HorizontalShiftGridSet::~HorizontalShiftGridSet() = default;
1760
1761
// ---------------------------------------------------------------------------
1762
1763
class NullHorizontalShiftGrid : public HorizontalShiftGrid {
1764
1765
  public:
1766
    NullHorizontalShiftGrid()
1767
178
        : HorizontalShiftGrid("null", 3, 3, globalExtent()) {}
1768
1769
0
    bool isNullGrid() const override { return true; }
1770
1771
    bool valueAt(int, int, bool, float &longShift,
1772
                 float &latShift) const override;
1773
1774
0
    void reassign_context(PJ_CONTEXT *) override {}
1775
1776
0
    bool hasChanged() const override { return false; }
1777
1778
0
    const std::string &metadataItem(const std::string &, int) const override {
1779
0
        return emptyString;
1780
0
    }
1781
};
1782
1783
// ---------------------------------------------------------------------------
1784
1785
bool NullHorizontalShiftGrid::valueAt(int, int, bool, float &longShift,
1786
0
                                      float &latShift) const {
1787
0
    longShift = 0.0f;
1788
0
    latShift = 0.0f;
1789
0
    return true;
1790
0
}
1791
1792
// ---------------------------------------------------------------------------
1793
1794
0
static double to_double(const void *data) {
1795
0
    double d;
1796
0
    memcpy(&d, data, sizeof(d));
1797
0
    return d;
1798
0
}
1799
1800
// ---------------------------------------------------------------------------
1801
1802
class NTv1Grid : public HorizontalShiftGrid {
1803
    PJ_CONTEXT *m_ctx;
1804
    std::unique_ptr<File> m_fp;
1805
1806
    NTv1Grid(const NTv1Grid &) = delete;
1807
    NTv1Grid &operator=(const NTv1Grid &) = delete;
1808
1809
  public:
1810
    explicit NTv1Grid(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp,
1811
                      const std::string &nameIn, int widthIn, int heightIn,
1812
                      const ExtentAndRes &extentIn)
1813
0
        : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
1814
0
          m_fp(std::move(fp)) {}
1815
1816
    ~NTv1Grid() override;
1817
1818
    bool valueAt(int, int, bool, float &longShift,
1819
                 float &latShift) const override;
1820
1821
    static NTv1Grid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1822
                          const std::string &filename);
1823
1824
0
    void reassign_context(PJ_CONTEXT *ctx) override {
1825
0
        m_ctx = ctx;
1826
0
        m_fp->reassign_context(ctx);
1827
0
    }
1828
1829
0
    bool hasChanged() const override { return m_fp->hasChanged(); }
1830
1831
0
    const std::string &metadataItem(const std::string &, int) const override {
1832
0
        return emptyString;
1833
0
    }
1834
};
1835
1836
// ---------------------------------------------------------------------------
1837
1838
0
NTv1Grid::~NTv1Grid() = default;
1839
1840
// ---------------------------------------------------------------------------
1841
1842
NTv1Grid *NTv1Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1843
0
                         const std::string &filename) {
1844
0
    unsigned char header[192];
1845
1846
    /* -------------------------------------------------------------------- */
1847
    /*      Read the header.                                                */
1848
    /* -------------------------------------------------------------------- */
1849
0
    if (fp->read(header, sizeof(header)) != sizeof(header)) {
1850
0
        proj_context_errno_set(ctx,
1851
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
1852
0
        return nullptr;
1853
0
    }
1854
1855
    /* -------------------------------------------------------------------- */
1856
    /*      Regularize fields of interest.                                  */
1857
    /* -------------------------------------------------------------------- */
1858
0
    if (IS_LSB) {
1859
0
        swap_words(header + 8, sizeof(int), 1);
1860
0
        swap_words(header + 24, sizeof(double), 1);
1861
0
        swap_words(header + 40, sizeof(double), 1);
1862
0
        swap_words(header + 56, sizeof(double), 1);
1863
0
        swap_words(header + 72, sizeof(double), 1);
1864
0
        swap_words(header + 88, sizeof(double), 1);
1865
0
        swap_words(header + 104, sizeof(double), 1);
1866
0
    }
1867
1868
0
    int recordCount;
1869
0
    memcpy(&recordCount, header + 8, sizeof(recordCount));
1870
0
    if (recordCount != 12) {
1871
0
        pj_log(ctx, PJ_LOG_ERROR,
1872
0
               _("NTv1 grid shift file has wrong record count, corrupt?"));
1873
0
        proj_context_errno_set(ctx,
1874
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
1875
0
        return nullptr;
1876
0
    }
1877
1878
0
    ExtentAndRes extent;
1879
0
    extent.isGeographic = true;
1880
0
    extent.west = -to_double(header + 72) * DEG_TO_RAD;
1881
0
    extent.south = to_double(header + 24) * DEG_TO_RAD;
1882
0
    extent.east = -to_double(header + 56) * DEG_TO_RAD;
1883
0
    extent.north = to_double(header + 40) * DEG_TO_RAD;
1884
0
    extent.resX = to_double(header + 104) * DEG_TO_RAD;
1885
0
    extent.resY = to_double(header + 88) * DEG_TO_RAD;
1886
0
    extent.computeInvRes();
1887
1888
0
    if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.east) <= 4 * M_PI &&
1889
0
          fabs(extent.north) <= M_PI + 1e-5 &&
1890
0
          fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east &&
1891
0
          extent.south < extent.north && extent.resX > 1e-10 &&
1892
0
          extent.resY > 1e-10)) {
1893
0
        pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
1894
0
               filename.c_str());
1895
0
        proj_context_errno_set(ctx,
1896
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
1897
0
        return nullptr;
1898
0
    }
1899
0
    const int columns = static_cast<int>(
1900
0
        fabs((extent.east - extent.west) * extent.invResX + 0.5) + 1);
1901
0
    const int rows = static_cast<int>(
1902
0
        fabs((extent.north - extent.south) * extent.invResY + 0.5) + 1);
1903
1904
0
    return new NTv1Grid(ctx, std::move(fp), filename, columns, rows, extent);
1905
0
}
1906
1907
// ---------------------------------------------------------------------------
1908
1909
bool NTv1Grid::valueAt(int x, int y, bool compensateNTConvention,
1910
0
                       float &longShift, float &latShift) const {
1911
0
    assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
1912
1913
0
    double two_doubles[2];
1914
    // NTv1 is organized from east to west !
1915
0
    m_fp->seek(192 + 2 * sizeof(double) * (y * m_width + m_width - 1 - x));
1916
0
    if (m_fp->read(&two_doubles[0], sizeof(two_doubles)) !=
1917
0
        sizeof(two_doubles)) {
1918
0
        proj_context_errno_set(m_ctx,
1919
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
1920
0
        return false;
1921
0
    }
1922
0
    if (IS_LSB) {
1923
0
        swap_words(&two_doubles[0], sizeof(double), 2);
1924
0
    }
1925
    /* convert seconds to radians */
1926
0
    latShift = static_cast<float>(two_doubles[0] * ((M_PI / 180.0) / 3600.0));
1927
    // west longitude positive convention !
1928
0
    longShift = (compensateNTConvention ? -1 : 1) *
1929
0
                static_cast<float>(two_doubles[1] * ((M_PI / 180.0) / 3600.0));
1930
1931
0
    return true;
1932
0
}
1933
1934
// ---------------------------------------------------------------------------
1935
1936
class CTable2Grid : public HorizontalShiftGrid {
1937
    PJ_CONTEXT *m_ctx;
1938
    std::unique_ptr<File> m_fp;
1939
1940
    CTable2Grid(const CTable2Grid &) = delete;
1941
    CTable2Grid &operator=(const CTable2Grid &) = delete;
1942
1943
  public:
1944
    CTable2Grid(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1945
                const std::string &nameIn, int widthIn, int heightIn,
1946
                const ExtentAndRes &extentIn)
1947
0
        : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
1948
0
          m_fp(std::move(fp)) {}
1949
1950
    ~CTable2Grid() override;
1951
1952
    bool valueAt(int, int, bool, float &longShift,
1953
                 float &latShift) const override;
1954
1955
    static CTable2Grid *open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1956
                             const std::string &filename);
1957
1958
0
    void reassign_context(PJ_CONTEXT *ctx) override {
1959
0
        m_ctx = ctx;
1960
0
        m_fp->reassign_context(ctx);
1961
0
    }
1962
1963
0
    bool hasChanged() const override { return m_fp->hasChanged(); }
1964
1965
0
    const std::string &metadataItem(const std::string &, int) const override {
1966
0
        return emptyString;
1967
0
    }
1968
};
1969
1970
// ---------------------------------------------------------------------------
1971
1972
0
CTable2Grid::~CTable2Grid() = default;
1973
1974
// ---------------------------------------------------------------------------
1975
1976
CTable2Grid *CTable2Grid::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1977
0
                               const std::string &filename) {
1978
0
    unsigned char header[160];
1979
1980
    /* -------------------------------------------------------------------- */
1981
    /*      Read the header.                                                */
1982
    /* -------------------------------------------------------------------- */
1983
0
    if (fp->read(header, sizeof(header)) != sizeof(header)) {
1984
0
        proj_context_errno_set(ctx,
1985
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
1986
0
        return nullptr;
1987
0
    }
1988
1989
    /* -------------------------------------------------------------------- */
1990
    /*      Regularize fields of interest.                                  */
1991
    /* -------------------------------------------------------------------- */
1992
0
    if (!IS_LSB) {
1993
0
        swap_words(header + 96, sizeof(double), 4);
1994
0
        swap_words(header + 128, sizeof(int), 2);
1995
0
    }
1996
1997
0
    ExtentAndRes extent;
1998
0
    extent.isGeographic = true;
1999
0
    static_assert(sizeof(extent.west) == 8, "wrong sizeof");
2000
0
    static_assert(sizeof(extent.south) == 8, "wrong sizeof");
2001
0
    static_assert(sizeof(extent.resX) == 8, "wrong sizeof");
2002
0
    static_assert(sizeof(extent.resY) == 8, "wrong sizeof");
2003
0
    memcpy(&extent.west, header + 96, 8);
2004
0
    memcpy(&extent.south, header + 104, 8);
2005
0
    memcpy(&extent.resX, header + 112, 8);
2006
0
    memcpy(&extent.resY, header + 120, 8);
2007
0
    if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.south) <= M_PI + 1e-5 &&
2008
0
          extent.resX > 1e-10 && extent.resY > 1e-10)) {
2009
0
        pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
2010
0
               filename.c_str());
2011
0
        proj_context_errno_set(ctx,
2012
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2013
0
        return nullptr;
2014
0
    }
2015
0
    int width;
2016
0
    int height;
2017
0
    memcpy(&width, header + 128, 4);
2018
0
    memcpy(&height, header + 132, 4);
2019
0
    if (width <= 0 || height <= 0) {
2020
0
        proj_context_errno_set(ctx,
2021
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2022
0
        return nullptr;
2023
0
    }
2024
0
    extent.east = extent.west + (width - 1) * extent.resX;
2025
0
    extent.north = extent.south + (height - 1) * extent.resX;
2026
0
    extent.computeInvRes();
2027
2028
0
    return new CTable2Grid(ctx, std::move(fp), filename, width, height, extent);
2029
0
}
2030
2031
// ---------------------------------------------------------------------------
2032
2033
bool CTable2Grid::valueAt(int x, int y, bool compensateNTConvention,
2034
0
                          float &longShift, float &latShift) const {
2035
0
    assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
2036
2037
0
    float two_floats[2];
2038
0
    m_fp->seek(160 + 2 * sizeof(float) * (y * m_width + x));
2039
0
    if (m_fp->read(&two_floats[0], sizeof(two_floats)) != sizeof(two_floats)) {
2040
0
        proj_context_errno_set(m_ctx,
2041
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2042
0
        return false;
2043
0
    }
2044
0
    if (!IS_LSB) {
2045
0
        swap_words(&two_floats[0], sizeof(float), 2);
2046
0
    }
2047
2048
0
    latShift = two_floats[1];
2049
    // west longitude positive convention !
2050
0
    longShift = (compensateNTConvention ? -1 : 1) * two_floats[0];
2051
2052
0
    return true;
2053
0
}
2054
2055
// ---------------------------------------------------------------------------
2056
2057
class NTv2GridSet : public HorizontalShiftGridSet {
2058
    std::unique_ptr<File> m_fp;
2059
    std::unique_ptr<FloatLineCache> m_cache{};
2060
2061
    NTv2GridSet(const NTv2GridSet &) = delete;
2062
    NTv2GridSet &operator=(const NTv2GridSet &) = delete;
2063
2064
0
    explicit NTv2GridSet(std::unique_ptr<File> &&fp) : m_fp(std::move(fp)) {}
2065
2066
  public:
2067
    ~NTv2GridSet() override;
2068
2069
    static std::unique_ptr<NTv2GridSet> open(PJ_CONTEXT *ctx,
2070
                                             std::unique_ptr<File> fp,
2071
                                             const std::string &filename);
2072
2073
0
    void reassign_context(PJ_CONTEXT *ctx) override {
2074
0
        HorizontalShiftGridSet::reassign_context(ctx);
2075
0
        m_fp->reassign_context(ctx);
2076
0
    }
2077
};
2078
2079
// ---------------------------------------------------------------------------
2080
2081
class NTv2Grid : public HorizontalShiftGrid {
2082
    friend class NTv2GridSet;
2083
2084
    PJ_CONTEXT *m_ctx;                 // owned by the parent NTv2GridSet
2085
    File *m_fp;                        // owned by the parent NTv2GridSet
2086
    FloatLineCache *m_cache = nullptr; // owned by the parent NTv2GridSet
2087
    uint32_t m_gridIdx;
2088
    unsigned long long m_offset;
2089
    bool m_mustSwap;
2090
    mutable std::vector<float> m_buffer{};
2091
2092
    NTv2Grid(const NTv2Grid &) = delete;
2093
    NTv2Grid &operator=(const NTv2Grid &) = delete;
2094
2095
  public:
2096
    NTv2Grid(const std::string &nameIn, PJ_CONTEXT *ctx, File *fp,
2097
             uint32_t gridIdx, unsigned long long offsetIn, bool mustSwapIn,
2098
             int widthIn, int heightIn, const ExtentAndRes &extentIn)
2099
0
        : HorizontalShiftGrid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx),
2100
0
          m_fp(fp), m_gridIdx(gridIdx), m_offset(offsetIn),
2101
0
          m_mustSwap(mustSwapIn) {}
2102
2103
    bool valueAt(int, int, bool, float &longShift,
2104
                 float &latShift) const override;
2105
2106
0
    const std::string &metadataItem(const std::string &, int) const override {
2107
0
        return emptyString;
2108
0
    }
2109
2110
0
    void setCache(FloatLineCache *cache) { m_cache = cache; }
2111
2112
0
    void reassign_context(PJ_CONTEXT *ctx) override {
2113
0
        m_ctx = ctx;
2114
0
        m_fp->reassign_context(ctx);
2115
0
    }
2116
2117
0
    bool hasChanged() const override { return m_fp->hasChanged(); }
2118
};
2119
2120
// ---------------------------------------------------------------------------
2121
2122
bool NTv2Grid::valueAt(int x, int y, bool compensateNTConvention,
2123
0
                       float &longShift, float &latShift) const {
2124
0
    assert(x >= 0 && y >= 0 && x < m_width && y < m_height);
2125
2126
0
    const std::vector<float> *pBuffer = m_cache->get(m_gridIdx, y);
2127
0
    if (pBuffer == nullptr) {
2128
0
        try {
2129
0
            m_buffer.resize(4 * m_width);
2130
0
        } catch (const std::exception &e) {
2131
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
2132
0
            return false;
2133
0
        }
2134
2135
0
        const size_t nLineSizeInBytes = 4 * sizeof(float) * m_width;
2136
        // there are 4 components: lat shift, long shift, lat error, long error
2137
0
        m_fp->seek(m_offset +
2138
0
                   nLineSizeInBytes * static_cast<unsigned long long>(y));
2139
0
        if (m_fp->read(&m_buffer[0], nLineSizeInBytes) != nLineSizeInBytes) {
2140
0
            proj_context_errno_set(
2141
0
                m_ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2142
0
            return false;
2143
0
        }
2144
        // Remove lat and long error
2145
0
        for (int i = 1; i < m_width; ++i) {
2146
0
            m_buffer[2 * i] = m_buffer[4 * i];
2147
0
            m_buffer[2 * i + 1] = m_buffer[4 * i + 1];
2148
0
        }
2149
0
        m_buffer.resize(2 * m_width);
2150
0
        if (m_mustSwap) {
2151
0
            swap_words(&m_buffer[0], sizeof(float), 2 * m_width);
2152
0
        }
2153
        // NTv2 is organized from east to west !
2154
0
        for (int i = 0; i < m_width / 2; ++i) {
2155
0
            std::swap(m_buffer[2 * i], m_buffer[2 * (m_width - 1 - i)]);
2156
0
            std::swap(m_buffer[2 * i + 1], m_buffer[2 * (m_width - 1 - i) + 1]);
2157
0
        }
2158
2159
0
        try {
2160
0
            m_cache->insert(m_gridIdx, y, m_buffer);
2161
0
        } catch (const std::exception &e) {
2162
            // Should normally not happen
2163
0
            pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
2164
0
        }
2165
0
    }
2166
0
    const std::vector<float> &buffer = pBuffer ? *pBuffer : m_buffer;
2167
2168
    /* convert seconds to radians */
2169
0
    latShift = static_cast<float>(buffer[2 * x] * ((M_PI / 180.0) / 3600.0));
2170
    // west longitude positive convention !
2171
0
    longShift =
2172
0
        (compensateNTConvention ? -1 : 1) *
2173
0
        static_cast<float>(buffer[2 * x + 1] * ((M_PI / 180.0) / 3600.0));
2174
0
    return true;
2175
0
}
2176
2177
// ---------------------------------------------------------------------------
2178
2179
0
NTv2GridSet::~NTv2GridSet() = default;
2180
2181
// ---------------------------------------------------------------------------
2182
2183
std::unique_ptr<NTv2GridSet> NTv2GridSet::open(PJ_CONTEXT *ctx,
2184
                                               std::unique_ptr<File> fp,
2185
0
                                               const std::string &filename) {
2186
0
    File *fpRaw = fp.get();
2187
0
    auto set = std::unique_ptr<NTv2GridSet>(new NTv2GridSet(std::move(fp)));
2188
0
    set->m_name = filename;
2189
0
    set->m_format = "ntv2";
2190
2191
0
    char header[11 * 16];
2192
2193
    /* -------------------------------------------------------------------- */
2194
    /*      Read the header.                                                */
2195
    /* -------------------------------------------------------------------- */
2196
0
    if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
2197
0
        proj_context_errno_set(ctx,
2198
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2199
0
        return nullptr;
2200
0
    }
2201
2202
0
    constexpr int OFFSET_GS_TYPE = 56;
2203
0
    if (memcmp(header + OFFSET_GS_TYPE, "SECONDS", 7) != 0) {
2204
0
        pj_log(ctx, PJ_LOG_ERROR, _("Only GS_TYPE=SECONDS is supported"));
2205
0
        proj_context_errno_set(ctx,
2206
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2207
0
        return nullptr;
2208
0
    }
2209
2210
0
    const bool must_swap = (header[8] == 11) ? !IS_LSB : IS_LSB;
2211
0
    constexpr int OFFSET_NUM_SUBFILES = 8 + 32;
2212
0
    if (must_swap) {
2213
        // swap_words( header+8, 4, 1 );
2214
        // swap_words( header+8+16, 4, 1 );
2215
0
        swap_words(header + OFFSET_NUM_SUBFILES, 4, 1);
2216
        // swap_words( header+8+7*16, 8, 1 );
2217
        // swap_words( header+8+8*16, 8, 1 );
2218
        // swap_words( header+8+9*16, 8, 1 );
2219
        // swap_words( header+8+10*16, 8, 1 );
2220
0
    }
2221
2222
    /* -------------------------------------------------------------------- */
2223
    /*      Get the subfile count out ... all we really use for now.        */
2224
    /* -------------------------------------------------------------------- */
2225
0
    unsigned int num_subfiles;
2226
0
    memcpy(&num_subfiles, header + OFFSET_NUM_SUBFILES, 4);
2227
2228
0
    std::map<std::string, NTv2Grid *> mapGrids;
2229
2230
    /* ==================================================================== */
2231
    /*      Step through the subfiles, creating a grid for each.            */
2232
    /* ==================================================================== */
2233
0
    int largestLine = 1;
2234
0
    for (unsigned subfile = 0; subfile < num_subfiles; subfile++) {
2235
        // Read header
2236
0
        if (fpRaw->read(header, sizeof(header)) != sizeof(header)) {
2237
0
            proj_context_errno_set(
2238
0
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2239
0
            return nullptr;
2240
0
        }
2241
2242
0
        if (strncmp(header, "SUB_NAME", 8) != 0) {
2243
0
            proj_context_errno_set(
2244
0
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2245
0
            return nullptr;
2246
0
        }
2247
2248
        // Byte swap interesting fields if needed.
2249
0
        constexpr int OFFSET_GS_COUNT = 8 + 16 * 10;
2250
0
        constexpr int OFFSET_SOUTH_LAT = 8 + 16 * 4;
2251
0
        if (must_swap) {
2252
            // 6 double values: south, north, east, west, resY,
2253
            // resX
2254
0
            for (int i = 0; i < 6; i++) {
2255
0
                swap_words(header + OFFSET_SOUTH_LAT + 16 * i, sizeof(double),
2256
0
                           1);
2257
0
            }
2258
0
            swap_words(header + OFFSET_GS_COUNT, sizeof(int), 1);
2259
0
        }
2260
2261
0
        std::string gridName;
2262
0
        gridName.append(header + 8, 8);
2263
2264
0
        ExtentAndRes extent;
2265
0
        extent.isGeographic = true;
2266
0
        extent.south = to_double(header + OFFSET_SOUTH_LAT) * DEG_TO_RAD /
2267
0
                       3600.0; /* S_LAT */
2268
0
        extent.north = to_double(header + OFFSET_SOUTH_LAT + 16) * DEG_TO_RAD /
2269
0
                       3600.0; /* N_LAT */
2270
0
        extent.east = -to_double(header + OFFSET_SOUTH_LAT + 16 * 2) *
2271
0
                      DEG_TO_RAD / 3600.0; /* E_LONG */
2272
0
        extent.west = -to_double(header + OFFSET_SOUTH_LAT + 16 * 3) *
2273
0
                      DEG_TO_RAD / 3600.0; /* W_LONG */
2274
0
        extent.resY =
2275
0
            to_double(header + OFFSET_SOUTH_LAT + 16 * 4) * DEG_TO_RAD / 3600.0;
2276
0
        extent.resX =
2277
0
            to_double(header + OFFSET_SOUTH_LAT + 16 * 5) * DEG_TO_RAD / 3600.0;
2278
0
        extent.computeInvRes();
2279
2280
0
        if (!(fabs(extent.west) <= 4 * M_PI && fabs(extent.east) <= 4 * M_PI &&
2281
0
              fabs(extent.north) <= M_PI + 1e-5 &&
2282
0
              fabs(extent.south) <= M_PI + 1e-5 && extent.west < extent.east &&
2283
0
              extent.south < extent.north && extent.resX > 1e-10 &&
2284
0
              extent.resY > 1e-10)) {
2285
0
            pj_log(ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
2286
0
                   filename.c_str());
2287
0
            proj_context_errno_set(
2288
0
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2289
0
            return nullptr;
2290
0
        }
2291
0
        const int columns = static_cast<int>(
2292
0
            fabs((extent.east - extent.west) * extent.invResX + 0.5) + 1);
2293
0
        const int rows = static_cast<int>(
2294
0
            fabs((extent.north - extent.south) * extent.invResY + 0.5) + 1);
2295
0
        if (columns > largestLine)
2296
0
            largestLine = columns;
2297
2298
0
        pj_log(ctx, PJ_LOG_TRACE,
2299
0
               "NTv2 %s %dx%d: LL=(%.9g,%.9g) UR=(%.9g,%.9g)", gridName.c_str(),
2300
0
               columns, rows, extent.west * RAD_TO_DEG,
2301
0
               extent.south * RAD_TO_DEG, extent.east * RAD_TO_DEG,
2302
0
               extent.north * RAD_TO_DEG);
2303
2304
0
        unsigned int gs_count;
2305
0
        memcpy(&gs_count, header + OFFSET_GS_COUNT, 4);
2306
0
        if (gs_count / columns != static_cast<unsigned>(rows)) {
2307
0
            pj_log(ctx, PJ_LOG_ERROR,
2308
0
                   _("GS_COUNT(%u) does not match expected cells (%dx%d)"),
2309
0
                   gs_count, columns, rows);
2310
0
            proj_context_errno_set(
2311
0
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2312
0
            return nullptr;
2313
0
        }
2314
2315
0
        const auto offset = fpRaw->tell();
2316
0
        auto grid = std::unique_ptr<NTv2Grid>(new NTv2Grid(
2317
0
            std::string(filename).append(", ").append(gridName), ctx, fpRaw,
2318
0
            subfile, offset, must_swap, columns, rows, extent));
2319
0
        std::string parentName;
2320
0
        parentName.assign(header + 24, 8);
2321
0
        auto iter = mapGrids.find(parentName);
2322
0
        auto gridPtr = grid.get();
2323
0
        if (iter == mapGrids.end()) {
2324
0
            set->m_grids.emplace_back(std::move(grid));
2325
0
        } else {
2326
0
            iter->second->m_children.emplace_back(std::move(grid));
2327
0
        }
2328
0
        mapGrids[gridName] = gridPtr;
2329
2330
        // Skip grid data. 4 components of size float
2331
0
        fpRaw->seek(static_cast<unsigned long long>(gs_count) * 4 * 4,
2332
0
                    SEEK_CUR);
2333
0
    }
2334
2335
    // Cache up to 1 megapixel per NTv2 file
2336
0
    const int maxLinesInCache = 1024 * 1024 / largestLine;
2337
0
    set->m_cache = std::make_unique<FloatLineCache>(maxLinesInCache);
2338
0
    for (const auto &kv : mapGrids) {
2339
0
        kv.second->setCache(set->m_cache.get());
2340
0
    }
2341
2342
0
    return set;
2343
0
}
2344
2345
#ifdef TIFF_ENABLED
2346
2347
// ---------------------------------------------------------------------------
2348
2349
class GTiffHGridShiftSet : public HorizontalShiftGridSet {
2350
2351
    std::unique_ptr<GTiffDataset> m_GTiffDataset;
2352
2353
    GTiffHGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
2354
0
        : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {}
2355
2356
  public:
2357
    ~GTiffHGridShiftSet() override;
2358
2359
    static std::unique_ptr<GTiffHGridShiftSet>
2360
    open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
2361
         const std::string &filename);
2362
2363
0
    void reassign_context(PJ_CONTEXT *ctx) override {
2364
0
        HorizontalShiftGridSet::reassign_context(ctx);
2365
0
        if (m_GTiffDataset) {
2366
0
            m_GTiffDataset->reassign_context(ctx);
2367
0
        }
2368
0
    }
2369
2370
0
    bool reopen(PJ_CONTEXT *ctx) override {
2371
0
        pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
2372
0
               m_name.c_str());
2373
0
        m_grids.clear();
2374
0
        m_GTiffDataset.reset();
2375
0
        auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
2376
0
        if (!fp) {
2377
0
            return false;
2378
0
        }
2379
0
        auto newGS = open(ctx, std::move(fp), m_name);
2380
0
        if (newGS) {
2381
0
            m_grids = std::move(newGS->m_grids);
2382
0
            m_GTiffDataset = std::move(newGS->m_GTiffDataset);
2383
0
        }
2384
0
        return !m_grids.empty();
2385
0
    }
2386
};
2387
2388
// ---------------------------------------------------------------------------
2389
2390
class GTiffHGrid : public HorizontalShiftGrid {
2391
    friend void insertIntoHierarchy<GTiffHGrid, HorizontalShiftGrid>(
2392
        PJ_CONTEXT *ctx, std::unique_ptr<GTiffHGrid> &&grid,
2393
        const std::string &gridName, const std::string &parentName,
2394
        std::vector<std::unique_ptr<HorizontalShiftGrid>> &topGrids,
2395
        std::map<std::string, GTiffHGrid *> &mapGrids);
2396
2397
    std::unique_ptr<GTiffGrid> m_grid;
2398
    uint16_t m_idxLatShift;
2399
    uint16_t m_idxLongShift;
2400
    double m_convFactorToRadian;
2401
    bool m_positiveEast;
2402
2403
  public:
2404
    GTiffHGrid(std::unique_ptr<GTiffGrid> &&grid, uint16_t idxLatShift,
2405
               uint16_t idxLongShift, double convFactorToRadian,
2406
               bool positiveEast);
2407
2408
    ~GTiffHGrid() override;
2409
2410
    bool valueAt(int x, int y, bool, float &longShift,
2411
                 float &latShift) const override;
2412
2413
    const std::string &metadataItem(const std::string &key,
2414
0
                                    int sample = -1) const override {
2415
0
        return m_grid->metadataItem(key, sample);
2416
0
    }
2417
2418
    void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffHGrid> &&subgrid);
2419
2420
0
    void reassign_context(PJ_CONTEXT *ctx) override {
2421
0
        m_grid->reassign_context(ctx);
2422
0
    }
2423
2424
0
    bool hasChanged() const override { return m_grid->hasChanged(); }
2425
};
2426
2427
// ---------------------------------------------------------------------------
2428
2429
0
GTiffHGridShiftSet::~GTiffHGridShiftSet() = default;
2430
2431
// ---------------------------------------------------------------------------
2432
2433
GTiffHGrid::GTiffHGrid(std::unique_ptr<GTiffGrid> &&grid, uint16_t idxLatShift,
2434
                       uint16_t idxLongShift, double convFactorToRadian,
2435
                       bool positiveEast)
2436
0
    : HorizontalShiftGrid(grid->name(), grid->width(), grid->height(),
2437
0
                          grid->extentAndRes()),
2438
0
      m_grid(std::move(grid)), m_idxLatShift(idxLatShift),
2439
0
      m_idxLongShift(idxLongShift), m_convFactorToRadian(convFactorToRadian),
2440
0
      m_positiveEast(positiveEast) {}
2441
2442
// ---------------------------------------------------------------------------
2443
2444
0
GTiffHGrid::~GTiffHGrid() = default;
2445
2446
// ---------------------------------------------------------------------------
2447
2448
bool GTiffHGrid::valueAt(int x, int y, bool, float &longShift,
2449
0
                         float &latShift) const {
2450
0
    if (!m_grid->valueAt(m_idxLatShift, x, y, latShift) ||
2451
0
        !m_grid->valueAt(m_idxLongShift, x, y, longShift)) {
2452
0
        return false;
2453
0
    }
2454
    // From arc-seconds to radians
2455
0
    latShift = static_cast<float>(latShift * m_convFactorToRadian);
2456
0
    longShift = static_cast<float>(longShift * m_convFactorToRadian);
2457
0
    if (!m_positiveEast) {
2458
0
        longShift = -longShift;
2459
0
    }
2460
0
    return true;
2461
0
}
2462
2463
// ---------------------------------------------------------------------------
2464
2465
void GTiffHGrid::insertGrid(PJ_CONTEXT *ctx,
2466
0
                            std::unique_ptr<GTiffHGrid> &&subgrid) {
2467
0
    bool gridInserted = false;
2468
0
    const auto &extent = subgrid->extentAndRes();
2469
0
    for (const auto &candidateParent : m_children) {
2470
0
        const auto &candidateParentExtent = candidateParent->extentAndRes();
2471
0
        if (candidateParentExtent.contains(extent)) {
2472
0
            static_cast<GTiffHGrid *>(candidateParent.get())
2473
0
                ->insertGrid(ctx, std::move(subgrid));
2474
0
            gridInserted = true;
2475
0
            break;
2476
0
        } else if (candidateParentExtent.intersects(extent)) {
2477
0
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
2478
0
        }
2479
0
    }
2480
0
    if (!gridInserted) {
2481
0
        m_children.emplace_back(std::move(subgrid));
2482
0
    }
2483
0
}
2484
2485
// ---------------------------------------------------------------------------
2486
2487
std::unique_ptr<GTiffHGridShiftSet>
2488
GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
2489
0
                         const std::string &filename) {
2490
0
    auto set = std::unique_ptr<GTiffHGridShiftSet>(
2491
0
        new GTiffHGridShiftSet(ctx, std::move(fp)));
2492
0
    set->m_name = filename;
2493
0
    set->m_format = "gtiff";
2494
0
    if (!set->m_GTiffDataset->openTIFF(filename)) {
2495
0
        return nullptr;
2496
0
    }
2497
2498
    // Defaults inspired from NTv2
2499
0
    uint16_t idxLatShift = 0;
2500
0
    uint16_t idxLongShift = 1;
2501
0
    constexpr double ARC_SECOND_TO_RADIAN = (M_PI / 180.0) / 3600.0;
2502
0
    double convFactorToRadian = ARC_SECOND_TO_RADIAN;
2503
0
    bool positiveEast = true;
2504
2505
0
    std::map<std::string, GTiffHGrid *> mapGrids;
2506
0
    for (int ifd = 0;; ++ifd) {
2507
0
        auto grid = set->m_GTiffDataset->nextGrid();
2508
0
        if (!grid) {
2509
0
            if (ifd == 0) {
2510
0
                return nullptr;
2511
0
            }
2512
0
            break;
2513
0
        }
2514
2515
0
        const auto subfileType = grid->subfileType();
2516
0
        if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
2517
0
            if (ifd == 0) {
2518
0
                pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
2519
0
                return nullptr;
2520
0
            } else {
2521
0
                pj_log(ctx, PJ_LOG_DEBUG,
2522
0
                       _("Ignoring IFD %d as it has a unsupported subfileType"),
2523
0
                       ifd);
2524
0
                continue;
2525
0
            }
2526
0
        }
2527
2528
0
        if (grid->samplesPerPixel() < 2) {
2529
0
            if (ifd == 0) {
2530
0
                pj_log(ctx, PJ_LOG_ERROR,
2531
0
                       _("At least 2 samples per pixel needed"));
2532
0
                return nullptr;
2533
0
            } else {
2534
0
                pj_log(ctx, PJ_LOG_DEBUG,
2535
0
                       _("Ignoring IFD %d as it has not at least 2 samples"),
2536
0
                       ifd);
2537
0
                continue;
2538
0
            }
2539
0
        }
2540
2541
        // Identify the index of the latitude and longitude offset channels
2542
0
        bool foundDescriptionForAtLeastOneSample = false;
2543
0
        bool foundDescriptionForLatOffset = false;
2544
0
        bool foundDescriptionForLongOffset = false;
2545
0
        for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
2546
0
            const auto &desc = grid->metadataItem("DESCRIPTION", i);
2547
0
            if (!desc.empty()) {
2548
0
                foundDescriptionForAtLeastOneSample = true;
2549
0
            }
2550
0
            if (desc == "latitude_offset") {
2551
0
                idxLatShift = static_cast<uint16_t>(i);
2552
0
                foundDescriptionForLatOffset = true;
2553
0
            } else if (desc == "longitude_offset") {
2554
0
                idxLongShift = static_cast<uint16_t>(i);
2555
0
                foundDescriptionForLongOffset = true;
2556
0
            }
2557
0
        }
2558
2559
0
        if (foundDescriptionForAtLeastOneSample) {
2560
0
            if (!foundDescriptionForLongOffset &&
2561
0
                !foundDescriptionForLatOffset) {
2562
0
                if (ifd > 0) {
2563
                    // Assuming that extra IFD without
2564
                    // longitude_offset/latitude_offset can be ignored
2565
                    // One could imagine to put the accuracy values in separate
2566
                    // IFD for example
2567
0
                    pj_log(ctx, PJ_LOG_DEBUG,
2568
0
                           "Ignoring IFD %d as it has no "
2569
0
                           "longitude_offset/latitude_offset channel",
2570
0
                           ifd);
2571
0
                    continue;
2572
0
                } else {
2573
0
                    pj_log(ctx, PJ_LOG_DEBUG,
2574
0
                           "IFD 0 has channel descriptions, but no "
2575
0
                           "longitude_offset/latitude_offset channel");
2576
0
                    return nullptr;
2577
0
                }
2578
0
            }
2579
0
        }
2580
0
        if (foundDescriptionForLatOffset && !foundDescriptionForLongOffset) {
2581
0
            pj_log(
2582
0
                ctx, PJ_LOG_ERROR,
2583
0
                _("Found latitude_offset channel, but not longitude_offset"));
2584
0
            return nullptr;
2585
0
        } else if (foundDescriptionForLongOffset &&
2586
0
                   !foundDescriptionForLatOffset) {
2587
0
            pj_log(
2588
0
                ctx, PJ_LOG_ERROR,
2589
0
                _("Found longitude_offset channel, but not latitude_offset"));
2590
0
            return nullptr;
2591
0
        }
2592
2593
0
        if (idxLatShift >= grid->samplesPerPixel() ||
2594
0
            idxLongShift >= grid->samplesPerPixel()) {
2595
0
            pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index"));
2596
0
            return nullptr;
2597
0
        }
2598
2599
0
        if (foundDescriptionForLongOffset) {
2600
0
            const std::string &positiveValue =
2601
0
                grid->metadataItem("positive_value", idxLongShift);
2602
0
            if (!positiveValue.empty()) {
2603
0
                if (positiveValue == "west") {
2604
0
                    positiveEast = false;
2605
0
                } else if (positiveValue == "east") {
2606
0
                    positiveEast = true;
2607
0
                } else {
2608
0
                    pj_log(ctx, PJ_LOG_ERROR,
2609
0
                           _("Unsupported value %s for 'positive_value'"),
2610
0
                           positiveValue.c_str());
2611
0
                    return nullptr;
2612
0
                }
2613
0
            }
2614
0
        }
2615
2616
        // Identify their unit
2617
0
        {
2618
0
            const auto &unitLatShift =
2619
0
                grid->metadataItem("UNITTYPE", idxLatShift);
2620
0
            const auto &unitLongShift =
2621
0
                grid->metadataItem("UNITTYPE", idxLongShift);
2622
0
            if (unitLatShift != unitLongShift) {
2623
0
                pj_log(ctx, PJ_LOG_ERROR,
2624
0
                       _("Different unit for longitude and latitude offset"));
2625
0
                return nullptr;
2626
0
            }
2627
0
            if (!unitLatShift.empty()) {
2628
0
                if (unitLatShift == "arc-second" ||
2629
0
                    unitLatShift == "arc-seconds per year") {
2630
0
                    convFactorToRadian = ARC_SECOND_TO_RADIAN;
2631
0
                } else if (unitLatShift == "radian") {
2632
0
                    convFactorToRadian = 1.0;
2633
0
                } else if (unitLatShift == "degree") {
2634
0
                    convFactorToRadian = M_PI / 180.0;
2635
0
                } else {
2636
0
                    pj_log(ctx, PJ_LOG_ERROR, _("Unsupported unit %s"),
2637
0
                           unitLatShift.c_str());
2638
0
                    return nullptr;
2639
0
                }
2640
0
            }
2641
0
        }
2642
2643
0
        const std::string &gridName = grid->metadataItem("grid_name");
2644
0
        const std::string &parentName = grid->metadataItem("parent_grid_name");
2645
2646
0
        auto hgrid = std::make_unique<GTiffHGrid>(
2647
0
            std::move(grid), idxLatShift, idxLongShift, convFactorToRadian,
2648
0
            positiveEast);
2649
2650
0
        insertIntoHierarchy(ctx, std::move(hgrid), gridName, parentName,
2651
0
                            set->m_grids, mapGrids);
2652
0
    }
2653
0
    return set;
2654
0
}
2655
#endif // TIFF_ENABLED
2656
2657
// ---------------------------------------------------------------------------
2658
2659
std::unique_ptr<HorizontalShiftGridSet>
2660
3.53k
HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
2661
3.53k
    if (filename == "null") {
2662
178
        auto set = std::unique_ptr<HorizontalShiftGridSet>(
2663
178
            new HorizontalShiftGridSet());
2664
178
        set->m_name = filename;
2665
178
        set->m_format = "null";
2666
178
        set->m_grids.push_back(std::unique_ptr<NullHorizontalShiftGrid>(
2667
178
            new NullHorizontalShiftGrid()));
2668
178
        return set;
2669
178
    }
2670
2671
3.35k
    auto fp = FileManager::open_resource_file(ctx, filename.c_str());
2672
3.35k
    if (!fp) {
2673
2.98k
        return nullptr;
2674
2.98k
    }
2675
378
    const auto &actualName(fp->name());
2676
2677
378
    char header[160];
2678
    /* -------------------------------------------------------------------- */
2679
    /*      Load a header, to determine the file type.                      */
2680
    /* -------------------------------------------------------------------- */
2681
378
    size_t header_size = fp->read(header, sizeof(header));
2682
378
    if (header_size != sizeof(header)) {
2683
        /* some files may be smaller that sizeof(header), eg 160, so */
2684
354
        ctx->last_errno = 0; /* don't treat as a persistent error */
2685
354
        pj_log(ctx, PJ_LOG_DEBUG,
2686
354
               "pj_gridinfo_init: short header read of %d bytes",
2687
354
               (int)header_size);
2688
354
    }
2689
378
    fp->seek(0);
2690
2691
    /* -------------------------------------------------------------------- */
2692
    /*      Determine file type.                                            */
2693
    /* -------------------------------------------------------------------- */
2694
378
    if (header_size >= 144 + 16 && strncmp(header + 0, "HEADER", 6) == 0 &&
2695
0
        strncmp(header + 96, "W GRID", 6) == 0 &&
2696
0
        strncmp(header + 144, "TO      NAD83   ", 16) == 0) {
2697
0
        auto grid = NTv1Grid::open(ctx, std::move(fp), actualName);
2698
0
        if (!grid) {
2699
0
            return nullptr;
2700
0
        }
2701
0
        auto set = std::unique_ptr<HorizontalShiftGridSet>(
2702
0
            new HorizontalShiftGridSet());
2703
0
        set->m_name = actualName;
2704
0
        set->m_format = "ntv1";
2705
0
        set->m_grids.push_back(std::unique_ptr<HorizontalShiftGrid>(grid));
2706
0
        return set;
2707
378
    } else if (header_size >= 9 && strncmp(header + 0, "CTABLE V2", 9) == 0) {
2708
0
        auto grid = CTable2Grid::open(ctx, std::move(fp), actualName);
2709
0
        if (!grid) {
2710
0
            return nullptr;
2711
0
        }
2712
0
        auto set = std::unique_ptr<HorizontalShiftGridSet>(
2713
0
            new HorizontalShiftGridSet());
2714
0
        set->m_name = actualName;
2715
0
        set->m_format = "ctable2";
2716
0
        set->m_grids.push_back(std::unique_ptr<HorizontalShiftGrid>(grid));
2717
0
        return set;
2718
378
    } else if (header_size >= 48 + 7 &&
2719
24
               strncmp(header + 0, "NUM_OREC", 8) == 0 &&
2720
0
               strncmp(header + 48, "GS_TYPE", 7) == 0) {
2721
0
        return NTv2GridSet::open(ctx, std::move(fp), actualName);
2722
378
    } else if (IsTIFF(header_size,
2723
378
                      reinterpret_cast<const unsigned char *>(header))) {
2724
0
#ifdef TIFF_ENABLED
2725
0
        auto set = std::unique_ptr<HorizontalShiftGridSet>(
2726
0
            GTiffHGridShiftSet::open(ctx, std::move(fp), actualName));
2727
0
        if (!set)
2728
0
            proj_context_errno_set(
2729
0
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2730
0
        return set;
2731
#else
2732
        pj_log(ctx, PJ_LOG_ERROR,
2733
               _("TIFF grid, but TIFF support disabled in this build"));
2734
        return nullptr;
2735
#endif
2736
0
    }
2737
2738
378
    pj_log(ctx, PJ_LOG_ERROR,
2739
378
           "Unrecognized horizontal grid format for filename '%s'",
2740
378
           filename.c_str());
2741
378
    return nullptr;
2742
378
}
2743
2744
// ---------------------------------------------------------------------------
2745
2746
0
bool HorizontalShiftGridSet::reopen(PJ_CONTEXT *ctx) {
2747
0
    pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
2748
0
           m_name.c_str());
2749
0
    auto newGS = open(ctx, m_name);
2750
0
    m_grids.clear();
2751
0
    if (newGS) {
2752
0
        m_grids = std::move(newGS->m_grids);
2753
0
    }
2754
0
    return !m_grids.empty();
2755
0
}
2756
2757
// ---------------------------------------------------------------------------
2758
2759
0
#define REL_TOLERANCE_HGRIDSHIFT 1e-5
2760
2761
const HorizontalShiftGrid *HorizontalShiftGrid::gridAt(double longitude,
2762
0
                                                       double lat) const {
2763
0
    for (const auto &child : m_children) {
2764
0
        const auto &extentChild = child->extentAndRes();
2765
0
        const double epsilon =
2766
0
            (extentChild.resX + extentChild.resY) * REL_TOLERANCE_HGRIDSHIFT;
2767
0
        if (isPointInExtent(longitude, lat, extentChild, epsilon)) {
2768
0
            return child->gridAt(longitude, lat);
2769
0
        }
2770
0
    }
2771
0
    return this;
2772
0
}
2773
// ---------------------------------------------------------------------------
2774
2775
const HorizontalShiftGrid *HorizontalShiftGridSet::gridAt(double longitude,
2776
0
                                                          double lat) const {
2777
0
    for (const auto &grid : m_grids) {
2778
0
        if (grid->isNullGrid()) {
2779
0
            return grid.get();
2780
0
        }
2781
0
        const auto &extent = grid->extentAndRes();
2782
0
        const double epsilon =
2783
0
            (extent.resX + extent.resY) * REL_TOLERANCE_HGRIDSHIFT;
2784
0
        if (isPointInExtent(longitude, lat, extent, epsilon)) {
2785
0
            return grid->gridAt(longitude, lat);
2786
0
        }
2787
0
    }
2788
0
    return nullptr;
2789
0
}
2790
2791
// ---------------------------------------------------------------------------
2792
2793
0
void HorizontalShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
2794
0
    for (const auto &grid : m_grids) {
2795
0
        grid->reassign_context(ctx);
2796
0
    }
2797
0
}
2798
2799
#ifdef TIFF_ENABLED
2800
// ---------------------------------------------------------------------------
2801
2802
class GTiffGenericGridShiftSet : public GenericShiftGridSet {
2803
2804
    std::unique_ptr<GTiffDataset> m_GTiffDataset;
2805
2806
    GTiffGenericGridShiftSet(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
2807
0
        : m_GTiffDataset(new GTiffDataset(ctx, std::move(fp))) {}
2808
2809
  public:
2810
    ~GTiffGenericGridShiftSet() override;
2811
2812
    static std::unique_ptr<GTiffGenericGridShiftSet>
2813
    open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
2814
         const std::string &filename);
2815
2816
0
    void reassign_context(PJ_CONTEXT *ctx) override {
2817
0
        GenericShiftGridSet::reassign_context(ctx);
2818
0
        if (m_GTiffDataset) {
2819
0
            m_GTiffDataset->reassign_context(ctx);
2820
0
        }
2821
0
    }
2822
2823
0
    bool reopen(PJ_CONTEXT *ctx) override {
2824
0
        pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
2825
0
               m_name.c_str());
2826
0
        m_grids.clear();
2827
0
        m_GTiffDataset.reset();
2828
0
        auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
2829
0
        if (!fp) {
2830
0
            return false;
2831
0
        }
2832
0
        auto newGS = open(ctx, std::move(fp), m_name);
2833
0
        if (newGS) {
2834
0
            m_grids = std::move(newGS->m_grids);
2835
0
            m_GTiffDataset = std::move(newGS->m_GTiffDataset);
2836
0
        }
2837
0
        return !m_grids.empty();
2838
0
    }
2839
};
2840
2841
// ---------------------------------------------------------------------------
2842
2843
class GTiffGenericGrid final : public GenericShiftGrid {
2844
    friend void insertIntoHierarchy<GTiffGenericGrid, GenericShiftGrid>(
2845
        PJ_CONTEXT *ctx, std::unique_ptr<GTiffGenericGrid> &&grid,
2846
        const std::string &gridName, const std::string &parentName,
2847
        std::vector<std::unique_ptr<GenericShiftGrid>> &topGrids,
2848
        std::map<std::string, GTiffGenericGrid *> &mapGrids);
2849
2850
    std::unique_ptr<GTiffGrid> m_grid;
2851
    const GenericShiftGrid *m_firstGrid = nullptr;
2852
    mutable std::string m_type{};
2853
    mutable bool m_bTypeSet = false;
2854
2855
  public:
2856
    GTiffGenericGrid(std::unique_ptr<GTiffGrid> &&grid);
2857
2858
    ~GTiffGenericGrid() override;
2859
2860
    bool valueAt(int x, int y, int sample, float &out) const override;
2861
2862
    bool valuesAt(int x_start, int y_start, int x_count, int y_count,
2863
                  int sample_count, const int *sample_idx, float *out,
2864
                  bool &nodataFound) const override;
2865
2866
0
    int samplesPerPixel() const override { return m_grid->samplesPerPixel(); }
2867
2868
0
    std::string unit(int sample) const override {
2869
0
        return metadataItem("UNITTYPE", sample);
2870
0
    }
2871
2872
0
    std::string description(int sample) const override {
2873
0
        return metadataItem("DESCRIPTION", sample);
2874
0
    }
2875
2876
    const std::string &metadataItem(const std::string &key,
2877
0
                                    int sample = -1) const override {
2878
0
        const std::string &ret = m_grid->metadataItem(key, sample);
2879
0
        if (ret.empty() && m_firstGrid) {
2880
0
            return m_firstGrid->metadataItem(key, sample);
2881
0
        }
2882
0
        return ret;
2883
0
    }
2884
2885
0
    const std::string &type() const override {
2886
0
        if (!m_bTypeSet) {
2887
0
            m_bTypeSet = true;
2888
0
            m_type = metadataItem("TYPE");
2889
0
        }
2890
0
        return m_type;
2891
0
    }
2892
2893
0
    void setFirstGrid(const GenericShiftGrid *firstGrid) {
2894
0
        m_firstGrid = firstGrid;
2895
0
    }
2896
2897
    void insertGrid(PJ_CONTEXT *ctx,
2898
                    std::unique_ptr<GTiffGenericGrid> &&subgrid);
2899
2900
0
    void reassign_context(PJ_CONTEXT *ctx) override {
2901
0
        m_grid->reassign_context(ctx);
2902
0
    }
2903
2904
0
    bool hasChanged() const override { return m_grid->hasChanged(); }
2905
2906
  private:
2907
    GTiffGenericGrid(const GTiffGenericGrid &) = delete;
2908
    GTiffGenericGrid &operator=(const GTiffGenericGrid &) = delete;
2909
};
2910
2911
// ---------------------------------------------------------------------------
2912
2913
0
GTiffGenericGridShiftSet::~GTiffGenericGridShiftSet() = default;
2914
2915
// ---------------------------------------------------------------------------
2916
2917
GTiffGenericGrid::GTiffGenericGrid(std::unique_ptr<GTiffGrid> &&grid)
2918
0
    : GenericShiftGrid(grid->name(), grid->width(), grid->height(),
2919
0
                       grid->extentAndRes()),
2920
0
      m_grid(std::move(grid)) {}
2921
2922
// ---------------------------------------------------------------------------
2923
2924
0
GTiffGenericGrid::~GTiffGenericGrid() = default;
2925
2926
// ---------------------------------------------------------------------------
2927
2928
0
bool GTiffGenericGrid::valueAt(int x, int y, int sample, float &out) const {
2929
0
    if (sample < 0 ||
2930
0
        static_cast<unsigned>(sample) >= m_grid->samplesPerPixel())
2931
0
        return false;
2932
0
    return m_grid->valueAt(static_cast<uint16_t>(sample), x, y, out);
2933
0
}
2934
2935
// ---------------------------------------------------------------------------
2936
2937
bool GTiffGenericGrid::valuesAt(int x_start, int y_start, int x_count,
2938
                                int y_count, int sample_count,
2939
                                const int *sample_idx, float *out,
2940
0
                                bool &nodataFound) const {
2941
0
    return m_grid->valuesAt(x_start, y_start, x_count, y_count, sample_count,
2942
0
                            sample_idx, out, nodataFound);
2943
0
}
2944
2945
// ---------------------------------------------------------------------------
2946
2947
void GTiffGenericGrid::insertGrid(PJ_CONTEXT *ctx,
2948
0
                                  std::unique_ptr<GTiffGenericGrid> &&subgrid) {
2949
0
    bool gridInserted = false;
2950
0
    const auto &extent = subgrid->extentAndRes();
2951
0
    for (const auto &candidateParent : m_children) {
2952
0
        const auto &candidateParentExtent = candidateParent->extentAndRes();
2953
0
        if (candidateParentExtent.contains(extent)) {
2954
0
            static_cast<GTiffGenericGrid *>(candidateParent.get())
2955
0
                ->insertGrid(ctx, std::move(subgrid));
2956
0
            gridInserted = true;
2957
0
            break;
2958
0
        } else if (candidateParentExtent.intersects(extent)) {
2959
0
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
2960
0
        }
2961
0
    }
2962
0
    if (!gridInserted) {
2963
0
        m_children.emplace_back(std::move(subgrid));
2964
0
    }
2965
0
}
2966
#endif // TIFF_ENABLED
2967
2968
// ---------------------------------------------------------------------------
2969
2970
class NullGenericShiftGrid : public GenericShiftGrid {
2971
2972
  public:
2973
139
    NullGenericShiftGrid() : GenericShiftGrid("null", 3, 3, globalExtent()) {}
2974
2975
0
    bool isNullGrid() const override { return true; }
2976
    bool valueAt(int, int, int, float &out) const override;
2977
2978
0
    const std::string &type() const override { return emptyString; }
2979
2980
0
    int samplesPerPixel() const override { return 0; }
2981
2982
0
    std::string unit(int) const override { return std::string(); }
2983
2984
0
    std::string description(int) const override { return std::string(); }
2985
2986
4
    const std::string &metadataItem(const std::string &, int) const override {
2987
4
        return emptyString;
2988
4
    }
2989
2990
0
    void reassign_context(PJ_CONTEXT *) override {}
2991
2992
0
    bool hasChanged() const override { return false; }
2993
};
2994
2995
// ---------------------------------------------------------------------------
2996
2997
0
bool NullGenericShiftGrid::valueAt(int, int, int, float &out) const {
2998
0
    out = 0.0f;
2999
0
    return true;
3000
0
}
3001
3002
// ---------------------------------------------------------------------------
3003
3004
#ifdef TIFF_ENABLED
3005
3006
std::unique_ptr<GTiffGenericGridShiftSet>
3007
GTiffGenericGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
3008
0
                               const std::string &filename) {
3009
0
    auto set = std::unique_ptr<GTiffGenericGridShiftSet>(
3010
0
        new GTiffGenericGridShiftSet(ctx, std::move(fp)));
3011
0
    set->m_name = filename;
3012
0
    set->m_format = "gtiff";
3013
0
    if (!set->m_GTiffDataset->openTIFF(filename)) {
3014
0
        return nullptr;
3015
0
    }
3016
3017
0
    std::map<std::string, GTiffGenericGrid *> mapGrids;
3018
0
    for (int ifd = 0;; ++ifd) {
3019
0
        auto grid = set->m_GTiffDataset->nextGrid();
3020
0
        if (!grid) {
3021
0
            if (ifd == 0) {
3022
0
                return nullptr;
3023
0
            }
3024
0
            break;
3025
0
        }
3026
3027
0
        const auto subfileType = grid->subfileType();
3028
0
        if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
3029
0
            if (ifd == 0) {
3030
0
                pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
3031
0
                return nullptr;
3032
0
            } else {
3033
0
                pj_log(ctx, PJ_LOG_DEBUG,
3034
0
                       _("Ignoring IFD %d as it has a unsupported subfileType"),
3035
0
                       ifd);
3036
0
                continue;
3037
0
            }
3038
0
        }
3039
3040
0
        const std::string &gridName = grid->metadataItem("grid_name");
3041
0
        const std::string &parentName = grid->metadataItem("parent_grid_name");
3042
3043
0
        auto ggrid = std::make_unique<GTiffGenericGrid>(std::move(grid));
3044
0
        if (!set->m_grids.empty() && ggrid->metadataItem("TYPE").empty() &&
3045
0
            !set->m_grids[0]->metadataItem("TYPE").empty()) {
3046
0
            ggrid->setFirstGrid(set->m_grids[0].get());
3047
0
        }
3048
3049
0
        insertIntoHierarchy(ctx, std::move(ggrid), gridName, parentName,
3050
0
                            set->m_grids, mapGrids);
3051
0
    }
3052
0
    return set;
3053
0
}
3054
#endif // TIFF_ENABLED
3055
3056
// ---------------------------------------------------------------------------
3057
3058
GenericShiftGrid::GenericShiftGrid(const std::string &nameIn, int widthIn,
3059
                                   int heightIn, const ExtentAndRes &extentIn)
3060
139
    : Grid(nameIn, widthIn, heightIn, extentIn) {}
3061
3062
// ---------------------------------------------------------------------------
3063
3064
139
GenericShiftGrid::~GenericShiftGrid() = default;
3065
3066
// ---------------------------------------------------------------------------
3067
3068
bool GenericShiftGrid::valuesAt(int x_start, int y_start, int x_count,
3069
                                int y_count, int sample_count,
3070
                                const int *sample_idx, float *out,
3071
0
                                bool &nodataFound) const {
3072
0
    nodataFound = false;
3073
0
    for (int y = y_start; y < y_start + y_count; ++y) {
3074
0
        for (int x = x_start; x < x_start + x_count; ++x) {
3075
0
            for (int isample = 0; isample < sample_count; ++isample) {
3076
0
                if (!valueAt(x, y, sample_idx[isample], *out))
3077
0
                    return false;
3078
0
                ++out;
3079
0
            }
3080
0
        }
3081
0
    }
3082
0
    return true;
3083
0
}
3084
3085
// ---------------------------------------------------------------------------
3086
3087
139
GenericShiftGridSet::GenericShiftGridSet() = default;
3088
3089
// ---------------------------------------------------------------------------
3090
3091
139
GenericShiftGridSet::~GenericShiftGridSet() = default;
3092
3093
// ---------------------------------------------------------------------------
3094
3095
std::unique_ptr<GenericShiftGridSet>
3096
2.18k
GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
3097
2.18k
    if (filename == "null") {
3098
139
        auto set =
3099
139
            std::unique_ptr<GenericShiftGridSet>(new GenericShiftGridSet());
3100
139
        set->m_name = filename;
3101
139
        set->m_format = "null";
3102
139
        set->m_grids.push_back(
3103
139
            std::unique_ptr<NullGenericShiftGrid>(new NullGenericShiftGrid()));
3104
139
        return set;
3105
139
    }
3106
3107
2.04k
    auto fp = FileManager::open_resource_file(ctx, filename.c_str());
3108
2.04k
    if (!fp) {
3109
1.84k
        return nullptr;
3110
1.84k
    }
3111
3112
    /* -------------------------------------------------------------------- */
3113
    /*      Load a header, to determine the file type.                      */
3114
    /* -------------------------------------------------------------------- */
3115
202
    unsigned char header[4];
3116
202
    size_t header_size = fp->read(header, sizeof(header));
3117
202
    if (header_size != sizeof(header)) {
3118
189
        return nullptr;
3119
189
    }
3120
13
    fp->seek(0);
3121
3122
13
    if (IsTIFF(header_size, header)) {
3123
0
#ifdef TIFF_ENABLED
3124
0
        const std::string actualName(fp->name());
3125
0
        auto set = std::unique_ptr<GenericShiftGridSet>(
3126
0
            GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName));
3127
0
        if (!set)
3128
0
            proj_context_errno_set(
3129
0
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3130
0
        return set;
3131
#else
3132
        pj_log(ctx, PJ_LOG_ERROR,
3133
               _("TIFF grid, but TIFF support disabled in this build"));
3134
        return nullptr;
3135
#endif
3136
0
    }
3137
3138
13
    pj_log(ctx, PJ_LOG_ERROR,
3139
13
           "Unrecognized generic grid format for filename '%s'",
3140
13
           filename.c_str());
3141
13
    return nullptr;
3142
13
}
3143
3144
// ---------------------------------------------------------------------------
3145
3146
0
bool GenericShiftGridSet::reopen(PJ_CONTEXT *ctx) {
3147
0
    pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
3148
0
           m_name.c_str());
3149
0
    auto newGS = open(ctx, m_name);
3150
0
    m_grids.clear();
3151
0
    if (newGS) {
3152
0
        m_grids = std::move(newGS->m_grids);
3153
0
    }
3154
0
    return !m_grids.empty();
3155
0
}
3156
3157
// ---------------------------------------------------------------------------
3158
3159
0
const GenericShiftGrid *GenericShiftGrid::gridAt(double x, double y) const {
3160
0
    for (const auto &child : m_children) {
3161
0
        const auto &extentChild = child->extentAndRes();
3162
0
        if (isPointInExtent(x, y, extentChild)) {
3163
0
            return child->gridAt(x, y);
3164
0
        }
3165
0
    }
3166
0
    return this;
3167
0
}
3168
3169
// ---------------------------------------------------------------------------
3170
3171
0
const GenericShiftGrid *GenericShiftGridSet::gridAt(double x, double y) const {
3172
0
    for (const auto &grid : m_grids) {
3173
0
        if (grid->isNullGrid()) {
3174
0
            return grid.get();
3175
0
        }
3176
0
        const auto &extent = grid->extentAndRes();
3177
0
        if (isPointInExtent(x, y, extent)) {
3178
0
            return grid->gridAt(x, y);
3179
0
        }
3180
0
    }
3181
0
    return nullptr;
3182
0
}
3183
3184
// ---------------------------------------------------------------------------
3185
3186
const GenericShiftGrid *GenericShiftGridSet::gridAt(const std::string &type,
3187
0
                                                    double x, double y) const {
3188
0
    for (const auto &grid : m_grids) {
3189
0
        if (grid->isNullGrid()) {
3190
0
            return grid.get();
3191
0
        }
3192
0
        if (grid->type() != type) {
3193
0
            continue;
3194
0
        }
3195
0
        const auto &extent = grid->extentAndRes();
3196
0
        if (isPointInExtent(x, y, extent)) {
3197
0
            return grid->gridAt(x, y);
3198
0
        }
3199
0
    }
3200
0
    return nullptr;
3201
0
}
3202
3203
// ---------------------------------------------------------------------------
3204
3205
0
void GenericShiftGridSet::reassign_context(PJ_CONTEXT *ctx) {
3206
0
    for (const auto &grid : m_grids) {
3207
0
        grid->reassign_context(ctx);
3208
0
    }
3209
0
}
3210
3211
// ---------------------------------------------------------------------------
3212
3213
447
ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *gridkey) {
3214
447
    std::string key("s");
3215
447
    key += gridkey;
3216
447
    const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s;
3217
447
    if (gridnames == nullptr)
3218
0
        return {};
3219
3220
447
    auto listOfGridNames = internal::split(std::string(gridnames), ',');
3221
447
    ListOfGenericGrids grids;
3222
2.18k
    for (const auto &gridnameStr : listOfGridNames) {
3223
2.18k
        const char *gridname = gridnameStr.c_str();
3224
2.18k
        bool canFail = false;
3225
2.18k
        if (gridname[0] == '@') {
3226
1.99k
            canFail = true;
3227
1.99k
            gridname++;
3228
1.99k
        }
3229
2.18k
        auto gridSet = GenericShiftGridSet::open(P->ctx, gridname);
3230
2.18k
        if (!gridSet) {
3231
2.04k
            if (!canFail) {
3232
51
                if (proj_context_errno(P->ctx) !=
3233
51
                    PROJ_ERR_OTHER_NETWORK_ERROR) {
3234
51
                    proj_context_errno_set(
3235
51
                        P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3236
51
                }
3237
51
                return {};
3238
51
            }
3239
1.99k
            proj_context_errno_set(P->ctx,
3240
1.99k
                                   0); // don't treat as a persistent error
3241
1.99k
        } else {
3242
139
            grids.emplace_back(std::move(gridSet));
3243
139
        }
3244
2.18k
    }
3245
3246
396
    return grids;
3247
447
}
3248
3249
// ---------------------------------------------------------------------------
3250
3251
static const HorizontalShiftGrid *
3252
findGrid(const ListOfHGrids &grids, const PJ_LP &input,
3253
0
         HorizontalShiftGridSet *&gridSetOut) {
3254
0
    for (const auto &gridset : grids) {
3255
0
        auto grid = gridset->gridAt(input.lam, input.phi);
3256
0
        if (grid) {
3257
0
            gridSetOut = gridset.get();
3258
0
            return grid;
3259
0
        }
3260
0
    }
3261
0
    return nullptr;
3262
0
}
3263
3264
// ---------------------------------------------------------------------------
3265
3266
997
static ListOfHGrids getListOfGridSets(PJ_CONTEXT *ctx, const char *grids) {
3267
997
    ListOfHGrids list;
3268
997
    auto listOfGrids = internal::split(std::string(grids), ',');
3269
3.53k
    for (const auto &grid : listOfGrids) {
3270
3.53k
        const char *gridname = grid.c_str();
3271
3.53k
        bool canFail = false;
3272
3.53k
        if (gridname[0] == '@') {
3273
2.53k
            canFail = true;
3274
2.53k
            gridname++;
3275
2.53k
        }
3276
3.53k
        auto gridSet = HorizontalShiftGridSet::open(ctx, gridname);
3277
3.53k
        if (!gridSet) {
3278
3.35k
            if (!canFail) {
3279
821
                if (proj_context_errno(ctx) != PROJ_ERR_OTHER_NETWORK_ERROR) {
3280
821
                    proj_context_errno_set(
3281
821
                        ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3282
821
                }
3283
821
                return {};
3284
821
            }
3285
2.53k
            proj_context_errno_set(ctx, 0); // don't treat as a persistent error
3286
2.53k
        } else {
3287
178
            list.emplace_back(std::move(gridSet));
3288
178
        }
3289
3.53k
    }
3290
176
    return list;
3291
997
}
3292
3293
/**********************************************/
3294
997
ListOfHGrids pj_hgrid_init(PJ *P, const char *gridkey) {
3295
    /**********************************************
3296
3297
      Initizalize and populate list of horizontal
3298
      grids.
3299
3300
        Takes a PJ-object and the plus-parameter
3301
        name that is used in the proj-string to
3302
        specify the grids to load, e.g. "+grids".
3303
        The + should be left out here.
3304
3305
        Returns the number of loaded grids.
3306
3307
    ***********************************************/
3308
3309
997
    std::string key("s");
3310
997
    key += gridkey;
3311
997
    const char *grids = pj_param(P->ctx, P->params, key.c_str()).s;
3312
997
    if (grids == nullptr)
3313
0
        return {};
3314
3315
997
    return getListOfGridSets(P->ctx, grids);
3316
997
}
3317
3318
// ---------------------------------------------------------------------------
3319
3320
typedef struct {
3321
    int32_t lam, phi;
3322
} ILP;
3323
3324
// Apply bilinear interpolation for horizontal shift grids
3325
static PJ_LP pj_hgrid_interpolate(PJ_LP t, const HorizontalShiftGrid *grid,
3326
0
                                  bool compensateNTConvention) {
3327
0
    PJ_LP val, frct;
3328
0
    ILP indx;
3329
0
    int in;
3330
3331
0
    const auto &extent = grid->extentAndRes();
3332
0
    t.lam /= extent.resX;
3333
0
    indx.lam = std::isnan(t.lam) ? 0 : (int32_t)lround(floor(t.lam));
3334
0
    t.phi /= extent.resY;
3335
0
    indx.phi = std::isnan(t.phi) ? 0 : (int32_t)lround(floor(t.phi));
3336
3337
0
    frct.lam = t.lam - indx.lam;
3338
0
    frct.phi = t.phi - indx.phi;
3339
0
    val.lam = val.phi = HUGE_VAL;
3340
0
    if (indx.lam < 0) {
3341
0
        if (indx.lam == -1 && frct.lam > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) {
3342
0
            ++indx.lam;
3343
0
            frct.lam = 0.;
3344
0
        } else
3345
0
            return val;
3346
0
    } else if ((in = indx.lam + 1) >= grid->width()) {
3347
0
        if (in == grid->width() && frct.lam < 10 * REL_TOLERANCE_HGRIDSHIFT) {
3348
0
            --indx.lam;
3349
0
            frct.lam = 1.;
3350
0
        } else
3351
0
            return val;
3352
0
    }
3353
0
    if (indx.phi < 0) {
3354
0
        if (indx.phi == -1 && frct.phi > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) {
3355
0
            ++indx.phi;
3356
0
            frct.phi = 0.;
3357
0
        } else
3358
0
            return val;
3359
0
    } else if ((in = indx.phi + 1) >= grid->height()) {
3360
0
        if (in == grid->height() && frct.phi < 10 * REL_TOLERANCE_HGRIDSHIFT) {
3361
0
            --indx.phi;
3362
0
            frct.phi = 1.;
3363
0
        } else
3364
0
            return val;
3365
0
    }
3366
3367
0
    float f00Long = 0, f00Lat = 0;
3368
0
    float f10Long = 0, f10Lat = 0;
3369
0
    float f01Long = 0, f01Lat = 0;
3370
0
    float f11Long = 0, f11Lat = 0;
3371
0
    if (!grid->valueAt(indx.lam, indx.phi, compensateNTConvention, f00Long,
3372
0
                       f00Lat) ||
3373
0
        !grid->valueAt(indx.lam + 1, indx.phi, compensateNTConvention, f10Long,
3374
0
                       f10Lat) ||
3375
0
        !grid->valueAt(indx.lam, indx.phi + 1, compensateNTConvention, f01Long,
3376
0
                       f01Lat) ||
3377
0
        !grid->valueAt(indx.lam + 1, indx.phi + 1, compensateNTConvention,
3378
0
                       f11Long, f11Lat)) {
3379
0
        return val;
3380
0
    }
3381
3382
0
    double m10 = frct.lam;
3383
0
    double m11 = m10;
3384
0
    double m01 = 1. - frct.lam;
3385
0
    double m00 = m01;
3386
0
    m11 *= frct.phi;
3387
0
    m01 *= frct.phi;
3388
0
    frct.phi = 1. - frct.phi;
3389
0
    m00 *= frct.phi;
3390
0
    m10 *= frct.phi;
3391
0
    val.lam = m00 * f00Long + m10 * f10Long + m01 * f01Long + m11 * f11Long;
3392
0
    val.phi = m00 * f00Lat + m10 * f10Lat + m01 * f01Lat + m11 * f11Lat;
3393
0
    return val;
3394
0
}
3395
3396
// ---------------------------------------------------------------------------
3397
3398
0
#define MAX_ITERATIONS 10
3399
0
#define TOL 1e-12
3400
3401
static PJ_LP pj_hgrid_apply_internal(PJ_CONTEXT *ctx, PJ_LP in,
3402
                                     PJ_DIRECTION direction,
3403
                                     const HorizontalShiftGrid *grid,
3404
                                     HorizontalShiftGridSet *gridset,
3405
                                     const ListOfHGrids &grids,
3406
0
                                     bool &shouldRetry) {
3407
0
    PJ_LP t, tb, del, dif;
3408
0
    int i = MAX_ITERATIONS;
3409
0
    const double toltol = TOL * TOL;
3410
3411
0
    shouldRetry = false;
3412
0
    if (in.lam == HUGE_VAL)
3413
0
        return in;
3414
3415
    /* normalize input to ll origin */
3416
0
    tb = in;
3417
0
    const auto *extent = &(grid->extentAndRes());
3418
0
    const double epsilon =
3419
0
        (extent->resX + extent->resY) * REL_TOLERANCE_HGRIDSHIFT;
3420
0
    tb.lam -= extent->west;
3421
0
    if (tb.lam + epsilon < 0)
3422
0
        tb.lam += 2 * M_PI;
3423
0
    else if (tb.lam - epsilon > extent->east - extent->west)
3424
0
        tb.lam -= 2 * M_PI;
3425
0
    tb.phi -= extent->south;
3426
3427
0
    t = pj_hgrid_interpolate(tb, grid, true);
3428
0
    if (grid->hasChanged()) {
3429
0
        shouldRetry = gridset->reopen(ctx);
3430
0
        return t;
3431
0
    }
3432
0
    if (t.lam == HUGE_VAL)
3433
0
        return t;
3434
3435
0
    if (direction == PJ_FWD) {
3436
0
        in.lam += t.lam;
3437
0
        in.phi += t.phi;
3438
0
        return in;
3439
0
    }
3440
3441
0
    t.lam = tb.lam - t.lam;
3442
0
    t.phi = tb.phi - t.phi;
3443
3444
0
    do {
3445
0
        del = pj_hgrid_interpolate(t, grid, true);
3446
0
        if (grid->hasChanged()) {
3447
0
            shouldRetry = gridset->reopen(ctx);
3448
0
            return t;
3449
0
        }
3450
3451
        /* We can possibly go outside of the initial guessed grid, so try */
3452
        /* to fetch a new grid into which iterate... */
3453
0
        if (del.lam == HUGE_VAL) {
3454
0
            PJ_LP lp;
3455
0
            lp.lam = t.lam + extent->west;
3456
0
            lp.phi = t.phi + extent->south;
3457
0
            auto newGrid = findGrid(grids, lp, gridset);
3458
0
            if (newGrid == nullptr || newGrid == grid || newGrid->isNullGrid())
3459
0
                break;
3460
0
            pj_log(ctx, PJ_LOG_TRACE, "Switching from grid %s to grid %s",
3461
0
                   grid->name().c_str(), newGrid->name().c_str());
3462
0
            grid = newGrid;
3463
0
            extent = &(grid->extentAndRes());
3464
0
            t.lam = lp.lam - extent->west;
3465
0
            t.phi = lp.phi - extent->south;
3466
0
            tb = in;
3467
0
            tb.lam -= extent->west;
3468
0
            if (tb.lam + epsilon < 0)
3469
0
                tb.lam += 2 * M_PI;
3470
0
            else if (tb.lam - epsilon > extent->east - extent->west)
3471
0
                tb.lam -= 2 * M_PI;
3472
0
            tb.phi -= extent->south;
3473
0
            dif.lam = std::numeric_limits<double>::max();
3474
0
            dif.phi = std::numeric_limits<double>::max();
3475
0
            continue;
3476
0
        }
3477
3478
0
        dif.lam = t.lam + del.lam - tb.lam;
3479
0
        dif.phi = t.phi + del.phi - tb.phi;
3480
0
        t.lam -= dif.lam;
3481
0
        t.phi -= dif.phi;
3482
3483
0
    } while (--i && (dif.lam * dif.lam + dif.phi * dif.phi >
3484
0
                     toltol)); /* prob. slightly faster than hypot() */
3485
3486
0
    if (i == 0) {
3487
0
        pj_log(ctx, PJ_LOG_TRACE,
3488
0
               "Inverse grid shift iterator failed to converge.\n");
3489
0
        proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
3490
0
        t.lam = t.phi = HUGE_VAL;
3491
0
        return t;
3492
0
    }
3493
3494
    /* and again: pj_log and ctx->errno */
3495
0
    if (del.lam == HUGE_VAL) {
3496
0
        pj_log(ctx, PJ_LOG_TRACE,
3497
0
               "Inverse grid shift iteration failed, presumably at "
3498
0
               "grid edge.\nUsing first approximation.\n");
3499
0
    }
3500
3501
0
    in.lam = adjlon(t.lam + extent->west);
3502
0
    in.phi = t.phi + extent->south;
3503
0
    return in;
3504
0
}
3505
3506
// ---------------------------------------------------------------------------
3507
3508
PJ_LP pj_hgrid_apply(PJ_CONTEXT *ctx, const ListOfHGrids &grids, PJ_LP lp,
3509
0
                     PJ_DIRECTION direction) {
3510
0
    PJ_LP out;
3511
3512
0
    out.lam = HUGE_VAL;
3513
0
    out.phi = HUGE_VAL;
3514
3515
0
    while (true) {
3516
0
        HorizontalShiftGridSet *gridset = nullptr;
3517
0
        const auto grid = findGrid(grids, lp, gridset);
3518
0
        if (!grid) {
3519
0
            proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
3520
0
            return out;
3521
0
        }
3522
0
        if (grid->isNullGrid()) {
3523
0
            return lp;
3524
0
        }
3525
3526
0
        bool shouldRetry = false;
3527
0
        out = pj_hgrid_apply_internal(ctx, lp, direction, grid, gridset, grids,
3528
0
                                      shouldRetry);
3529
0
        if (!shouldRetry) {
3530
0
            break;
3531
0
        }
3532
0
    }
3533
3534
0
    if (out.lam == HUGE_VAL || out.phi == HUGE_VAL)
3535
0
        proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
3536
3537
0
    return out;
3538
0
}
3539
3540
/********************************************/
3541
/*           proj_hgrid_value()             */
3542
/*                                          */
3543
/*    Return coordinate offset in grid      */
3544
/********************************************/
3545
0
PJ_LP pj_hgrid_value(PJ *P, const ListOfHGrids &grids, PJ_LP lp) {
3546
0
    PJ_LP out = proj_coord_error().lp;
3547
3548
0
    HorizontalShiftGridSet *gridset = nullptr;
3549
0
    const auto grid = findGrid(grids, lp, gridset);
3550
0
    if (!grid) {
3551
0
        proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
3552
0
        return out;
3553
0
    }
3554
3555
    /* normalize input to ll origin */
3556
0
    const auto &extent = grid->extentAndRes();
3557
0
    if (!extent.isGeographic) {
3558
0
        pj_log(P->ctx, PJ_LOG_ERROR,
3559
0
               _("Can only handle grids referenced in a geographic CRS"));
3560
0
        proj_context_errno_set(P->ctx,
3561
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3562
0
        return out;
3563
0
    }
3564
3565
0
    const double epsilon =
3566
0
        (extent.resX + extent.resY) * REL_TOLERANCE_HGRIDSHIFT;
3567
0
    lp.lam -= extent.west;
3568
0
    if (lp.lam + epsilon < 0)
3569
0
        lp.lam += 2 * M_PI;
3570
0
    else if (lp.lam - epsilon > extent.east - extent.west)
3571
0
        lp.lam -= 2 * M_PI;
3572
0
    lp.phi -= extent.south;
3573
3574
0
    out = pj_hgrid_interpolate(lp, grid, false);
3575
0
    if (grid->hasChanged()) {
3576
0
        if (gridset->reopen(P->ctx)) {
3577
0
            return pj_hgrid_value(P, grids, lp);
3578
0
        }
3579
0
        out.lam = HUGE_VAL;
3580
0
        out.phi = HUGE_VAL;
3581
0
    }
3582
3583
0
    if (out.lam == HUGE_VAL || out.phi == HUGE_VAL) {
3584
0
        proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
3585
0
    }
3586
3587
0
    return out;
3588
0
}
3589
3590
// ---------------------------------------------------------------------------
3591
3592
static double read_vgrid_value(PJ_CONTEXT *ctx, const ListOfVGrids &grids,
3593
0
                               const PJ_LP &input, const double vmultiplier) {
3594
3595
    /* do not deal with NaN coordinates */
3596
    /* cppcheck-suppress duplicateExpression */
3597
0
    if (std::isnan(input.phi) || std::isnan(input.lam)) {
3598
0
        return HUGE_VAL;
3599
0
    }
3600
3601
0
    VerticalShiftGridSet *curGridset = nullptr;
3602
0
    const VerticalShiftGrid *grid = nullptr;
3603
0
    for (const auto &gridset : grids) {
3604
0
        grid = gridset->gridAt(input.lam, input.phi);
3605
0
        if (grid) {
3606
0
            curGridset = gridset.get();
3607
0
            break;
3608
0
        }
3609
0
    }
3610
0
    if (!grid) {
3611
0
        proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
3612
0
        return HUGE_VAL;
3613
0
    }
3614
0
    if (grid->isNullGrid()) {
3615
0
        return 0;
3616
0
    }
3617
3618
0
    const auto &extent = grid->extentAndRes();
3619
0
    if (!extent.isGeographic) {
3620
0
        pj_log(ctx, PJ_LOG_ERROR,
3621
0
               _("Can only handle grids referenced in a geographic CRS"));
3622
0
        proj_context_errno_set(ctx,
3623
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3624
0
        return HUGE_VAL;
3625
0
    }
3626
3627
    /* Interpolation of a location within the grid */
3628
0
    double grid_x = (input.lam - extent.west) * extent.invResX;
3629
0
    if (input.lam < extent.west) {
3630
0
        if (extent.fullWorldLongitude()) {
3631
            // The first fmod goes to ]-lim, lim[ range
3632
            // So we add lim again to be in ]0, 2*lim[ and fmod again
3633
0
            grid_x = fmod(fmod(grid_x + grid->width(), grid->width()) +
3634
0
                              grid->width(),
3635
0
                          grid->width());
3636
0
        } else {
3637
0
            grid_x = (input.lam + 2 * M_PI - extent.west) * extent.invResX;
3638
0
        }
3639
0
    } else if (input.lam > extent.east) {
3640
0
        if (extent.fullWorldLongitude()) {
3641
            // The first fmod goes to ]-lim, lim[ range
3642
            // So we add lim again to be in ]0, 2*lim[ and fmod again
3643
0
            grid_x = fmod(fmod(grid_x + grid->width(), grid->width()) +
3644
0
                              grid->width(),
3645
0
                          grid->width());
3646
0
        } else {
3647
0
            grid_x = (input.lam - 2 * M_PI - extent.west) * extent.invResX;
3648
0
        }
3649
0
    }
3650
0
    double grid_y = (input.phi - extent.south) * extent.invResY;
3651
0
    int grid_ix = static_cast<int>(lround(floor(grid_x)));
3652
0
    if (!(grid_ix >= 0 && grid_ix < grid->width())) {
3653
        // in the unlikely case we end up here...
3654
0
        pj_log(ctx, PJ_LOG_ERROR, _("grid_ix not in grid"));
3655
0
        proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID);
3656
0
        return HUGE_VAL;
3657
0
    }
3658
0
    int grid_iy = static_cast<int>(lround(floor(grid_y)));
3659
0
    assert(grid_iy >= 0 && grid_iy < grid->height());
3660
0
    grid_x -= grid_ix;
3661
0
    grid_y -= grid_iy;
3662
3663
0
    int grid_ix2 = grid_ix + 1;
3664
0
    if (grid_ix2 >= grid->width()) {
3665
0
        if (extent.fullWorldLongitude()) {
3666
0
            grid_ix2 = 0;
3667
0
        } else {
3668
0
            grid_ix2 = grid->width() - 1;
3669
0
        }
3670
0
    }
3671
0
    int grid_iy2 = grid_iy + 1;
3672
0
    if (grid_iy2 >= grid->height())
3673
0
        grid_iy2 = grid->height() - 1;
3674
3675
0
    float value_a = 0;
3676
0
    float value_b = 0;
3677
0
    float value_c = 0;
3678
0
    float value_d = 0;
3679
0
    bool error = (!grid->valueAt(grid_ix, grid_iy, value_a) ||
3680
0
                  !grid->valueAt(grid_ix2, grid_iy, value_b) ||
3681
0
                  !grid->valueAt(grid_ix, grid_iy2, value_c) ||
3682
0
                  !grid->valueAt(grid_ix2, grid_iy2, value_d));
3683
0
    if (grid->hasChanged()) {
3684
0
        if (curGridset->reopen(ctx)) {
3685
0
            return read_vgrid_value(ctx, grids, input, vmultiplier);
3686
0
        }
3687
0
        error = true;
3688
0
    }
3689
3690
0
    if (error) {
3691
0
        return HUGE_VAL;
3692
0
    }
3693
3694
0
    double value = 0.0;
3695
3696
0
    const double grid_x_y = grid_x * grid_y;
3697
0
    const bool a_valid = !grid->isNodata(value_a, vmultiplier);
3698
0
    const bool b_valid = !grid->isNodata(value_b, vmultiplier);
3699
0
    const bool c_valid = !grid->isNodata(value_c, vmultiplier);
3700
0
    const bool d_valid = !grid->isNodata(value_d, vmultiplier);
3701
0
    const int countValid =
3702
0
        static_cast<int>(a_valid) + static_cast<int>(b_valid) +
3703
0
        static_cast<int>(c_valid) + static_cast<int>(d_valid);
3704
0
    if (countValid == 4) {
3705
0
        {
3706
0
            double weight = 1.0 - grid_x - grid_y + grid_x_y;
3707
0
            value = value_a * weight;
3708
0
        }
3709
0
        {
3710
0
            double weight = grid_x - grid_x_y;
3711
0
            value += value_b * weight;
3712
0
        }
3713
0
        {
3714
0
            double weight = grid_y - grid_x_y;
3715
0
            value += value_c * weight;
3716
0
        }
3717
0
        {
3718
0
            double weight = grid_x_y;
3719
0
            value += value_d * weight;
3720
0
        }
3721
0
    } else if (countValid == 0) {
3722
0
        proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_GRID_AT_NODATA);
3723
0
        value = HUGE_VAL;
3724
0
    } else {
3725
0
        double total_weight = 0.0;
3726
0
        if (a_valid) {
3727
0
            double weight = 1.0 - grid_x - grid_y + grid_x_y;
3728
0
            value = value_a * weight;
3729
0
            total_weight = weight;
3730
0
        }
3731
0
        if (b_valid) {
3732
0
            double weight = grid_x - grid_x_y;
3733
0
            value += value_b * weight;
3734
0
            total_weight += weight;
3735
0
        }
3736
0
        if (c_valid) {
3737
0
            double weight = grid_y - grid_x_y;
3738
0
            value += value_c * weight;
3739
0
            total_weight += weight;
3740
0
        }
3741
0
        if (d_valid) {
3742
0
            double weight = grid_x_y;
3743
0
            value += value_d * weight;
3744
0
            total_weight += weight;
3745
0
        }
3746
0
        value /= total_weight;
3747
0
    }
3748
3749
0
    return value * vmultiplier;
3750
0
}
3751
3752
/**********************************************/
3753
8.22k
ListOfVGrids pj_vgrid_init(PJ *P, const char *gridkey) {
3754
    /**********************************************
3755
3756
      Initizalize and populate gridlist.
3757
3758
        Takes a PJ-object and the plus-parameter
3759
        name that is used in the proj-string to
3760
        specify the grids to load, e.g. "+grids".
3761
        The + should be left out here.
3762
3763
        Returns the number of loaded grids.
3764
3765
    ***********************************************/
3766
3767
8.22k
    std::string key("s");
3768
8.22k
    key += gridkey;
3769
8.22k
    const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s;
3770
8.22k
    if (gridnames == nullptr)
3771
0
        return {};
3772
3773
8.22k
    auto listOfGridNames = internal::split(std::string(gridnames), ',');
3774
8.22k
    ListOfVGrids grids;
3775
9.12k
    for (const auto &gridnameStr : listOfGridNames) {
3776
9.12k
        const char *gridname = gridnameStr.c_str();
3777
9.12k
        bool canFail = false;
3778
9.12k
        if (gridname[0] == '@') {
3779
1.00k
            canFail = true;
3780
1.00k
            gridname++;
3781
1.00k
        }
3782
9.12k
        auto gridSet = VerticalShiftGridSet::open(P->ctx, gridname);
3783
9.12k
        if (!gridSet) {
3784
9.10k
            if (!canFail) {
3785
8.09k
                if (proj_context_errno(P->ctx) !=
3786
8.09k
                    PROJ_ERR_OTHER_NETWORK_ERROR) {
3787
8.09k
                    proj_context_errno_set(
3788
8.09k
                        P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3789
8.09k
                }
3790
8.09k
                return {};
3791
8.09k
            }
3792
1.00k
            proj_context_errno_set(P->ctx,
3793
1.00k
                                   0); // don't treat as a persistent error
3794
1.00k
        } else {
3795
25
            grids.emplace_back(std::move(gridSet));
3796
25
        }
3797
9.12k
    }
3798
3799
129
    return grids;
3800
8.22k
}
3801
3802
/***********************************************/
3803
double pj_vgrid_value(PJ *P, const ListOfVGrids &grids, PJ_LP lp,
3804
0
                      double vmultiplier) {
3805
    /***********************************************
3806
3807
      Read grid value at position lp in grids loaded
3808
      with proj_grid_init.
3809
3810
      Returns the grid value of the given coordinate.
3811
3812
    ************************************************/
3813
3814
0
    double value;
3815
3816
0
    value = read_vgrid_value(P->ctx, grids, lp, vmultiplier);
3817
0
    if (pj_log_active(P->ctx, PJ_LOG_TRACE)) {
3818
0
        proj_log_trace(P, "proj_vgrid_value: (%f, %f) = %f",
3819
0
                       lp.lam * RAD_TO_DEG, lp.phi * RAD_TO_DEG, value);
3820
0
    }
3821
3822
0
    return value;
3823
0
}
3824
3825
// ---------------------------------------------------------------------------
3826
3827
const GenericShiftGrid *pj_find_generic_grid(const ListOfGenericGrids &grids,
3828
                                             const PJ_LP &input,
3829
0
                                             GenericShiftGridSet *&gridSetOut) {
3830
0
    for (const auto &gridset : grids) {
3831
0
        auto grid = gridset->gridAt(input.lam, input.phi);
3832
0
        if (grid) {
3833
0
            gridSetOut = gridset.get();
3834
0
            return grid;
3835
0
        }
3836
0
    }
3837
0
    return nullptr;
3838
0
}
3839
3840
// ---------------------------------------------------------------------------
3841
3842
// Used by +proj=deformation and +proj=xyzgridshift to do bilinear interpolation
3843
// on 3 sample values per node.
3844
bool pj_bilinear_interpolation_three_samples(
3845
    PJ_CONTEXT *ctx, const GenericShiftGrid *grid, const PJ_LP &lp, int idx1,
3846
0
    int idx2, int idx3, double &v1, double &v2, double &v3, bool &must_retry) {
3847
0
    must_retry = false;
3848
0
    if (grid->isNullGrid()) {
3849
0
        v1 = 0.0;
3850
0
        v2 = 0.0;
3851
0
        v3 = 0.0;
3852
0
        return true;
3853
0
    }
3854
3855
0
    const auto &extent = grid->extentAndRes();
3856
0
    if (!extent.isGeographic) {
3857
0
        pj_log(ctx, PJ_LOG_ERROR,
3858
0
               "Can only handle grids referenced in a geographic CRS");
3859
0
        proj_context_errno_set(ctx,
3860
0
                               PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3861
0
        return false;
3862
0
    }
3863
3864
    // From a input location lp, determine the grid cell into which it falls,
3865
    // by identifying the lower-left x,y of it (ix, iy), and the upper-right
3866
    // (ix2, iy2)
3867
3868
0
    double grid_x = (lp.lam - extent.west) * extent.invResX;
3869
    // Special case for grids with world extent, and dealing with wrap-around
3870
0
    if (lp.lam < extent.west) {
3871
0
        grid_x = (lp.lam + 2 * M_PI - extent.west) * extent.invResX;
3872
0
    } else if (lp.lam > extent.east) {
3873
0
        grid_x = (lp.lam - 2 * M_PI - extent.west) * extent.invResX;
3874
0
    }
3875
0
    double grid_y = (lp.phi - extent.south) * extent.invResY;
3876
0
    int ix = static_cast<int>(grid_x);
3877
0
    int iy = static_cast<int>(grid_y);
3878
0
    int ix2 = std::min(ix + 1, grid->width() - 1);
3879
0
    int iy2 = std::min(iy + 1, grid->height() - 1);
3880
3881
0
    float dx1 = 0.0f, dy1 = 0.0f, dz1 = 0.0f;
3882
0
    float dx2 = 0.0f, dy2 = 0.0f, dz2 = 0.0f;
3883
0
    float dx3 = 0.0f, dy3 = 0.0f, dz3 = 0.0f;
3884
0
    float dx4 = 0.0f, dy4 = 0.0f, dz4 = 0.0f;
3885
0
    bool error = (!grid->valueAt(ix, iy, idx1, dx1) ||
3886
0
                  !grid->valueAt(ix, iy, idx2, dy1) ||
3887
0
                  !grid->valueAt(ix, iy, idx3, dz1) ||
3888
0
                  !grid->valueAt(ix2, iy, idx1, dx2) ||
3889
0
                  !grid->valueAt(ix2, iy, idx2, dy2) ||
3890
0
                  !grid->valueAt(ix2, iy, idx3, dz2) ||
3891
0
                  !grid->valueAt(ix, iy2, idx1, dx3) ||
3892
0
                  !grid->valueAt(ix, iy2, idx2, dy3) ||
3893
0
                  !grid->valueAt(ix, iy2, idx3, dz3) ||
3894
0
                  !grid->valueAt(ix2, iy2, idx1, dx4) ||
3895
0
                  !grid->valueAt(ix2, iy2, idx2, dy4) ||
3896
0
                  !grid->valueAt(ix2, iy2, idx3, dz4));
3897
0
    if (grid->hasChanged()) {
3898
0
        must_retry = true;
3899
0
        return false;
3900
0
    }
3901
0
    if (error) {
3902
0
        return false;
3903
0
    }
3904
3905
    // Bilinear interpolation
3906
0
    double frct_lam = grid_x - ix;
3907
0
    double frct_phi = grid_y - iy;
3908
0
    double m10 = frct_lam;
3909
0
    double m11 = m10;
3910
0
    double m01 = 1. - frct_lam;
3911
0
    double m00 = m01;
3912
0
    m11 *= frct_phi;
3913
0
    m01 *= frct_phi;
3914
0
    frct_phi = 1. - frct_phi;
3915
0
    m00 *= frct_phi;
3916
0
    m10 *= frct_phi;
3917
3918
0
    v1 = m00 * dx1 + m10 * dx2 + m01 * dx3 + m11 * dx4;
3919
0
    v2 = m00 * dy1 + m10 * dy2 + m01 * dy3 + m11 * dy4;
3920
0
    v3 = m00 * dz1 + m10 * dz2 + m01 * dz3 + m11 * dz4;
3921
0
    return true;
3922
0
}
3923
3924
NS_PROJ_END
3925
3926
/*****************************************************************************/
3927
0
PJ_GRID_INFO proj_grid_info(const char *gridname) {
3928
    /******************************************************************************
3929
        Information about a named datum grid.
3930
3931
        Returns PJ_GRID_INFO struct.
3932
    ******************************************************************************/
3933
0
    PJ_GRID_INFO grinfo;
3934
3935
    /*PJ_CONTEXT *ctx = proj_context_create(); */
3936
0
    PJ_CONTEXT *ctx = pj_get_default_ctx();
3937
0
    memset(&grinfo, 0, sizeof(PJ_GRID_INFO));
3938
3939
0
    const auto fillGridInfo = [&grinfo, ctx,
3940
0
                               gridname](const NS_PROJ::Grid &grid,
3941
0
                                         const std::string &format) {
3942
0
        const auto &extent = grid.extentAndRes();
3943
3944
        /* name of grid */
3945
0
        strncpy(grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1);
3946
3947
        /* full path of grid */
3948
0
        if (!pj_find_file(ctx, gridname, grinfo.filename,
3949
0
                          sizeof(grinfo.filename) - 1)) {
3950
            // Can happen when using a remote grid
3951
0
            grinfo.filename[0] = 0;
3952
0
        }
3953
3954
        /* grid format */
3955
0
        strncpy(grinfo.format, format.c_str(), sizeof(grinfo.format) - 1);
3956
3957
        /* grid size */
3958
0
        grinfo.n_lon = grid.width();
3959
0
        grinfo.n_lat = grid.height();
3960
3961
        /* cell size */
3962
0
        grinfo.cs_lon = extent.resX;
3963
0
        grinfo.cs_lat = extent.resY;
3964
3965
        /* bounds of grid */
3966
0
        grinfo.lowerleft.lam = extent.west;
3967
0
        grinfo.lowerleft.phi = extent.south;
3968
0
        grinfo.upperright.lam = extent.east;
3969
0
        grinfo.upperright.phi = extent.north;
3970
0
    };
3971
3972
0
    {
3973
0
        const auto gridSet = NS_PROJ::VerticalShiftGridSet::open(ctx, gridname);
3974
0
        if (gridSet) {
3975
0
            const auto &grids = gridSet->grids();
3976
0
            if (!grids.empty()) {
3977
0
                const auto &grid = grids.front();
3978
0
                fillGridInfo(*grid, gridSet->format());
3979
0
                return grinfo;
3980
0
            }
3981
0
        }
3982
0
    }
3983
3984
0
    {
3985
0
        const auto gridSet =
3986
0
            NS_PROJ::HorizontalShiftGridSet::open(ctx, gridname);
3987
0
        if (gridSet) {
3988
0
            const auto &grids = gridSet->grids();
3989
0
            if (!grids.empty()) {
3990
0
                const auto &grid = grids.front();
3991
0
                fillGridInfo(*grid, gridSet->format());
3992
0
                return grinfo;
3993
0
            }
3994
0
        }
3995
0
    }
3996
0
    strcpy(grinfo.format, "missing");
3997
0
    return grinfo;
3998
0
}
3999
4000
/*****************************************************************************/
4001
0
PJ_INIT_INFO proj_init_info(const char *initname) {
4002
    /******************************************************************************
4003
        Information about a named init file.
4004
4005
        Maximum length of initname is 64.
4006
4007
        Returns PJ_INIT_INFO struct.
4008
4009
        If the init file is not found all members of the return struct are set
4010
        to the empty string.
4011
4012
        If the init file is found, but the metadata is missing, the value is
4013
        set to "Unknown".
4014
    ******************************************************************************/
4015
0
    int file_found;
4016
0
    char param[80], key[74];
4017
0
    paralist *start, *next;
4018
0
    PJ_INIT_INFO ininfo;
4019
0
    PJ_CONTEXT *ctx = pj_get_default_ctx();
4020
4021
0
    memset(&ininfo, 0, sizeof(PJ_INIT_INFO));
4022
4023
0
    file_found =
4024
0
        pj_find_file(ctx, initname, ininfo.filename, sizeof(ininfo.filename));
4025
0
    if (!file_found || strlen(initname) > 64) {
4026
0
        if (strcmp(initname, "epsg") == 0 || strcmp(initname, "EPSG") == 0) {
4027
0
            const char *val;
4028
4029
0
            proj_context_errno_set(ctx, 0);
4030
4031
0
            strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1);
4032
0
            strcpy(ininfo.origin, "EPSG");
4033
0
            val = proj_context_get_database_metadata(ctx, "EPSG.VERSION");
4034
0
            if (val) {
4035
0
                strncpy(ininfo.version, val, sizeof(ininfo.version) - 1);
4036
0
            }
4037
0
            val = proj_context_get_database_metadata(ctx, "EPSG.DATE");
4038
0
            if (val) {
4039
0
                strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1);
4040
0
            }
4041
0
            return ininfo;
4042
0
        }
4043
4044
0
        if (strcmp(initname, "IGNF") == 0) {
4045
0
            const char *val;
4046
4047
0
            proj_context_errno_set(ctx, 0);
4048
4049
0
            strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1);
4050
0
            strcpy(ininfo.origin, "IGNF");
4051
0
            val = proj_context_get_database_metadata(ctx, "IGNF.VERSION");
4052
0
            if (val) {
4053
0
                strncpy(ininfo.version, val, sizeof(ininfo.version) - 1);
4054
0
            }
4055
0
            val = proj_context_get_database_metadata(ctx, "IGNF.DATE");
4056
0
            if (val) {
4057
0
                strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1);
4058
0
            }
4059
0
            return ininfo;
4060
0
        }
4061
4062
0
        return ininfo;
4063
0
    }
4064
4065
    /* The initial memset (0) makes strncpy safe here */
4066
0
    strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1);
4067
0
    strcpy(ininfo.origin, "Unknown");
4068
0
    strcpy(ininfo.version, "Unknown");
4069
0
    strcpy(ininfo.lastupdate, "Unknown");
4070
4071
0
    strncpy(key, initname, 64); /* make room for ":metadata\0" at the end */
4072
0
    key[64] = 0;
4073
0
    memcpy(key + strlen(key), ":metadata", 9 + 1);
4074
0
    strcpy(param, "+init=");
4075
    /* The +strlen(param) avoids a cppcheck false positive warning */
4076
0
    strncat(param + strlen(param), key, sizeof(param) - 1 - strlen(param));
4077
4078
0
    start = pj_mkparam(param);
4079
0
    pj_expand_init(ctx, start);
4080
4081
0
    if (pj_param(ctx, start, "tversion").i)
4082
0
        strncpy(ininfo.version, pj_param(ctx, start, "sversion").s,
4083
0
                sizeof(ininfo.version) - 1);
4084
4085
0
    if (pj_param(ctx, start, "torigin").i)
4086
0
        strncpy(ininfo.origin, pj_param(ctx, start, "sorigin").s,
4087
0
                sizeof(ininfo.origin) - 1);
4088
4089
0
    if (pj_param(ctx, start, "tlastupdate").i)
4090
0
        strncpy(ininfo.lastupdate, pj_param(ctx, start, "slastupdate").s,
4091
0
                sizeof(ininfo.lastupdate) - 1);
4092
4093
0
    for (; start; start = next) {
4094
0
        next = start->next;
4095
0
        free(start);
4096
0
    }
4097
4098
0
    return ininfo;
4099
0
}