Line data Source code
1 : /**********************************************************************
2 : *
3 : * Name: mitab_ogr_datasource.cpp
4 : * Project: MapInfo Mid/Mif, Tab ogr support
5 : * Language: C++
6 : * Purpose: Implementation of OGRTABDataSource.
7 : * Author: Stephane Villeneuve, stephane.v@videotron.ca
8 : * and Frank Warmerdam, warmerdam@pobox.com
9 : *
10 : **********************************************************************
11 : * Copyright (c) 1999, 2000, Stephane Villeneuve
12 : * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com>
13 : *
14 : * SPDX-License-Identifier: MIT
15 : **********************************************************************/
16 :
17 : #include "mitab_ogr_driver.h"
18 :
19 : /*=======================================================================
20 : * OGRTABDataSource
21 : *
22 : * We need one single OGRDataSource/Driver set of classes to handle all
23 : * the MapInfo file types. They all deal with the IMapInfoFile abstract
24 : * class.
25 : *=====================================================================*/
26 :
27 : /************************************************************************/
28 : /* OGRTABDataSource() */
29 : /************************************************************************/
30 :
31 3073 : OGRTABDataSource::OGRTABDataSource()
32 : : m_pszDirectory(nullptr), m_nLayerCount(0), m_papoLayers(nullptr),
33 : m_papszOptions(nullptr), m_bCreateMIF(FALSE), m_bSingleFile(FALSE),
34 : m_bSingleLayerAlreadyCreated(FALSE), m_bQuickSpatialIndexMode(-1),
35 3073 : m_nBlockSize(512)
36 : {
37 3073 : }
38 :
39 : /************************************************************************/
40 : /* ~OGRTABDataSource() */
41 : /************************************************************************/
42 :
43 6146 : OGRTABDataSource::~OGRTABDataSource()
44 :
45 : {
46 3073 : CPLFree(m_pszDirectory);
47 :
48 5384 : for (int i = 0; i < m_nLayerCount; i++)
49 2311 : delete m_papoLayers[i];
50 :
51 3073 : CPLFree(m_papoLayers);
52 3073 : CSLDestroy(m_papszOptions);
53 6146 : }
54 :
55 : /************************************************************************/
56 : /* Create() */
57 : /* */
58 : /* Create a new dataset (directory or file). */
59 : /************************************************************************/
60 :
61 194 : int OGRTABDataSource::Create(const char *pszName, char **papszOptions)
62 :
63 : {
64 194 : SetDescription(pszName);
65 194 : m_papszOptions = CSLDuplicate(papszOptions);
66 194 : eAccess = GA_Update;
67 :
68 194 : const char *pszOpt = CSLFetchNameValue(papszOptions, "FORMAT");
69 194 : if (pszOpt != nullptr && EQUAL(pszOpt, "MIF"))
70 7 : m_bCreateMIF = TRUE;
71 482 : else if (EQUAL(CPLGetExtensionSafe(pszName).c_str(), "mif") ||
72 295 : EQUAL(CPLGetExtensionSafe(pszName).c_str(), "mid"))
73 79 : m_bCreateMIF = TRUE;
74 :
75 194 : if ((pszOpt = CSLFetchNameValue(papszOptions, "SPATIAL_INDEX_MODE")) !=
76 : nullptr)
77 : {
78 0 : if (EQUAL(pszOpt, "QUICK"))
79 0 : m_bQuickSpatialIndexMode = TRUE;
80 0 : else if (EQUAL(pszOpt, "OPTIMIZED"))
81 0 : m_bQuickSpatialIndexMode = FALSE;
82 : }
83 :
84 194 : m_nBlockSize = atoi(CSLFetchNameValueDef(papszOptions, "BLOCKSIZE", "512"));
85 :
86 : // Create a new empty directory.
87 : VSIStatBufL sStat;
88 :
89 194 : if (strlen(CPLGetExtensionSafe(pszName).c_str()) == 0)
90 : {
91 42 : if (VSIStatL(pszName, &sStat) == 0)
92 : {
93 6 : if (!VSI_ISDIR(sStat.st_mode))
94 : {
95 0 : CPLError(CE_Failure, CPLE_OpenFailed,
96 : "Attempt to create dataset named %s,\n"
97 : "but that is an existing file.",
98 : pszName);
99 0 : return FALSE;
100 : }
101 : }
102 : else
103 : {
104 36 : if (VSIMkdir(pszName, 0755) != 0)
105 : {
106 1 : CPLError(CE_Failure, CPLE_AppDefined,
107 : "Unable to create directory %s.", pszName);
108 1 : return FALSE;
109 : }
110 : }
111 :
112 41 : m_pszDirectory = CPLStrdup(pszName);
113 : }
114 :
115 : // Create a new single file.
116 : else
117 : {
118 152 : IMapInfoFile *poFile = nullptr;
119 152 : const char *pszEncoding(CSLFetchNameValue(papszOptions, "ENCODING"));
120 152 : const char *pszCharset(IMapInfoFile::EncodingToCharset(pszEncoding));
121 152 : bool bStrictLaundering = CPLTestBool(CSLFetchNameValueDef(
122 : papszOptions, "STRICT_FIELDS_NAME_LAUNDERING", "YES"));
123 152 : if (m_bCreateMIF)
124 : {
125 84 : poFile = new MIFFile(this);
126 84 : if (poFile->Open(pszName, TABWrite, FALSE, pszCharset) != 0)
127 : {
128 0 : delete poFile;
129 0 : return FALSE;
130 : }
131 : }
132 : else
133 : {
134 68 : TABFile *poTabFile = new TABFile(this);
135 68 : if (poTabFile->Open(pszName, TABWrite, FALSE, m_nBlockSize,
136 68 : pszCharset) != 0)
137 : {
138 1 : delete poTabFile;
139 1 : return FALSE;
140 : }
141 67 : poFile = poTabFile;
142 : }
143 151 : poFile->SetStrictLaundering(bStrictLaundering);
144 151 : m_nLayerCount = 1;
145 151 : m_papoLayers = static_cast<IMapInfoFile **>(CPLMalloc(sizeof(void *)));
146 151 : m_papoLayers[0] = poFile;
147 :
148 151 : m_pszDirectory = CPLStrdup(CPLGetPathSafe(pszName).c_str());
149 151 : m_bSingleFile = TRUE;
150 : }
151 :
152 192 : return TRUE;
153 : }
154 :
155 : /************************************************************************/
156 : /* Open() */
157 : /* */
158 : /* Open an existing file, or directory of files. */
159 : /************************************************************************/
160 :
161 2879 : int OGRTABDataSource::Open(GDALOpenInfo *poOpenInfo, int bTestOpen)
162 :
163 : {
164 2879 : SetDescription(poOpenInfo->pszFilename);
165 2879 : eAccess = poOpenInfo->eAccess;
166 :
167 : // If it is a file, try to open as a Mapinfo file.
168 2879 : if (!poOpenInfo->bIsDirectory)
169 : {
170 4226 : IMapInfoFile *poFile = IMapInfoFile::SmartOpen(
171 2113 : this, poOpenInfo->pszFilename, GetUpdate(), bTestOpen);
172 2113 : if (poFile == nullptr)
173 58 : return FALSE;
174 :
175 2055 : poFile->SetDescription(poFile->GetName());
176 :
177 2055 : m_nLayerCount = 1;
178 2055 : m_papoLayers = static_cast<IMapInfoFile **>(CPLMalloc(sizeof(void *)));
179 2055 : m_papoLayers[0] = poFile;
180 :
181 2055 : m_pszDirectory =
182 2055 : CPLStrdup(CPLGetPathSafe(poOpenInfo->pszFilename).c_str());
183 :
184 2055 : m_bSingleFile = TRUE;
185 2055 : m_bSingleLayerAlreadyCreated = TRUE;
186 : }
187 :
188 : // Otherwise, we need to scan the whole directory for files
189 : // ending in .tab or .mif.
190 : else
191 : {
192 766 : char **papszFileList = VSIReadDir(poOpenInfo->pszFilename);
193 :
194 766 : m_pszDirectory = CPLStrdup(poOpenInfo->pszFilename);
195 :
196 26663 : for (int iFile = 0;
197 26663 : papszFileList != nullptr && papszFileList[iFile] != nullptr;
198 : iFile++)
199 : {
200 : const std::string osExtension =
201 25915 : CPLGetExtensionSafe(papszFileList[iFile]);
202 :
203 51771 : if (!EQUAL(osExtension.c_str(), "tab") &&
204 25856 : !EQUAL(osExtension.c_str(), "mif"))
205 25850 : continue;
206 :
207 : char *pszSubFilename =
208 65 : CPLStrdup(CPLFormFilenameSafe(m_pszDirectory,
209 65 : papszFileList[iFile], nullptr)
210 : .c_str());
211 :
212 65 : IMapInfoFile *poFile = IMapInfoFile::SmartOpen(
213 65 : this, pszSubFilename, GetUpdate(), bTestOpen);
214 65 : CPLFree(pszSubFilename);
215 :
216 65 : if (poFile == nullptr)
217 : {
218 18 : CSLDestroy(papszFileList);
219 18 : return FALSE;
220 : }
221 47 : poFile->SetDescription(poFile->GetName());
222 :
223 47 : m_nLayerCount++;
224 47 : m_papoLayers = static_cast<IMapInfoFile **>(
225 47 : CPLRealloc(m_papoLayers, sizeof(void *) * m_nLayerCount));
226 47 : m_papoLayers[m_nLayerCount - 1] = poFile;
227 : }
228 :
229 748 : CSLDestroy(papszFileList);
230 :
231 748 : if (m_nLayerCount == 0)
232 : {
233 719 : if (!bTestOpen)
234 0 : CPLError(CE_Failure, CPLE_OpenFailed,
235 : "No mapinfo files found in directory %s.",
236 : m_pszDirectory);
237 :
238 719 : return FALSE;
239 : }
240 : }
241 :
242 2084 : return TRUE;
243 : }
244 :
245 : /************************************************************************/
246 : /* GetLayerCount() */
247 : /************************************************************************/
248 :
249 2350 : int OGRTABDataSource::GetLayerCount() const
250 :
251 : {
252 2350 : if (m_bSingleFile && !m_bSingleLayerAlreadyCreated)
253 12 : return 0;
254 : else
255 2338 : return m_nLayerCount;
256 : }
257 :
258 : /************************************************************************/
259 : /* GetLayer() */
260 : /************************************************************************/
261 :
262 2176 : const OGRLayer *OGRTABDataSource::GetLayer(int iLayer) const
263 :
264 : {
265 2176 : if (iLayer < 0 || iLayer >= GetLayerCount())
266 8 : return nullptr;
267 : else
268 2168 : return m_papoLayers[iLayer];
269 : }
270 :
271 : /************************************************************************/
272 : /* ICreateLayer() */
273 : /************************************************************************/
274 :
275 : OGRLayer *
276 210 : OGRTABDataSource::ICreateLayer(const char *pszLayerName,
277 : const OGRGeomFieldDefn *poGeomFieldDefnIn,
278 : CSLConstList papszOptions)
279 :
280 : {
281 210 : if (!GetUpdate())
282 : {
283 0 : CPLError(CE_Failure, CPLE_AppDefined,
284 : "Cannot create layer on read-only dataset.");
285 0 : return nullptr;
286 : }
287 :
288 : const auto poSRSIn =
289 210 : poGeomFieldDefnIn ? poGeomFieldDefnIn->GetSpatialRef() : nullptr;
290 :
291 : // If it is a single file mode file, then we may have already
292 : // instantiated the low level layer. We would just need to
293 : // reset the coordinate system and (potentially) bounds.
294 210 : IMapInfoFile *poFile = nullptr;
295 210 : char *pszFullFilename = nullptr;
296 :
297 210 : const char *pszEncoding = CSLFetchNameValue(papszOptions, "ENCODING");
298 210 : const char *pszCharset(IMapInfoFile::EncodingToCharset(pszEncoding));
299 210 : const char *pszDescription(CSLFetchNameValue(papszOptions, "DESCRIPTION"));
300 : const char *pszStrictLaundering =
301 210 : CSLFetchNameValue(papszOptions, "STRICT_FIELDS_NAME_LAUNDERING");
302 210 : if (pszStrictLaundering == nullptr)
303 : {
304 208 : pszStrictLaundering = CSLFetchNameValueDef(
305 208 : m_papszOptions, "STRICT_FIELDS_NAME_LAUNDERING", "YES");
306 : }
307 210 : bool bStrictLaundering = CPLTestBool(pszStrictLaundering);
308 :
309 210 : if (m_bSingleFile)
310 : {
311 151 : if (m_bSingleLayerAlreadyCreated)
312 : {
313 0 : CPLError(
314 : CE_Failure, CPLE_AppDefined,
315 : "Unable to create new layers in this single file dataset.");
316 0 : return nullptr;
317 : }
318 :
319 151 : m_bSingleLayerAlreadyCreated = TRUE;
320 :
321 151 : poFile = m_papoLayers[0];
322 151 : if (pszEncoding)
323 3 : poFile->SetCharset(pszCharset);
324 :
325 151 : if (poFile->GetFileClass() == TABFC_TABFile)
326 67 : poFile->SetMetadataItem("DESCRIPTION", pszDescription);
327 : }
328 :
329 : else
330 : {
331 59 : if (CPLLaunderForFilenameSafe(pszLayerName, nullptr) != pszLayerName)
332 : {
333 1 : CPLError(CE_Failure, CPLE_AppDefined,
334 : "Illegal characters in '%s' to form a valid filename",
335 : pszLayerName);
336 1 : return nullptr;
337 : }
338 :
339 58 : if (m_bCreateMIF)
340 : {
341 3 : pszFullFilename = CPLStrdup(
342 6 : CPLFormFilenameSafe(m_pszDirectory, pszLayerName, "mif")
343 : .c_str());
344 :
345 3 : poFile = new MIFFile(this);
346 :
347 3 : if (poFile->Open(pszFullFilename, TABWrite, FALSE, pszCharset) != 0)
348 : {
349 0 : CPLFree(pszFullFilename);
350 0 : delete poFile;
351 0 : return nullptr;
352 : }
353 : }
354 : else
355 : {
356 55 : pszFullFilename = CPLStrdup(
357 110 : CPLFormFilenameSafe(m_pszDirectory, pszLayerName, "tab")
358 : .c_str());
359 :
360 55 : TABFile *poTABFile = new TABFile(this);
361 :
362 55 : if (poTABFile->Open(pszFullFilename, TABWrite, FALSE, m_nBlockSize,
363 55 : pszCharset) != 0)
364 : {
365 0 : CPLFree(pszFullFilename);
366 0 : delete poTABFile;
367 0 : return nullptr;
368 : }
369 55 : poFile = poTABFile;
370 55 : poFile->SetMetadataItem("DESCRIPTION", pszDescription);
371 : }
372 :
373 58 : m_nLayerCount++;
374 58 : m_papoLayers = static_cast<IMapInfoFile **>(
375 58 : CPLRealloc(m_papoLayers, sizeof(void *) * m_nLayerCount));
376 58 : m_papoLayers[m_nLayerCount - 1] = poFile;
377 :
378 58 : CPLFree(pszFullFilename);
379 : }
380 :
381 209 : poFile->SetDescription(poFile->GetName());
382 209 : poFile->SetStrictLaundering(bStrictLaundering);
383 :
384 : // Assign the coordinate system (if provided) and set
385 : // reasonable bounds.
386 209 : if (poSRSIn != nullptr)
387 : {
388 100 : auto poSRSClone = poSRSIn->Clone();
389 100 : poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
390 100 : poFile->SetSpatialRef(poSRSClone);
391 100 : poSRSClone->Release();
392 : // SetSpatialRef() has cloned the passed geometry
393 100 : OGRLayer *poFileAsLayer = poFile;
394 : auto poGeomFieldDefn =
395 100 : poFileAsLayer->GetLayerDefn()->GetGeomFieldDefn(0);
396 200 : whileUnsealing(poGeomFieldDefn)
397 200 : ->SetSpatialRef(
398 100 : const_cast<OGRSpatialReference *>(poFile->GetSpatialRef()));
399 : }
400 :
401 : // Pull out the bounds if supplied
402 209 : const char *pszOpt = nullptr;
403 209 : if ((pszOpt = CSLFetchNameValue(papszOptions, "BOUNDS")) != nullptr)
404 : {
405 : double dfBounds[4];
406 5 : if (CPLsscanf(pszOpt, "%lf,%lf,%lf,%lf", &dfBounds[0], &dfBounds[1],
407 5 : &dfBounds[2], &dfBounds[3]) != 4)
408 : {
409 0 : CPLError(
410 : CE_Failure, CPLE_IllegalArg,
411 : "Invalid BOUNDS parameter, expected min_x,min_y,max_x,max_y");
412 : }
413 : else
414 : {
415 5 : poFile->SetBounds(dfBounds[0], dfBounds[1], dfBounds[2],
416 5 : dfBounds[3]);
417 : }
418 : }
419 :
420 209 : if (!poFile->IsBoundsSet() && !m_bCreateMIF)
421 : {
422 103 : if (poSRSIn != nullptr && poSRSIn->IsGeographic())
423 0 : poFile->SetBounds(-1000, -1000, 1000, 1000);
424 103 : else if (poSRSIn != nullptr && poSRSIn->IsProjected())
425 : {
426 11 : const double FE = poSRSIn->GetProjParm(SRS_PP_FALSE_EASTING, 0.0);
427 11 : const double FN = poSRSIn->GetProjParm(SRS_PP_FALSE_NORTHING, 0.0);
428 11 : poFile->SetBounds(-30000000 + FE, -15000000 + FN, 30000000 + FE,
429 11 : 15000000 + FN);
430 : }
431 : else
432 92 : poFile->SetBounds(-30000000, -15000000, 30000000, 15000000);
433 : }
434 :
435 209 : if (m_bQuickSpatialIndexMode == TRUE &&
436 0 : poFile->SetQuickSpatialIndexMode(TRUE) != 0)
437 : {
438 0 : CPLError(CE_Warning, CPLE_AppDefined,
439 : "Setting Quick Spatial Index Mode failed.");
440 : }
441 209 : else if (m_bQuickSpatialIndexMode == FALSE &&
442 0 : poFile->SetQuickSpatialIndexMode(FALSE) != 0)
443 : {
444 0 : CPLError(CE_Warning, CPLE_AppDefined,
445 : "Setting Normal Spatial Index Mode failed.");
446 : }
447 :
448 209 : return poFile;
449 : }
450 :
451 : /************************************************************************/
452 : /* TestCapability() */
453 : /************************************************************************/
454 :
455 92 : int OGRTABDataSource::TestCapability(const char *pszCap) const
456 :
457 : {
458 92 : if (EQUAL(pszCap, ODsCCreateLayer))
459 42 : return GetUpdate() && (!m_bSingleFile || !m_bSingleLayerAlreadyCreated);
460 50 : else if (EQUAL(pszCap, ODsCRandomLayerWrite))
461 0 : return GetUpdate();
462 : else
463 50 : return FALSE;
464 : }
465 :
466 : /************************************************************************/
467 : /* GetFileList() */
468 : /************************************************************************/
469 :
470 36 : char **OGRTABDataSource::GetFileList()
471 : {
472 : VSIStatBufL sStatBuf;
473 72 : CPLStringList osList;
474 :
475 72 : if (VSIStatL(GetDescription(), &sStatBuf) == 0 &&
476 36 : VSI_ISDIR(sStatBuf.st_mode))
477 : {
478 : static const char *const apszExtensions[] = {
479 : "mif", "mid", "tab", "map", "ind", "dat", "id", nullptr};
480 18 : char **papszDirEntries = VSIReadDir(GetDescription());
481 :
482 156 : for (int iFile = 0;
483 156 : papszDirEntries != nullptr && papszDirEntries[iFile] != nullptr;
484 : iFile++)
485 : {
486 138 : if (CSLFindString(
487 : apszExtensions,
488 276 : CPLGetExtensionSafe(papszDirEntries[iFile]).c_str()) != -1)
489 : {
490 134 : osList.push_back(CPLFormFilenameSafe(
491 134 : GetDescription(), papszDirEntries[iFile], nullptr));
492 : }
493 : }
494 :
495 18 : CSLDestroy(papszDirEntries);
496 : }
497 : else
498 : {
499 : static const char *const apszMIFExtensions[] = {"mif", "mid", nullptr};
500 : static const char *const apszTABExtensions[] = {"tab", "map", "ind",
501 : "dat", "id", nullptr};
502 18 : const char *const *papszExtensions = nullptr;
503 50 : if (EQUAL(CPLGetExtensionSafe(GetDescription()).c_str(), "mif") ||
504 32 : EQUAL(CPLGetExtensionSafe(GetDescription()).c_str(), "mid"))
505 : {
506 4 : papszExtensions = apszMIFExtensions;
507 : }
508 : else
509 : {
510 14 : papszExtensions = apszTABExtensions;
511 : }
512 18 : const char *const *papszIter = papszExtensions;
513 96 : while (*papszIter)
514 : {
515 : std::string osFile =
516 78 : CPLResetExtensionSafe(GetDescription(), *papszIter);
517 78 : if (VSIStatL(osFile.c_str(), &sStatBuf) != 0)
518 : {
519 28 : osFile = CPLResetExtensionSafe(GetDescription(),
520 42 : CPLString(*papszIter).toupper());
521 14 : if (VSIStatL(osFile.c_str(), &sStatBuf) != 0)
522 : {
523 14 : osFile.clear();
524 : }
525 : }
526 78 : if (!osFile.empty())
527 64 : osList.AddString(osFile.c_str());
528 78 : papszIter++;
529 : }
530 : }
531 72 : return osList.StealList();
532 : }
533 :
534 : /************************************************************************/
535 : /* ExecuteSQL() */
536 : /************************************************************************/
537 :
538 35 : OGRLayer *OGRTABDataSource::ExecuteSQL(const char *pszStatement,
539 : OGRGeometry *poSpatialFilter,
540 : const char *pszDialect)
541 : {
542 35 : char **papszTokens = CSLTokenizeString(pszStatement);
543 40 : if (CSLCount(papszTokens) == 6 && EQUAL(papszTokens[0], "CREATE") &&
544 45 : EQUAL(papszTokens[1], "INDEX") && EQUAL(papszTokens[2], "ON") &&
545 5 : EQUAL(papszTokens[4], "USING"))
546 : {
547 : IMapInfoFile *poLayer =
548 5 : dynamic_cast<IMapInfoFile *>(GetLayerByName(papszTokens[3]));
549 5 : if (poLayer == nullptr)
550 : {
551 1 : CPLError(CE_Failure, CPLE_AppDefined,
552 : "`%s' failed failed, no such layer as `%s'.", pszStatement,
553 1 : papszTokens[3]);
554 1 : CSLDestroy(papszTokens);
555 1 : return nullptr;
556 : }
557 4 : int nFieldIdx = poLayer->GetLayerDefn()->GetFieldIndex(papszTokens[5]);
558 4 : CSLDestroy(papszTokens);
559 4 : if (nFieldIdx < 0)
560 : {
561 1 : CPLError(CE_Failure, CPLE_AppDefined,
562 : "`%s' failed, field not found.", pszStatement);
563 1 : return nullptr;
564 : }
565 3 : poLayer->SetFieldIndexed(nFieldIdx);
566 3 : return nullptr;
567 : }
568 :
569 30 : CSLDestroy(papszTokens);
570 30 : return GDALDataset::ExecuteSQL(pszStatement, poSpatialFilter, pszDialect);
571 : }
|