Coverage Report

Created: 2026-02-14 06:52

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
1.99k
void ExtentAndRes::computeInvRes() {
84
1.99k
    invResX = 1.0 / resX;
85
1.99k
    invResY = 1.0 / resY;
86
1.99k
}
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
1.99k
    : m_name(nameIn), m_width(widthIn), m_height(heightIn), m_extent(extentIn) {
113
1.99k
}
114
115
// ---------------------------------------------------------------------------
116
117
1.99k
Grid::~Grid() = default;
118
119
// ---------------------------------------------------------------------------
120
121
VerticalShiftGrid::VerticalShiftGrid(const std::string &nameIn, int widthIn,
122
                                     int heightIn, const ExtentAndRes &extentIn)
123
316
    : Grid(nameIn, widthIn, heightIn, extentIn) {}
124
125
// ---------------------------------------------------------------------------
126
127
316
VerticalShiftGrid::~VerticalShiftGrid() = default;
128
129
// ---------------------------------------------------------------------------
130
131
1.99k
static ExtentAndRes globalExtent() {
132
1.99k
    ExtentAndRes extent;
133
1.99k
    extent.isGeographic = true;
134
1.99k
    extent.west = -M_PI;
135
1.99k
    extent.south = -M_PI / 2;
136
1.99k
    extent.east = M_PI;
137
1.99k
    extent.north = M_PI / 2;
138
1.99k
    extent.resX = M_PI;
139
1.99k
    extent.resY = M_PI / 2;
140
1.99k
    extent.computeInvRes();
141
1.99k
    return extent;
142
1.99k
}
143
144
// ---------------------------------------------------------------------------
145
146
static const std::string emptyString;
147
148
class NullVerticalShiftGrid : public VerticalShiftGrid {
149
150
  public:
151
316
    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
316
VerticalShiftGridSet::VerticalShiftGridSet() = default;
370
371
// ---------------------------------------------------------------------------
372
373
316
VerticalShiftGridSet::~VerticalShiftGridSet() = default;
374
375
// ---------------------------------------------------------------------------
376
377
890
static bool IsTIFF(size_t header_size, const unsigned char *header) {
378
    // Test combinations of signature for ClassicTIFF/BigTIFF little/big endian
379
890
    return header_size >= 4 && (((header[0] == 'I' && header[1] == 'I') ||
380
14
                                 (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
890
}
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
                        const std::vector<unsigned char> &data) {
429
    cache_.insert((static_cast<uint64_t>(ifdIdx) << 32) | blockNumber, data);
430
}
431
432
// ---------------------------------------------------------------------------
433
434
const std::vector<unsigned char> *BlockCache::get(uint32_t ifdIdx,
435
                                                  uint32_t blockNumber) {
436
    return cache_.getPtr((static_cast<uint64_t>(ifdIdx) << 32) | blockNumber);
437
}
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
    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
    uint32_t subfileType() const { return m_subfileType; }
499
500
    void reassign_context(PJ_CONTEXT *ctx) { m_ctx = ctx; }
501
502
    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
    : Grid(nameIn, widthIn, heightIn, extentIn), m_ctx(ctx), m_hTIFF(hTIFF),
513
      m_cache(cache), m_fp(fp), m_ifdIdx(ifdIdx), m_dt(dtIn),
514
      m_samplesPerPixel(samplesPerPixelIn),
515
      m_planarConfig(samplesPerPixelIn == 1 ? static_cast<uint16_t>(-1)
516
                                            : planarConfig),
517
      m_bottomUp(bottomUpIn), m_dirOffset(TIFFCurrentDirOffset(hTIFF)),
518
      m_tiled(TIFFIsTiled(hTIFF) != 0) {
519
520
    if (m_tiled) {
521
        TIFFGetField(m_hTIFF, TIFFTAG_TILEWIDTH, &m_blockWidth);
522
        TIFFGetField(m_hTIFF, TIFFTAG_TILELENGTH, &m_blockHeight);
523
    } else {
524
        m_blockWidth = widthIn;
525
        TIFFGetField(m_hTIFF, TIFFTAG_ROWSPERSTRIP, &m_blockHeight);
526
        if (m_blockHeight > static_cast<unsigned>(m_height))
527
            m_blockHeight = m_height;
528
    }
529
530
    m_blockIs256Pixel = (m_blockWidth == 256) && (m_blockHeight == 256);
531
    m_isSingleBlock = (m_blockWidth == static_cast<uint32_t>(m_width)) &&
532
                      (m_blockHeight == static_cast<uint32_t>(m_height));
533
534
    TIFFGetField(m_hTIFF, TIFFTAG_SUBFILETYPE, &m_subfileType);
535
536
    m_blocksPerRow = (m_width + m_blockWidth - 1) / m_blockWidth;
537
    m_blocksPerCol = (m_height + m_blockHeight - 1) / m_blockHeight;
538
    m_blocks = m_blocksPerRow * m_blocksPerCol;
539
540
    const char *text = nullptr;
541
    // Poor-man XML parsing of TIFFTAG_GDAL_METADATA tag. Hopefully good
542
    // enough for our purposes.
543
    if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_METADATA, &text)) {
544
        const char *ptr = text;
545
        while (true) {
546
            ptr = strstr(ptr, "<Item ");
547
            if (ptr == nullptr)
548
                break;
549
            const char *endTag = strchr(ptr, '>');
550
            if (endTag == nullptr)
551
                break;
552
            const char *endValue = strchr(endTag, '<');
553
            if (endValue == nullptr)
554
                break;
555
556
            std::string tag;
557
            tag.append(ptr, endTag - ptr);
558
559
            std::string value;
560
            value.append(endTag + 1, endValue - (endTag + 1));
561
562
            std::string gridName;
563
            auto namePos = tag.find("name=\"");
564
            if (namePos == std::string::npos)
565
                break;
566
            {
567
                namePos += strlen("name=\"");
568
                const auto endQuote = tag.find('"', namePos);
569
                if (endQuote == std::string::npos)
570
                    break;
571
                gridName = tag.substr(namePos, endQuote - namePos);
572
            }
573
574
            const auto samplePos = tag.find("sample=\"");
575
            int sample = -1;
576
            if (samplePos != std::string::npos) {
577
                sample = atoi(tag.c_str() + samplePos + strlen("sample=\""));
578
            }
579
580
            m_metadata[std::pair<int, std::string>(sample, gridName)] = value;
581
582
            auto rolePos = tag.find("role=\"");
583
            if (rolePos != std::string::npos) {
584
                rolePos += strlen("role=\"");
585
                const auto endQuote = tag.find('"', rolePos);
586
                if (endQuote == std::string::npos)
587
                    break;
588
                const auto role = tag.substr(rolePos, endQuote - rolePos);
589
                if (role == "offset") {
590
                    if (sample >= 0 &&
591
                        static_cast<unsigned>(sample) <= m_samplesPerPixel) {
592
                        try {
593
                            if (m_adfOffset.empty()) {
594
                                m_adfOffset.resize(m_samplesPerPixel);
595
                                m_adfScale.resize(m_samplesPerPixel, 1);
596
                            }
597
                            m_adfOffset[sample] = c_locale_stod(value);
598
                        } catch (const std::exception &) {
599
                        }
600
                    }
601
                } else if (role == "scale") {
602
                    if (sample >= 0 &&
603
                        static_cast<unsigned>(sample) <= m_samplesPerPixel) {
604
                        try {
605
                            if (m_adfOffset.empty()) {
606
                                m_adfOffset.resize(m_samplesPerPixel);
607
                                m_adfScale.resize(m_samplesPerPixel, 1);
608
                            }
609
                            m_adfScale[sample] = c_locale_stod(value);
610
                        } catch (const std::exception &) {
611
                        }
612
                    }
613
                }
614
            }
615
616
            ptr = endValue + 1;
617
        }
618
    }
619
620
    if (TIFFGetField(m_hTIFF, TIFFTAG_GDAL_NODATA, &text)) {
621
        try {
622
            m_noData = static_cast<float>(c_locale_stod(text));
623
            m_hasNodata = true;
624
        } catch (const std::exception &) {
625
        }
626
    }
627
628
    auto oIter = m_metadata.find(std::pair<int, std::string>(-1, "grid_name"));
629
    if (oIter != m_metadata.end()) {
630
        m_name += ", " + oIter->second;
631
    }
632
}
633
634
// ---------------------------------------------------------------------------
635
636
GTiffGrid::~GTiffGrid() = default;
637
638
// ---------------------------------------------------------------------------
639
640
template <class T>
641
float GTiffGrid::readValue(const std::vector<unsigned char> &buffer,
642
                           uint32_t offsetInBlock, uint16_t sample) const {
643
    const auto ptr = reinterpret_cast<const T *>(buffer.data());
644
    assert(offsetInBlock < buffer.size() / sizeof(T));
645
    const auto val = ptr[offsetInBlock];
646
    if ((!m_hasNodata || static_cast<float>(val) != m_noData) &&
647
        sample < m_adfScale.size()) {
648
        double scale = m_adfScale[sample];
649
        double offset = m_adfOffset[sample];
650
        return static_cast<float>(val * scale + offset);
651
    } else {
652
        return static_cast<float>(val);
653
    }
654
}
655
656
// ---------------------------------------------------------------------------
657
658
bool GTiffGrid::valueAt(uint16_t sample, int x, int yFromBottom,
659
                        float &out) const {
660
    assert(x >= 0 && yFromBottom >= 0 && x < m_width && yFromBottom < m_height);
661
    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
    const int yTIFF = m_bottomUp ? yFromBottom : m_height - 1 - yFromBottom;
669
670
    int blockXOff;
671
    int blockYOff;
672
    uint32_t blockId;
673
674
    if (m_blockIs256Pixel) {
675
        const int blockX = x / 256;
676
        blockXOff = x % 256;
677
        const int blockY = yTIFF / 256;
678
        blockYOff = yTIFF % 256;
679
        blockId = blockY * m_blocksPerRow + blockX;
680
    } else if (m_isSingleBlock) {
681
        blockXOff = x;
682
        blockYOff = yTIFF;
683
        blockId = 0;
684
    } else {
685
        const int blockX = x / m_blockWidth;
686
        blockXOff = x % m_blockWidth;
687
        const int blockY = yTIFF / m_blockHeight;
688
        blockYOff = yTIFF % m_blockHeight;
689
        blockId = blockY * m_blocksPerRow + blockX;
690
    }
691
692
    if (m_planarConfig == PLANARCONFIG_SEPARATE) {
693
        blockId += sample * m_blocks;
694
    }
695
696
    const std::vector<unsigned char> *pBuffer =
697
        blockId == m_bufferBlockId ? &m_buffer : m_cache.get(m_ifdIdx, blockId);
698
    if (pBuffer == nullptr) {
699
        if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset &&
700
            !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) {
701
            return false;
702
        }
703
        if (m_buffer.empty()) {
704
            const auto blockSize = static_cast<size_t>(
705
                m_tiled ? TIFFTileSize64(m_hTIFF) : TIFFStripSize64(m_hTIFF));
706
            try {
707
                m_buffer.resize(blockSize);
708
            } catch (const std::exception &e) {
709
                pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
710
                return false;
711
            }
712
        }
713
714
        if (m_tiled) {
715
            if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(),
716
                                    m_buffer.size()) == -1) {
717
                return false;
718
            }
719
        } else {
720
            if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(),
721
                                     m_buffer.size()) == -1) {
722
                return false;
723
            }
