Coverage Report

Created: 2025-11-15 08:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/gmt/ogrgmtlayer.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  OpenGIS Simple Features Reference Implementation
4
 * Purpose:  Implements OGRGmtLayer class.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2007, Frank Warmerdam <warmerdam@pobox.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "ogr_gmt.h"
14
#include "cpl_conv.h"
15
#include "ogr_p.h"
16
17
#include <algorithm>
18
19
/************************************************************************/
20
/*                            OGRGmtLayer()                             */
21
/************************************************************************/
22
23
OGRGmtLayer::OGRGmtLayer(GDALDataset *poDS, const char *pszFilename,
24
                         VSILFILE *fp, const OGRSpatialReference *poSRS,
25
                         int bUpdateIn)
26
12.5k
    : m_poDS(poDS), poFeatureDefn(nullptr), iNextFID(0),
27
12.5k
      bUpdate(CPL_TO_BOOL(bUpdateIn)),
28
      // Assume header complete in readonly mode.
29
12.5k
      bHeaderComplete(CPL_TO_BOOL(!bUpdate)), bRegionComplete(false),
30
12.5k
      nRegionOffset(0),
31
12.5k
      m_fp(fp ? fp : VSIFOpenL(pszFilename, (bUpdateIn ? "r+" : "r"))),
32
12.5k
      papszKeyedValues(nullptr), bValidFile(false)
