Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/gtfs/ogrgtfsdriver.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Implements GTFS driver.
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_port.h"
14
#include "gdal_priv.h"
15
#include "ogrsf_frmts.h"
16
17
#include <cassert>
18
#include <map>
19
#include <new>
20
#include <utility>
21
22
constexpr const char *const apszCSVDriver[] = {"CSV", nullptr};
23
24
/************************************************************************/
25
/*                            OGRGTFSDataset                            */
26
/************************************************************************/
27
28
class OGRGTFSDataset final : public GDALDataset
29
{
30
    std::vector<std::unique_ptr<OGRLayer>> m_apoLayers{};
31
32
  public:
33
626
    OGRGTFSDataset() = default;
34
35
    int GetLayerCount() const override
36
0
    {
37
0
        return static_cast<int>(m_apoLayers.size());
38
0
    }
39
40
    const OGRLayer *GetLayer(int nIdx) const override;
41
42
    static int Identify(GDALOpenInfo *poOpenInfo);
43
    static GDALDataset *Open(GDALOpenInfo *poOpenInfo);
44
};
45
46
/************************************************************************/
47
/*                              GetLayer()                              */
48
/************************************************************************/
49
50
const OGRLayer *OGRGTFSDataset::GetLayer(int nIdx) const
51
0
{
52
0
    return nIdx >= 0 && nIdx < static_cast<int>(m_apoLayers.size())
53
0
               ? m_apoLayers[nIdx].get()
54
0
               : nullptr;
55
0
}
56
57
/************************************************************************/
58
/*                             OGRGTFSLayer                             */
59
/************************************************************************/
60
61
class OGRGTFSLayer final : public OGRLayer
62
{
63
    const std::string m_osDirname;
64
    std::unique_ptr<GDALDataset> m_poUnderlyingDS{};
65
    OGRLayer *m_poUnderlyingLayer = nullptr;  // owned by m_poUnderlyingDS
66
    OGRFeatureDefn *m_poFeatureDefn = nullptr;
67
    int m_nTripIdIdx = -1;
68
    int m_nLatIdx = -1;
69
    int m_nLonIdx = -1;
70
    bool m_bIsTrips = false;
71
    bool m_bPrepared = false;
72
    std::map<std::string, std::pair<double, double>> m_oMapStopIdToLonLat{};
73
    std::map<std::string, std::map<int, std::string>> m_oMapTripIdToStopIds{};
74
75
    void PrepareTripsData();
76
77
  public:
78
    OGRGTFSLayer(const std::string &osDirname, const char *pszName,
79
                 std::unique_ptr<GDALDataset> &&poUnderlyingDS);
80
    ~OGRGTFSLayer() override;
81
82
    void ResetReading() override;
83
    OGRFeature *GetNextFeature() override;
84
    bool TestCapability(const char *) const override;
85
    GIntBig GetFeatureCount(int bForce) override;
86
87
    const OGRFeatureDefn *GetLayerDefn() const override
88
0
    {
89
0
        return m_poFeatureDefn;
90
0
    }
91
};
92
93
/************************************************************************/
94
/*                            OGRGTFSLayer()                            */
95
/************************************************************************/
96
97
OGRGTFSLayer::OGRGTFSLayer(const std::string &osDirname, const char *pszName,
98
                           std::unique_ptr<GDALDataset> &&poUnderlyingDS)
99
7.17k
    : m_osDirname(osDirname), m_poUnderlyingDS(std::move(poUnderlyingDS))