724
        }
725
726
        pBuffer = &m_buffer;
727
        try {
728
            m_cache.insert(m_ifdIdx, blockId, m_buffer);
729
            m_bufferBlockId = blockId;
730
        } catch (const std::exception &e) {
731
            // Should normally not happen
732
            pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
733
        }
734
    }
735
736
    uint32_t offsetInBlock;
737
    if (m_blockIs256Pixel)
738
        offsetInBlock = blockXOff + blockYOff * 256U;
739
    else
740
        offsetInBlock = blockXOff + blockYOff * m_blockWidth;
741
    if (m_planarConfig == PLANARCONFIG_CONTIG)
742
        offsetInBlock = offsetInBlock * m_samplesPerPixel + sample;
743
744
    switch (m_dt) {
745
    case TIFFDataType::Int16:
746
        out = readValue<short>(*pBuffer, offsetInBlock, sample);
747
        break;
748
749
    case TIFFDataType::UInt16:
750
        out = readValue<unsigned short>(*pBuffer, offsetInBlock, sample);
751
        break;
752
753
    case TIFFDataType::Int32:
754
        out = readValue<int>(*pBuffer, offsetInBlock, sample);
755
        break;
756
757
    case TIFFDataType::UInt32:
758
        out = readValue<unsigned int>(*pBuffer, offsetInBlock, sample);
759
        break;
760
761
    case TIFFDataType::Float32:
762
        out = readValue<float>(*pBuffer, offsetInBlock, sample);
763
        break;
764
765
    case TIFFDataType::Float64:
766
        out = readValue<double>(*pBuffer, offsetInBlock, sample);
767
        break;
768
    }
769
770
    return true;
771
}
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
                         bool &nodataFound) const {
778
    const auto getTIFFRow = [this](int y) {
779
        return m_bottomUp ? y : m_height - 1 - y;
780
    };
781
    nodataFound = false;
782
    if (m_blockIs256Pixel && m_planarConfig == PLANARCONFIG_CONTIG &&
783
        m_dt == TIFFDataType::Float32 &&
784
        (x_start / 256) == (x_start + x_count - 1) / 256 &&
785
        getTIFFRow(y_start) / 256 == getTIFFRow(y_start + y_count - 1) / 256 &&
786
        !m_hasNodata && m_adfScale.empty() &&
787
        (sample_count == 1 ||
788
         (sample_count == 2 && sample_idx[1] == sample_idx[0] + 1) ||
789
         (sample_count == 3 && sample_idx[1] == sample_idx[0] + 1 &&
790
          sample_idx[2] == sample_idx[0] + 2))) {
791
        const int yTIFF = m_bottomUp ? y_start : m_height - (y_start + y_count);
792
        int blockXOff;
793
        int blockYOff;
794
        uint32_t blockId;
795
        const int blockX = x_start / 256;
796
        blockXOff = x_start % 256;
797
        const int blockY = yTIFF / 256;
798
        blockYOff = yTIFF % 256;
799
        blockId = blockY * m_blocksPerRow + blockX;
800
801
        const std::vector<unsigned char> *pBuffer =
802
            blockId == m_bufferBlockId ? &m_buffer
803
                                       : m_cache.get(m_ifdIdx, blockId);
804
        if (pBuffer == nullptr) {
805
            if (TIFFCurrentDirOffset(m_hTIFF) != m_dirOffset &&
806
                !TIFFSetSubDirectory(m_hTIFF, m_dirOffset)) {
807
                return false;
808
            }
809
            if (m_buffer.empty()) {
810
                const auto blockSize =
811
                    static_cast<size_t>(m_tiled ? TIFFTileSize64(m_hTIFF)
812
                                                : TIFFStripSize64(m_hTIFF));
813
                try {
814
                    m_buffer.resize(blockSize);
815
                } catch (const std::exception &e) {
816
                    pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
817
                    return false;
818
                }
819
            }
820
821
            if (m_tiled) {
822
                if (TIFFReadEncodedTile(m_hTIFF, blockId, m_buffer.data(),
823
                                        m_buffer.size()) == -1) {
824
                    return false;
825
                }
826
            } else {
827
                if (TIFFReadEncodedStrip(m_hTIFF, blockId, m_buffer.data(),
828
                                         m_buffer.size()) == -1) {
829
                    return false;
830
                }
831
            }
832
833
            pBuffer = &m_buffer;
834
            try {
835
                m_cache.insert(m_ifdIdx, blockId, m_buffer);
836
                m_bufferBlockId = blockId;
837
            } catch (const std::exception &e) {
838
                // Should normally not happen
839
                pj_log(m_ctx, PJ_LOG_ERROR, _("Exception %s"), e.what());
840
            }
841
        }
842
843
        uint32_t offsetInBlockStart = blockXOff + blockYOff * 256U;
844
845
        if (sample_count == m_samplesPerPixel) {
846
            const int sample_count_mul_x_count = sample_count * x_count;
847
            for (int y = 0; y < y_count; ++y) {
848
                uint32_t offsetInBlock =
849
                    (offsetInBlockStart +
850
                     256 * (m_bottomUp ? y : y_count - 1 - y)) *
851
                        m_samplesPerPixel +
852
                    sample_idx[0];
853
                memcpy(out,
854
                       reinterpret_cast<const float *>(pBuffer->data()) +
855
                           offsetInBlock,
856
                       sample_count_mul_x_count * sizeof(float));
857
                out += sample_count_mul_x_count;
858
            }
859
        } else {
860
            switch (sample_count) {
861
            case 1:
862
                for (int y = 0; y < y_count; ++y) {
863
                    uint32_t offsetInBlock =
864
                        (offsetInBlockStart +
865
                         256 * (m_bottomUp ? y : y_count - 1 - y)) *
866
                            m_samplesPerPixel +
867
                        sample_idx[0];
868
                    const float *in_ptr =
869
                        reinterpret_cast<const float *>(pBuffer->data()) +
870
                        offsetInBlock;
871
                    for (int x = 0; x < x_count; ++x) {
872
                        memcpy(out, in_ptr, sample_count * sizeof(float));
873
                        in_ptr += m_samplesPerPixel;
874
                        out += sample_count;
875
                    }
876
                }
877
                break;
878
            case 2:
879
                for (int y = 0; y < y_count; ++y) {
880
                    uint32_t offsetInBlock =
881
                        (offsetInBlockStart +
882
                         256 * (m_bottomUp ? y : y_count - 1 - y)) *
883
                            m_samplesPerPixel +
884
                        sample_idx[0];
885
                    const float *in_ptr =
886
                        reinterpret_cast<const float *>(pBuffer->data()) +
887
                        offsetInBlock;
888
                    for (int x = 0; x < x_count; ++x) {
889
                        memcpy(out, in_ptr, sample_count * sizeof(float));
890
                        in_ptr += m_samplesPerPixel;
891
                        out += sample_count;
892
                    }
893
                }
894
                break;
895
            case 3:
896
                for (int y = 0; y < y_count; ++y) {
897
                    uint32_t offsetInBlock =
898
                        (offsetInBlockStart +
899
                         256 * (m_bottomUp ? y : y_count - 1 - y)) *
900
                            m_samplesPerPixel +
901
                        sample_idx[0];
902
                    const float *in_ptr =
903
                        reinterpret_cast<const float *>(pBuffer->data()) +
904
                        offsetInBlock;
905
                    for (int x = 0; x < x_count; ++x) {
906
                        memcpy(out, in_ptr, sample_count * sizeof(float));
907
                        in_ptr += m_samplesPerPixel;
908
                        out += sample_count;
909
                    }
910
                }
911
                break;
912
            }
913
        }
914
        return true;
915
    }
916
917
    for (int y = y_start; y < y_start + y_count; ++y) {
918
        for (int x = x_start; x < x_start + x_count; ++x) {
919
            for (int isample = 0; isample < sample_count; ++isample) {
920
                if (!valueAt(static_cast<uint16_t>(sample_idx[isample]), x, y,
921
                             *out))
922
                    return false;
923
                if (isNodata(*out)) {
924
                    nodataFound = true;
925
                }
926
                ++out;
927
            }
928
        }
929
    }
930
    return true;
931
}
932
933
// ---------------------------------------------------------------------------
934
935
bool GTiffGrid::isNodata(float val) const {
936
    return (m_hasNodata && val == m_noData) || std::isnan(val);
937
}
938
939
// ---------------------------------------------------------------------------
940
941
const std::string &GTiffGrid::metadataItem(const std::string &key,
942
                                           int sample) const {
943
    auto iter = m_metadata.find(std::pair<int, std::string>(sample, key));
944
    if (iter == m_metadata.end()) {
945
        return emptyString;
946
    }
947
    return iter->second;
948
}
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
    static tsize_t tiffReadProc(thandle_t fd, tdata_t buf, tsize_t size) {
967
        GTiffDataset *self = static_cast<GTiffDataset *>(fd);
968
        return self->m_fp->read(buf, size);
969
    }