33
12.5k
{
34
12.5k
    if (m_fp == nullptr)
35
135
        return;
36
37
    /* -------------------------------------------------------------------- */
38
    /*      Create the feature definition                                   */
39
    /* -------------------------------------------------------------------- */
40
12.3k
    poFeatureDefn = new OGRFeatureDefn(CPLGetBasenameSafe(pszFilename).c_str());
41
12.3k
    SetDescription(poFeatureDefn->GetName());
42
12.3k
    poFeatureDefn->Reference();
43
44
    /* -------------------------------------------------------------------- */
45
    /*      Read the header.                                                */
46
    /* -------------------------------------------------------------------- */
47
12.3k
    if (!STARTS_WITH(pszFilename, "/vsistdout"))
48
12.3k
    {
49
12.3k
        CPLString osFieldNames;
50
12.3k
        CPLString osFieldTypes;
51
12.3k
        CPLString osGeometryType;
52
12.3k
        CPLString osRegion;
53
12.3k
        CPLString osWKT;
54
12.3k
        CPLString osProj4;
55
12.3k
        CPLString osEPSG;
56
12.3k
        vsi_l_offset nStartOfLine = 0;
57
58
12.3k
        VSIFSeekL(m_fp, 0, SEEK_SET);
59
60
420k
        while (ReadLine() && osLine[0] == '#')
61
408k
        {
62
408k
            if (strstr(osLine, "FEATURE_DATA"))
63
416
            {
64
416
                bHeaderComplete = true;
65
416
                ReadLine();
66
416
                break;
67
416
            }
68
69
408k
            if (STARTS_WITH_CI(osLine, "# REGION_STUB "))
70
1.58k
                nRegionOffset = nStartOfLine;
71
72
757k
            for (int iKey = 0; papszKeyedValues != nullptr &&
73
665k
                               papszKeyedValues[iKey] != nullptr;
74
408k
                 iKey++)
75
349k
            {
76
349k
                if (papszKeyedValues[iKey][0] == 'N')
77
46.1k
                    osFieldNames = papszKeyedValues[iKey] + 1;
78
349k
                if (papszKeyedValues[iKey][0] == 'T')
79
19.2k
                    osFieldTypes = papszKeyedValues[iKey] + 1;
80
349k
                if (papszKeyedValues[iKey][0] == 'G')
81
11.0k
                    osGeometryType = papszKeyedValues[iKey] + 1;
82
349k
                if (papszKeyedValues[iKey][0] == 'R')
83
19.5k
                    osRegion = papszKeyedValues[iKey] + 1;
84
349k
                if (papszKeyedValues[iKey][0] == 'J' &&
85
196k
                    papszKeyedValues[iKey][1] != 0 &&
86
193k
                    papszKeyedValues[iKey][2] != 0)
87
185k
                {
88
185k
                    std::string osArg = papszKeyedValues[iKey] + 2;
89
185k
                    if (osArg[0] == '"' && osArg.size() >= 2 &&
90
62.6k
                        osArg.back() == '"')
91
23.3k
                    {
92
23.3k
                        osArg = osArg.substr(1, osArg.length() - 2);
93
23.3k
                        char *pszArg = CPLUnescapeString(
94
23.3k
                            osArg.c_str(), nullptr, CPLES_BackslashQuotable);
95
23.3k
                        osArg = pszArg;
96
23.3k
                        CPLFree(pszArg);
97
23.3k
                    }
98
99
185k
                    if (papszKeyedValues[iKey][1] == 'e')
100
7.03k
                        osEPSG = std::move(osArg);
101
185k
                    if (papszKeyedValues[iKey][1] == 'p')
102
27.3k
                        osProj4 = std::move(osArg);
103
185k
                    if (papszKeyedValues[iKey][1] == 'w')
104
19.9k
                        osWKT = std::move(osArg);
105
185k
                }
106
349k
            }
107
108
408k
            nStartOfLine = VSIFTellL(m_fp);
109
408k
        }
110
111
        /* --------------------------------------------------------------------
112
         */
113
        /*      Handle coordinate system. */
114
        /* --------------------------------------------------------------------
115
         */
116
12.3k
        if (osWKT.length())
117
1.04k
        {
118
1.04k
            m_poSRS = new OGRSpatialReference();
119
1.04k
            m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
120
1.04k
            if (m_poSRS->importFromWkt(osWKT.c_str()) != OGRERR_NONE)
121
955
            {
122
955
                delete m_poSRS;
123
955
                m_poSRS = nullptr;
124
955
            }
125
1.04k
        }
126
11.3k
        else if (osEPSG.length())
127
92
        {
128
92
            m_poSRS = new OGRSpatialReference();
129
92
            m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
130
92
            if (m_poSRS->importFromEPSG(atoi(osEPSG)) != OGRERR_NONE)
131
87
            {
132
87
                delete m_poSRS;
133
87
                m_poSRS = nullptr;
134
87
            }
135
92
        }
136
11.2k
        else if (osProj4.length())
137
7.85k
        {
138
7.85k
            m_poSRS = new OGRSpatialReference();
139
7.85k
            m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
140
7.85k
            if (m_poSRS->importFromProj4(osProj4) != OGRERR_NONE)
141
5.53k
            {
142
5.53k
                delete m_poSRS;
143
5.53k
                m_poSRS = nullptr;
144
5.53k
            }
145
7.85k
        }
146
147
12.3k
        if (osGeometryType == "POINT")
148
3
            poFeatureDefn->SetGeomType(wkbPoint);
149
12.3k
        else if (osGeometryType == "MULTIPOINT")
150
0
            poFeatureDefn->SetGeomType(wkbMultiPoint);
151
12.3k
        else if (osGeometryType == "LINESTRING")
152
3
            poFeatureDefn->SetGeomType(wkbLineString);
153
12.3k
        else if (osGeometryType == "MULTILINESTRING")
154
222
            poFeatureDefn->SetGeomType(wkbMultiLineString);
155
12.1k
        else if (osGeometryType == "POLYGON")
156
55
            poFeatureDefn->SetGeomType(wkbPolygon);
157
12.1k
        else if (osGeometryType == "MULTIPOLYGON")
158
227
            poFeatureDefn->SetGeomType(wkbMultiPolygon);
159
160
        /* --------------------------------------------------------------------
161
         */
162
        /*      Process a region line. */
163
        /* --------------------------------------------------------------------
164
         */
165
12.3k
        if (osRegion.length() > 0)
166
767
        {
167
767
            char **papszTokens =
168
767
                CSLTokenizeStringComplex(osRegion.c_str(), "/", FALSE, FALSE);
169
170
767
            if (CSLCount(papszTokens) == 4)
171
416
            {
172
416
                sRegion.MinX = CPLAtofM(papszTokens[0]);
173
416
                sRegion.MaxX = CPLAtofM(papszTokens[1]);
174
416
                sRegion.MinY = CPLAtofM(papszTokens[2]);
175
416
                sRegion.MaxY = CPLAtofM(papszTokens[3]);
176
416
            }
177
178
767
            bRegionComplete = true;
179
180
767
            CSLDestroy(papszTokens);
181
767
        }
182
183
        /* --------------------------------------------------------------------
184
         */
185
        /*      Process fields. */
186
        /* --------------------------------------------------------------------
187
         */
188
12.3k
        if (osFieldNames.length() || osFieldTypes.length())
189
1.30k
        {
190
1.30k
            char **papszFN =
191
1.30k
                CSLTokenizeStringComplex(osFieldNames, "|", TRUE, TRUE);
192
1.30k
            char **papszFT =
193
1.30k
                CSLTokenizeStringComplex(osFieldTypes, "|", TRUE, TRUE);
194
1.30k
            const int nFNCount = CSLCount(papszFN);
195
1.30k
            const int nFTCount = CSLCount(papszFT);
196
1.30k
            const int nFieldCount = std::max(nFNCount, nFTCount);
197
198
8.77k
            for (int iField = 0; iField < nFieldCount; iField++)
199
7.47k
            {
200
7.47k
                OGRFieldDefn oField("", OFTString);
201
202
7.47k
                if (iField < nFNCount)
203
3.19k
                    oField.SetName(papszFN[iField]);
204
4.27k
                else
205
4.27k
                    oField.SetName(CPLString().Printf("Field_%d", iField + 1));
206
207
7.47k
                if (iField < nFTCount)
208
6.50k
                {
209
6.50k
                    if (EQUAL(papszFT[iField], "integer"))
210
155
                        oField.SetType(OFTInteger);
211
6.35k
                    else if (EQUAL(papszFT[iField], "double"))
212
18
                        oField.SetType(OFTReal);
213
6.33k
                    else if (EQUAL(papszFT[iField], "datetime"))
214
332
                        oField.SetType(OFTDateTime);
215
6.50k
                }
216
217
7.47k
                poFeatureDefn->AddFieldDefn(&oField);
218
7.47k
            }
219
220
1.30k
            CSLDestroy(papszFN);
221
1.30k
            CSLDestroy(papszFT);
222
1.30k
        }
223
12.3k
    }
224
0
    else
225
0
    {
226
0
        if (poSRS)
227
0
        {
228
0
            m_poSRS = poSRS->Clone();
229
0
            m_poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
230
0
        }
231
0
    }
232
233
12.3k
    poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(m_poSRS);
234
235
12.3k
    bValidFile = true;
236
12.3k
}
237
238
/************************************************************************/
239
/*                           ~OGRGmtLayer()                           */
240
/************************************************************************/
241
242
OGRGmtLayer::~OGRGmtLayer()
243
244
12.5k
{
245
12.5k
    if (m_nFeaturesRead > 0 && poFeatureDefn != nullptr)
246
1.97k
    {
247
1.97k
        CPLDebug("Gmt", "%d features read on layer '%s'.",
248
1.97k
                 static_cast<int>(m_nFeaturesRead), poFeatureDefn->GetName());
249
1.97k
    }
250
251
    /* -------------------------------------------------------------------- */
252
    /*      Write out the region bounds if we know where they go, and we    */
253
    /*      are in update mode.                                             */
254
    /* -------------------------------------------------------------------- */
255
12.5k
    if (nRegionOffset != 0 && bUpdate)
256
134
    {
257
134
        VSIFSeekL(m_fp, nRegionOffset, SEEK_SET);
258
134
        VSIFPrintfL(m_fp, "# @R%.12g/%.12g/%.12g/%.12g", sRegion.MinX,
259
134
                    sRegion.MaxX, sRegion.MinY, sRegion.MaxY);
260
134
    }
261
262
    /* -------------------------------------------------------------------- */
263
    /*      Clean up.                                                       */
264
    /* -------------------------------------------------------------------- */
265
12.5k
    CSLDestroy(papszKeyedValues);
266
267
12.5k
    if (poFeatureDefn)
268
12.3k
        poFeatureDefn->Release();
269
270
12.5k
    if (m_poSRS)
271
2.41k
        m_poSRS->Release();
272
273
12.5k
    if (m_fp != nullptr)
274
12.3k
        VSIFCloseL(m_fp);
275
12.5k
}
276
277
/************************************************************************/
278
/*                              ReadLine()                              */
279
/*                                                                      */
280
/*      Read a line into osLine.  If it is a comment line with @        */
281
/*      keyed values, parse out the keyed values into                   */
282
/*      papszKeyedValues.                                               */
283
/************************************************************************/
284
285
bool OGRGmtLayer::ReadLine()
286
287
1.35M
{
288
    /* -------------------------------------------------------------------- */
289
    /*      Clear last line.                                                */
290
    /* -------------------------------------------------------------------- */
291
1.35M
    osLine.erase();
292
1.35M
    if (papszKeyedValues)
293
443k
    {
294
443k
        CSLDestroy(papszKeyedValues);
295
443k
        papszKeyedValues = nullptr;
296
443k
    }
297
298
    /* -------------------------------------------------------------------- */
299
    /*      Read newline.                                                   */
300
    /* -------------------------------------------------------------------- */
301
1.35M
    const char *pszLine = CPLReadLineL(m_fp);
302
1.35M
    if (pszLine == nullptr)
303
10.5k
        return false;  // end of file.
304
305
1.33M
    osLine = pszLine;
306
307
    /* -------------------------------------------------------------------- */
308
    /*      If this is a comment line with keyed values, parse them.        */
309
    /* -------------------------------------------------------------------- */
310
311
1.33M
    if (osLine[0] != '#' || osLine.find_first_of('@') == std::string::npos)
312
886k
        return true;
313
314
453k
    CPLStringList aosKeyedValues;
315
7.72M
    for (size_t i = 0; i < osLine.length(); i++)
316
7.27M
    {
317
7.27M
        if (osLine[i] == '@' && i + 2 <= osLine.size())
318
952k
        {
319
952k
            bool bInQuotes = false;
320
321
952k
            size_t iValEnd = i + 2;  // Used after for.
322
30.8M
            for (; iValEnd < osLine.length(); iValEnd++)
323
30.4M
            {
324
30.4M
                if (!bInQuotes &&
325
13.7M
                    isspace(static_cast<unsigned char>(osLine[iValEnd])))
326
541k
                    break;
327
328
29.8M
                if (bInQuotes && iValEnd < osLine.length() - 1 &&
329
16.5M
                    osLine[iValEnd] == '\\')
330
40.5k
                {
331
40.5k
                    iValEnd++;
332
40.5k
                }
333
29.8M
                else if (osLine[iValEnd] == '"')
334
674k
                    bInQuotes = !bInQuotes;
335
29.8M
            }
336
337
952k
            const CPLString osValue = osLine.substr(i + 2, iValEnd - i - 2);
338
339
            // Unecape contents
340
952k
            char *pszUEValue =
341
952k
                CPLUnescapeString(osValue, nullptr, CPLES_BackslashQuotable);
342
343
952k
            CPLString osKeyValue = osLine.substr(i + 1, 1);
344
952k
            osKeyValue += pszUEValue;
345
952k
            CPLFree(pszUEValue);
346
952k
            aosKeyedValues.AddString(osKeyValue);
347
348
952k
            i = iValEnd;
349
952k
        }
350
7.27M
    }
351
453k
    papszKeyedValues = aosKeyedValues.StealList();
352
353
453k
    return true;
354
1.33M
}
355
356
/************************************************************************/
357
/*                            ResetReading()                            */
358
/************************************************************************/
359
360
void OGRGmtLayer::ResetReading()
361
362
33
{
363
33
    if (iNextFID == 0)
364
33
        return;
365
366
0
    iNextFID = 0;
367
0
    VSIFSeekL(m_fp, 0, SEEK_SET);
368
0
    ReadLine();
369
0
}
370
371
/************************************************************************/
372
/*                          ScanAheadForHole()                          */
373
/*                                                                      */
374
/*      Scan ahead to see if the next geometry is a hole.  If so        */
375
/*      return true, otherwise seek back to where we were and return    */
376
/*      false.                                                          */
377
/************************************************************************/
378
379
bool OGRGmtLayer::ScanAheadForHole()
380
381
28.5k
{
382
28.5k
    CPLString osSavedLine = osLine;
383
28.5k
    const vsi_l_offset nSavedLocation = VSIFTellL(m_fp);
384
385
43.3k
    while (ReadLine() && osLine[0] == '#')
386
19.0k
    {
387
19.0k
        if (papszKeyedValues != nullptr && papszKeyedValues[0][0] == 'H')
388
4.31k
            return true;
389
19.0k
    }
390
391
24.2k
    VSIFSeekL(m_fp, nSavedLocation, SEEK_SET);
392
24.2k
    osLine = std::move(osSavedLine);
393
394
    // We do not actually restore papszKeyedValues, but we
395
    // assume it does not matter since this method is only called
396
    // when processing the '>' line.
397
398
24.2k
    return false;
399
28.5k
}
400
401
/************************************************************************/
402
/*                           NextIsFeature()                            */
403
/*                                                                      */
404
/*      Returns true if the next line is a feature attribute line.      */
405
/*      This generally indicates the end of a multilinestring or        */
406
/*      multipolygon feature.                                           */
407
/************************************************************************/
408
409
bool OGRGmtLayer::NextIsFeature()
410
411
30.4k
{
412
30.4k
    CPLString osSavedLine = osLine;
413
30.4k
    const vsi_l_offset nSavedLocation = VSIFTellL(m_fp);
414
30.4k
    bool bReturn = false;
415
416
30.4k
    ReadLine();
417
418
30.4k
    if (osLine[0] == '#' && strstr(osLine, "@D") != nullptr)
419
5.87k
        bReturn = true;
420
421
30.4k
    VSIFSeekL(m_fp, nSavedLocation, SEEK_SET);
422
30.4k
    osLine = std::move(osSavedLine);
423
424
    // We do not actually restore papszKeyedValues, but we
425
    // assume it does not matter since this method is only called
426
    // when processing the '>' line.
427
428
30.4k
    return bReturn;
429
30.4k
}
430
431
/************************************************************************/
432
/*                         GetNextRawFeature()                          */
433
/************************************************************************/
434
435
OGRFeature *OGRGmtLayer::GetNextRawFeature()
436
437
75.8k
{
438
#if 0
439
    bool bMultiVertex =
440
        poFeatureDefn->GetGeomType() != wkbPoint
441
        && poFeatureDefn->GetGeomType() != wkbUnknown;
442
#endif
443
75.8k
    CPLString osFieldData;
444
75.8k
    OGRGeometry *poGeom = nullptr;
445
446
    /* -------------------------------------------------------------------- */
447
    /*      Read lines associated with this feature.                        */
448
    /* -------------------------------------------------------------------- */
449
901k
    for (; true; ReadLine())
450
901k
    {
451
901k
        if (osLine.length() == 0)
452
11.3k
            break;
453
454
890k
        if (osLine[0] == '>')
455
126k
        {
456
126k
            OGRwkbGeometryType eType = wkbUnknown;
457
126k
            if (poGeom)
458
64.3k
                eType = wkbFlatten(poGeom->getGeometryType());
459
126k
            if (eType == wkbMultiPolygon)
460
26.0k
            {
461
26.0k
                OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
462
26.0k
                if (ScanAheadForHole())
463
3.29k
                {
464
                    // Add a hole to the current polygon.
465
3.29k
                    poMP->getGeometryRef(poMP->getNumGeometries() - 1)
466
3.29k
                        ->addRingDirectly(new OGRLinearRing());
467
3.29k
                }
468
22.7k
                else if (!NextIsFeature())
469
20.9k
                {
470
20.9k
                    OGRPolygon *poPoly = new OGRPolygon();
471
472
20.9k
                    poPoly->addRingDirectly(new OGRLinearRing());
473
474
20.9k
                    poMP->addGeometryDirectly(poPoly);
475
20.9k
                }
476
1.82k
                else
477
1.82k
                    break; /* done geometry */
478
26.0k
            }
479
100k
            else if (eType == wkbPolygon)
480
2.55k
            {
481
2.55k
                if (ScanAheadForHole())
482
1.02k
                    poGeom->toPolygon()->addRingDirectly(new OGRLinearRing());
483
1.53k
                else
484
1.53k
                    break; /* done geometry */
485
2.55k
            }
486
98.0k
            else if (eType == wkbMultiLineString && !NextIsFeature())
487
3.68k
            {
488
3.68k
                poGeom->toMultiLineString()->addGeometryDirectly(
489
3.68k
                    new OGRLineString());
490
3.68k
            }
491
94.3k
            else if (poGeom != nullptr)
492
32.0k
            {
493
32.0k
                break;
494
32.0k
            }
495
62.3k
            else if (poFeatureDefn->GetGeomType() == wkbUnknown)
496
603
            {
497
603
                poFeatureDefn->SetGeomType(wkbLineString);
498
                // bMultiVertex = true;
499
603
            }
500
126k
        }
501
763k
        else if (osLine[0] == '#')
502
164k
        {
503
164k
            for (int i = 0;
504
742k
                 papszKeyedValues != nullptr && papszKeyedValues[i] != nullptr;
505
578k
                 i++)
506
578k
            {
507
578k
                if (papszKeyedValues[i][0] == 'D')
508
60.6k
                    osFieldData = papszKeyedValues[i] + 1;
509
578k
            }
510
164k
        }
511
599k
        else
512
599k
        {
513
            // Parse point line.
514
599k
            double dfX = 0.0;
515
599k
            double dfY = 0.0;
516
599k
            double dfZ = 0.0;
517
599k
            const int nDim = CPLsscanf(osLine, "%lf %lf %lf", &dfX, &dfY, &dfZ);
518
519
599k
            if (nDim >= 2)
520
126k
            {
521
126k
                if (poGeom == nullptr)
522
65.2k
                {
523
65.2k
                    switch (poFeatureDefn->GetGeomType())
524
65.2k
                    {
525
28.3k
                        case wkbLineString:
526
28.3k
                            poGeom = new OGRLineString();
527
28.3k
                            break;
528
529
1.56k
                        case wkbPolygon:
530
1.56k
                        {
531
1.56k
                            OGRPolygon *poPoly = new OGRPolygon();
532
1.56k
                            poGeom = poPoly;
533
1.56k
                            poPoly->addRingDirectly(new OGRLinearRing());
534
1.56k
                            break;
535
0
                        }
536
537
2.03k
                        case wkbMultiPolygon:
538
2.03k
                        {
539
2.03k
                            OGRPolygon *poPoly = new OGRPolygon();
540
2.03k
                            poPoly->addRingDirectly(new OGRLinearRing());
541
542
2.03k
                            OGRMultiPolygon *poMP = new OGRMultiPolygon();
543
2.03k
                            poGeom = poMP;
544
2.03k
                            poMP->addGeometryDirectly(poPoly);
545
2.03k
                        }
546
2.03k
                        break;
547
548
0
                        case wkbMultiPoint:
549
0
                            poGeom = new OGRMultiPoint();
550
0
                            break;
551
552
4.20k
                        case wkbMultiLineString:
553
4.20k
                        {
554
4.20k
                            OGRMultiLineString *poMLS =
555
4.20k
                                new OGRMultiLineString();
556
4.20k
                            poGeom = poMLS;
557
4.20k
                            poMLS->addGeometryDirectly(new OGRLineString());
558
4.20k
                            break;
559
0
                        }
560
561
0
                        case wkbPoint:
562
29.1k
                        case wkbUnknown:
563
29.1k
                        default:
564
29.1k
                            poGeom = new OGRPoint();
565
29.1k
                            break;
566
65.2k
                    }
567
65.2k
                }
568
569
126k
                CPLAssert(poGeom != nullptr);
570
                // cppcheck-suppress nullPointerRedundantCheck
571
126k
                switch (wkbFlatten(poGeom->getGeometryType()))
572
126k
                {
573
29.1k
                    case wkbPoint:
574
29.1k
                    {
575
29.1k
                        OGRPoint *poPoint = poGeom->toPoint();
576
29.1k
                        poPoint->setX(dfX);
577
29.1k
                        poPoint->setY(dfY);
578
29.1k
                        if (nDim == 3)
579
6.37k
                            poPoint->setZ(dfZ);
580
29.1k
                        break;
581
0
                    }
582
583
62.5k
                    case wkbLineString:
584
62.5k
                    {
585
62.5k
                        OGRLineString *poLS = poGeom->toLineString();
586
62.5k
                        if (nDim == 3)
587
8.19k
                            poLS->addPoint(dfX, dfY, dfZ);
588
54.3k
                        else
589
54.3k
                            poLS->addPoint(dfX, dfY);
590
62.5k
                        break;
591
0
                    }
592
593
4.44k
                    case wkbPolygon:
594
25.4k
                    case wkbMultiPolygon:
595
25.4k
                    {
596
25.4k
                        OGRPolygon *poPoly = nullptr;
597
598
25.4k
                        if (wkbFlatten(poGeom->getGeometryType()) ==
599
25.4k
                            wkbMultiPolygon)
600
21.0k
                        {
601
21.0k
                            OGRMultiPolygon *poMP = poGeom->toMultiPolygon();
602
21.0k
                            poPoly = poMP->getGeometryRef(
603
21.0k
                                poMP->getNumGeometries() - 1);
604
21.0k
                        }
605
4.44k
                        else
606
4.44k
                            poPoly = poGeom->toPolygon();
607
608
25.4k
                        OGRLinearRing *poRing = nullptr;
609
25.4k
                        if (poPoly->getNumInteriorRings() == 0)
610
16.0k
                            poRing = poPoly->getExteriorRing();
611
9.38k
                        else
612
9.38k
                            poRing = poPoly->getInteriorRing(
613
9.38k
                                poPoly->getNumInteriorRings() - 1);
614
615
25.4k
                        if (nDim == 3)
616
4.21k
                            poRing->addPoint(dfX, dfY, dfZ);
617
21.2k
                        else
618
21.2k
                            poRing->addPoint(dfX, dfY);
619
25.4k
                    }
620
25.4k
                    break;
621
622
8.98k
                    case wkbMultiLineString:
623
8.98k
                    {
624
8.98k
                        OGRMultiLineString *poML = poGeom->toMultiLineString();
625
8.98k
                        OGRLineString *poLine =
626
8.98k
                            poML->getGeometryRef(poML->getNumGeometries() - 1);
627
628
8.98k
                        if (nDim == 3)
629
859
                            poLine->addPoint(dfX, dfY, dfZ);
630
8.12k
                        else
631
8.12k
                            poLine->addPoint(dfX, dfY);
632
8.98k
                    }
633
8.98k
                    break;
634
635
0
                    default:
636
0
                        CPLAssert(false);
637
126k
                }
638
126k
            }
639
599k
        }
640
641
855k
        if (poGeom && wkbFlatten(poGeom->getGeometryType()) == wkbPoint)
642
29.1k
        {
643
29.1k
            ReadLine();
644
29.1k
            break;
645
29.1k
        }
646
855k
    }
647
648
75.8k
    if (poGeom == nullptr)
649
10.6k
        return nullptr;
650
651
    /* -------------------------------------------------------------------- */
652
    /*      Create feature.                                                 */
653
    /* -------------------------------------------------------------------- */
654
65.2k
    OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
655
65.2k
    poGeom->assignSpatialReference(m_poSRS);
656
65.2k
    poFeature->SetGeometryDirectly(poGeom);
657
65.2k
    poFeature->SetFID(iNextFID++);
658
659
    /* -------------------------------------------------------------------- */
660
    /*      Process field values.                                           */
661
    /* -------------------------------------------------------------------- */
662
65.2k
    char **papszFD = CSLTokenizeStringComplex(osFieldData, "|", TRUE, TRUE);
663
664
95.6k
    for (int iField = 0; papszFD != nullptr && papszFD[iField] != nullptr;
665
65.2k
         iField++)
666
37.3k
    {
667
37.3k
        if (iField >= poFeatureDefn->GetFieldCount())
668
6.91k
            break;
669
670
30.4k
        poFeature->SetField(iField, papszFD[iField]);
671
30.4k
    }
672
673
65.2k
    CSLDestroy(papszFD);
674
675
65.2k
    m_nFeaturesRead++;
676
677
65.2k
    return poFeature;
678
75.8k
}
679
680
/************************************************************************/
681
/*                           CompleteHeader()                           */
682
/*                                                                      */
683
/*      Finish writing out the header with field definitions and the    */
684
/*      layer geometry type.                                            */
685
/************************************************************************/
686
687
OGRErr OGRGmtLayer::CompleteHeader(OGRGeometry *poThisGeom)
688
689
127
{
690
    /* -------------------------------------------------------------------- */
691
    /*      If we do not already have a geometry type, try to work one      */
692
    /*      out and write it now.                                           */
693
    /* -------------------------------------------------------------------- */
694
127
    if (poFeatureDefn->GetGeomType() == wkbUnknown && poThisGeom != nullptr)
695
9
    {
696
9
        poFeatureDefn->SetGeomType(wkbFlatten(poThisGeom->getGeometryType()));
697
698
9
        const char *pszGeom = nullptr;
699
9
        switch (wkbFlatten(poFeatureDefn->GetGeomType()))
700
9
        {
701
0
            case wkbPoint:
702
0
                pszGeom = " @GPOINT";
703
0
                break;
704
0
            case wkbLineString:
705
0
                pszGeom = " @GLINESTRING";
706
0
                break;
707
0
            case wkbPolygon:
708
0
                pszGeom = " @GPOLYGON";
709
0
                break;
710
0
            case wkbMultiPoint:
711
0
                pszGeom = " @GMULTIPOINT";
712
0
                break;
713
9
            case wkbMultiLineString:
714
9
                pszGeom = " @GMULTILINESTRING";
715
9
                break;
716
0
            case wkbMultiPolygon:
717
0
                pszGeom = " @GMULTIPOLYGON";
718
0
                break;
719
0
            default:
720
0
                pszGeom = "";
721
0
                break;
722
9
        }
723
724
9
        VSIFPrintfL(m_fp, "#%s\n", pszGeom);
725
9
    }
726
727
    /* -------------------------------------------------------------------- */
728
    /*      Prepare and write the field names and types.                    */
729
    /* -------------------------------------------------------------------- */
730
127
    CPLString osFieldNames;
731
127
    CPLString osFieldTypes;
732
733
1.70k
    for (int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
734
1.57k
    {
735
1.57k
        if (iField > 0)
736
1.45k
        {
737
1.45k
            osFieldNames += "|";
738
1.45k
            osFieldTypes += "|";
739
1.45k
        }
740
741
1.57k
        osFieldNames += poFeatureDefn->GetFieldDefn(iField)->GetNameRef();
742
1.57k
        switch (poFeatureDefn->GetFieldDefn(iField)->GetType())
743
1.57k
        {
744
4
            case OFTInteger:
745
4
                osFieldTypes += "integer";
746
4
                break;
747
748
9
            case OFTReal:
749
9
                osFieldTypes += "double";
750
9
                break;
751
752
0
            case OFTDateTime:
753
0
                osFieldTypes += "datetime";
754
0
                break;
755
756
1.56k
            default:
757
1.56k
                osFieldTypes += "string";
758
1.56k
                break;
759
1.57k
        }
760
1.57k
    }
761
762
127
    if (poFeatureDefn->GetFieldCount() > 0)
763
126
    {
764
126
        VSIFPrintfL(m_fp, "# @N%s\n", osFieldNames.c_str());
765
126
        VSIFPrintfL(m_fp, "# @T%s\n", osFieldTypes.c_str());
766
126
    }
767
768
    /* -------------------------------------------------------------------- */
769
    /*      Mark the end of the header, and start of feature data.          */
770
    /* -------------------------------------------------------------------- */
771
127
    VSIFPrintfL(m_fp, "# FEATURE_DATA\n");
772
773
127
    bHeaderComplete = true;
774
127
    bRegionComplete = true;  // no feature written, so we know them all!
775
776
127
    return OGRERR_NONE;
777
127
}
778
779
/************************************************************************/
780
/*                           ICreateFeature()                            */
781
/************************************************************************/
782
783
OGRErr OGRGmtLayer::ICreateFeature(OGRFeature *poFeature)
784
785
46.4k
{
786
46.4k
    if (!bUpdate)
787
0
    {
788
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
789
0
                 "Cannot create features on read-only dataset.");
790
0
        return OGRERR_FAILURE;
791
0
    }
792
793
    /* -------------------------------------------------------------------- */
794
    /*      Do we need to write the header describing the fields?           */
795
    /* -------------------------------------------------------------------- */
796
46.4k
    if (!bHeaderComplete)
797
127
    {
798
127
        OGRErr eErr = CompleteHeader(poFeature->GetGeometryRef());
799
800
127
        if (eErr != OGRERR_NONE)
801
0
            return eErr;
802
127
    }
803
804
    /* -------------------------------------------------------------------- */
805
    /*      Write out the feature                                           */
806
    /* -------------------------------------------------------------------- */
807
46.4k
    OGRGeometry *poGeom = poFeature->GetGeometryRef();
808
809
46.4k
    if (poGeom == nullptr)
810
44.8k
    {
811
44.8k
        CPLError(CE_Failure, CPLE_AppDefined,
812
44.8k
                 "Features without geometry not supported by GMT writer.");
813
44.8k
        return OGRERR_FAILURE;
814
44.8k
    }
815
816
1.54k
    if (poFeatureDefn->GetGeomType() == wkbUnknown)
817
55
        poFeatureDefn->SetGeomType(wkbFlatten(poGeom->getGeometryType()));
818
819
    // Do we need a vertex collection marker grouping vertices.
820
1.54k
    if (poFeatureDefn->GetGeomType() != wkbPoint)
821
1.48k
        VSIFPrintfL(m_fp, ">\n");
822
823
    /* -------------------------------------------------------------------- */
824
    /*      Write feature properties()                                      */
825
    /* -------------------------------------------------------------------- */
826
1.54k
    if (poFeatureDefn->GetFieldCount() > 0)
827
1.54k
    {
828
1.54k
        CPLString osFieldData;
829
830
5.92k
        for (int iField = 0; iField < poFeatureDefn->GetFieldCount(); iField++)
831
4.38k
        {
832
4.38k
            OGRFieldType eFType =
833
4.38k
                poFeatureDefn->GetFieldDefn(iField)->GetType();
834
4.38k
            const char *pszRawValue = poFeature->GetFieldAsString(iField);
835
836
4.38k
            if (iField > 0)
837
2.84k
                osFieldData += "|";
838
839
            // We do not want prefix spaces for numeric values.
840
4.38k
            if (eFType == OFTInteger || eFType == OFTReal)
841
8
                while (*pszRawValue == ' ')
842
0
                    pszRawValue++;
843
844
4.38k
            if (strchr(pszRawValue, ' ') || strchr(pszRawValue, '|') ||
845
3.86k
                strchr(pszRawValue, '\t') || strchr(pszRawValue, '\n'))
846
959
            {
847
959
                osFieldData += "\"";
848
849
959
                char *pszEscapedVal =
850
959
                    CPLEscapeString(pszRawValue, -1, CPLES_BackslashQuotable);
851
959
                osFieldData += pszEscapedVal;
852
959
                CPLFree(pszEscapedVal);
853
854
959
                osFieldData += "\"";
855
959
            }
856
3.42k
            else
857
3.42k
                osFieldData += pszRawValue;
858
4.38k
        }
859
860
1.54k
        VSIFPrintfL(m_fp, "# @D%s\n", osFieldData.c_str());
861
1.54k
    }
862
863
    /* -------------------------------------------------------------------- */
864
    /*      Write Geometry                                                  */
865
    /* -------------------------------------------------------------------- */
866
1.54k
    return WriteGeometry(OGRGeometry::ToHandle(poGeom), true);
867
46.4k
}
868
869
/************************************************************************/
870
/*                           WriteGeometry()                            */
871
/*                                                                      */
872
/*      Write a geometry to the file.  If bHaveAngle is true it         */
873
/*      means the angle bracket preceding the point stream has          */
874
/*      already been written out.                                       */
875
/*                                                                      */
876
/*      We use the C API for geometry access because of its            */
877
/*      simplified access to vertices and children geometries.          */
878
/************************************************************************/
879
880
OGRErr OGRGmtLayer::WriteGeometry(OGRGeometryH hGeom, bool bHaveAngle)
881
882
1.88k
{
883
    /* -------------------------------------------------------------------- */
884
    /*      This is a geometry with sub-geometries.                         */
885
    /* -------------------------------------------------------------------- */
886
1.88k
    if (OGR_G_GetGeometryCount(hGeom) > 0)
887
232
    {
888
232
        OGRErr eErr = OGRERR_NONE;
889
890
232
        for (int iGeom = 0;
891
572
             iGeom < OGR_G_GetGeometryCount(hGeom) && eErr == OGRERR_NONE;
892
340
             iGeom++)
893
340
        {
894
            // We need to emit polygon @P and @H items while we still
895
            // know this is a polygon and which is the outer and inner
896
            // ring.
897
340
            if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) == wkbPolygon)
898
130
            {
899
130
                if (!bHaveAngle)
900
38
                {
901
38
                    VSIFPrintfL(m_fp, ">\n");
902
38
                    bHaveAngle = true;
903
38
                }
904
130
                if (iGeom == 0)
905
92
                    VSIFPrintfL(m_fp, "# @P\n");
906
38
                else
907
38
                    VSIFPrintfL(m_fp, "# @H\n");
908
130
            }
909
910
340
            eErr =
911
340
                WriteGeometry(OGR_G_GetGeometryRef(hGeom, iGeom), bHaveAngle);
912
340
            bHaveAngle = false;
913
340
        }