100
7.17k
{
101
7.17k
    m_poFeatureDefn = new OGRFeatureDefn(pszName);
102
7.17k
    SetDescription(pszName);
103
7.17k
    m_poFeatureDefn->SetGeomType(wkbNone);
104
7.17k
    m_poFeatureDefn->Reference();
105
106
7.17k
    m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
107
7.17k
    assert(m_poUnderlyingLayer);
108
7.17k
#if defined(__GNUC__)
109
7.17k
#pragma GCC diagnostic push
110
7.17k
#pragma GCC diagnostic ignored "-Wnull-dereference"
111
7.17k
#endif
112
7.17k
    auto poSrcLayerDefn = m_poUnderlyingLayer->GetLayerDefn();
113
7.17k
#if defined(__GNUC__)
114
7.17k
#pragma GCC diagnostic pop
115
7.17k
#endif
116
7.17k
    const int nFieldCount = poSrcLayerDefn->GetFieldCount();
117
7.17k
    m_nTripIdIdx = poSrcLayerDefn->GetFieldIndex("trip_id");
118
7.17k
    if (EQUAL(pszName, "stops"))
119
0
    {
120
0
        m_nLatIdx = poSrcLayerDefn->GetFieldIndex("stop_lat");
121
0
        m_nLonIdx = poSrcLayerDefn->GetFieldIndex("stop_lon");
122
0
    }
123
7.17k
    else if (EQUAL(pszName, "shapes"))
124
0
    {
125
0
        m_nLatIdx = poSrcLayerDefn->GetFieldIndex("shape_pt_lat");
126
0
        m_nLonIdx = poSrcLayerDefn->GetFieldIndex("shape_pt_lon");
127
0
    }
128
7.17k
    m_bIsTrips = EQUAL(pszName, "trips") && m_nTripIdIdx >= 0;
129
130
7.17k
    if (m_nLatIdx >= 0 && m_nLonIdx >= 0)
131
0
        m_poFeatureDefn->SetGeomType(wkbPoint);
132
7.17k
    else if (m_bIsTrips)
133
0
        m_poFeatureDefn->SetGeomType(wkbLineString);
134
135
23.9k
    for (int i = 0; i < nFieldCount; ++i)
136
16.7k
    {
137
16.7k
        OGRFieldDefn oFieldDefn(poSrcLayerDefn->GetFieldDefn(i));
138
16.7k
        const char *pszFieldName = oFieldDefn.GetNameRef();
139
16.7k
        if (i == m_nLatIdx || i == m_nLonIdx ||
140
16.7k
            EQUAL(pszFieldName, "shape_dist_traveled"))
141
2
        {
142
2
            oFieldDefn.SetType(OFTReal);
143
2
        }
144
16.7k
        else if (EQUAL(pszFieldName, "shape_pt_sequence"))
145
0
        {
146
0
            oFieldDefn.SetType(OFTInteger);
147
0
        }
148
16.7k
        else if (EQUAL(pszFieldName, "date") ||
149
16.7k
                 EQUAL(pszFieldName, "start_date") ||
150
16.7k
                 EQUAL(pszFieldName, "end_date"))
151
0
        {
152
0
            oFieldDefn.SetType(OFTDate);
153
0
        }
154
16.7k
        else if (EQUAL(pszFieldName, "arrival_time") ||
155
16.7k
                 EQUAL(pszFieldName, "departure_time"))
156
0
        {
157
0
            oFieldDefn.SetType(OFTTime);
158
0
        }
159
16.7k
        else if (strstr(pszFieldName, "_type") ||
160
16.5k
                 EQUAL(pszFieldName, "stop_sequence"))
161
176
        {
162
176
            oFieldDefn.SetType(OFTInteger);
163
176
        }
164
16.5k
        else if (EQUAL(pszFieldName, "monday") ||
165
16.5k
                 EQUAL(pszFieldName, "tuesday") ||
166
16.5k
                 EQUAL(pszFieldName, "wednesday") ||
167
16.5k
                 EQUAL(pszFieldName, "thursday") ||
168
16.5k
                 EQUAL(pszFieldName, "friday") ||
169
16.1k
                 EQUAL(pszFieldName, "saturday") ||
170
15.6k
                 EQUAL(pszFieldName, "sunday"))
171
885
        {
172
885
            oFieldDefn.SetType(OFTInteger);
173
885
            oFieldDefn.SetSubType(OFSTBoolean);
174
885
        }
175
16.7k
        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
176
16.7k
    }
177
7.17k
}
178
179
/************************************************************************/
180
/*                           ~OGRGTFSLayer()                            */
181
/************************************************************************/
182
183
OGRGTFSLayer::~OGRGTFSLayer()
184
7.17k
{
185
7.17k
    m_poFeatureDefn->Release();
186
7.17k
}
187
188
/************************************************************************/
189
/*                            ResetReading()                            */
190
/************************************************************************/
191
192
void OGRGTFSLayer::ResetReading()
193
0
{
194
0
    m_poUnderlyingLayer->ResetReading();
195
0
}
196
197
/************************************************************************/
198
/*                          PrepareTripsData()                          */
199
/************************************************************************/
200
201
void OGRGTFSLayer::PrepareTripsData()
202
0
{
203
0
    m_bPrepared = true;
204
0
    try
205
0
    {
206
0
        {
207
0
            auto poStopsDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
208
0
                std::string(m_osDirname).append("/stops.txt").c_str(),
209
0
                GDAL_OF_VECTOR, apszCSVDriver));
210
0
            if (!poStopsDS)
211
0
                return;
212
0
            auto poStopsLyr = poStopsDS->GetLayer(0);
213
0
            if (!poStopsLyr)
214
0
                return;
215
0
            const auto poStopsLyrDefn = poStopsLyr->GetLayerDefn();
216
0
            const int nStopIdIdx = poStopsLyrDefn->GetFieldIndex("stop_id");
217
0
            const int nStopLatIdx = poStopsLyrDefn->GetFieldIndex("stop_lat");
218
0
            const int nStopLonIdx = poStopsLyrDefn->GetFieldIndex("stop_lon");
219
0
            if (nStopIdIdx < 0 || nStopLatIdx < 0 || nStopLonIdx < 0)
220
0
                return;
221
0
            for (auto &&poFeature : poStopsLyr)
222
0
            {
223
0
                const char *pszStopId = poFeature->GetFieldAsString(nStopIdIdx);
224
0
                if (pszStopId)
225
0
                {
226
0
                    m_oMapStopIdToLonLat[pszStopId] = std::make_pair(
227
0
                        poFeature->GetFieldAsDouble(nStopLonIdx),
228
0
                        poFeature->GetFieldAsDouble(nStopLatIdx));
229
0
                }
230
0
            }
231
0
        }