970
971
    static tsize_t tiffWriteProc(thandle_t, tdata_t, tsize_t) {
972
        assert(false);
973
        return 0;
974
    }
975
976
    static toff_t tiffSeekProc(thandle_t fd, toff_t off, int whence) {
977
        GTiffDataset *self = static_cast<GTiffDataset *>(fd);
978
        if (self->m_fp->seek(off, whence))
979
            return static_cast<toff_t>(self->m_fp->tell());
980
        else
981
            return static_cast<toff_t>(-1);
982
    }
983
984
    static int tiffCloseProc(thandle_t) {
985
        // done in destructor
986
        return 0;
987
    }
988
989
    static toff_t tiffSizeProc(thandle_t fd) {
990
        GTiffDataset *self = static_cast<GTiffDataset *>(fd);
991
        const auto old_off = self->m_fp->tell();
992
        self->m_fp->seek(0, SEEK_END);
993
        const auto file_size = static_cast<toff_t>(self->m_fp->tell());
994
        self->m_fp->seek(old_off);
995
        return file_size;
996
    }
997
998
    static int tiffMapProc(thandle_t, tdata_t *, toff_t *) { return (0); }
999
1000
    static void tiffUnmapProc(thandle_t, tdata_t, toff_t) {}
1001
1002
  public:
1003
    GTiffDataset(PJ_CONTEXT *ctx, std::unique_ptr<File> &&fp)
1004
        : 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
    void reassign_context(PJ_CONTEXT *ctx) {
1012
        m_ctx = ctx;
1013
        m_fp->reassign_context(ctx);
1014
    }
1015
};
1016
1017
// ---------------------------------------------------------------------------
1018
1019
GTiffDataset::~GTiffDataset() {
1020
    if (m_hTIFF)
1021
        TIFFClose(m_hTIFF);
1022
}
1023
1024
// ---------------------------------------------------------------------------
1025
class OneTimeTIFFTagInit {
1026
1027
    static TIFFExtendProc ParentExtender;
1028
1029
    // Function called by libtiff when initializing a TIFF directory
1030
    static void GTiffTagExtender(TIFF *tif) {
1031
        static const TIFFFieldInfo xtiffFieldInfo[] = {
1032
            // GeoTIFF tags
1033
            {TIFFTAG_GEOPIXELSCALE, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1034
             TRUE, const_cast<char *>("GeoPixelScale")},
1035
            {TIFFTAG_GEOTIEPOINTS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1036
             TRUE, const_cast<char *>("GeoTiePoints")},
1037
            {TIFFTAG_GEOTRANSMATRIX, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1038
             TRUE, const_cast<char *>("GeoTransformationMatrix")},
1039
1040
            {TIFFTAG_GEOKEYDIRECTORY, -1, -1, TIFF_SHORT, FIELD_CUSTOM, TRUE,
1041
             TRUE, const_cast<char *>("GeoKeyDirectory")},
1042
            {TIFFTAG_GEODOUBLEPARAMS, -1, -1, TIFF_DOUBLE, FIELD_CUSTOM, TRUE,
1043
             TRUE, const_cast<char *>("GeoDoubleParams")},
1044
            {TIFFTAG_GEOASCIIPARAMS, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
1045
             FALSE, const_cast<char *>("GeoASCIIParams")},
1046
1047
            // GDAL tags
1048
            {TIFFTAG_GDAL_METADATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE,
1049
             FALSE, const_cast<char *>("GDALMetadata")},
1050
            {TIFFTAG_GDAL_NODATA, -1, -1, TIFF_ASCII, FIELD_CUSTOM, TRUE, FALSE,
1051
             const_cast<char *>("GDALNoDataValue")},
1052
1053
        };
1054
1055
        if (ParentExtender)
1056
            (*ParentExtender)(tif);
1057
1058
        TIFFMergeFieldInfo(tif, xtiffFieldInfo,
1059
                           sizeof(xtiffFieldInfo) / sizeof(xtiffFieldInfo[0]));
1060
    }
1061
1062
  public:
1063
    OneTimeTIFFTagInit() {
1064
        assert(ParentExtender == nullptr);
1065
        // Install our TIFF tag extender
1066
        ParentExtender = TIFFSetTagExtender(GTiffTagExtender);
1067
    }
1068
};
1069
1070
TIFFExtendProc OneTimeTIFFTagInit::ParentExtender = nullptr;
1071
1072
// ---------------------------------------------------------------------------
1073
1074
bool GTiffDataset::openTIFF(const std::string &filename) {
1075
    static OneTimeTIFFTagInit oneTimeTIFFTagInit;
1076
    m_hTIFF =
1077
        TIFFClientOpen(filename.c_str(), "r", static_cast<thandle_t>(this),
1078
                       GTiffDataset::tiffReadProc, GTiffDataset::tiffWriteProc,
1079
                       GTiffDataset::tiffSeekProc, GTiffDataset::tiffCloseProc,
1080
                       GTiffDataset::tiffSizeProc, GTiffDataset::tiffMapProc,
1081
                       GTiffDataset::tiffUnmapProc);
1082
1083
    m_filename = filename;
1084
    m_hasNextGrid = true;
1085
    return m_hTIFF != nullptr;
1086
}
1087
// ---------------------------------------------------------------------------
1088
1089
std::unique_ptr<GTiffGrid> GTiffDataset::nextGrid() {
1090
    if (!m_hasNextGrid)
1091
        return nullptr;
1092
    if (m_nextDirOffset) {
1093
        TIFFSetSubDirectory(m_hTIFF, m_nextDirOffset);
1094
    }
1095
1096
    uint32_t width = 0;
1097
    uint32_t height = 0;
1098
    TIFFGetField(m_hTIFF, TIFFTAG_IMAGEWIDTH, &width);
1099
    TIFFGetField(m_hTIFF, TIFFTAG_IMAGELENGTH, &height);
1100
    if (width == 0 || height == 0 || width > INT_MAX || height > INT_MAX) {
1101
        pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid image size"));
1102
        return nullptr;
1103
    }
1104
1105
    uint16_t samplesPerPixel = 0;
1106
    if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel)) {
1107
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SamplesPerPixel tag"));
1108
        return nullptr;
1109
    }
1110
    if (samplesPerPixel == 0) {
1111
        pj_log(m_ctx, PJ_LOG_ERROR, _("Invalid SamplesPerPixel value"));
1112
        return nullptr;
1113
    }
1114
1115
    uint16_t bitsPerSample = 0;
1116
    if (!TIFFGetField(m_hTIFF, TIFFTAG_BITSPERSAMPLE, &bitsPerSample)) {
1117
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing BitsPerSample tag"));
1118
        return nullptr;
1119
    }
1120
1121
    uint16_t planarConfig = 0;
1122
    if (!TIFFGetField(m_hTIFF, TIFFTAG_PLANARCONFIG, &planarConfig)) {
1123
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing PlanarConfig tag"));
1124
        return nullptr;
1125
    }
1126
1127
    uint16_t sampleFormat = 0;
1128
    if (!TIFFGetField(m_hTIFF, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) {
1129
        pj_log(m_ctx, PJ_LOG_ERROR, _("Missing SampleFormat tag"));
1130
        return nullptr;
1131
    }
1132
1133
    TIFFDataType dt;
1134
    if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 16)
1135
        dt = TIFFDataType::Int16;
1136
    else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 16)
1137
        dt = TIFFDataType::UInt16;
1138
    else if (sampleFormat == SAMPLEFORMAT_INT && bitsPerSample == 32)
1139
        dt = TIFFDataType::Int32;
1140
    else if (sampleFormat == SAMPLEFORMAT_UINT && bitsPerSample == 32)
1141
        dt = TIFFDataType::UInt32;
1142
    else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 32)
1143
        dt = TIFFDataType::Float32;
1144
    else if (sampleFormat == SAMPLEFORMAT_IEEEFP && bitsPerSample == 64)
1145
        dt = TIFFDataType::Float64;
1146
    else {
1147
        pj_log(m_ctx, PJ_LOG_ERROR,
1148
               _("Unsupported combination of SampleFormat "
1149
                 "and BitsPerSample values"));
1150
        return nullptr;
1151
    }
1152
1153
    uint16_t photometric = PHOTOMETRIC_MINISBLACK;
1154
    if (!TIFFGetField(m_hTIFF, TIFFTAG_PHOTOMETRIC, &photometric))