914
232
        return eErr;
915
232
    }
916
917
    /* -------------------------------------------------------------------- */
918
    /*      If this is not a point we need to have an angle bracket to      */
919
    /*      mark the vertex list.                                           */
920
    /* -------------------------------------------------------------------- */
921
1.64k
    if (wkbFlatten(OGR_G_GetGeometryType(hGeom)) != wkbPoint && !bHaveAngle)
922
70
        VSIFPrintfL(m_fp, ">\n");
923
924
    /* -------------------------------------------------------------------- */
925
    /*      Dump vertices.                                                  */
926
    /* -------------------------------------------------------------------- */
927
1.64k
    const int nPointCount = OGR_G_GetPointCount(hGeom);
928
1.64k
    const int nDim = OGR_G_GetCoordinateDimension(hGeom);
929
    // For testing only. Ticket #6453
930
1.64k
    const bool bUseTab =
931
1.64k
        CPLTestBool(CPLGetConfigOption("GMT_USE_TAB", "FALSE"));
932
933
4.20k
    for (int iPoint = 0; iPoint < nPointCount; iPoint++)
934
2.55k
    {
935
2.55k
        const double dfX = OGR_G_GetX(hGeom, iPoint);
936
2.55k
        const double dfY = OGR_G_GetY(hGeom, iPoint);
937
2.55k
        const double dfZ = OGR_G_GetZ(hGeom, iPoint);
938
939
2.55k
        sRegion.Merge(dfX, dfY);
940
2.55k
        char szLine[128];
941
2.55k
        OGRMakeWktCoordinate(szLine, dfX, dfY, dfZ, nDim);
942
2.55k
        if (bUseTab)
943
0
        {
944
0
            for (char *szPtr = szLine; *szPtr != '\0'; ++szPtr)
945
0
            {
946
0
                if (*szPtr == ' ')
947
0
                    *szPtr = '\t';
948
0
            }
949
0
        }
950
2.55k
        if (VSIFPrintfL(m_fp, "%s\n", szLine) < 1)
951
0
        {
952
0
            CPLError(CE_Failure, CPLE_FileIO, "Gmt write failure: %s",
953
0
                     VSIStrerror(errno));
954
0
            return OGRERR_FAILURE;
955
0
        }
956
2.55k
    }