232
233
0
        auto poStopTimesDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
234
0
            std::string(m_osDirname).append("/stop_times.txt").c_str(),
235
0
            GDAL_OF_VECTOR, apszCSVDriver));
236
0
        if (!poStopTimesDS)
237
0
            return;
238
0
        auto poStopTimesLyr = poStopTimesDS->GetLayer(0);
239
0
        if (!poStopTimesLyr)
240
0
            return;
241
0
        const auto poStopTimesLyrDefn = poStopTimesLyr->GetLayerDefn();
242
0
        const int nStopIdIdx = poStopTimesLyrDefn->GetFieldIndex("stop_id");
243
0
        const int nTripIdIdx = poStopTimesLyrDefn->GetFieldIndex("trip_id");
244
0
        const int nStopSequenceIdx =
245
0
            poStopTimesLyrDefn->GetFieldIndex("stop_sequence");
246
0
        if (nStopIdIdx < 0 || nTripIdIdx < 0 || nStopSequenceIdx < 0)
247
0
            return;
248
0
        for (auto &&poFeature : poStopTimesLyr)
249
0
        {
250
0
            const char *pszStopId = poFeature->GetFieldAsString(nStopIdIdx);
251
0
            const char *pszTripId = poFeature->GetFieldAsString(nTripIdIdx);
252
0
            const int nStopSequence =
253
0
                poFeature->GetFieldAsInteger(nStopSequenceIdx);
254
0
            if (pszStopId && pszTripId)
255
0
            {
256
0
                m_oMapTripIdToStopIds[pszTripId][nStopSequence] = pszStopId;
257
0
            }
258
0
        }
259
0
    }
260
0
    catch (const std::bad_alloc &)
261
0
    {
262
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "Not enough memory");
263
0
    }