1155
        photometric = PHOTOMETRIC_MINISBLACK;
1156
    if (photometric != PHOTOMETRIC_MINISBLACK) {
1157
        pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported Photometric value"));
1158
        return nullptr;
1159
    }
1160
1161
    uint16_t compression = COMPRESSION_NONE;
1162
    if (!TIFFGetField(m_hTIFF, TIFFTAG_COMPRESSION, &compression))
1163
        compression = COMPRESSION_NONE;
1164
1165
    if (compression != COMPRESSION_NONE &&
1166
        !TIFFIsCODECConfigured(compression)) {
1167
        pj_log(m_ctx, PJ_LOG_ERROR,
1168
               _("Cannot open TIFF file due to missing codec."));
1169
        return nullptr;
1170
    }
1171
    // We really don't want to try dealing with old-JPEG images
1172
    if (compression == COMPRESSION_OJPEG) {
1173
        pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported compression method."));
1174
        return nullptr;
1175
    }
1176
1177
    const auto blockSize = TIFFIsTiled(m_hTIFF) ? TIFFTileSize64(m_hTIFF)
1178
                                                : TIFFStripSize64(m_hTIFF);
1179
    if (blockSize == 0 || blockSize > 64 * 1024 * 2014) {
1180
        pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported block size."));
1181
        return nullptr;
1182
    }
1183
1184
    unsigned short count = 0;
1185
    unsigned short *geokeys = nullptr;
1186
    bool pixelIsArea = false;
1187
1188
    ExtentAndRes extent;
1189
    extent.isGeographic = true;
1190
1191
    if (!TIFFGetField(m_hTIFF, TIFFTAG_GEOKEYDIRECTORY, &count, &geokeys)) {
1192
        pj_log(m_ctx, PJ_LOG_TRACE, "No GeoKeys tag");
1193
    } else {
1194
        if (count < 4 || (count % 4) != 0) {
1195
            pj_log(m_ctx, PJ_LOG_ERROR,
1196
                   _("Wrong number of values in GeoKeys tag"));
1197
            return nullptr;
1198
        }
1199
1200
        if (geokeys[0] != 1) {
1201
            pj_log(m_ctx, PJ_LOG_ERROR, _("Unsupported GeoTIFF major version"));
1202
            return nullptr;
1203
        }
1204
        // We only know that we support GeoTIFF 1.0 and 1.1 at that time
1205
        if (geokeys[1] != 1 || geokeys[2] > 1) {
1206
            pj_log(m_ctx, PJ_LOG_TRACE, "GeoTIFF %d.%d possibly not handled",
1207
                   geokeys[1], geokeys[2]);
1208
        }
1209
1210
        for (unsigned int i = 4; i + 3 < count; i += 4) {
1211
            constexpr unsigned short GTModelTypeGeoKey = 1024;
1212
            constexpr unsigned short ModelTypeProjected = 1;
1213
            constexpr unsigned short ModelTypeGeographic = 2;
1214
1215
            constexpr unsigned short GTRasterTypeGeoKey = 1025;
1216
            constexpr unsigned short RasterPixelIsArea = 1;
1217
            // constexpr unsigned short RasterPixelIsPoint = 2;
1218
1219
            if (geokeys[i] == GTModelTypeGeoKey) {
1220
                if (geokeys[i + 3] == ModelTypeProjected) {
1221
                    extent.isGeographic = false;
1222
                } else if (geokeys[i + 3] != ModelTypeGeographic) {
1223
                    pj_log(m_ctx, PJ_LOG_ERROR,
1224
                           _("Only GTModelTypeGeoKey = "
1225
                             "ModelTypeGeographic or ModelTypeProjected are "
1226
                             "supported"));
1227
                    return nullptr;
1228
                }
1229
            } else if (geokeys[i] == GTRasterTypeGeoKey) {
1230
                if (geokeys[i + 3] == RasterPixelIsArea) {
1231
                    pixelIsArea = true;
1232
                }
1233
            }
1234
        }
1235
    }
1236
1237
    double hRes = 0;
1238
    double vRes = 0;
1239
    double west = 0;
1240
    double north = 0;
1241
1242
    double *matrix = nullptr;
1243
    if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTRANSMATRIX, &count, &matrix) &&
1244
        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
        if (matrix[1] != 0 || matrix[4] != 0) {
1249
            pj_log(m_ctx, PJ_LOG_ERROR,
1250
                   _("Rotational terms not supported in "
1251
                     "GeoTransformationMatrix tag"));
1252
            return nullptr;
1253
        }
1254
1255
        west = matrix[3];
1256
        hRes = matrix[0];
1257
        north = matrix[7];
1258
        vRes = -matrix[5]; // negation to simulate GeoPixelScale convention
1259
    } else {
1260
        double *geopixelscale = nullptr;
1261
        if (TIFFGetField(m_hTIFF, TIFFTAG_GEOPIXELSCALE, &count,
1262
                         &geopixelscale) != 1) {
1263
            pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoPixelScale tag"));
1264
            return nullptr;
1265
        }
1266
        if (count != 3) {
1267
            pj_log(m_ctx, PJ_LOG_ERROR,
1268
                   _("Wrong number of values in GeoPixelScale tag"));
1269
            return nullptr;
1270
        }
1271
        hRes = geopixelscale[0];
1272
        vRes = geopixelscale[1];
1273
1274
        double *geotiepoints = nullptr;
1275
        if (TIFFGetField(m_hTIFF, TIFFTAG_GEOTIEPOINTS, &count,
1276
                         &geotiepoints) != 1) {
1277
            pj_log(m_ctx, PJ_LOG_ERROR, _("No GeoTiePoints tag"));
1278
            return nullptr;
1279
        }
1280
        if (count != 6) {
1281
            pj_log(m_ctx, PJ_LOG_ERROR,
1282
                   _("Wrong number of values in GeoTiePoints tag"));
1283
            return nullptr;
1284
        }
1285
1286
        west = geotiepoints[3] - geotiepoints[0] * hRes;
1287
        north = geotiepoints[4] + geotiepoints[1] * vRes;
1288
    }
1289
1290
    if (pixelIsArea) {
1291
        west += 0.5 * hRes;
1292
        north -= 0.5 * vRes;
1293
    }
1294
1295
    const double mulFactor = extent.isGeographic ? DEG_TO_RAD : 1;
1296
    extent.west = west * mulFactor;
1297
    extent.north = north * mulFactor;
1298
    extent.resX = hRes * mulFactor;
1299
    extent.resY = fabs(vRes) * mulFactor;
1300
    extent.east = (west + hRes * (width - 1)) * mulFactor;
1301
    extent.south = (north - vRes * (height - 1)) * mulFactor;
1302
    extent.computeInvRes();
1303
1304
    if (vRes < 0) {
1305
        std::swap(extent.north, extent.south);
1306
    }
1307
1308
    if (!((!extent.isGeographic ||
1309
           (fabs(extent.west) <= 4 * M_PI && fabs(extent.east) <= 4 * M_PI &&
1310
            fabs(extent.north) <= M_PI + 1e-5 &&
1311
            fabs(extent.south) <= M_PI + 1e-5)) &&
1312
          extent.west < extent.east && extent.south < extent.north &&
1313
          extent.resX > 1e-10 && extent.resY > 1e-10)) {
1314
        pj_log(m_ctx, PJ_LOG_ERROR, _("Inconsistent georeferencing for %s"),
1315
               m_filename.c_str());
1316
        return nullptr;
1317
    }
1318
1319
    auto ret = std::unique_ptr<GTiffGrid>(new GTiffGrid(
1320
        m_ctx, m_hTIFF, m_cache, m_fp.get(), m_ifdIdx, m_filename, width,
1321
        height, extent, dt, samplesPerPixel, planarConfig, vRes < 0));
1322
    m_ifdIdx++;
1323
    m_hasNextGrid = TIFFReadDirectory(m_hTIFF) != 0;
1324
    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
    if (m_ifdIdx >= 2 || m_hasNextGrid) {
1329
        ret->m_name += " (index ";
1330
        ret->m_name += std::to_string(m_ifdIdx); // 1-based
1331
        ret->m_name += ')';
1332
    }
1333
1334
    return ret;
1335
}
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
        : 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
    void reassign_context(PJ_CONTEXT *ctx) override {
1354
        VerticalShiftGridSet::reassign_context(ctx);
1355
        if (m_GTiffDataset) {
1356
            m_GTiffDataset->reassign_context(ctx);
1357
        }
1358
    }
1359
1360
    bool reopen(PJ_CONTEXT *ctx) override {
1361
        pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
1362
               m_name.c_str());
1363
        m_grids.clear();
1364
        m_GTiffDataset.reset();
1365
        auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
1366
        if (!fp) {
1367
            return false;
1368
        }
1369
        auto newGS = open(ctx, std::move(fp), m_name);
1370
        if (newGS) {
1371
            m_grids = std::move(newGS->m_grids);
1372
            m_GTiffDataset = std::move(newGS->m_GTiffDataset);
1373
        }
1374
        return !m_grids.empty();