957
958
1.64k
    return OGRERR_NONE;
959
1.64k
}
960
961
/************************************************************************/
962
/*                            IGetExtent()                              */
963
/*                                                                      */
964
/*      Fetch extent of the data currently stored in the dataset.       */
965
/*      The bForce flag has no effect on SHO files since that value     */
966
/*      is always in the header.                                        */
967
/*                                                                      */
968
/*      Returns OGRERR_NONE/OGRRERR_FAILURE.                            */
969
/************************************************************************/
970
971
OGRErr OGRGmtLayer::IGetExtent(int iGeomField, OGREnvelope *psExtent,
972
                               bool bForce)
973
974
0
{
975
0
    if (bRegionComplete && sRegion.IsInit())
976
0
    {
977
0
        *psExtent = sRegion;
978
0
        return OGRERR_NONE;
979
0
    }
980
981
0
    return OGRLayer::IGetExtent(iGeomField, psExtent, bForce);
982
0
}
983
984
/************************************************************************/
985
/*                           TestCapability()                           */
986
/************************************************************************/
987
988
int OGRGmtLayer::TestCapability(const char *pszCap) const
989
990
421
{
991
421
    if (EQUAL(pszCap, OLCRandomRead))
992
0
        return FALSE;
993
994
421
    if (EQUAL(pszCap, OLCSequentialWrite))
995
0
        return TRUE;
996
997
421
    if (EQUAL(pszCap, OLCFastSpatialFilter))
998
0
        return FALSE;
999
1000
421
    if (EQUAL(pszCap, OLCFastGetExtent))
1001
0
        return bRegionComplete;
1002
1003
421
    if (EQUAL(pszCap, OLCCreateField))
1004
0
        return TRUE;
1005
1006
421
    if (EQUAL(pszCap, OLCZGeometries))
1007
0
        return TRUE;
1008
1009
421
    return FALSE;
1010
421
}
1011
1012
/************************************************************************/
1013
/*                            CreateField()                             */
1014
/************************************************************************/
1015
1016
OGRErr OGRGmtLayer::CreateField(const OGRFieldDefn *poField, int bApproxOK)
1017
1018
1.81k
{
1019
1.81k
    if (!bUpdate)
1020
0
    {
1021
0
        CPLError(CE_Failure, CPLE_NoWriteAccess,
1022
0
                 "Cannot create fields on read-only dataset.");
1023
0
        return OGRERR_FAILURE;
1024
0
    }
1025
1026
1.81k
    if (bHeaderComplete)
1027
0
    {
1028
0
        CPLError(CE_Failure, CPLE_AppDefined,
1029
0
                 "Unable to create fields after features have been created.");
1030
0
        return OGRERR_FAILURE;
1031
0
    }
1032
1033
1.81k
    switch (poField->GetType())
1034
1.81k
    {
1035
4
        case OFTInteger:
1036
13
        case OFTReal:
1037
1.81k
        case OFTString:
1038
1.81k
        case OFTDateTime:
1039
1.81k
            poFeatureDefn->AddFieldDefn(poField);
1040
1.81k
            return OGRERR_NONE;
1041
1042
0
        default:
1043
0
            if (!bApproxOK)
1044
0
            {
1045
0
                CPLError(CE_Failure, CPLE_AppDefined,
1046
0
                         "Field %s is of unsupported type %s.",
1047
0
                         poField->GetNameRef(),
1048
0
                         poField->GetFieldTypeName(poField->GetType()));
1049
0
                return OGRERR_FAILURE;
1050
0
            }
1051
0
            else if (poField->GetType() == OFTDate ||
1052
0
                     poField->GetType() == OFTTime)
1053
0
            {
1054
0
                OGRFieldDefn oModDef(poField);
1055
0
                oModDef.SetType(OFTDateTime);
1056
0
                poFeatureDefn->AddFieldDefn(poField);
1057
0
                return OGRERR_NONE;
1058
0
            }
1059
0
            else
1060
0
            {
1061
0
                OGRFieldDefn oModDef(poField);
1062
0
                oModDef.SetType(OFTString);
1063
0
                poFeatureDefn->AddFieldDefn(poField);
1064
0
                return OGRERR_NONE;
1065
0
            }
1066
1.81k
    }
1067
1.81k
}