264
0
}
265
266
/************************************************************************/
267
/*                           GetNextFeature()                           */
268
/************************************************************************/
269
270
OGRFeature *OGRGTFSLayer::GetNextFeature()
271
0
{
272
0
    if (m_bIsTrips && !m_bPrepared)
273
0
        PrepareTripsData();
274
275
0
    while (true)
276
0
    {
277
0
        auto poSrcFeature =
278
0
            std::unique_ptr<OGRFeature>(m_poUnderlyingLayer->GetNextFeature());
279
0
        if (poSrcFeature == nullptr)
280
0
            return nullptr;
281
282
0
        auto poFeature = std::make_unique<OGRFeature>(m_poFeatureDefn);
283
0
        const int nFieldCount = poSrcFeature->GetFieldCount();
284
0
        poFeature->SetFID(poSrcFeature->GetFID());
285
0
        auto poSrcLayerDefn = m_poUnderlyingLayer->GetLayerDefn();
286
0
        for (int i = 0; i < nFieldCount; ++i)
287
0
        {
288
0
            const auto eType = m_poFeatureDefn->GetFieldDefn(i)->GetType();
289
0
            if (poSrcLayerDefn->GetFieldDefn(i)->GetType() == eType)
290
0
            {
291
0
                poFeature->SetField(i, poSrcFeature->GetRawFieldRef(i));
292
0
            }
293
0
            else if (eType == OFTDate)
294
0
            {
295
0
                const char *pszVal = poSrcFeature->GetFieldAsString(i);
296
0
                constexpr char ZERO_DIGIT = '0';
297
0
                if (pszVal && strlen(pszVal) == 8)
298
0
                {
299
0
                    const int nYear = (pszVal[0] - ZERO_DIGIT) * 1000 +
300
0
                                      (pszVal[1] - ZERO_DIGIT) * 100 +
301
0
                                      (pszVal[2] - ZERO_DIGIT) * 10 +
302
0
                                      (pszVal[3] - ZERO_DIGIT);
303
0
                    const int nMonth = (pszVal[4] - ZERO_DIGIT) * 10 +
304
0
                                       (pszVal[5] - ZERO_DIGIT);
305
0
                    const int nDay = (pszVal[6] - ZERO_DIGIT) * 10 +
306
0
                                     (pszVal[7] - ZERO_DIGIT);
307
0
                    poFeature->SetField(i, nYear, nMonth, nDay, 0, 0, 0, 0);
308
0
                }
309
0
            }
310
0
            else if (eType == OFTInteger)
311
0
            {
312
0
                poFeature->SetField(i, poSrcFeature->GetFieldAsInteger(i));
313
0
            }
314
0
            else
315
0
            {
316
0
                const char *pszVal = poSrcFeature->GetFieldAsString(i);
317
0
                poFeature->SetField(i, pszVal);
318
0
            }
319
0
        }
320
0
        if (m_nLatIdx >= 0 && m_nLonIdx >= 0)
321
0
        {
322
0
            poFeature->SetGeometryDirectly(
323
0
                new OGRPoint(poFeature->GetFieldAsDouble(m_nLonIdx),
324
0
                             poFeature->GetFieldAsDouble(m_nLatIdx)));
325
0
        }
326
0
        else if (m_bIsTrips)
327
0
        {
328
0
            const char *pszTripId = poFeature->GetFieldAsString(m_nTripIdIdx);
329
0
            if (pszTripId)
330
0
            {
331
0
                const auto oIter = m_oMapTripIdToStopIds.find(pszTripId);
332
0
                if (oIter != m_oMapTripIdToStopIds.end())
333
0
                {
334
0
                    OGRLineString *poLS = new OGRLineString();
335
0
                    for (const auto &kv : oIter->second)
336
0
                    {
337
0
                        const auto oIter2 =
338
0
                            m_oMapStopIdToLonLat.find(kv.second);
339
0
                        if (oIter2 != m_oMapStopIdToLonLat.end())
340
0
                            poLS->addPoint(oIter2->second.first,
341
0
                                           oIter2->second.second);
342
0
                    }
343
0
                    poFeature->SetGeometryDirectly(poLS);
344
0
                }
345
0
            }
346
0
        }
347
0
        if ((!m_poFilterGeom || FilterGeometry(poFeature->GetGeometryRef())) &&
348
0
            (!m_poAttrQuery || m_poAttrQuery->Evaluate(poFeature.get())))
349
0
        {
350
0
            return poFeature.release();
351
0
        }
352
0
    }
353
0
}
354
355
/************************************************************************/
356
/*                          GetFeatureCount()                           */
357
/************************************************************************/
358
359
GIntBig OGRGTFSLayer::GetFeatureCount(int bForce)
360
0
{
361
0
    if (m_poFilterGeom || m_poAttrQuery)
362
0
        return OGRLayer::GetFeatureCount(bForce);
363
0
    return m_poUnderlyingLayer->GetFeatureCount(bForce);
364
0
}
365
366
/************************************************************************/
367
/*                           TestCapability()                           */
368
/************************************************************************/
369
370
bool OGRGTFSLayer::TestCapability(const char *pszCap) const
371
0
{
372
0
    return EQUAL(pszCap, OLCStringsAsUTF8);
373
0
}
374
375
/************************************************************************/
376
/*                        OGRGTFSShapesGeomLayer                        */
377
/************************************************************************/
378
379
class OGRGTFSShapesGeomLayer final : public OGRLayer
380
{
381
    std::unique_ptr<GDALDataset> m_poUnderlyingDS{};
382
    OGRLayer *m_poUnderlyingLayer = nullptr;  // owned by m_poUnderlyingDS
383
    OGRFeatureDefn *m_poFeatureDefn = nullptr;
384
    bool m_bPrepared = false;
385
    std::vector<std::unique_ptr<OGRFeature>> m_apoFeatures{};
386
    size_t m_nIdx = 0;
387
388
    void Prepare();
389
390
  public:
391
    explicit OGRGTFSShapesGeomLayer(
392
        std::unique_ptr<GDALDataset> &&poUnderlyingDS);
393
    ~OGRGTFSShapesGeomLayer() override;
394
395
    void ResetReading() override;
396
    OGRFeature *GetNextFeature() override;
397
    bool TestCapability(const char *) const override;
398
399
    const OGRFeatureDefn *GetLayerDefn() const override
400
0
    {
401
0
        return m_poFeatureDefn;
402
0
    }
403
404
    GIntBig GetFeatureCount(int bForce) override;
405
};
406
407
/************************************************************************/
408
/*                       OGRGTFSShapesGeomLayer()                       */
409
/************************************************************************/
410
411
OGRGTFSShapesGeomLayer::OGRGTFSShapesGeomLayer(
412
    std::unique_ptr<GDALDataset> &&poUnderlyingDS)