1375
    }
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
                    std::map<std::string, GridType *> &mapGrids) {
1386
    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
    if (!gridName.empty()) {
1391
        if (mapGrids.find(gridName) != mapGrids.end()) {
1392
            pj_log(ctx, PJ_LOG_DEBUG, "Several grids called %s found!",
1393
                   gridName.c_str());
1394
        }
1395
        mapGrids[gridName] = grid.get();
1396
    }
1397
1398
    if (!parentName.empty()) {
1399
        auto iter = mapGrids.find(parentName);
1400
        if (iter == mapGrids.end()) {
1401
            pj_log(ctx, PJ_LOG_DEBUG,
1402
                   "Grid %s refers to non-existing parent %s. "
1403
                   "Using bounding-box method.",
1404
                   gridName.c_str(), parentName.c_str());
1405
        } else {
1406
            if (iter->second->extentAndRes().contains(extent)) {
1407
                iter->second->m_children.emplace_back(std::move(grid));
1408
                return;
1409
            } else {
1410
                pj_log(ctx, PJ_LOG_DEBUG,
1411
                       "Grid %s refers to parent %s, but its extent is "
1412
                       "not included in it. Using bounding-box method.",
1413
                       gridName.c_str(), parentName.c_str());
1414
            }
1415
        }
1416
    } else if (!gridName.empty()) {
1417
        topGrids.emplace_back(std::move(grid));
1418
        return;
1419
    }
1420
1421
    const std::string &type = grid->metadataItem("TYPE");
1422
1423
    // Fallback to analyzing spatial extents
1424
    for (const auto &candidateParent : topGrids) {
1425
        if (!type.empty() && candidateParent->metadataItem("TYPE") != type) {
1426
            continue;
1427
        }
1428
1429
        const auto &candidateParentExtent = candidateParent->extentAndRes();
1430
        if (candidateParentExtent.contains(extent)) {
1431
            static_cast<GridType *>(candidateParent.get())
1432
                ->insertGrid(ctx, std::move(grid));
1433
            return;
1434
        } else if (candidateParentExtent.intersects(extent)) {
1435
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
1436
        }
1437
    }
1438
1439
    topGrids.emplace_back(std::move(grid));
1440
}
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
    bool valueAt(int x, int y, float &out) const override {
1460
        return m_grid->valueAt(m_idxSample, x, y, out);
1461
    }
1462
1463
    bool isNodata(float val, double /* multiplier */) const override {
1464
        return m_grid->isNodata(val);
1465
    }
1466
1467
    const std::string &metadataItem(const std::string &key,
1468
                                    int sample = -1) const override {
1469
        return m_grid->metadataItem(key, sample);
1470
    }
1471
1472
    void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffVGrid> &&subgrid);
1473
1474
    void reassign_context(PJ_CONTEXT *ctx) override {
1475
        m_grid->reassign_context(ctx);
1476
    }
1477
1478
    bool hasChanged() const override { return m_grid->hasChanged(); }
1479
};
1480
1481
// ---------------------------------------------------------------------------
1482
1483
GTiffVGridShiftSet::~GTiffVGridShiftSet() = default;
1484
1485
// ---------------------------------------------------------------------------
1486
1487
GTiffVGrid::GTiffVGrid(std::unique_ptr<GTiffGrid> &&grid, uint16_t idxSample)
1488
    : VerticalShiftGrid(grid->name(), grid->width(), grid->height(),
1489
                        grid->extentAndRes()),
1490
      m_grid(std::move(grid)), m_idxSample(idxSample) {}
1491
1492
// ---------------------------------------------------------------------------
1493
1494
GTiffVGrid::~GTiffVGrid() = default;
1495
1496
// ---------------------------------------------------------------------------
1497
1498
void GTiffVGrid::insertGrid(PJ_CONTEXT *ctx,
1499
                            std::unique_ptr<GTiffVGrid> &&subgrid) {
1500
    bool gridInserted = false;
1501
    const auto &extent = subgrid->extentAndRes();
1502
    for (const auto &candidateParent : m_children) {
1503
        const auto &candidateParentExtent = candidateParent->extentAndRes();
1504
        if (candidateParentExtent.contains(extent)) {
1505
            static_cast<GTiffVGrid *>(candidateParent.get())
1506
                ->insertGrid(ctx, std::move(subgrid));
1507
            gridInserted = true;
1508
            break;
1509
        } else if (candidateParentExtent.intersects(extent)) {
1510
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
1511
        }
1512
    }
1513
    if (!gridInserted) {
1514
        m_children.emplace_back(std::move(subgrid));
1515
    }
1516
}
1517
1518
// ---------------------------------------------------------------------------
1519
1520
std::unique_ptr<GTiffVGridShiftSet>
1521
GTiffVGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
1522
                         const std::string &filename) {
1523
    auto set = std::unique_ptr<GTiffVGridShiftSet>(
1524
        new GTiffVGridShiftSet(ctx, std::move(fp)));
1525
    set->m_name = filename;
1526
    set->m_format = "gtiff";
1527
    if (!set->m_GTiffDataset->openTIFF(filename)) {
1528
        return nullptr;
1529
    }
1530
    uint16_t idxSample = 0;
1531
1532
    std::map<std::string, GTiffVGrid *> mapGrids;
1533
    for (int ifd = 0;; ++ifd) {
1534
        auto grid = set->m_GTiffDataset->nextGrid();
1535
        if (!grid) {
1536
            if (ifd == 0) {
1537
                return nullptr;
1538
            }
1539
            break;
1540
        }
1541
1542
        const auto subfileType = grid->subfileType();
1543
        if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
1544
            if (ifd == 0) {
1545
                pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
1546
                return nullptr;
1547
            } else {
1548
                pj_log(ctx, PJ_LOG_DEBUG,
1549
                       "Ignoring IFD %d as it has a unsupported subfileType",
1550
                       ifd);
1551
                continue;
1552
            }
1553
        }
1554
1555
        // Identify the index of the vertical correction
1556
        bool foundDescriptionForAtLeastOneSample = false;
1557
        bool foundDescriptionForShift = false;
1558
        for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
1559
            const auto &desc = grid->metadataItem("DESCRIPTION", i);
1560
            if (!desc.empty()) {
1561
                foundDescriptionForAtLeastOneSample = true;
1562
            }
1563
            if (desc == "geoid_undulation" || desc == "vertical_offset" ||
1564
                desc == "hydroid_height" ||
1565
                desc == "ellipsoidal_height_offset") {
1566
                idxSample = static_cast<uint16_t>(i);
1567
                foundDescriptionForShift = true;
1568
            }
1569
        }
1570
1571
        if (foundDescriptionForAtLeastOneSample) {
1572
            if (!foundDescriptionForShift) {
1573
                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
                    pj_log(ctx, PJ_LOG_DEBUG,
1579
                           "Ignoring IFD %d as it has no "
1580
                           "geoid_undulation/vertical_offset/hydroid_height/"
1581
                           "ellipsoidal_height_offset channel",
1582
                           ifd);
1583
                    continue;
1584
                } else {
1585
                    pj_log(ctx, PJ_LOG_DEBUG,
1586
                           "IFD 0 has channel descriptions, but no "
1587
                           "geoid_undulation/vertical_offset/hydroid_height/"
1588
                           "ellipsoidal_height_offset channel");
1589
                    return nullptr;
1590
                }
1591
            }
1592
        }
1593
1594
        if (idxSample >= grid->samplesPerPixel()) {
1595
            pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index"));
1596
            return nullptr;
1597
        }
1598
1599
        const std::string &gridName = grid->metadataItem("grid_name");
1600
        const std::string &parentName = grid->metadataItem("parent_grid_name");
1601
1602
        auto vgrid = std::make_unique<GTiffVGrid>(std::move(grid), idxSample);
1603
1604
        insertIntoHierarchy(ctx, std::move(vgrid), gridName, parentName,
1605
                            set->m_grids, mapGrids);
1606
    }
1607
    return set;
1608
}
1609
#endif // TIFF_ENABLED
1610
1611
// ---------------------------------------------------------------------------
1612
1613
std::unique_ptr<VerticalShiftGridSet>
1614
7.19k
VerticalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
1615
7.19k
    if (filename == "null") {
1616
316
        auto set =
1617
316
            std::unique_ptr<VerticalShiftGridSet>(new VerticalShiftGridSet());
1618
316
        set->m_name = filename;
1619
316
        set->m_format = "null";
1620
316
        set->m_grids.push_back(std::unique_ptr<NullVerticalShiftGrid>(
1621
316
            new NullVerticalShiftGrid()));
1622
316
        return set;
1623
316
    }
1624
1625
6.87k
    auto fp = FileManager::open_resource_file(ctx, filename.c_str());
1626
6.87k
    if (!fp) {
1627
5.45k
        return nullptr;
1628
5.45k
    }
1629
1.42k
    const auto &actualName(fp->name());
1630
1.42k
    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
1.42k
    unsigned char header[4];
1647
1.42k
    size_t header_size = fp->read(header, sizeof(header));
1648
1.42k
    if (header_size != sizeof(header)) {
1649
1.40k
        return nullptr;
1650
1.40k
    }
1651
12
    fp->seek(0);
1652
1653
12
    if (IsTIFF(header_size, header)) {
1654
#ifdef TIFF_ENABLED
1655
        auto set = std::unique_ptr<VerticalShiftGridSet>(
1656
            GTiffVGridShiftSet::open(ctx, std::move(fp), actualName));
1657
        if (!set)
1658
            proj_context_errno_set(
1659
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
1660
        return set;
1661
#else
1662
0
        pj_log(ctx, PJ_LOG_ERROR,
1663
0
               _("TIFF grid, but TIFF support disabled in this build"));
1664
0
        return nullptr;
1665
0
#endif
1666
0
    }
1667
1668
12
    pj_log(ctx, PJ_LOG_ERROR,
1669
12
           "Unrecognized vertical grid format for filename '%s'",
1670
12
           filename.c_str());
1671
12
    return nullptr;
1672
12
}
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
164
    : Grid(nameIn, widthIn, heightIn, extentIn) {}
