Coverage Report

Created: 2026-09-14 06:50

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