413
0
    : m_poUnderlyingDS(std::move(poUnderlyingDS))
414
0
{
415
0
    m_poFeatureDefn = new OGRFeatureDefn("shapes_geom");
416
0
    SetDescription("shapes_geom");
417
0
    m_poFeatureDefn->SetGeomType(wkbLineString);
418
0
    m_poFeatureDefn->Reference();
419
0
    OGRFieldDefn oField("shape_id", OFTString);
420
0
    m_poFeatureDefn->AddFieldDefn(&oField);
421
422
0
    m_poUnderlyingLayer = m_poUnderlyingDS->GetLayer(0);
423
0
}
424
425
/************************************************************************/
426
/*                      ~OGRGTFSShapesGeomLayer()                       */
427
/************************************************************************/
428
429
OGRGTFSShapesGeomLayer::~OGRGTFSShapesGeomLayer()
430
0
{
431
0
    m_poFeatureDefn->Release();
432
0
}
433
434
/************************************************************************/
435
/*                            ResetReading()                            */
436
/************************************************************************/
437
438
void OGRGTFSShapesGeomLayer::ResetReading()
439
0
{
440
0
    m_nIdx = 0;
441
0
}
442
443
/************************************************************************/
444
/*                              Prepare()                               */
445
/************************************************************************/
446
447
void OGRGTFSShapesGeomLayer::Prepare()
448
0
{
449
0
    m_bPrepared = true;
450
0
    const auto poSrcLayerDefn = m_poUnderlyingLayer->GetLayerDefn();
451
0
    const int nShapeIdIdx = poSrcLayerDefn->GetFieldIndex("shape_id");
452
0
    const int nLonIdx = poSrcLayerDefn->GetFieldIndex("shape_pt_lon");
453
0
    const int nLatIdx = poSrcLayerDefn->GetFieldIndex("shape_pt_lat");
454
0
    const int nSeqIdx = poSrcLayerDefn->GetFieldIndex("shape_pt_sequence");
455
0
    if (nShapeIdIdx < 0 || nLonIdx < 0 || nLatIdx < 0 || nSeqIdx < 0)
456
0
        return;
457
0
    std::map<std::string, std::map<int, std::pair<double, double>>> oMap;
458
0
    try
459
0
    {
460
0
        for (auto &&poFeature : m_poUnderlyingLayer)
461
0
        {
462
0
            const char *pszShapeId = poFeature->GetFieldAsString(nShapeIdIdx);
463
0
            if (pszShapeId)
464
0
            {
465
0
                const int nSeq = poFeature->GetFieldAsInteger(nSeqIdx);
466
0
                const double dfLon = poFeature->GetFieldAsDouble(nLonIdx);
467
0
                const double dfLat = poFeature->GetFieldAsDouble(nLatIdx);
468
0
                oMap[pszShapeId][nSeq] = std::make_pair(dfLon, dfLat);
469
0
            }
470
0
        }
471
0
        for (const auto &kv : oMap)
472
0
        {
473
0
            const auto &osShapeId = kv.first;
474
0
            const auto &oMapPoints = kv.second;
475
0
            auto poFeature = std::make_unique<OGRFeature>(m_poFeatureDefn);
476
0
            poFeature->SetField(0, osShapeId.c_str());
477
0
            OGRLineString *poLS = new OGRLineString();
478
0
            for (const auto &kv2 : oMapPoints)
479
0
            {
480
0
                poLS->addPoint(kv2.second.first, kv2.second.second);
481
0
            }
482
0
            poFeature->SetGeometryDirectly(poLS);
483
0
            poFeature->SetFID(static_cast<GIntBig>(m_apoFeatures.size()));
484
0
            m_apoFeatures.emplace_back(std::move(poFeature));
485
0
        }
486
0
    }
487
0
    catch (const std::bad_alloc &)
488
0
    {
489
0
        CPLError(CE_Failure, CPLE_OutOfMemory, "Not enough memory");
490
0
    }
491
0
}
492
493
/************************************************************************/
494
/*                           GetNextFeature()                           */
495
/************************************************************************/
496
497
OGRFeature *OGRGTFSShapesGeomLayer::GetNextFeature()
498
0
{
499
0
    if (!m_bPrepared)
500
0
        Prepare();
501
0
    while (true)
502
0
    {
503
0
        if (m_nIdx >= m_apoFeatures.size())
504
0
            return nullptr;
505
0
        if ((m_poFilterGeom == nullptr ||
506
0
             FilterGeometry(m_apoFeatures[m_nIdx]->GetGeometryRef())) &&
507
0
            (m_poAttrQuery == nullptr ||
508
0
             m_poAttrQuery->Evaluate(m_apoFeatures[m_nIdx].get())))
509
0
        {
510
0
            auto poRet = m_apoFeatures[m_nIdx]->Clone();
511
0
            m_nIdx++;
512
0
            return poRet;
513
0
        }
514
0
        m_nIdx++;
515
0
    }
516
0
}
517
518
/************************************************************************/
519
/*                           TestCapability()                           */
520
/************************************************************************/
521
522
bool OGRGTFSShapesGeomLayer::TestCapability(const char *pszCap) const
523
0
{
524
0
    return EQUAL(pszCap, OLCStringsAsUTF8);
525
0
}
526
527
/************************************************************************/
528
/*                          GetFeatureCount()                           */
529
/************************************************************************/
530
531
GIntBig OGRGTFSShapesGeomLayer::GetFeatureCount(int bForce)
532
0
{
533
0
    if (m_poAttrQuery != nullptr || m_poFilterGeom != nullptr)
534
0
        return OGRLayer::GetFeatureCount(bForce);
535
0
    if (!m_bPrepared)
536
0
        Prepare();
537
0
    return static_cast<GIntBig>(m_apoFeatures.size());
538
0
}
539
540
/************************************************************************/
541
/*                              Identify()                              */
542
/************************************************************************/
543
544
static const char *const apszRequiredFiles[] = {"agency.txt", "routes.txt",
545
                                                "trips.txt",  "stop_times.txt",
546
                                                "stops.txt",  "calendar.txt"};