1748
1749
// ---------------------------------------------------------------------------
1750
1751
164
HorizontalShiftGrid::~HorizontalShiftGrid() = default;
1752
1753
// ---------------------------------------------------------------------------
1754
1755
164
HorizontalShiftGridSet::HorizontalShiftGridSet() = default;
1756
1757
// ---------------------------------------------------------------------------
1758
1759
164
HorizontalShiftGridSet::~HorizontalShiftGridSet() = default;
1760
1761
// ---------------------------------------------------------------------------
1762
1763
class NullHorizontalShiftGrid : public HorizontalShiftGrid {
1764
1765
  public:
1766
    NullHorizontalShiftGrid()
1767
164
        : 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
        : 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
    void reassign_context(PJ_CONTEXT *ctx) override {
2364
        HorizontalShiftGridSet::reassign_context(ctx);
2365
        if (m_GTiffDataset) {
2366
            m_GTiffDataset->reassign_context(ctx);
2367
        }
2368
    }
2369
2370
    bool reopen(PJ_CONTEXT *ctx) override {
2371
        pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
2372
               m_name.c_str());
2373
        m_grids.clear();
2374
        m_GTiffDataset.reset();
2375
        auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
2376
        if (!fp) {
2377
            return false;
2378
        }
2379
        auto newGS = open(ctx, std::move(fp), m_name);
2380
        if (newGS) {
2381
            m_grids = std::move(newGS->m_grids);
2382
            m_GTiffDataset = std::move(newGS->m_GTiffDataset);
2383
        }
2384
        return !m_grids.empty();
2385
    }
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
                                    int sample = -1) const override {
2415
        return m_grid->metadataItem(key, sample);
2416
    }
2417
2418
    void insertGrid(PJ_CONTEXT *ctx, std::unique_ptr<GTiffHGrid> &&subgrid);
2419
2420
    void reassign_context(PJ_CONTEXT *ctx) override {
2421
        m_grid->reassign_context(ctx);
2422
    }
2423
2424
    bool hasChanged() const override { return m_grid->hasChanged(); }
2425
};
2426
2427
// ---------------------------------------------------------------------------
2428
2429
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
    : HorizontalShiftGrid(grid->name(), grid->width(), grid->height(),
2437
                          grid->extentAndRes()),
2438
      m_grid(std::move(grid)), m_idxLatShift(idxLatShift),
2439
      m_idxLongShift(idxLongShift), m_convFactorToRadian(convFactorToRadian),
2440
      m_positiveEast(positiveEast) {}
2441
2442
// ---------------------------------------------------------------------------
2443
2444
GTiffHGrid::~GTiffHGrid() = default;
2445
2446
// ---------------------------------------------------------------------------
2447
2448
bool GTiffHGrid::valueAt(int x, int y, bool, float &longShift,
2449
                         float &latShift) const {
2450
    if (!m_grid->valueAt(m_idxLatShift, x, y, latShift) ||
2451
        !m_grid->valueAt(m_idxLongShift, x, y, longShift)) {
2452
        return false;
2453
    }
2454
    // From arc-seconds to radians
2455
    latShift = static_cast<float>(latShift * m_convFactorToRadian);
2456
    longShift = static_cast<float>(longShift * m_convFactorToRadian);
2457
    if (!m_positiveEast) {
2458
        longShift = -longShift;
2459
    }
2460
    return true;
2461
}
2462
2463
// ---------------------------------------------------------------------------
2464
2465
void GTiffHGrid::insertGrid(PJ_CONTEXT *ctx,
2466
                            std::unique_ptr<GTiffHGrid> &&subgrid) {
2467
    bool gridInserted = false;
2468
    const auto &extent = subgrid->extentAndRes();
2469
    for (const auto &candidateParent : m_children) {
2470
        const auto &candidateParentExtent = candidateParent->extentAndRes();
2471
        if (candidateParentExtent.contains(extent)) {
2472
            static_cast<GTiffHGrid *>(candidateParent.get())
2473
                ->insertGrid(ctx, std::move(subgrid));
2474
            gridInserted = true;
2475
            break;
2476
        } else if (candidateParentExtent.intersects(extent)) {
2477
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
2478
        }
2479
    }
2480
    if (!gridInserted) {
2481
        m_children.emplace_back(std::move(subgrid));
2482
    }
2483
}
2484
2485
// ---------------------------------------------------------------------------
2486
2487
std::unique_ptr<GTiffHGridShiftSet>
2488
GTiffHGridShiftSet::open(PJ_CONTEXT *ctx, std::unique_ptr<File> fp,
2489
                         const std::string &filename) {
2490
    auto set = std::unique_ptr<GTiffHGridShiftSet>(
2491
        new GTiffHGridShiftSet(ctx, std::move(fp)));
2492
    set->m_name = filename;
2493
    set->m_format = "gtiff";
2494
    if (!set->m_GTiffDataset->openTIFF(filename)) {
2495
        return nullptr;
2496
    }
2497
2498
    // Defaults inspired from NTv2
2499
    uint16_t idxLatShift = 0;
2500
    uint16_t idxLongShift = 1;
2501
    constexpr double ARC_SECOND_TO_RADIAN = (M_PI / 180.0) / 3600.0;
2502
    double convFactorToRadian = ARC_SECOND_TO_RADIAN;
2503
    bool positiveEast = true;
2504
2505
    std::map<std::string, GTiffHGrid *> mapGrids;
2506
    for (int ifd = 0;; ++ifd) {
2507
        auto grid = set->m_GTiffDataset->nextGrid();
2508
        if (!grid) {
2509
            if (ifd == 0) {
2510
                return nullptr;
2511
            }
2512
            break;
2513
        }
2514
2515
        const auto subfileType = grid->subfileType();
2516
        if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
2517
            if (ifd == 0) {
2518
                pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
2519
                return nullptr;
2520
            } else {
2521
                pj_log(ctx, PJ_LOG_DEBUG,
2522
                       _("Ignoring IFD %d as it has a unsupported subfileType"),
2523
                       ifd);
2524
                continue;
2525
            }
2526
        }
2527
2528
        if (grid->samplesPerPixel() < 2) {
2529
            if (ifd == 0) {
2530
                pj_log(ctx, PJ_LOG_ERROR,
2531
                       _("At least 2 samples per pixel needed"));
2532
                return nullptr;
2533
            } else {
2534
                pj_log(ctx, PJ_LOG_DEBUG,
2535
                       _("Ignoring IFD %d as it has not at least 2 samples"),
2536
                       ifd);
2537
                continue;
2538
            }
2539
        }
2540
2541
        // Identify the index of the latitude and longitude offset channels
2542
        bool foundDescriptionForAtLeastOneSample = false;
2543
        bool foundDescriptionForLatOffset = false;
2544
        bool foundDescriptionForLongOffset = false;
2545
        for (int i = 0; i < static_cast<int>(grid->samplesPerPixel()); ++i) {
2546
            const auto &desc = grid->metadataItem("DESCRIPTION", i);
2547
            if (!desc.empty()) {
2548
                foundDescriptionForAtLeastOneSample = true;
2549
            }
2550
            if (desc == "latitude_offset") {
2551
                idxLatShift = static_cast<uint16_t>(i);
2552
                foundDescriptionForLatOffset = true;
2553
            } else if (desc == "longitude_offset") {
2554
                idxLongShift = static_cast<uint16_t>(i);
2555
                foundDescriptionForLongOffset = true;
2556
            }
2557
        }
2558
2559
        if (foundDescriptionForAtLeastOneSample) {
2560
            if (!foundDescriptionForLongOffset &&
2561
                !foundDescriptionForLatOffset) {
2562
                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
                    pj_log(ctx, PJ_LOG_DEBUG,
2568
                           "Ignoring IFD %d as it has no "
2569
                           "longitude_offset/latitude_offset channel",
2570
                           ifd);
2571
                    continue;
2572
                } else {
2573
                    pj_log(ctx, PJ_LOG_DEBUG,
2574
                           "IFD 0 has channel descriptions, but no "
2575
                           "longitude_offset/latitude_offset channel");
2576
                    return nullptr;
2577
                }
2578
            }
2579
        }
2580
        if (foundDescriptionForLatOffset && !foundDescriptionForLongOffset) {
2581
            pj_log(
2582
                ctx, PJ_LOG_ERROR,
2583
                _("Found latitude_offset channel, but not longitude_offset"));
2584
            return nullptr;
2585
        } else if (foundDescriptionForLongOffset &&
2586
                   !foundDescriptionForLatOffset) {
2587
            pj_log(
2588
                ctx, PJ_LOG_ERROR,
2589
                _("Found longitude_offset channel, but not latitude_offset"));
2590
            return nullptr;
2591
        }
2592
2593
        if (idxLatShift >= grid->samplesPerPixel() ||
2594
            idxLongShift >= grid->samplesPerPixel()) {
2595
            pj_log(ctx, PJ_LOG_ERROR, _("Invalid sample index"));
2596
            return nullptr;
2597
        }
2598
2599
        if (foundDescriptionForLongOffset) {
2600
            const std::string &positiveValue =
2601
                grid->metadataItem("positive_value", idxLongShift);
2602
            if (!positiveValue.empty()) {
2603
                if (positiveValue == "west") {
2604
                    positiveEast = false;
2605
                } else if (positiveValue == "east") {
2606
                    positiveEast = true;
2607
                } else {
2608
                    pj_log(ctx, PJ_LOG_ERROR,
2609
                           _("Unsupported value %s for 'positive_value'"),
2610
                           positiveValue.c_str());
2611
                    return nullptr;
2612
                }
2613
            }
2614
        }
2615
2616
        // Identify their unit
2617
        {
2618
            const auto &unitLatShift =
2619
                grid->metadataItem("UNITTYPE", idxLatShift);
2620
            const auto &unitLongShift =
2621
                grid->metadataItem("UNITTYPE", idxLongShift);
2622
            if (unitLatShift != unitLongShift) {
2623
                pj_log(ctx, PJ_LOG_ERROR,
2624
                       _("Different unit for longitude and latitude offset"));
2625
                return nullptr;
2626
            }
2627
            if (!unitLatShift.empty()) {
2628
                if (unitLatShift == "arc-second" ||
2629
                    unitLatShift == "arc-seconds per year") {
2630
                    convFactorToRadian = ARC_SECOND_TO_RADIAN;
2631
                } else if (unitLatShift == "radian") {
2632
                    convFactorToRadian = 1.0;
2633
                } else if (unitLatShift == "degree") {
2634
                    convFactorToRadian = M_PI / 180.0;
2635
                } else {
2636
                    pj_log(ctx, PJ_LOG_ERROR, _("Unsupported unit %s"),
2637
                           unitLatShift.c_str());
2638
                    return nullptr;
2639
                }
2640
            }
2641
        }
2642
2643
        const std::string &gridName = grid->metadataItem("grid_name");
2644
        const std::string &parentName = grid->metadataItem("parent_grid_name");
2645
2646
        auto hgrid = std::make_unique<GTiffHGrid>(
2647
            std::move(grid), idxLatShift, idxLongShift, convFactorToRadian,
2648
            positiveEast);
2649
2650
        insertIntoHierarchy(ctx, std::move(hgrid), gridName, parentName,
2651
                            set->m_grids, mapGrids);
2652
    }
2653
    return set;
2654
}
2655
#endif // TIFF_ENABLED
2656
2657
// ---------------------------------------------------------------------------
2658
2659
std::unique_ptr<HorizontalShiftGridSet>
2660
16.0k
HorizontalShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
2661
16.0k
    if (filename == "null") {
2662
164
        auto set = std::unique_ptr<HorizontalShiftGridSet>(
2663
164
            new HorizontalShiftGridSet());
2664
164
        set->m_name = filename;
2665
164
        set->m_format = "null";
2666
164
        set->m_grids.push_back(std::unique_ptr<NullHorizontalShiftGrid>(
2667
164
            new NullHorizontalShiftGrid()));
2668
164
        return set;
2669
164
    }
2670
2671
15.8k
    auto fp = FileManager::open_resource_file(ctx, filename.c_str());
2672
15.8k
    if (!fp) {
2673
14.9k
        return nullptr;
2674
14.9k
    }
2675
878
    const auto &actualName(fp->name());
2676
2677
878
    char header[160];
2678
    /* -------------------------------------------------------------------- */
2679
    /*      Load a header, to determine the file type.                      */
2680
    /* -------------------------------------------------------------------- */
2681
878
    size_t header_size = fp->read(header, sizeof(header));
2682
878
    if (header_size != sizeof(header)) {
2683
        /* some files may be smaller that sizeof(header), eg 160, so */
2684
876
        ctx->last_errno = 0; /* don't treat as a persistent error */
2685
876
        pj_log(ctx, PJ_LOG_DEBUG,
2686
876
               "pj_gridinfo_init: short header read of %d bytes",
2687
876
               (int)header_size);
2688
876
    }
2689
878
    fp->seek(0);
2690
2691
    /* -------------------------------------------------------------------- */
2692
    /*      Determine file type.                                            */
2693
    /* -------------------------------------------------------------------- */
2694
878
    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
878
    } 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
878
    } else if (header_size >= 48 + 7 &&
2719
2
               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
878
    } else if (IsTIFF(header_size,
2723
878
                      reinterpret_cast<const unsigned char *>(header))) {
2724
#ifdef TIFF_ENABLED
2725
        auto set = std::unique_ptr<HorizontalShiftGridSet>(
2726
            GTiffHGridShiftSet::open(ctx, std::move(fp), actualName));
2727
        if (!set)
2728
            proj_context_errno_set(
2729
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
2730
        return set;
2731
#else
2732
0
        pj_log(ctx, PJ_LOG_ERROR,
2733
0
               _("TIFF grid, but TIFF support disabled in this build"));
2734
0
        return nullptr;
2735
0
#endif
2736
0
    }
2737
2738
878
    pj_log(ctx, PJ_LOG_ERROR,
2739
878
           "Unrecognized horizontal grid format for filename '%s'",
2740
878
           filename.c_str());
2741
878
    return nullptr;
2742
878
}
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
        : 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
    void reassign_context(PJ_CONTEXT *ctx) override {
2817
        GenericShiftGridSet::reassign_context(ctx);
2818
        if (m_GTiffDataset) {
2819
            m_GTiffDataset->reassign_context(ctx);
2820
        }
2821
    }
2822
2823
    bool reopen(PJ_CONTEXT *ctx) override {
2824
        pj_log(ctx, PJ_LOG_DEBUG, "Grid %s has changed. Re-loading it",
2825
               m_name.c_str());
2826
        m_grids.clear();
2827
        m_GTiffDataset.reset();
2828
        auto fp = FileManager::open_resource_file(ctx, m_name.c_str());
2829
        if (!fp) {
2830
            return false;
2831
        }
2832
        auto newGS = open(ctx, std::move(fp), m_name);
2833
        if (newGS) {
2834
            m_grids = std::move(newGS->m_grids);
2835
            m_GTiffDataset = std::move(newGS->m_GTiffDataset);
2836
        }
2837
        return !m_grids.empty();
2838
    }
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
    int samplesPerPixel() const override { return m_grid->samplesPerPixel(); }
2867
2868
    std::string unit(int sample) const override {
2869
        return metadataItem("UNITTYPE", sample);
2870
    }
2871
2872
    std::string description(int sample) const override {
2873
        return metadataItem("DESCRIPTION", sample);
2874
    }
2875
2876
    const std::string &metadataItem(const std::string &key,
2877
                                    int sample = -1) const override {
2878
        const std::string &ret = m_grid->metadataItem(key, sample);
2879
        if (ret.empty() && m_firstGrid) {
2880
            return m_firstGrid->metadataItem(key, sample);
2881
        }
2882
        return ret;
2883
    }
2884
2885
    const std::string &type() const override {
2886
        if (!m_bTypeSet) {
2887
            m_bTypeSet = true;
2888
            m_type = metadataItem("TYPE");
2889
        }
2890
        return m_type;
2891
    }
2892
2893
    void setFirstGrid(const GenericShiftGrid *firstGrid) {
2894
        m_firstGrid = firstGrid;
2895
    }
2896
2897
    void insertGrid(PJ_CONTEXT *ctx,
2898
                    std::unique_ptr<GTiffGenericGrid> &&subgrid);
2899
2900
    void reassign_context(PJ_CONTEXT *ctx) override {
2901
        m_grid->reassign_context(ctx);
2902
    }
2903
2904
    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
GTiffGenericGridShiftSet::~GTiffGenericGridShiftSet() = default;
2914
2915
// ---------------------------------------------------------------------------
2916
2917
GTiffGenericGrid::GTiffGenericGrid(std::unique_ptr<GTiffGrid> &&grid)
2918
    : GenericShiftGrid(grid->name(), grid->width(), grid->height(),
2919
                       grid->extentAndRes()),
2920
      m_grid(std::move(grid)) {}
2921
2922
// ---------------------------------------------------------------------------
2923
2924
GTiffGenericGrid::~GTiffGenericGrid() = default;
2925
2926
// ---------------------------------------------------------------------------
2927
2928
bool GTiffGenericGrid::valueAt(int x, int y, int sample, float &out) const {
2929
    if (sample < 0 ||
2930
        static_cast<unsigned>(sample) >= m_grid->samplesPerPixel())
2931
        return false;
2932
    return m_grid->valueAt(static_cast<uint16_t>(sample), x, y, out);
2933
}
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
                                bool &nodataFound) const {
2941
    return m_grid->valuesAt(x_start, y_start, x_count, y_count, sample_count,
2942
                            sample_idx, out, nodataFound);
2943
}
2944
2945
// ---------------------------------------------------------------------------
2946
2947
void GTiffGenericGrid::insertGrid(PJ_CONTEXT *ctx,
2948
                                  std::unique_ptr<GTiffGenericGrid> &&subgrid) {
2949
    bool gridInserted = false;
2950
    const auto &extent = subgrid->extentAndRes();
2951
    for (const auto &candidateParent : m_children) {
2952
        const auto &candidateParentExtent = candidateParent->extentAndRes();
2953
        if (candidateParentExtent.contains(extent)) {
2954
            static_cast<GTiffGenericGrid *>(candidateParent.get())
2955
                ->insertGrid(ctx, std::move(subgrid));
2956
            gridInserted = true;
2957
            break;
2958
        } else if (candidateParentExtent.intersects(extent)) {
2959
            pj_log(ctx, PJ_LOG_DEBUG, "Partially intersecting grids found!");
2960
        }
2961
    }
2962
    if (!gridInserted) {
2963
        m_children.emplace_back(std::move(subgrid));
2964
    }
2965
}
2966
#endif // TIFF_ENABLED
2967
2968
// ---------------------------------------------------------------------------
2969
2970
class NullGenericShiftGrid : public GenericShiftGrid {
2971
2972
  public:
2973
1.51k
    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
7
    const std::string &metadataItem(const std::string &, int) const override {
2987
7
        return emptyString;
2988
7
    }
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
                               const std::string &filename) {
3009
    auto set = std::unique_ptr<GTiffGenericGridShiftSet>(
3010
        new GTiffGenericGridShiftSet(ctx, std::move(fp)));
3011
    set->m_name = filename;
3012
    set->m_format = "gtiff";
3013
    if (!set->m_GTiffDataset->openTIFF(filename)) {
3014
        return nullptr;
3015
    }
3016
3017
    std::map<std::string, GTiffGenericGrid *> mapGrids;
3018
    for (int ifd = 0;; ++ifd) {
3019
        auto grid = set->m_GTiffDataset->nextGrid();
3020
        if (!grid) {
3021
            if (ifd == 0) {
3022
                return nullptr;
3023
            }
3024
            break;
3025
        }
3026
3027
        const auto subfileType = grid->subfileType();
3028
        if (subfileType != 0 && subfileType != FILETYPE_PAGE) {
3029
            if (ifd == 0) {
3030
                pj_log(ctx, PJ_LOG_ERROR, _("Invalid subfileType"));
3031
                return nullptr;
3032
            } else {
3033
                pj_log(ctx, PJ_LOG_DEBUG,
3034
                       _("Ignoring IFD %d as it has a unsupported subfileType"),
3035
                       ifd);
3036
                continue;
3037
            }
3038
        }
3039
3040
        const std::string &gridName = grid->metadataItem("grid_name");
3041
        const std::string &parentName = grid->metadataItem("parent_grid_name");
3042
3043
        auto ggrid = std::make_unique<GTiffGenericGrid>(std::move(grid));
3044
        if (!set->m_grids.empty() && ggrid->metadataItem("TYPE").empty() &&
3045
            !set->m_grids[0]->metadataItem("TYPE").empty()) {
3046
            ggrid->setFirstGrid(set->m_grids[0].get());
3047
        }
3048
3049
        insertIntoHierarchy(ctx, std::move(ggrid), gridName, parentName,
3050
                            set->m_grids, mapGrids);
3051
    }
3052
    return set;
3053
}
3054
#endif // TIFF_ENABLED
3055
3056
// ---------------------------------------------------------------------------
3057
3058
GenericShiftGrid::GenericShiftGrid(const std::string &nameIn, int widthIn,
3059
                                   int heightIn, const ExtentAndRes &extentIn)