547
548
int OGRGTFSDataset::Identify(GDALOpenInfo *poOpenInfo)
549
102k
{
550
102k
    if (STARTS_WITH(poOpenInfo->pszFilename, "GTFS:"))
551
1.25k
        return TRUE;
552
553
101k
    if (poOpenInfo->IsSingleAllowedDriver("GTFS") && poOpenInfo->bIsDirectory)
554
0
    {
555
0
        return TRUE;
556
0
    }
557
558
101k
    if (!poOpenInfo->IsExtensionEqualToCI("zip"))
559
99.0k
        return FALSE;
560
561
    // Check first filename in ZIP
562
563
2.03k
    constexpr int OFFSET_FILENAME_SIZE = 26;
564
2.03k
    constexpr int OFFSET_FILENAME_VAL = 30;
565
2.03k
    if (poOpenInfo->nHeaderBytes < OFFSET_FILENAME_VAL ||
566
214
        memcmp(poOpenInfo->pabyHeader, "PK\x03\x04", 4) != 0)
567
2.02k
    {
568
2.02k
        return FALSE;
569
2.02k
    }
570
571
4
    for (const char *pszFilename : apszRequiredFiles)
572
24
    {
573
24
        const int nLen = static_cast<int>(strlen(pszFilename));
574
24
        if (CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + OFFSET_FILENAME_SIZE) ==
575
24
                nLen &&
576
2
            poOpenInfo->nHeaderBytes > OFFSET_FILENAME_VAL + nLen &&
577
2
            memcmp(poOpenInfo->pabyHeader + OFFSET_FILENAME_VAL, pszFilename,
578
2
                   nLen) == 0)
579
0
        {
580
0
            return TRUE;
581
0
        }
582
24
    }