3060
1.51k
    : Grid(nameIn, widthIn, heightIn, extentIn) {}
3061
3062
// ---------------------------------------------------------------------------
3063
3064
1.51k
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
1.51k
GenericShiftGridSet::GenericShiftGridSet() = default;
3088
3089
// ---------------------------------------------------------------------------
3090
3091
1.51k
GenericShiftGridSet::~GenericShiftGridSet() = default;
3092
3093
// ---------------------------------------------------------------------------
3094
3095
std::unique_ptr<GenericShiftGridSet>
3096
16.9k
GenericShiftGridSet::open(PJ_CONTEXT *ctx, const std::string &filename) {
3097
16.9k
    if (filename == "null") {
3098
1.51k
        auto set =
3099
1.51k
            std::unique_ptr<GenericShiftGridSet>(new GenericShiftGridSet());
3100
1.51k
        set->m_name = filename;
3101
1.51k
        set->m_format = "null";
3102
1.51k
        set->m_grids.push_back(
3103
1.51k
            std::unique_ptr<NullGenericShiftGrid>(new NullGenericShiftGrid()));
3104
1.51k
        return set;
3105
1.51k
    }
3106
3107
15.4k
    auto fp = FileManager::open_resource_file(ctx, filename.c_str());
3108
15.4k
    if (!fp) {
3109
14.7k
        return nullptr;
3110
14.7k
    }
3111
3112
    /* -------------------------------------------------------------------- */
3113
    /*      Load a header, to determine the file type.                      */
3114
    /* -------------------------------------------------------------------- */
3115
646
    unsigned char header[4];
3116
646
    size_t header_size = fp->read(header, sizeof(header));
3117
646
    if (header_size != sizeof(header)) {
3118
646
        return nullptr;
3119
646
    }
3120
0
    fp->seek(0);
3121
3122
0
    if (IsTIFF(header_size, header)) {
3123
#ifdef TIFF_ENABLED
3124
        const std::string actualName(fp->name());
3125
        auto set = std::unique_ptr<GenericShiftGridSet>(
3126
            GTiffGenericGridShiftSet::open(ctx, std::move(fp), actualName));
3127
        if (!set)
3128
            proj_context_errno_set(
3129
                ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3130
        return set;
3131
#else
3132
0
        pj_log(ctx, PJ_LOG_ERROR,
3133
0
               _("TIFF grid, but TIFF support disabled in this build"));
3134
0
        return nullptr;
3135
0
#endif
3136
0
    }
3137
3138
0
    pj_log(ctx, PJ_LOG_ERROR,
3139
0
           "Unrecognized generic grid format for filename '%s'",
3140
0
           filename.c_str());
3141
0
    return nullptr;
3142
0
}
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
463
ListOfGenericGrids pj_generic_grid_init(PJ *P, const char *gridkey) {
3214
463
    std::string key("s");
3215
463
    key += gridkey;
3216
463
    const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s;
3217
463
    if (gridnames == nullptr)
3218
0
        return {};
3219
3220
463
    auto listOfGridNames = internal::split(std::string(gridnames), ',');
3221
463
    ListOfGenericGrids grids;
3222
16.9k
    for (const auto &gridnameStr : listOfGridNames) {
3223
16.9k
        const char *gridname = gridnameStr.c_str();
3224
16.9k
        bool canFail = false;
3225
16.9k
        if (gridname[0] == '@') {
3226
15.3k
            canFail = true;
3227
15.3k
            gridname++;
3228
15.3k
        }
3229
16.9k
        auto gridSet = GenericShiftGridSet::open(P->ctx, gridname);
3230
16.9k
        if (!gridSet) {
3231
15.4k
            if (!canFail) {
3232
67
                if (proj_context_errno(P->ctx) !=
3233
67
                    PROJ_ERR_OTHER_NETWORK_ERROR) {
3234
67
                    proj_context_errno_set(
3235
67
                        P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3236
67
                }
3237
67
                return {};
3238
67
            }
3239
15.3k
            proj_context_errno_set(P->ctx,
3240
15.3k
                                   0); // don't treat as a persistent error
3241
15.3k
        } else {
3242
1.51k
            grids.emplace_back(std::move(gridSet));
3243
1.51k
        }
3244
16.9k
    }
3245
3246
396
    return grids;
3247
463
}
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
363
static ListOfHGrids getListOfGridSets(PJ_CONTEXT *ctx, const char *grids) {
3267
363
    ListOfHGrids list;
3268
363
    auto listOfGrids = internal::split(std::string(grids), ',');
3269
16.0k
    for (const auto &grid : listOfGrids) {
3270
16.0k
        const char *gridname = grid.c_str();
3271
16.0k
        bool canFail = false;
3272
16.0k
        if (gridname[0] == '@') {
3273
15.6k
            canFail = true;
3274
15.6k
            gridname++;
3275
15.6k
        }
3276
16.0k
        auto gridSet = HorizontalShiftGridSet::open(ctx, gridname);
3277
16.0k
        if (!gridSet) {
3278
15.8k
            if (!canFail) {
3279
239
                if (proj_context_errno(ctx) != PROJ_ERR_OTHER_NETWORK_ERROR) {
3280
239
                    proj_context_errno_set(
3281
239
                        ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3282
239
                }
3283
239
                return {};
3284
239
            }
3285
15.6k
            proj_context_errno_set(ctx, 0); // don't treat as a persistent error
3286
15.6k
        } else {
3287
164
            list.emplace_back(std::move(gridSet));
3288
164
        }
3289
16.0k
    }
3290
124
    return list;
3291
363
}
3292
3293
/**********************************************/
3294
363
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
363
    std::string key("s");
3310
363
    key += gridkey;
3311
363
    const char *grids = pj_param(P->ctx, P->params, key.c_str()).s;
3312
363
    if (grids == nullptr)
3313
0
        return {};
3314
3315
363
    return getListOfGridSets(P->ctx, grids);
3316
363
}
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
546
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
546
    std::string key("s");
3768
546
    key += gridkey;
3769
546
    const char *gridnames = pj_param(P->ctx, P->params, key.c_str()).s;
3770
546
    if (gridnames == nullptr)
3771
0
        return {};
3772
3773
546
    auto listOfGridNames = internal::split(std::string(gridnames), ',');
3774
546
    ListOfVGrids grids;
3775
7.19k
    for (const auto &gridnameStr : listOfGridNames) {
3776
7.19k
        const char *gridname = gridnameStr.c_str();
3777
7.19k
        bool canFail = false;
3778
7.19k
        if (gridname[0] == '@') {
3779
6.56k
            canFail = true;
3780
6.56k
            gridname++;
3781
6.56k
        }
3782
7.19k
        auto gridSet = VerticalShiftGridSet::open(P->ctx, gridname);
3783
7.19k
        if (!gridSet) {
3784
6.87k
            if (!canFail) {
3785
445
                if (proj_context_errno(P->ctx) !=
3786
445
                    PROJ_ERR_OTHER_NETWORK_ERROR) {
3787
445
                    proj_context_errno_set(
3788
445
                        P->ctx, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID);
3789
445
                }
3790
445
                return {};
3791
445
            }
3792
6.43k
            proj_context_errno_set(P->ctx,
3793
6.43k
                                   0); // don't treat as a persistent error
3794
6.43k
        } else {
3795
316
            grids.emplace_back(std::move(gridSet));
3796
316
        }
3797
7.19k
    }
3798
3799
101
    return grids;
3800
546
}
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
}