583
584
4
    static const char *const apszOptionalFiles[] = {
585
4
        "calendar_dates.txt", "fare_attributes.txt", "fare_rules.txt",
586
4
        "shapes.txt",         "frequencies.txt",     "transfers.txt",
587
4
        "feed_info.txt"};
588
4
    for (const char *pszFilename : apszOptionalFiles)
589
28
    {
590
28
        const int nLen = static_cast<int>(strlen(pszFilename));
591
28
        if (CPL_LSBSINT16PTR(poOpenInfo->pabyHeader + OFFSET_FILENAME_SIZE) ==
592
28
                nLen &&
593
0
            poOpenInfo->nHeaderBytes > OFFSET_FILENAME_VAL + nLen &&
594
0
            memcmp(poOpenInfo->pabyHeader + OFFSET_FILENAME_VAL, pszFilename,
595
0
                   nLen) == 0)
596
0
        {
597
0
            return TRUE;
598
0
        }
599
28
    }
600
4
    return FALSE;
601
4
}
602
603
/************************************************************************/
604
/*                                Open()                                */
605
/************************************************************************/
606
607
GDALDataset *OGRGTFSDataset::Open(GDALOpenInfo *poOpenInfo)
608
626
{
609
626
    if (!Identify(poOpenInfo))
610
0
        return nullptr;
611
612
626
    const char *pszGTFSFilename = poOpenInfo->pszFilename;
613
626
    if (STARTS_WITH(pszGTFSFilename, "GTFS:"))
614
626
        pszGTFSFilename += strlen("GTFS:");
615
616
626
    std::string osBaseDir(
617
626
        (!STARTS_WITH(pszGTFSFilename, "/vsizip/") &&
618
623
         EQUAL(CPLGetExtensionSafe(pszGTFSFilename).c_str(), "zip"))
619
626
            ? std::string("/vsizip/{").append(pszGTFSFilename).append("}")
620
626
            : std::string(pszGTFSFilename));
621
622
626
    auto poDS = std::make_unique<OGRGTFSDataset>();
623
624
626
    const CPLStringList aosFilenames(VSIReadDir(osBaseDir.c_str()));
625
626
    size_t nCountFound = 0;
626
626
    std::string osShapesFilename;
627
626
    for (const char *pszFilename : cpl::Iterate(aosFilenames))
628
12.5k
    {
629
12.5k
        if (!EQUAL(CPLGetExtensionSafe(pszFilename).c_str(), "txt"))
630
3.42k
            continue;
631
9.10k
        for (const char *pszFilenameInDir : apszRequiredFiles)
632
54.6k
        {
633
54.6k
            if (EQUAL(pszFilename, pszFilenameInDir))
634
0
            {
635
0
                nCountFound++;
636
0
                break;
637
0
            }
638
54.6k
        }
639
9.10k
        if (EQUAL(pszFilename, "shapes.txt"))
640
0
            osShapesFilename = pszFilename;
641
642
9.10k
        auto poCSVDataset = std::unique_ptr<GDALDataset>(GDALDataset::Open(
643
9.10k
            std::string(osBaseDir).append("/").append(pszFilename).c_str(),
644
9.10k
            GDAL_OF_VERBOSE_ERROR | GDAL_OF_VECTOR, apszCSVDriver));
645
9.10k
        if (poCSVDataset)
646
7.88k
        {
647
7.88k
            auto poUnderlyingLayer = poCSVDataset->GetLayer(0);
648
7.88k
            if (poUnderlyingLayer)
649
7.88k
            {
650
7.88k
                auto poSrcLayerDefn = poUnderlyingLayer->GetLayerDefn();
651
7.88k
                if (poSrcLayerDefn->GetFieldIndex("field_1") < 0)
652
7.17k
                {
653
7.17k
                    poDS->m_apoLayers.emplace_back(
654
7.17k
                        std::make_unique<OGRGTFSLayer>(
655
7.17k
                            osBaseDir, CPLGetBasenameSafe(pszFilename).c_str(),
656
7.17k
                            std::move(poCSVDataset)));
657
7.17k
                }
658
7.88k
            }
659
7.88k
        }
660
9.10k
    }
661
662
626
    if (nCountFound != sizeof(apszRequiredFiles) / sizeof(apszRequiredFiles[0]))
663
626
    {
664
626
        CPLError(CE_Failure, CPLE_AppDefined,
665
626
                 "GTFS: required .txt files missing");
666
626
        return nullptr;
667
626
    }
668
669
0
    if (!osShapesFilename.empty())
670
0
    {
671
0
        auto poCSVDataset = std::unique_ptr<GDALDataset>(GDALDataset::Open(
672
0
            std::string(osBaseDir).append("/").append(osShapesFilename).c_str(),
673
0
            GDAL_OF_VERBOSE_ERROR | GDAL_OF_VECTOR, apszCSVDriver));
674
0
        CPL_IGNORE_RET_VAL(osBaseDir);
675
0
        if (poCSVDataset)
676
0
        {
677
0
            auto poUnderlyingLayer = poCSVDataset->GetLayer(0);
678
0
            if (poUnderlyingLayer)
679
0
            {
680
0
                poDS->m_apoLayers.emplace_back(
681
0
                    std::make_unique<OGRGTFSShapesGeomLayer>(
682
0
                        std::move(poCSVDataset)));
683
0
            }
684
0
        }
685
0
    }
686
687
0
    return poDS.release();
688
626
}
689
690
/************************************************************************/
691
/*                          RegisterOGRGTFS()                           */
692
/************************************************************************/
693
694
void RegisterOGRGTFS()
695
696
24
{
697
24
    if (GDALGetDriverByName("GTFS") != nullptr)
698
0
        return;
699
700
24
    GDALDriver *poDriver = new GDALDriver();
701
702
24
    poDriver->SetDescription("GTFS");
703
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
704
24
                              "General Transit Feed Specification");
705
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/gtfs.html");
706
24
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
707
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
708
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "zip");
709
710
24
    poDriver->pfnOpen = OGRGTFSDataset::Open;
711
24
    poDriver->pfnIdentify = OGRGTFSDataset::Identify;
712
713
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
714
24
}