/src/gdal/ogr/ogrsf_frmts/mitab/mitab_tabview.cpp
Line | Count | Source |
1 | | /********************************************************************** |
2 | | * |
3 | | * Name: mitab_tabfile.cpp |
4 | | * Project: MapInfo TAB Read/Write library |
5 | | * Language: C++ |
6 | | * Purpose: Implementation of the TABView class, used to handle .TAB |
7 | | * datasets composed of a number of .TAB files linked through |
8 | | * indexed fields. |
9 | | * Author: Daniel Morissette, dmorissette@dmsolutions.ca |
10 | | * |
11 | | ********************************************************************** |
12 | | * Copyright (c) 1999-2002, Daniel Morissette |
13 | | * Copyright (c) 2014, Even Rouault <even.rouault at spatialys.com> |
14 | | * |
15 | | * SPDX-License-Identifier: MIT |
16 | | **********************************************************************/ |
17 | | |
18 | | #include "cpl_port.h" |
19 | | #include "mitab.h" |
20 | | |
21 | | #include <cassert> |
22 | | #include <cctype> |
23 | | #include <cstddef> |
24 | | #include <cstdio> |
25 | | #include <cstring> |
26 | | |
27 | | #include "cpl_conv.h" |
28 | | #include "cpl_error.h" |
29 | | #include "cpl_string.h" |
30 | | #include "cpl_vsi.h" |
31 | | #include "mitab_priv.h" |
32 | | #include "mitab_utils.h" |
33 | | #include "ogr_core.h" |
34 | | #include "ogr_feature.h" |
35 | | #include "ogr_geometry.h" |
36 | | #include "ogr_spatialref.h" |
37 | | |
38 | | /*===================================================================== |
39 | | * class TABView |
40 | | *====================================================================*/ |
41 | | |
42 | | /********************************************************************** |
43 | | * TABView::TABView() |
44 | | * |
45 | | * Constructor. |
46 | | **********************************************************************/ |
47 | | TABView::TABView(GDALDataset *poDS) |
48 | 1.19k | : IMapInfoFile(poDS), m_pszFname(nullptr), m_eAccessMode(TABRead), |
49 | 1.19k | m_papszTABFile(nullptr), m_pszVersion(nullptr), m_papszTABFnames(nullptr), |
50 | 1.19k | m_papoTABFiles(nullptr), m_numTABFiles(0), m_nMainTableIndex(-1), |
51 | 1.19k | m_papszFieldNames(nullptr), m_papszWhereClause(nullptr), |
52 | 1.19k | m_poRelation(nullptr), m_bRelFieldsCreated(FALSE) |
53 | 1.19k | { |
54 | 1.19k | } |
55 | | |
56 | | /********************************************************************** |
57 | | * TABView::~TABView() |
58 | | * |
59 | | * Destructor. |
60 | | **********************************************************************/ |
61 | | TABView::~TABView() |
62 | 1.19k | { |
63 | 1.19k | TABView::Close(); |
64 | 1.19k | } |
65 | | |
66 | | GIntBig TABView::GetFeatureCount(int bForce) |
67 | 0 | { |
68 | |
|
69 | 0 | if (m_nMainTableIndex != -1) |
70 | 0 | return m_papoTABFiles[m_nMainTableIndex]->GetFeatureCount(bForce); |
71 | | |
72 | 0 | return 0; |
73 | 0 | } |
74 | | |
75 | | void TABView::ResetReading() |
76 | 0 | { |
77 | 0 | if (m_nMainTableIndex != -1) |
78 | 0 | m_papoTABFiles[m_nMainTableIndex]->ResetReading(); |
79 | 0 | } |
80 | | |
81 | | /********************************************************************** |
82 | | * TABView::Open() |
83 | | * |
84 | | * Open a .TAB dataset and the associated files, and initialize the |
85 | | * structures to be ready to read features from it. |
86 | | * |
87 | | * This class is used to open .TAB files that define a view on |
88 | | * two other .TAB files. Regular .TAB datasets should be opened using |
89 | | * the TABFile class instead. |
90 | | * |
91 | | * Set bTestOpenNoError=TRUE to silently return -1 with no error message |
92 | | * if the file cannot be opened. This is intended to be used in the |
93 | | * context of a TestOpen() function. The default value is FALSE which |
94 | | * means that an error is reported if the file cannot be opened. |
95 | | * |
96 | | * Returns 0 on success, -1 on error. |
97 | | **********************************************************************/ |
98 | | int TABView::Open(const char *pszFname, TABAccess eAccess, |
99 | | GBool bTestOpenNoError /*= FALSE*/, |
100 | | const char *pszCharset /* = NULL */) |
101 | 1.19k | { |
102 | 1.19k | char nStatus = 0; |
103 | | |
104 | 1.19k | if (m_numTABFiles > 0) |
105 | 0 | { |
106 | 0 | CPLError(CE_Failure, CPLE_AssertionFailed, |
107 | 0 | "Open() failed: object already contains an open file"); |
108 | 0 | return -1; |
109 | 0 | } |
110 | | |
111 | | /*----------------------------------------------------------------- |
112 | | * Validate access mode and call the right open method |
113 | | *----------------------------------------------------------------*/ |
114 | 1.19k | if (eAccess == TABRead) |
115 | 1.19k | { |
116 | 1.19k | m_eAccessMode = TABRead; |
117 | 1.19k | nStatus = static_cast<char>(OpenForRead(pszFname, bTestOpenNoError)); |
118 | 1.19k | } |
119 | 0 | else if (eAccess == TABWrite) |
120 | 0 | { |
121 | 0 | m_eAccessMode = TABWrite; |
122 | 0 | if (pszCharset != nullptr) |
123 | 0 | SetCharset(pszCharset); |
124 | 0 | nStatus = static_cast<char>(OpenForWrite(pszFname)); |
125 | 0 | } |
126 | 0 | else |
127 | 0 | { |
128 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
129 | 0 | "Open() failed: access mode \"%d\" not supported", eAccess); |
130 | 0 | return -1; |
131 | 0 | } |
132 | | |
133 | 1.19k | return nStatus; |
134 | 1.19k | } |
135 | | |
136 | | /********************************************************************** |
137 | | * TABView::OpenForRead() |
138 | | * |
139 | | * Open for reading |
140 | | * |
141 | | * Returns 0 on success, -1 on error. |
142 | | **********************************************************************/ |
143 | | int TABView::OpenForRead(const char *pszFname, |
144 | | GBool bTestOpenNoError /*= FALSE*/) |
145 | 1.19k | { |
146 | 1.19k | char *pszPath = nullptr; |
147 | 1.19k | int nFnameLen = 0; |
148 | | |
149 | 1.19k | m_eAccessMode = TABRead; |
150 | | |
151 | | /*----------------------------------------------------------------- |
152 | | * Read main .TAB (text) file |
153 | | *----------------------------------------------------------------*/ |
154 | 1.19k | m_pszFname = CPLStrdup(pszFname); |
155 | | |
156 | 1.19k | #ifndef _WIN32 |
157 | | /*----------------------------------------------------------------- |
158 | | * On Unix, make sure extension uses the right cases |
159 | | * We do it even for write access because if a file with the same |
160 | | * extension already exists we want to overwrite it. |
161 | | *----------------------------------------------------------------*/ |
162 | 1.19k | TABAdjustFilenameExtension(m_pszFname); |
163 | 1.19k | #endif |
164 | | |
165 | | /*----------------------------------------------------------------- |
166 | | * Open .TAB file... since it is a small text file, we will just load |
167 | | * it as a stringlist in memory. |
168 | | *----------------------------------------------------------------*/ |
169 | 1.19k | m_papszTABFile = TAB_CSLLoad(m_pszFname); |
170 | 1.19k | if (m_papszTABFile == nullptr) |
171 | 0 | { |
172 | 0 | if (!bTestOpenNoError) |
173 | 0 | { |
174 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Failed opening %s.", m_pszFname); |
175 | 0 | } |
176 | |
|
177 | 0 | CPLFree(m_pszFname); |
178 | 0 | m_pszFname = nullptr; |
179 | 0 | return -1; |
180 | 0 | } |
181 | | |
182 | | /*------------------------------------------------------------- |
183 | | * Look for a line with the "create view" keyword. |
184 | | * If there is no "create view", then we may have a valid .TAB file, |
185 | | * but we do not support it in this class. |
186 | | *------------------------------------------------------------*/ |
187 | 1.19k | GBool bCreateViewFound = FALSE; |
188 | 139k | for (int i = 0; !bCreateViewFound && m_papszTABFile[i]; i++) |
189 | 138k | { |
190 | 138k | const char *pszStr = m_papszTABFile[i]; |
191 | 162k | while (*pszStr != '\0' && isspace(static_cast<unsigned char>(*pszStr))) |
192 | 24.2k | pszStr++; |
193 | 138k | if (STARTS_WITH_CI(pszStr, "create view")) |
194 | 1.19k | bCreateViewFound = TRUE; |
195 | 138k | } |
196 | | |
197 | 1.19k | if (!bCreateViewFound) |
198 | 0 | { |
199 | 0 | if (!bTestOpenNoError) |
200 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
201 | 0 | "%s contains no table view definition. " |
202 | 0 | "This type of .TAB file cannot be read by this library.", |
203 | 0 | m_pszFname); |
204 | 0 | else |
205 | 0 | CPLErrorReset(); |
206 | |
|
207 | 0 | CPLFree(m_pszFname); |
208 | 0 | m_pszFname = nullptr; |
209 | |
|
210 | 0 | return -1; |
211 | 0 | } |
212 | | |
213 | | /*----------------------------------------------------------------- |
214 | | * OK, this appears to be a valid TAB view dataset... |
215 | | * Extract the path component from the main .TAB filename |
216 | | * to build the filename of the sub-tables |
217 | | *----------------------------------------------------------------*/ |
218 | 1.19k | pszPath = CPLStrdup(m_pszFname); |
219 | 1.19k | nFnameLen = static_cast<int>(strlen(pszPath)); |
220 | 18.1k | for (; nFnameLen > 0; nFnameLen--) |
221 | 18.1k | { |
222 | 18.1k | if (pszPath[nFnameLen - 1] == '/' || pszPath[nFnameLen - 1] == '\\') |
223 | 1.19k | { |
224 | 1.19k | break; |
225 | 1.19k | } |
226 | 16.9k | pszPath[nFnameLen - 1] = '\0'; |
227 | 16.9k | } |
228 | | |
229 | | /*----------------------------------------------------------------- |
230 | | * Extract the useful info from the TAB header |
231 | | *----------------------------------------------------------------*/ |
232 | 1.19k | if (ParseTABFile(pszPath, bTestOpenNoError) != 0) |
233 | 551 | { |
234 | | // Failed parsing... an error has already been produced if necessary |
235 | 551 | CPLFree(pszPath); |
236 | 551 | Close(); |
237 | 551 | return -1; |
238 | 551 | } |
239 | 642 | CPLFree(pszPath); |
240 | 642 | pszPath = nullptr; |
241 | | |
242 | | /*----------------------------------------------------------------- |
243 | | * __TODO__ For now, we support only 2 files linked through a single |
244 | | * field... so we'll do some validation first to make sure |
245 | | * that what we found in the header respects these limitations. |
246 | | *----------------------------------------------------------------*/ |
247 | 642 | if (m_numTABFiles != 2) |
248 | 40 | { |
249 | 40 | if (!bTestOpenNoError) |
250 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
251 | 0 | "Open Failed: Dataset %s defines a view on %d tables. " |
252 | 0 | "This is not currently supported.", |
253 | 0 | m_pszFname, m_numTABFiles); |
254 | 40 | Close(); |
255 | 40 | return -1; |
256 | 40 | } |
257 | | |
258 | | /*----------------------------------------------------------------- |
259 | | * Open all the tab files listed in the view |
260 | | *----------------------------------------------------------------*/ |
261 | 602 | m_papoTABFiles = |
262 | 602 | static_cast<TABFile **>(CPLCalloc(m_numTABFiles, sizeof(TABFile *))); |
263 | | |
264 | 1.58k | for (int iFile = 0; iFile < m_numTABFiles; iFile++) |
265 | 1.11k | { |
266 | 1.11k | #ifndef _WIN32 |
267 | 1.11k | TABAdjustFilenameExtension(m_papszTABFnames[iFile]); |
268 | 1.11k | #endif |
269 | | |
270 | 1.11k | m_papoTABFiles[iFile] = new TABFile(m_poDS); |
271 | | |
272 | 1.11k | if (m_papoTABFiles[iFile]->Open(m_papszTABFnames[iFile], m_eAccessMode, |
273 | 1.11k | bTestOpenNoError) != 0) |
274 | 128 | { |
275 | | // Open Failed... an error has already been reported, just return. |
276 | 128 | if (bTestOpenNoError) |
277 | 128 | CPLErrorReset(); |
278 | 128 | Close(); |
279 | 128 | return -1; |
280 | 128 | } |
281 | 1.11k | } |
282 | | |
283 | | /*----------------------------------------------------------------- |
284 | | * Create TABRelation... this will build FeatureDefn, etc. |
285 | | * __TODO__ For now this assumes only 2 tables in the view... |
286 | | *----------------------------------------------------------------*/ |
287 | 474 | m_poRelation = new TABRelation; |
288 | | |
289 | 474 | CPLAssert(m_nMainTableIndex == 0); |
290 | 474 | CPLAssert(CSLCount(m_papszWhereClause) == 5); |
291 | 474 | char *pszTableName = TABGetBasename(m_pszFname); |
292 | 474 | if (m_poRelation->Init(pszTableName, m_papoTABFiles[0], m_papoTABFiles[1], |
293 | 474 | m_papszWhereClause[4], m_papszWhereClause[2], |
294 | 474 | m_papszFieldNames) != 0) |
295 | 83 | { |
296 | | // An error should already have been reported |
297 | 83 | CPLFree(pszTableName); |
298 | 83 | Close(); |
299 | 83 | return -1; |
300 | 83 | } |
301 | 391 | CPLFree(pszTableName); |
302 | | |
303 | 391 | return 0; |
304 | 474 | } |
305 | | |
306 | | /********************************************************************** |
307 | | * TABView::OpenForWrite() |
308 | | * |
309 | | * Create a new TABView dataset |
310 | | * |
311 | | * Returns 0 on success, -1 on error. |
312 | | **********************************************************************/ |
313 | | int TABView::OpenForWrite(const char *pszFname) |
314 | 0 | { |
315 | 0 | int nFnameLen = 0; |
316 | |
|
317 | 0 | m_eAccessMode = TABWrite; |
318 | | |
319 | | /*----------------------------------------------------------------- |
320 | | * Read main .TAB (text) file |
321 | | *----------------------------------------------------------------*/ |
322 | 0 | m_pszFname = CPLStrdup(pszFname); |
323 | |
|
324 | 0 | #ifndef _WIN32 |
325 | | /*----------------------------------------------------------------- |
326 | | * On Unix, make sure extension uses the right cases |
327 | | * We do it even for write access because if a file with the same |
328 | | * extension already exists we want to overwrite it. |
329 | | *----------------------------------------------------------------*/ |
330 | 0 | TABAdjustFilenameExtension(m_pszFname); |
331 | 0 | #endif |
332 | | |
333 | | /*----------------------------------------------------------------- |
334 | | * Extract the path component from the main .TAB filename |
335 | | *----------------------------------------------------------------*/ |
336 | 0 | char *pszPath = CPLStrdup(m_pszFname); |
337 | 0 | nFnameLen = static_cast<int>(strlen(pszPath)); |
338 | 0 | for (; nFnameLen > 0; nFnameLen--) |
339 | 0 | { |
340 | 0 | if (pszPath[nFnameLen - 1] == '/' || pszPath[nFnameLen - 1] == '\\') |
341 | 0 | { |
342 | 0 | break; |
343 | 0 | } |
344 | 0 | pszPath[nFnameLen - 1] = '\0'; |
345 | 0 | } |
346 | |
|
347 | 0 | char *pszBasename = TABGetBasename(m_pszFname); |
348 | | |
349 | | /*----------------------------------------------------------------- |
350 | | * Create the 2 TAB files for the view. |
351 | | * |
352 | | * __TODO__ For now, we support only 2 files linked through a single |
353 | | * field... not sure if anything else than that can be useful |
354 | | * anyways. |
355 | | *----------------------------------------------------------------*/ |
356 | 0 | m_numTABFiles = 2; |
357 | 0 | m_papszTABFnames = nullptr; |
358 | 0 | m_nMainTableIndex = 0; |
359 | 0 | m_bRelFieldsCreated = FALSE; |
360 | |
|
361 | 0 | m_papoTABFiles = |
362 | 0 | static_cast<TABFile **>(CPLCalloc(m_numTABFiles, sizeof(TABFile *))); |
363 | |
|
364 | 0 | for (int iFile = 0; iFile < m_numTABFiles; iFile++) |
365 | 0 | { |
366 | 0 | m_papszTABFnames = CSLAppendPrintf(m_papszTABFnames, "%s%s%d.tab", |
367 | 0 | pszPath, pszBasename, iFile + 1); |
368 | 0 | #ifndef _WIN32 |
369 | 0 | assert(m_papszTABFnames); |
370 | 0 | assert(m_papszTABFnames[iFile]); |
371 | 0 | TABAdjustFilenameExtension(m_papszTABFnames[iFile]); |
372 | 0 | #endif |
373 | |
|
374 | 0 | m_papoTABFiles[iFile] = new TABFile(m_poDS); |
375 | |
|
376 | 0 | if (m_papoTABFiles[iFile]->Open(m_papszTABFnames[iFile], m_eAccessMode, |
377 | 0 | FALSE, GetCharset()) != 0) |
378 | 0 | { |
379 | | // Open Failed... an error has already been reported, just return. |
380 | 0 | CPLFree(pszPath); |
381 | 0 | CPLFree(pszBasename); |
382 | 0 | Close(); |
383 | 0 | return -1; |
384 | 0 | } |
385 | 0 | } |
386 | | |
387 | | /*----------------------------------------------------------------- |
388 | | * Create TABRelation... |
389 | | *----------------------------------------------------------------*/ |
390 | 0 | m_poRelation = new TABRelation; |
391 | |
|
392 | 0 | if (m_poRelation->Init(pszBasename, m_papoTABFiles[0], m_papoTABFiles[1], |
393 | 0 | nullptr, nullptr, nullptr) != 0) |
394 | 0 | { |
395 | | // An error should already have been reported |
396 | 0 | CPLFree(pszPath); |
397 | 0 | CPLFree(pszBasename); |
398 | 0 | Close(); |
399 | 0 | return -1; |
400 | 0 | } |
401 | | |
402 | 0 | CPLFree(pszPath); |
403 | 0 | CPLFree(pszBasename); |
404 | |
|
405 | 0 | return 0; |
406 | 0 | } |
407 | | |
408 | | /********************************************************************** |
409 | | * TABView::ParseTABFile() |
410 | | * |
411 | | * Scan the lines of the TAB file, and store any useful information into |
412 | | * class members. The main piece of information being the sub-table |
413 | | * names, and the list of fields to include in the view that we will |
414 | | * use to build the OGRFeatureDefn for this file. |
415 | | * |
416 | | * It is assumed that the TAB header file is already loaded in m_papszTABFile |
417 | | * |
418 | | * This private method should be used only during the Open() call. |
419 | | * |
420 | | * Returns 0 on success, -1 on error. |
421 | | **********************************************************************/ |
422 | | int TABView::ParseTABFile(const char *pszDatasetPath, |
423 | | GBool bTestOpenNoError /*=FALSE*/) |
424 | 1.19k | { |
425 | 1.19k | int iLine, numLines; |
426 | 1.19k | char **papszTok = nullptr; |
427 | 1.19k | GBool bInsideTableDef = FALSE; |
428 | | |
429 | 1.19k | if (m_eAccessMode != TABRead) |
430 | 0 | { |
431 | 0 | CPLError(CE_Failure, CPLE_AssertionFailed, |
432 | 0 | "ParseTABFile() can be used only with Read access."); |
433 | 0 | return -1; |
434 | 0 | } |
435 | | |
436 | 1.19k | numLines = CSLCount(m_papszTABFile); |
437 | | |
438 | 539k | for (iLine = 0; iLine < numLines; iLine++) |
439 | 538k | { |
440 | | /*------------------------------------------------------------- |
441 | | * Tokenize the next .TAB line, and check first keyword |
442 | | *------------------------------------------------------------*/ |
443 | 538k | CSLDestroy(papszTok); |
444 | 538k | papszTok = CSLTokenizeStringComplex(m_papszTABFile[iLine], " \t(),;", |
445 | 538k | TRUE, FALSE); |
446 | 538k | if (CSLCount(papszTok) < 2) |
447 | 344k | continue; // All interesting lines have at least 2 tokens |
448 | | |
449 | 193k | if (EQUAL(papszTok[0], "!version")) |
450 | 3.83k | { |
451 | 3.83k | CPLFree(m_pszVersion); |
452 | 3.83k | m_pszVersion = CPLStrdup(papszTok[1]); |
453 | 3.83k | } |
454 | 189k | else if (EQUAL(papszTok[0], "!charset")) |
455 | 1.89k | { |
456 | 1.89k | CPLFree(m_pszCharset); |
457 | 1.89k | m_pszCharset = CPLStrdup(papszTok[1]); |
458 | 1.89k | } |
459 | 187k | else if (EQUAL(papszTok[0], "open") && EQUAL(papszTok[1], "table") && |
460 | 13.2k | CSLCount(papszTok) >= 3) |
461 | 12.5k | { |
462 | | // Source table name may be either "filename" or "filename.tab" |
463 | 12.5k | int nLen = static_cast<int>(strlen(papszTok[2])); |
464 | 12.5k | if (nLen > 4 && EQUAL(papszTok[2] + nLen - 4, ".tab")) |
465 | 1.98k | papszTok[2][nLen - 4] = '\0'; |
466 | | |
467 | 12.5k | m_papszTABFnames = CSLAppendPrintf(m_papszTABFnames, "%s%s.tab", |
468 | 12.5k | pszDatasetPath, papszTok[2]); |
469 | 12.5k | } |
470 | 175k | else if (EQUAL(papszTok[0], "create") && EQUAL(papszTok[1], "view")) |
471 | 7.95k | { |
472 | 7.95k | bInsideTableDef = TRUE; |
473 | 7.95k | } |
474 | 167k | else if (bInsideTableDef && (EQUAL(papszTok[0], "Select"))) |
475 | 5.55k | { |
476 | | /*--------------------------------------------------------- |
477 | | * We found the list of table fields (comma-delimited list) |
478 | | *--------------------------------------------------------*/ |
479 | 17.9k | for (int iTok = 1; papszTok[iTok] != nullptr; iTok++) |
480 | 12.4k | m_papszFieldNames = |
481 | 12.4k | CSLAddString(m_papszFieldNames, papszTok[iTok]); |
482 | 5.55k | } |
483 | 161k | else if (bInsideTableDef && (EQUAL(papszTok[0], "where"))) |
484 | 782 | { |
485 | | /*--------------------------------------------------------- |
486 | | * We found the where clause that relates the 2 tables |
487 | | * Something in the form: |
488 | | * where table1.field1=table2.field2 |
489 | | * The tokenized array will contain: |
490 | | * {"where", "table1", "field1", "table2", "field2"} |
491 | | *--------------------------------------------------------*/ |
492 | 782 | CSLDestroy(m_papszWhereClause); |
493 | 782 | m_papszWhereClause = CSLTokenizeStringComplex( |
494 | 782 | m_papszTABFile[iLine], " \t(),;=.", TRUE, FALSE); |
495 | | |
496 | | /*--------------------------------------------------------- |
497 | | * For now we are very limiting on the format of the WHERE |
498 | | * clause... we will be more permitting as we learn more about |
499 | | * what it can contain... (I don't want to implement a full SQL |
500 | | * parser here!!!). If you encountered this error, |
501 | | * (and are reading this!) please report the test dataset |
502 | | * that produced the error and I'll see if we can support it. |
503 | | *--------------------------------------------------------*/ |
504 | 782 | if (CSLCount(m_papszWhereClause) != 5) |
505 | 24 | { |
506 | 24 | if (!bTestOpenNoError) |
507 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
508 | 0 | "WHERE clause in %s is not in a supported format: " |
509 | 0 | "\"%s\"", |
510 | 0 | m_pszFname, m_papszTABFile[iLine]); |
511 | 24 | CSLDestroy(papszTok); |
512 | 24 | return -1; |
513 | 24 | } |
514 | 782 | } |
515 | 161k | else |
516 | 161k | { |
517 | | // Simply Ignore unrecognized lines |
518 | 161k | } |
519 | 193k | } |
520 | | |
521 | 1.16k | CSLDestroy(papszTok); |
522 | | |
523 | | /*----------------------------------------------------------------- |
524 | | * The main table is the one from which we read the geometries, etc... |
525 | | * For now we assume it is always the first one in the list |
526 | | *----------------------------------------------------------------*/ |
527 | 1.16k | m_nMainTableIndex = 0; |
528 | | |
529 | | /*----------------------------------------------------------------- |
530 | | * Make sure all required class members are set |
531 | | *----------------------------------------------------------------*/ |
532 | 1.16k | m_numTABFiles = CSLCount(m_papszTABFnames); |
533 | | |
534 | 1.16k | if (m_pszCharset == nullptr) |
535 | 1.02k | m_pszCharset = CPLStrdup("Neutral"); |
536 | 1.16k | if (m_pszVersion == nullptr) |
537 | 666 | m_pszVersion = CPLStrdup("100"); |
538 | | |
539 | 1.16k | if (CSLCount(m_papszFieldNames) == 0) |
540 | 318 | { |
541 | 318 | if (!bTestOpenNoError) |
542 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
543 | 0 | "%s: header contains no table field definition. " |
544 | 0 | "This type of .TAB file cannot be read by this library.", |
545 | 0 | m_pszFname); |
546 | 318 | return -1; |
547 | 318 | } |
548 | | |
549 | 851 | if (CSLCount(m_papszWhereClause) == 0) |
550 | 209 | { |
551 | 209 | if (!bTestOpenNoError) |
552 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
553 | 0 | "%s: WHERE clause not found or missing in header. " |
554 | 0 | "This type of .TAB file cannot be read by this library.", |
555 | 0 | m_pszFname); |
556 | 209 | return -1; |
557 | 209 | } |
558 | 642 | return 0; |
559 | 851 | } |
560 | | |
561 | | /********************************************************************** |
562 | | * TABView::WriteTABFile() |
563 | | * |
564 | | * Generate the TAB header file. This is usually done during the |
565 | | * Close() call. |
566 | | * |
567 | | * Returns 0 on success, -1 on error. |
568 | | **********************************************************************/ |
569 | | int TABView::WriteTABFile() |
570 | 0 | { |
571 | 0 | CPLAssert(m_eAccessMode == TABWrite); |
572 | 0 | CPLAssert(m_numTABFiles == 2); |
573 | 0 | CPLAssert(GetLayerDefn()); |
574 | |
|
575 | 0 | char *pszTable = TABGetBasename(m_pszFname); |
576 | 0 | char *pszTable1 = TABGetBasename(m_papszTABFnames[0]); |
577 | 0 | char *pszTable2 = TABGetBasename(m_papszTABFnames[1]); |
578 | |
|
579 | 0 | VSILFILE *fp = VSIFOpenL(m_pszFname, "wt"); |
580 | 0 | if (fp != nullptr) |
581 | 0 | { |
582 | | // Version is always 100, no matter what the sub-table's version is |
583 | 0 | VSIFPrintfL(fp, "!Table\n"); |
584 | 0 | VSIFPrintfL(fp, "!Version 100\n"); |
585 | |
|
586 | 0 | VSIFPrintfL(fp, "Open Table \"%s\" Hide\n", pszTable1); |
587 | 0 | VSIFPrintfL(fp, "Open Table \"%s\" Hide\n", pszTable2); |
588 | 0 | VSIFPrintfL(fp, "\n"); |
589 | 0 | VSIFPrintfL(fp, "Create View %s As\n", pszTable); |
590 | 0 | VSIFPrintfL(fp, "Select "); |
591 | |
|
592 | 0 | const OGRFeatureDefn *poDefn = GetLayerDefn(); |
593 | 0 | for (int iField = 0; iField < poDefn->GetFieldCount(); iField++) |
594 | 0 | { |
595 | 0 | const OGRFieldDefn *poFieldDefn = poDefn->GetFieldDefn(iField); |
596 | 0 | if (iField == 0) |
597 | 0 | VSIFPrintfL(fp, "%s", poFieldDefn->GetNameRef()); |
598 | 0 | else |
599 | 0 | VSIFPrintfL(fp, ",%s", poFieldDefn->GetNameRef()); |
600 | 0 | } |
601 | 0 | VSIFPrintfL(fp, "\n"); |
602 | |
|
603 | 0 | VSIFPrintfL(fp, "From %s, %s\n", pszTable2, pszTable1); |
604 | 0 | VSIFPrintfL(fp, "Where %s.%s=%s.%s\n", pszTable2, |
605 | 0 | m_poRelation->GetRelFieldName(), pszTable1, |
606 | 0 | m_poRelation->GetMainFieldName()); |
607 | |
|
608 | 0 | VSIFCloseL(fp); |
609 | 0 | } |
610 | 0 | else |
611 | 0 | { |
612 | 0 | CPLFree(pszTable); |
613 | 0 | CPLFree(pszTable1); |
614 | 0 | CPLFree(pszTable2); |
615 | |
|
616 | 0 | CPLError(CE_Failure, CPLE_FileIO, "Failed to create file `%s'", |
617 | 0 | m_pszFname); |
618 | 0 | return -1; |
619 | 0 | } |
620 | | |
621 | 0 | CPLFree(pszTable); |
622 | 0 | CPLFree(pszTable1); |
623 | 0 | CPLFree(pszTable2); |
624 | |
|
625 | 0 | return 0; |
626 | 0 | } |
627 | | |
628 | | /********************************************************************** |
629 | | * TABView::Close() |
630 | | * |
631 | | * Close current file, and release all memory used. |
632 | | * |
633 | | * Returns 0 on success, -1 on error. |
634 | | **********************************************************************/ |
635 | | int TABView::Close() |
636 | 1.99k | { |
637 | | // In write access, the main .TAB file has not been written yet. |
638 | 1.99k | if (m_eAccessMode == TABWrite && m_poRelation) |
639 | 0 | WriteTABFile(); |
640 | | |
641 | 3.19k | for (int i = 0; m_papoTABFiles && i < m_numTABFiles; i++) |
642 | 1.20k | { |
643 | 1.20k | if (m_papoTABFiles[i]) |
644 | 1.11k | delete m_papoTABFiles[i]; // Automatically closes. |
645 | 1.20k | } |
646 | 1.99k | CPLFree(m_papoTABFiles); |
647 | 1.99k | m_papoTABFiles = nullptr; |
648 | 1.99k | m_numTABFiles = 0; |
649 | | |
650 | | /*----------------------------------------------------------------- |
651 | | * __TODO__ OK, MapInfo does not like to see a .map and .id file |
652 | | * attached to the second table, even if they're empty. |
653 | | * We'll use a little hack to delete them now, but eventually we |
654 | | * should avoid creating them at all. |
655 | | *----------------------------------------------------------------*/ |
656 | 1.99k | if (m_eAccessMode == TABWrite && m_pszFname) |
657 | 0 | { |
658 | 0 | m_pszFname[strlen(m_pszFname) - 4] = '\0'; |
659 | 0 | char *pszFile = CPLStrdup(CPLSPrintf("%s2.map", m_pszFname)); |
660 | 0 | TABAdjustFilenameExtension(pszFile); |
661 | 0 | VSIUnlink(pszFile); |
662 | |
|
663 | 0 | snprintf(pszFile, strlen(pszFile) + 1, "%s2.id", m_pszFname); |
664 | 0 | TABAdjustFilenameExtension(pszFile); |
665 | 0 | VSIUnlink(pszFile); |
666 | |
|
667 | 0 | CPLFree(pszFile); |
668 | 0 | } |
669 | | // End of hack! |
670 | | |
671 | 1.99k | CPLFree(m_pszFname); |
672 | 1.99k | m_pszFname = nullptr; |
673 | | |
674 | 1.99k | CSLDestroy(m_papszTABFile); |
675 | 1.99k | m_papszTABFile = nullptr; |
676 | | |
677 | 1.99k | CPLFree(m_pszVersion); |
678 | 1.99k | m_pszVersion = nullptr; |
679 | 1.99k | CPLFree(m_pszCharset); |
680 | 1.99k | m_pszCharset = nullptr; |
681 | | |
682 | 1.99k | CSLDestroy(m_papszTABFnames); |
683 | 1.99k | m_papszTABFnames = nullptr; |
684 | | |
685 | 1.99k | CSLDestroy(m_papszFieldNames); |
686 | 1.99k | m_papszFieldNames = nullptr; |
687 | 1.99k | CSLDestroy(m_papszWhereClause); |
688 | 1.99k | m_papszWhereClause = nullptr; |
689 | | |
690 | 1.99k | m_nMainTableIndex = -1; |
691 | | |
692 | 1.99k | if (m_poRelation) |
693 | 474 | delete m_poRelation; |
694 | 1.99k | m_poRelation = nullptr; |
695 | | |
696 | 1.99k | m_bRelFieldsCreated = FALSE; |
697 | | |
698 | 1.99k | return 0; |
699 | 1.99k | } |
700 | | |
701 | | /********************************************************************** |
702 | | * TABView::SetQuickSpatialIndexMode() |
703 | | * |
704 | | * Select "quick spatial index mode". |
705 | | * |
706 | | * The default behavior of MITAB is to generate an optimized spatial index, |
707 | | * but this results in slower write speed. |
708 | | * |
709 | | * Applications that want faster write speed and do not care |
710 | | * about the performance of spatial queries on the resulting file can |
711 | | * use SetQuickSpatialIndexMode() to require the creation of a non-optimal |
712 | | * spatial index (actually emulating the type of spatial index produced |
713 | | * by MITAB before version 1.6.0). In this mode writing files can be |
714 | | * about 5 times faster, but spatial queries can be up to 30 times slower. |
715 | | * |
716 | | * Returns 0 on success, -1 on error. |
717 | | **********************************************************************/ |
718 | | int TABView::SetQuickSpatialIndexMode(GBool bQuickSpatialIndexMode /*=TRUE*/) |
719 | 0 | { |
720 | 0 | if (m_eAccessMode != TABWrite || m_numTABFiles == 0) |
721 | 0 | { |
722 | 0 | CPLError(CE_Failure, CPLE_AssertionFailed, |
723 | 0 | "SetQuickSpatialIndexMode() failed: file not opened for write " |
724 | 0 | "access."); |
725 | 0 | return -1; |
726 | 0 | } |
727 | | |
728 | 0 | for (int iFile = 0; iFile < m_numTABFiles; iFile++) |
729 | 0 | { |
730 | 0 | if (m_papoTABFiles[iFile]->SetQuickSpatialIndexMode( |
731 | 0 | bQuickSpatialIndexMode) != 0) |
732 | 0 | { |
733 | | // An error has already been reported, just return. |
734 | 0 | return -1; |
735 | 0 | } |
736 | 0 | } |
737 | | |
738 | 0 | return 0; |
739 | 0 | } |
740 | | |
741 | | /********************************************************************** |
742 | | * TABView::GetNextFeatureId() |
743 | | * |
744 | | * Returns feature id that follows nPrevId, or -1 if it is the |
745 | | * last feature id. Pass nPrevId=-1 to fetch the first valid feature id. |
746 | | **********************************************************************/ |
747 | | GIntBig TABView::GetNextFeatureId(GIntBig nPrevId) |
748 | 10.5k | { |
749 | 10.5k | if (m_nMainTableIndex != -1) |
750 | 10.5k | return m_papoTABFiles[m_nMainTableIndex]->GetNextFeatureId(nPrevId); |
751 | | |
752 | 0 | return -1; |
753 | 10.5k | } |
754 | | |
755 | | /********************************************************************** |
756 | | * TABView::GetFeatureRef() |
757 | | * |
758 | | * Fill and return a TABFeature object for the specified feature id. |
759 | | * |
760 | | * The returned pointer is a reference to an object owned and maintained |
761 | | * by this TABView object. It should not be altered or freed by the |
762 | | * caller and its contents is guaranteed to be valid only until the next |
763 | | * call to GetFeatureRef() or Close(). |
764 | | * |
765 | | * Returns NULL if the specified feature id does not exist of if an |
766 | | * error happened. In any case, CPLError() will have been called to |
767 | | * report the reason of the failure. |
768 | | **********************************************************************/ |
769 | | TABFeature *TABView::GetFeatureRef(GIntBig nFeatureId) |
770 | 10.4k | { |
771 | | |
772 | | /*----------------------------------------------------------------- |
773 | | * Make sure file is open. |
774 | | *----------------------------------------------------------------*/ |
775 | 10.4k | if (m_poRelation == nullptr) |
776 | 0 | { |
777 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
778 | 0 | "GetFeatureRef() failed: file is not opened!"); |
779 | 0 | return nullptr; |
780 | 0 | } |
781 | | |
782 | 10.4k | if (!CPL_INT64_FITS_ON_INT32(nFeatureId)) |
783 | 0 | return nullptr; |
784 | | |
785 | 10.4k | if (m_poCurFeature) |
786 | 0 | { |
787 | 0 | delete m_poCurFeature; |
788 | 0 | m_poCurFeature = nullptr; |
789 | 0 | } |
790 | | |
791 | 10.4k | m_poCurFeature = m_poRelation->GetFeature(static_cast<int>(nFeatureId)); |
792 | 10.4k | m_nCurFeatureId = nFeatureId; |
793 | 10.4k | if (m_poCurFeature) |
794 | 10.4k | { |
795 | 10.4k | m_poCurFeature->SetFID(m_nCurFeatureId); |
796 | 10.4k | } |
797 | 10.4k | return m_poCurFeature; |
798 | 10.4k | } |
799 | | |
800 | | /********************************************************************** |
801 | | * TABView::CreateFeature() |
802 | | * |
803 | | * Write a new feature to this dataset. The passed in feature is updated |
804 | | * with the new feature id. |
805 | | * |
806 | | * Returns OGRERR_NONE on success, or an appropriate OGRERR_ code if an |
807 | | * error happened in which case, CPLError() will have been called to |
808 | | * report the reason of the failure. |
809 | | **********************************************************************/ |
810 | | OGRErr TABView::CreateFeature(TABFeature *poFeature) |
811 | 0 | { |
812 | 0 | if (m_eAccessMode != TABWrite) |
813 | 0 | { |
814 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
815 | 0 | "CreateFeature() can be used only with Write access."); |
816 | 0 | return OGRERR_UNSUPPORTED_OPERATION; |
817 | 0 | } |
818 | | |
819 | 0 | if (m_poRelation == nullptr) |
820 | 0 | { |
821 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
822 | 0 | "CreateFeature() failed: file is not opened!"); |
823 | 0 | return OGRERR_FAILURE; |
824 | 0 | } |
825 | | |
826 | | /*----------------------------------------------------------------- |
827 | | * If we're about to write the first feature, then we must finish |
828 | | * the initialization of the view first by creating the MI_refnum fields |
829 | | *----------------------------------------------------------------*/ |
830 | 0 | if (!m_bRelFieldsCreated) |
831 | 0 | { |
832 | 0 | if (m_poRelation->CreateRelFields() != 0) |
833 | 0 | return OGRERR_FAILURE; |
834 | 0 | m_bRelFieldsCreated = TRUE; |
835 | 0 | } |
836 | | |
837 | 0 | int nFeatureId = m_poRelation->WriteFeature(poFeature); |
838 | 0 | if (nFeatureId < 0) |
839 | 0 | return OGRERR_FAILURE; |
840 | | |
841 | 0 | poFeature->SetFID(nFeatureId); |
842 | |
|
843 | 0 | return OGRERR_NONE; |
844 | 0 | } |
845 | | |
846 | | /********************************************************************** |
847 | | * TABView::GetLayerDefn() const |
848 | | * |
849 | | * Returns a reference to the OGRFeatureDefn that will be used to create |
850 | | * features in this dataset. |
851 | | * |
852 | | * Returns a reference to an object that is maintained by this TABView |
853 | | * object (and thus should not be modified or freed by the caller) or |
854 | | * NULL if the OGRFeatureDefn has not been initialized yet (i.e. no file |
855 | | * opened yet) |
856 | | **********************************************************************/ |
857 | | const OGRFeatureDefn *TABView::GetLayerDefn() const |
858 | 715 | { |
859 | 715 | if (m_poRelation) |
860 | 715 | return m_poRelation->GetFeatureDefn(); |
861 | | |
862 | 0 | return nullptr; |
863 | 715 | } |
864 | | |
865 | | /********************************************************************** |
866 | | * TABView::SetFeatureDefn() |
867 | | * |
868 | | * Set the FeatureDefn for this dataset. |
869 | | * |
870 | | * For now, fields passed through SetFeatureDefn will not be mapped |
871 | | * properly, so this function can be used only with an empty feature defn. |
872 | | **********************************************************************/ |
873 | | int TABView::SetFeatureDefn( |
874 | | OGRFeatureDefn *poFeatureDefn, |
875 | | CPL_UNUSED TABFieldType *paeMapInfoNativeFieldTypes /* =NULL */) |
876 | 0 | { |
877 | 0 | if (m_poRelation) |
878 | 0 | return m_poRelation->SetFeatureDefn(poFeatureDefn); |
879 | | |
880 | 0 | return -1; |
881 | 0 | } |
882 | | |
883 | | /********************************************************************** |
884 | | * TABView::GetNativeFieldType() |
885 | | * |
886 | | * Returns the native MapInfo field type for the specified field. |
887 | | * |
888 | | * Returns TABFUnknown if file is not opened, or if specified field index is |
889 | | * invalid. |
890 | | * |
891 | | * Note that field ids are positive and start at 0. |
892 | | **********************************************************************/ |
893 | | TABFieldType TABView::GetNativeFieldType(int nFieldId) |
894 | 0 | { |
895 | 0 | if (m_poRelation) |
896 | 0 | return m_poRelation->GetNativeFieldType(nFieldId); |
897 | | |
898 | 0 | return TABFUnknown; |
899 | 0 | } |
900 | | |
901 | | /********************************************************************** |
902 | | * TABView::AddFieldNative() |
903 | | * |
904 | | * Create a new field using a native mapinfo data type... this is an |
905 | | * alternative to defining fields through the OGR interface. |
906 | | * This function should be called after creating a new dataset, but before |
907 | | * writing the first feature. |
908 | | * |
909 | | * This function will build/update the OGRFeatureDefn that will have to be |
910 | | * used when writing features to this dataset. |
911 | | * |
912 | | * A reference to the OGRFeatureDefn can be obtained using GetLayerDefn(). |
913 | | * |
914 | | * Returns 0 on success, -1 on error. |
915 | | **********************************************************************/ |
916 | | int TABView::AddFieldNative(const char *pszName, TABFieldType eMapInfoType, |
917 | | int nWidth /*=0*/, int nPrecision /*=0*/, |
918 | | GBool bIndexed /*=FALSE*/, GBool bUnique /*=FALSE*/, |
919 | | int bApproxOK) |
920 | 0 | { |
921 | 0 | if (m_poRelation) |
922 | 0 | return m_poRelation->AddFieldNative(pszName, eMapInfoType, nWidth, |
923 | 0 | nPrecision, bIndexed, bUnique, |
924 | 0 | bApproxOK); |
925 | | |
926 | 0 | return -1; |
927 | 0 | } |
928 | | |
929 | | /********************************************************************** |
930 | | * TABView::SetFieldIndexed() |
931 | | * |
932 | | * Request that a field be indexed. This will create the .IND file if |
933 | | * necessary, etc. |
934 | | * |
935 | | * Note that field ids are positive and start at 0. |
936 | | * |
937 | | * Returns 0 on success, -1 on error. |
938 | | **********************************************************************/ |
939 | | int TABView::SetFieldIndexed(int nFieldId) |
940 | 0 | { |
941 | 0 | if (m_poRelation) |
942 | 0 | return m_poRelation->SetFieldIndexed(nFieldId); |
943 | | |
944 | 0 | return -1; |
945 | 0 | } |
946 | | |
947 | | int TABView::SetCharset(const char *pszCharset) |
948 | 0 | { |
949 | 0 | if (0 != IMapInfoFile::SetCharset(pszCharset)) |
950 | 0 | { |
951 | 0 | return -1; |
952 | 0 | } |
953 | | |
954 | 0 | for (int i = 0; i != m_numTABFiles; ++i) |
955 | 0 | { |
956 | 0 | m_papoTABFiles[i]->SetCharset(pszCharset); |
957 | 0 | } |
958 | |
|
959 | 0 | return 0; |
960 | 0 | } |
961 | | |
962 | | /********************************************************************** |
963 | | * TABView::IsFieldIndexed() |
964 | | * |
965 | | * Returns TRUE if field is indexed, or FALSE otherwise. |
966 | | **********************************************************************/ |
967 | | GBool TABView::IsFieldIndexed(int nFieldId) |
968 | 0 | { |
969 | 0 | if (m_poRelation) |
970 | 0 | return m_poRelation->IsFieldIndexed(nFieldId); |
971 | | |
972 | 0 | return FALSE; |
973 | 0 | } |
974 | | |
975 | | /********************************************************************** |
976 | | * TABView::IsFieldUnique() |
977 | | * |
978 | | * Returns TRUE if field is in the Unique table, or FALSE otherwise. |
979 | | **********************************************************************/ |
980 | | GBool TABView::IsFieldUnique(int nFieldId) |
981 | 0 | { |
982 | 0 | if (m_poRelation) |
983 | 0 | return m_poRelation->IsFieldUnique(nFieldId); |
984 | | |
985 | 0 | return FALSE; |
986 | 0 | } |
987 | | |
988 | | /********************************************************************** |
989 | | * TABView::GetBounds() |
990 | | * |
991 | | * Fetch projection coordinates bounds of a dataset. |
992 | | * |
993 | | * The bForce flag has no effect on TAB files since the bounds are |
994 | | * always in the header. |
995 | | * |
996 | | * Returns 0 on success, -1 on error. |
997 | | **********************************************************************/ |
998 | | int TABView::GetBounds(double &dXMin, double &dYMin, double &dXMax, |
999 | | double &dYMax, GBool bForce /*= TRUE*/) |
1000 | 0 | { |
1001 | 0 | if (m_nMainTableIndex == -1) |
1002 | 0 | { |
1003 | 0 | CPLError( |
1004 | 0 | CE_Failure, CPLE_AppDefined, |
1005 | 0 | "GetBounds() can be called only after dataset has been opened."); |
1006 | 0 | return -1; |
1007 | 0 | } |
1008 | | |
1009 | 0 | return m_papoTABFiles[m_nMainTableIndex]->GetBounds(dXMin, dYMin, dXMax, |
1010 | 0 | dYMax, bForce); |
1011 | 0 | } |
1012 | | |
1013 | | /********************************************************************** |
1014 | | * TABView::IGetExtent() |
1015 | | * |
1016 | | * Fetch extent of the data currently stored in the dataset. |
1017 | | * |
1018 | | * The bForce flag has no effect on TAB files since that value is |
1019 | | * always in the header. |
1020 | | * |
1021 | | * Returns OGRERR_NONE/OGRRERR_FAILURE. |
1022 | | **********************************************************************/ |
1023 | | OGRErr TABView::IGetExtent(int iGeomField, OGREnvelope *psExtent, bool bForce) |
1024 | 0 | { |
1025 | 0 | if (m_nMainTableIndex == -1) |
1026 | 0 | { |
1027 | 0 | CPLError( |
1028 | 0 | CE_Failure, CPLE_AppDefined, |
1029 | 0 | "GetExtent() can be called only after dataset has been opened."); |
1030 | 0 | return OGRERR_FAILURE; |
1031 | 0 | } |
1032 | | |
1033 | 0 | return m_papoTABFiles[m_nMainTableIndex]->GetExtent(iGeomField, psExtent, |
1034 | 0 | bForce); |
1035 | 0 | } |
1036 | | |
1037 | | /********************************************************************** |
1038 | | * TABView::GetFeatureCountByType() |
1039 | | * |
1040 | | * Return number of features of each type. |
1041 | | * |
1042 | | * Note that the sum of the 4 returned values may be different from |
1043 | | * the total number of features since features with NONE geometry |
1044 | | * are not taken into account here. |
1045 | | * |
1046 | | * Note: the bForce flag has nmo effect on .TAB files since the info |
1047 | | * is always in the header. |
1048 | | * |
1049 | | * Returns 0 on success, or silently returns -1 (with no error) if this |
1050 | | * information is not available. |
1051 | | **********************************************************************/ |
1052 | | int TABView::GetFeatureCountByType(int &numPoints, int &numLines, |
1053 | | int &numRegions, int &numTexts, |
1054 | | GBool bForce /*= TRUE*/) |
1055 | 0 | { |
1056 | 0 | if (m_nMainTableIndex == -1) |
1057 | 0 | return -1; |
1058 | | |
1059 | 0 | return m_papoTABFiles[m_nMainTableIndex]->GetFeatureCountByType( |
1060 | 0 | numPoints, numLines, numRegions, numTexts, bForce); |
1061 | 0 | } |
1062 | | |
1063 | | /********************************************************************** |
1064 | | * TABView::GetSpatialRef() |
1065 | | * |
1066 | | * Returns a reference to an OGRSpatialReference for this dataset. |
1067 | | * If the projection parameters have not been parsed yet, then we will |
1068 | | * parse them before returning. |
1069 | | * |
1070 | | * The returned object is owned and maintained by this TABFile and |
1071 | | * should not be modified or freed by the caller. |
1072 | | * |
1073 | | * Returns NULL if the SpatialRef cannot be accessed. |
1074 | | **********************************************************************/ |
1075 | | const OGRSpatialReference *TABView::GetSpatialRef() const |
1076 | 163 | { |
1077 | 163 | if (m_nMainTableIndex == -1) |
1078 | 0 | { |
1079 | 0 | CPLError(CE_Failure, CPLE_AssertionFailed, |
1080 | 0 | "GetSpatialRef() failed: file has not been opened yet."); |
1081 | 0 | return nullptr; |
1082 | 0 | } |
1083 | | |
1084 | 163 | return m_papoTABFiles[m_nMainTableIndex]->GetSpatialRef(); |
1085 | 163 | } |
1086 | | |
1087 | | /********************************************************************** |
1088 | | * TABView::SetSpatialRef() |
1089 | | **********************************************************************/ |
1090 | | int TABView::SetSpatialRef(OGRSpatialReference *poSpatialRef) |
1091 | 0 | { |
1092 | 0 | if (m_nMainTableIndex == -1) |
1093 | 0 | { |
1094 | 0 | CPLError(CE_Failure, CPLE_AssertionFailed, |
1095 | 0 | "SetSpatialRef() failed: file has not been opened yet."); |
1096 | 0 | return -1; |
1097 | 0 | } |
1098 | | |
1099 | 0 | return m_papoTABFiles[m_nMainTableIndex]->SetSpatialRef(poSpatialRef); |
1100 | 0 | } |
1101 | | |
1102 | | /********************************************************************** |
1103 | | * TABView::SetBounds() |
1104 | | **********************************************************************/ |
1105 | | int TABView::SetBounds(double dXMin, double dYMin, double dXMax, double dYMax) |
1106 | 0 | { |
1107 | 0 | if (m_nMainTableIndex == -1) |
1108 | 0 | { |
1109 | 0 | CPLError(CE_Failure, CPLE_AssertionFailed, |
1110 | 0 | "SetBounds() failed: file has not been opened yet."); |
1111 | 0 | return -1; |
1112 | 0 | } |
1113 | | |
1114 | 0 | return m_papoTABFiles[m_nMainTableIndex]->SetBounds(dXMin, dYMin, dXMax, |
1115 | 0 | dYMax); |
1116 | 0 | } |
1117 | | |
1118 | | /************************************************************************/ |
1119 | | /* TestCapability() */ |
1120 | | /************************************************************************/ |
1121 | | |
1122 | | int TABView::TestCapability(const char *pszCap) const |
1123 | | |
1124 | 0 | { |
1125 | 0 | if (EQUAL(pszCap, OLCRandomRead)) |
1126 | 0 | return TRUE; |
1127 | | |
1128 | 0 | else if (EQUAL(pszCap, OLCSequentialWrite)) |
1129 | 0 | return TRUE; |
1130 | | |
1131 | 0 | else if (EQUAL(pszCap, OLCRandomWrite)) |
1132 | 0 | return FALSE; |
1133 | | |
1134 | 0 | else if (EQUAL(pszCap, OLCFastFeatureCount)) |
1135 | 0 | return m_poFilterGeom == nullptr; |
1136 | | |
1137 | 0 | else if (EQUAL(pszCap, OLCFastSpatialFilter)) |
1138 | 0 | return FALSE; |
1139 | | |
1140 | 0 | else if (EQUAL(pszCap, OLCFastGetExtent)) |
1141 | 0 | return TRUE; |
1142 | | |
1143 | 0 | else if (EQUAL(pszCap, OLCStringsAsUTF8)) |
1144 | 0 | return TestUtf8Capability(); |
1145 | | |
1146 | 0 | else |
1147 | 0 | return FALSE; |
1148 | 0 | } |
1149 | | |
1150 | | /********************************************************************** |
1151 | | * TABView::Dump() |
1152 | | * |
1153 | | * Dump block contents... available only in DEBUG mode. |
1154 | | **********************************************************************/ |
1155 | | #ifdef DEBUG |
1156 | | |
1157 | | void TABView::Dump(FILE *fpOut /*=NULL*/) |
1158 | | { |
1159 | | if (fpOut == nullptr) |
1160 | | fpOut = stdout; |
1161 | | |
1162 | | fprintf(fpOut, "----- TABView::Dump() -----\n"); |
1163 | | |
1164 | | if (m_numTABFiles > 0) |
1165 | | { |
1166 | | fprintf(fpOut, "File is not opened.\n"); |
1167 | | } |
1168 | | else |
1169 | | { |
1170 | | fprintf(fpOut, "File is opened: %s\n", m_pszFname); |
1171 | | fprintf(fpOut, "View contains %d tables\n", m_numTABFiles); |
1172 | | } |
1173 | | |
1174 | | fflush(fpOut); |
1175 | | } |
1176 | | |
1177 | | #endif // DEBUG |
1178 | | |
1179 | | /*===================================================================== |
1180 | | * class TABRelation |
1181 | | *====================================================================*/ |
1182 | | |
1183 | | /********************************************************************** |
1184 | | * TABRelation::TABRelation() |
1185 | | * |
1186 | | * Constructor. |
1187 | | **********************************************************************/ |
1188 | | TABRelation::TABRelation() |
1189 | 474 | : m_poMainTable(nullptr), m_pszMainFieldName(nullptr), m_nMainFieldNo(-1), |
1190 | 474 | m_poRelTable(nullptr), m_pszRelFieldName(nullptr), m_nRelFieldNo(-1), |
1191 | 474 | m_poRelINDFileRef(nullptr), m_nRelFieldIndexNo(-1), m_nUniqueRecordNo(0), |
1192 | 474 | m_panMainTableFieldMap(nullptr), m_panRelTableFieldMap(nullptr), |
1193 | 474 | m_poDefn(nullptr) |
1194 | 474 | { |
1195 | 474 | } |
1196 | | |
1197 | | /********************************************************************** |
1198 | | * TABRelation::~TABRelation() |
1199 | | * |
1200 | | * Destructor. |
1201 | | **********************************************************************/ |
1202 | | TABRelation::~TABRelation() |
1203 | 474 | { |
1204 | 474 | ResetAllMembers(); |
1205 | 474 | } |
1206 | | |
1207 | | /********************************************************************** |
1208 | | * TABRelation::ResetAllMembers() |
1209 | | * |
1210 | | * Reset all class members. |
1211 | | **********************************************************************/ |
1212 | | void TABRelation::ResetAllMembers() |
1213 | 948 | { |
1214 | 948 | m_poMainTable = nullptr; |
1215 | 948 | CPLFree(m_pszMainFieldName); |
1216 | 948 | m_pszMainFieldName = nullptr; |
1217 | 948 | m_nMainFieldNo = -1; |
1218 | | |
1219 | 948 | m_poRelTable = nullptr; |
1220 | 948 | CPLFree(m_pszRelFieldName); |
1221 | 948 | m_pszRelFieldName = nullptr; |
1222 | 948 | m_nRelFieldNo = -1; |
1223 | 948 | m_nRelFieldIndexNo = -1; |
1224 | | |
1225 | 948 | m_nUniqueRecordNo = 0; |
1226 | | |
1227 | | // No need to close m_poRelINDFileRef since we only got a ref. to it |
1228 | 948 | m_poRelINDFileRef = nullptr; |
1229 | | |
1230 | 948 | CPLFree(m_panMainTableFieldMap); |
1231 | 948 | m_panMainTableFieldMap = nullptr; |
1232 | 948 | CPLFree(m_panRelTableFieldMap); |
1233 | 948 | m_panRelTableFieldMap = nullptr; |
1234 | | |
1235 | | /*----------------------------------------------------------------- |
1236 | | * Note: we have to check the reference count before deleting m_poDefn |
1237 | | *----------------------------------------------------------------*/ |
1238 | 948 | if (m_poDefn) |
1239 | 391 | m_poDefn->Release(); |
1240 | 948 | m_poDefn = nullptr; |
1241 | 948 | } |
1242 | | |
1243 | | /********************************************************************** |
1244 | | * TABRelation::Init() |
1245 | | * |
1246 | | * Set the details of the relation: the main and related tables, the fields |
1247 | | * through which they will be connected, and the list of fields to select. |
1248 | | * After this call, we are ready to read data records. |
1249 | | * |
1250 | | * For write access, Init() is called with pszMain/RelFieldName and |
1251 | | * **papszSelectedFields passed as NULL. They will have to be set through |
1252 | | * other methods before a first feature can be written. |
1253 | | * |
1254 | | * A new OGRFeatureDefn is also built for the combined tables. |
1255 | | * |
1256 | | * Returns 0 on success, or -1 or error. |
1257 | | **********************************************************************/ |
1258 | | int TABRelation::Init(const char *pszViewName, TABFile *poMainTable, |
1259 | | TABFile *poRelTable, const char *pszMainFieldName, |
1260 | | const char *pszRelFieldName, char **papszSelectedFields) |
1261 | 474 | { |
1262 | 474 | if (poMainTable == nullptr || poRelTable == nullptr) |
1263 | 0 | return -1; |
1264 | | |
1265 | | // We'll need the feature Defn later... |
1266 | 474 | const OGRFeatureDefn *poMainDefn = poMainTable->GetLayerDefn(); |
1267 | 474 | const OGRFeatureDefn *poRelDefn = poRelTable->GetLayerDefn(); |
1268 | | |
1269 | | /*----------------------------------------------------------------- |
1270 | | * Keep info for later use about source tables, etc. |
1271 | | *----------------------------------------------------------------*/ |
1272 | 474 | ResetAllMembers(); |
1273 | | |
1274 | 474 | m_poMainTable = poMainTable; |
1275 | 474 | if (pszMainFieldName) |
1276 | 474 | { |
1277 | 474 | m_pszMainFieldName = CPLStrdup(pszMainFieldName); |
1278 | 474 | m_nMainFieldNo = poMainDefn->GetFieldIndex(pszMainFieldName); |
1279 | 474 | } |
1280 | | |
1281 | 474 | m_poRelTable = poRelTable; |
1282 | 474 | if (pszRelFieldName) |
1283 | 474 | { |
1284 | 474 | m_pszRelFieldName = CPLStrdup(pszRelFieldName); |
1285 | 474 | m_nRelFieldNo = poRelDefn->GetFieldIndex(pszRelFieldName); |
1286 | 474 | m_nRelFieldIndexNo = poRelTable->GetFieldIndexNumber(m_nRelFieldNo); |
1287 | 474 | m_poRelINDFileRef = poRelTable->GetINDFileRef(); |
1288 | | |
1289 | 474 | if (m_nRelFieldIndexNo >= 0 && m_poRelINDFileRef == nullptr) |
1290 | 83 | { |
1291 | 83 | CPLError(CE_Failure, CPLE_FileIO, |
1292 | 83 | "Field %s is indexed but the .IND file is missing.", |
1293 | 83 | pszRelFieldName); |
1294 | 83 | return -1; |
1295 | 83 | } |
1296 | 474 | } |
1297 | | |
1298 | | /*----------------------------------------------------------------- |
1299 | | * Init field maps. For each field in each table, a -1 means that |
1300 | | * the field is not selected, and a value >=0 is the index of the |
1301 | | * field in the view's FeatureDefn |
1302 | | *----------------------------------------------------------------*/ |
1303 | 391 | const int numFields1 = poMainDefn ? poMainDefn->GetFieldCount() : 0; |
1304 | 391 | const int numFields2 = poRelDefn ? poRelDefn->GetFieldCount() : 0; |
1305 | | |
1306 | 391 | m_panMainTableFieldMap = |
1307 | 391 | static_cast<int *>(CPLMalloc((numFields1 + 1) * sizeof(int))); |
1308 | 1.17k | for (int i = 0; i < numFields1; i++) |
1309 | 780 | m_panMainTableFieldMap[i] = -1; |
1310 | 391 | m_panRelTableFieldMap = |
1311 | 391 | static_cast<int *>(CPLMalloc((numFields2 + 1) * sizeof(int))); |
1312 | 1.03k | for (int i = 0; i < numFields2; i++) |
1313 | 646 | m_panRelTableFieldMap[i] = -1; |
1314 | | |
1315 | | /*----------------------------------------------------------------- |
1316 | | * If selectedFields = "*" then select all fields from both tables |
1317 | | *----------------------------------------------------------------*/ |
1318 | 391 | papszSelectedFields = CSLDuplicate(papszSelectedFields); |
1319 | 391 | if (papszSelectedFields != nullptr && papszSelectedFields[0] != nullptr && |
1320 | 391 | papszSelectedFields[1] == nullptr && EQUAL(papszSelectedFields[0], "*")) |
1321 | 0 | { |
1322 | 0 | CSLDestroy(papszSelectedFields); |
1323 | 0 | papszSelectedFields = nullptr; |
1324 | |
|
1325 | 0 | for (int i = 0; i < numFields1; i++) |
1326 | 0 | { |
1327 | 0 | const OGRFieldDefn *poFieldDefn = poMainDefn->GetFieldDefn(i); |
1328 | |
|
1329 | 0 | papszSelectedFields = |
1330 | 0 | CSLAddString(papszSelectedFields, poFieldDefn->GetNameRef()); |
1331 | 0 | } |
1332 | |
|
1333 | 0 | for (int i = 0; i < numFields2; i++) |
1334 | 0 | { |
1335 | 0 | const OGRFieldDefn *poFieldDefn = poRelDefn->GetFieldDefn(i); |
1336 | |
|
1337 | 0 | if (CSLFindString(papszSelectedFields, poFieldDefn->GetNameRef()) != |
1338 | 0 | -1) |
1339 | 0 | continue; // Avoid duplicate field name in view |
1340 | | |
1341 | 0 | papszSelectedFields = |
1342 | 0 | CSLAddString(papszSelectedFields, poFieldDefn->GetNameRef()); |
1343 | 0 | } |
1344 | 0 | } |
1345 | | |
1346 | | /*----------------------------------------------------------------- |
1347 | | * Create new FeatureDefn and copy selected fields definitions |
1348 | | * while updating the appropriate field maps. |
1349 | | *----------------------------------------------------------------*/ |
1350 | 391 | const OGRFieldDefn *poFieldDefn = nullptr; |
1351 | | |
1352 | 391 | m_poDefn = new OGRFeatureDefn(pszViewName); |
1353 | | // Ref count defaults to 0... set it to 1 |
1354 | 391 | m_poDefn->Reference(); |
1355 | | |
1356 | 391 | for (int i = 0; |
1357 | 1.48k | papszSelectedFields != nullptr && papszSelectedFields[i] != nullptr; |
1358 | 1.08k | i++) |
1359 | 1.08k | { |
1360 | 1.08k | int nIndex; |
1361 | 1.08k | if (poMainDefn && |
1362 | 1.08k | (nIndex = poMainDefn->GetFieldIndex(papszSelectedFields[i])) >= 0) |
1363 | 457 | { |
1364 | | /* Field from the main table |
1365 | | */ |
1366 | 457 | poFieldDefn = poMainDefn->GetFieldDefn(nIndex); |
1367 | 457 | m_poDefn->AddFieldDefn(poFieldDefn); |
1368 | 457 | m_panMainTableFieldMap[nIndex] = m_poDefn->GetFieldCount() - 1; |
1369 | 457 | } |
1370 | 632 | else if (poRelDefn && (nIndex = poRelDefn->GetFieldIndex( |
1371 | 632 | papszSelectedFields[i])) >= 0) |
1372 | 88 | { |
1373 | | /* Field from the related table |
1374 | | */ |
1375 | 88 | poFieldDefn = poRelDefn->GetFieldDefn(nIndex); |
1376 | 88 | m_poDefn->AddFieldDefn(poFieldDefn); |
1377 | 88 | m_panRelTableFieldMap[nIndex] = m_poDefn->GetFieldCount() - 1; |
1378 | 88 | } |
1379 | 544 | else |
1380 | 544 | { |
1381 | | // Hummm... field does not exist... likely an unsupported feature! |
1382 | | // At least send a warning and ignore the field. |
1383 | 544 | CPLError(CE_Warning, CPLE_IllegalArg, |
1384 | 544 | "Selected Field %s not found in source tables %s and %s", |
1385 | 544 | papszSelectedFields[i], |
1386 | 544 | poMainDefn ? poMainDefn->GetName() : "(null)", |
1387 | 544 | poRelDefn ? poRelDefn->GetName() : "(null)"); |
1388 | 544 | } |
1389 | 1.08k | } |
1390 | 391 | CSLDestroy(papszSelectedFields); |
1391 | 391 | return 0; |
1392 | 474 | } |
1393 | | |
1394 | | /********************************************************************** |
1395 | | * TABRelation::CreateRelFields() |
1396 | | * |
1397 | | * For write access, create the integer fields in each table that will |
1398 | | * link them, and setup everything to be ready to write the first feature. |
1399 | | * |
1400 | | * This function should be called just before writing the first feature. |
1401 | | * |
1402 | | * Returns 0 on success, or -1 or error. |
1403 | | **********************************************************************/ |
1404 | | int TABRelation::CreateRelFields() |
1405 | 0 | { |
1406 | | /*----------------------------------------------------------------- |
1407 | | * Create the field in each table. |
1408 | | * The default name is "MI_refnum" but if a field with the same name |
1409 | | * already exists then we'll try to generate a unique name. |
1410 | | *----------------------------------------------------------------*/ |
1411 | 0 | m_pszMainFieldName = CPLStrdup("MI_Refnum "); |
1412 | 0 | const size_t nLen = strlen(m_pszMainFieldName) + 1; |
1413 | 0 | strcpy(m_pszMainFieldName, "MI_Refnum"); |
1414 | 0 | int i = 1; |
1415 | 0 | while (m_poDefn->GetFieldIndex(m_pszMainFieldName) >= 0) |
1416 | 0 | { |
1417 | 0 | snprintf(m_pszMainFieldName, nLen, "MI_Refnum_%d", i++); |
1418 | 0 | } |
1419 | 0 | m_pszRelFieldName = CPLStrdup(m_pszMainFieldName); |
1420 | |
|
1421 | 0 | m_nMainFieldNo = m_nRelFieldNo = -1; |
1422 | 0 | if (m_poMainTable->AddFieldNative(m_pszMainFieldName, TABFInteger, 0, 0) == |
1423 | 0 | 0) |
1424 | 0 | m_nMainFieldNo = m_poMainTable->GetLayerDefn()->GetFieldCount() - 1; |
1425 | |
|
1426 | 0 | if (m_poRelTable->AddFieldNative(m_pszRelFieldName, TABFInteger, 0, 0) == 0) |
1427 | 0 | m_nRelFieldNo = m_poRelTable->GetLayerDefn()->GetFieldCount() - 1; |
1428 | |
|
1429 | 0 | if (m_nMainFieldNo == -1 || m_nRelFieldNo == -1) |
1430 | 0 | return -1; |
1431 | | |
1432 | 0 | if (m_poMainTable->SetFieldIndexed(m_nMainFieldNo) == -1) |
1433 | 0 | return -1; |
1434 | | |
1435 | 0 | if ((m_nRelFieldIndexNo = m_poRelTable->SetFieldIndexed(m_nRelFieldNo)) == |
1436 | 0 | -1) |
1437 | 0 | return -1; |
1438 | | |
1439 | 0 | m_poRelINDFileRef = m_poRelTable->GetINDFileRef(); |
1440 | | |
1441 | | /*----------------------------------------------------------------- |
1442 | | * Update field maps |
1443 | | *----------------------------------------------------------------*/ |
1444 | 0 | const OGRFeatureDefn *poMainDefn = m_poMainTable->GetLayerDefn(); |
1445 | 0 | const OGRFeatureDefn *poRelDefn = m_poRelTable->GetLayerDefn(); |
1446 | |
|
1447 | 0 | m_panMainTableFieldMap = static_cast<int *>(CPLRealloc( |
1448 | 0 | m_panMainTableFieldMap, poMainDefn->GetFieldCount() * sizeof(int))); |
1449 | 0 | m_panMainTableFieldMap[poMainDefn->GetFieldCount() - 1] = -1; |
1450 | |
|
1451 | 0 | m_panRelTableFieldMap = static_cast<int *>(CPLRealloc( |
1452 | 0 | m_panRelTableFieldMap, poRelDefn->GetFieldCount() * sizeof(int))); |
1453 | 0 | m_panRelTableFieldMap[poRelDefn->GetFieldCount() - 1] = -1; |
1454 | | |
1455 | | /*----------------------------------------------------------------- |
1456 | | * Make sure the first unique field (in poRelTable) is indexed since |
1457 | | * it is the one against which we will try to match records. |
1458 | | *----------------------------------------------------------------*/ |
1459 | 0 | if (m_poRelTable->SetFieldIndexed(0) == -1) |
1460 | 0 | return -1; |
1461 | | |
1462 | 0 | return 0; |
1463 | 0 | } |
1464 | | |
1465 | | /********************************************************************** |
1466 | | * TABRelation::GetFeature() |
1467 | | * |
1468 | | * Fill and return a TABFeature object for the specified feature id. |
1469 | | * |
1470 | | * The returned pointer is a new TABFeature that will have to be freed |
1471 | | * by the caller. |
1472 | | * |
1473 | | * Returns NULL if the specified feature id does not exist of if an |
1474 | | * error happened. In any case, CPLError() will have been called to |
1475 | | * report the reason of the failure. |
1476 | | * |
1477 | | * __TODO__ The current implementation fetches the features from each table |
1478 | | * and creates a 3rd feature to merge them. There would be room for |
1479 | | * optimization, at least by avoiding the duplication of the geometry |
1480 | | * which can be big sometimes... but this would imply changes at the |
1481 | | * lower-level in the lib. and we won't go there yet. |
1482 | | **********************************************************************/ |
1483 | | TABFeature *TABRelation::GetFeature(int nFeatureId) |
1484 | 10.4k | { |
1485 | | /*----------------------------------------------------------------- |
1486 | | * Make sure init() has been called |
1487 | | *----------------------------------------------------------------*/ |
1488 | 10.4k | if (m_poMainTable == nullptr || m_poRelTable == nullptr) |
1489 | 0 | { |
1490 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
1491 | 0 | "GetFeatureRef() failed: object not initialized yet!"); |
1492 | 0 | return nullptr; |
1493 | 0 | } |
1494 | | |
1495 | | /*----------------------------------------------------------------- |
1496 | | * Read main feature and create a new one of the right type |
1497 | | *----------------------------------------------------------------*/ |
1498 | 10.4k | TABFeature *poMainFeature = m_poMainTable->GetFeatureRef(nFeatureId); |
1499 | 10.4k | if (poMainFeature == nullptr) |
1500 | 0 | { |
1501 | | // Feature cannot be read from main table... |
1502 | | // an error has already been reported. |
1503 | 0 | return nullptr; |
1504 | 0 | } |
1505 | | |
1506 | 10.4k | auto poCurFeature = |
1507 | 10.4k | std::unique_ptr<TABFeature>(poMainFeature->CloneTABFeature(m_poDefn)); |
1508 | | |
1509 | | /*----------------------------------------------------------------- |
1510 | | * Keep track of FID and copy the geometry |
1511 | | *----------------------------------------------------------------*/ |
1512 | 10.4k | poCurFeature->SetFID(nFeatureId); |
1513 | | |
1514 | 10.4k | if (poCurFeature->GetFeatureClass() != TABFCNoGeomFeature) |
1515 | 1 | { |
1516 | 1 | OGRGeometry *poGeom = poMainFeature->GetGeometryRef(); |
1517 | 1 | poCurFeature->SetGeometry(poGeom); |
1518 | 1 | } |
1519 | | |
1520 | | /*----------------------------------------------------------------- |
1521 | | * Fetch feature from related table |
1522 | | * |
1523 | | * __TODO__ Right now we support only many-to-1 relationships, but |
1524 | | * it might be possible to have several related entries |
1525 | | * for a single key, and in this case we should return |
1526 | | * one new feature for each of them. |
1527 | | *----------------------------------------------------------------*/ |
1528 | 10.4k | TABFeature *poRelFeature = nullptr; |
1529 | 10.4k | if (m_poRelINDFileRef) |
1530 | 9.43k | { |
1531 | 9.43k | GByte *pKey = |
1532 | 9.43k | BuildFieldKey(poMainFeature, m_nMainFieldNo, |
1533 | 9.43k | m_poMainTable->GetNativeFieldType(m_nMainFieldNo), |
1534 | 9.43k | m_nRelFieldIndexNo); |
1535 | 9.43k | if (!pKey) |
1536 | 6 | return nullptr; |
1537 | | |
1538 | 9.43k | int nRelFeatureId = |
1539 | 9.43k | m_poRelINDFileRef->FindFirst(m_nRelFieldIndexNo, pKey); |
1540 | | |
1541 | 9.43k | if (nRelFeatureId > 0) |
1542 | 839 | poRelFeature = m_poRelTable->GetFeatureRef(nRelFeatureId); |
1543 | 9.43k | } |
1544 | | |
1545 | | /*----------------------------------------------------------------- |
1546 | | * Copy fields from poMainFeature |
1547 | | *----------------------------------------------------------------*/ |
1548 | 31.2k | for (int i = 0; i < poMainFeature->GetFieldCount(); i++) |
1549 | 20.8k | { |
1550 | 20.8k | if (m_panMainTableFieldMap[i] != -1) |
1551 | 2.20k | { |
1552 | 2.20k | poCurFeature->SetField(m_panMainTableFieldMap[i], |
1553 | 2.20k | poMainFeature->GetRawFieldRef(i)); |
1554 | 2.20k | } |
1555 | 20.8k | } |
1556 | | |
1557 | | /*----------------------------------------------------------------- |
1558 | | * Copy fields from poRelFeature... |
1559 | | * |
1560 | | * NOTE: For now, if no corresponding feature is found in RelTable |
1561 | | * then we will just leave the corresponding fields unset. |
1562 | | *----------------------------------------------------------------*/ |
1563 | 10.4k | for (int i = 0; poRelFeature && i < poRelFeature->GetFieldCount(); i++) |
1564 | 4 | { |
1565 | 4 | if (m_panRelTableFieldMap[i] != -1) |
1566 | 2 | { |
1567 | 2 | poCurFeature->SetField(m_panRelTableFieldMap[i], |
1568 | 2 | poRelFeature->GetRawFieldRef(i)); |
1569 | 2 | } |
1570 | 4 | } |
1571 | | |
1572 | 10.4k | return poCurFeature.release(); |
1573 | 10.4k | } |
1574 | | |
1575 | | /********************************************************************** |
1576 | | * TABRelation::BuildFieldKey() |
1577 | | * |
1578 | | * Return the index key for the specified field in poFeature. |
1579 | | * Simply maps the call to the proper method in the TABINDFile class. |
1580 | | * |
1581 | | * Returns a reference to a TABINDFile internal buffer that should not |
1582 | | * be freed by the caller. |
1583 | | **********************************************************************/ |
1584 | | GByte *TABRelation::BuildFieldKey(TABFeature *poFeature, int nFieldNo, |
1585 | | TABFieldType eType, int nIndexNo) |
1586 | 9.43k | { |
1587 | 9.43k | GByte *pKey = nullptr; |
1588 | | |
1589 | 9.43k | switch (eType) |
1590 | 9.43k | { |
1591 | 0 | case TABFChar: |
1592 | 0 | pKey = m_poRelINDFileRef->BuildKey( |
1593 | 0 | nIndexNo, poFeature->GetFieldAsString(nFieldNo)); |
1594 | 0 | break; |
1595 | | |
1596 | 0 | case TABFDecimal: |
1597 | 0 | case TABFFloat: |
1598 | 0 | pKey = m_poRelINDFileRef->BuildKey( |
1599 | 0 | nIndexNo, poFeature->GetFieldAsDouble(nFieldNo)); |
1600 | 0 | break; |
1601 | | |
1602 | | // __TODO__ DateTime fields are 8 bytes long, not supported yet by |
1603 | | // the indexing code (see bug #1844). |
1604 | 0 | case TABFDateTime: |
1605 | 0 | CPLError( |
1606 | 0 | CE_Failure, CPLE_NotSupported, |
1607 | 0 | "TABRelation on field of type DateTime not supported yet."); |
1608 | 0 | break; |
1609 | | |
1610 | 24 | case TABFInteger: |
1611 | 24 | case TABFSmallInt: |
1612 | 24 | case TABFDate: |
1613 | 24 | case TABFTime: |
1614 | 24 | case TABFLogical: |
1615 | 9.43k | default: |
1616 | 9.43k | pKey = m_poRelINDFileRef->BuildKey( |
1617 | 9.43k | nIndexNo, poFeature->GetFieldAsInteger(nFieldNo)); |
1618 | 9.43k | break; |
1619 | 9.43k | } |
1620 | | |
1621 | 9.43k | return pKey; |
1622 | 9.43k | } |
1623 | | |
1624 | | /********************************************************************** |
1625 | | * TABRelation::GetNativeFieldType() |
1626 | | * |
1627 | | * Returns the native MapInfo field type for the specified field. |
1628 | | * |
1629 | | * Returns TABFUnknown if file is not opened, or if specified field index is |
1630 | | * invalid. |
1631 | | * |
1632 | | * Note that field ids are positive and start at 0. |
1633 | | **********************************************************************/ |
1634 | | TABFieldType TABRelation::GetNativeFieldType(int nFieldId) |
1635 | 0 | { |
1636 | 0 | int i, numFields; |
1637 | |
|
1638 | 0 | if (m_poMainTable == nullptr || m_poRelTable == nullptr || |
1639 | 0 | m_panMainTableFieldMap == nullptr || m_panRelTableFieldMap == nullptr) |
1640 | 0 | return TABFUnknown; |
1641 | | |
1642 | | /*----------------------------------------------------------------- |
1643 | | * Look for nFieldId in the field maps and call the corresponding |
1644 | | * TAB file's GetNativeFieldType() |
1645 | | *----------------------------------------------------------------*/ |
1646 | 0 | numFields = m_poMainTable->GetLayerDefn()->GetFieldCount(); |
1647 | 0 | for (i = 0; i < numFields; i++) |
1648 | 0 | { |
1649 | 0 | if (m_panMainTableFieldMap[i] == nFieldId) |
1650 | 0 | { |
1651 | 0 | return m_poMainTable->GetNativeFieldType(i); |
1652 | 0 | } |
1653 | 0 | } |
1654 | | |
1655 | 0 | numFields = m_poRelTable->GetLayerDefn()->GetFieldCount(); |
1656 | 0 | for (i = 0; i < numFields; i++) |
1657 | 0 | { |
1658 | 0 | if (m_panRelTableFieldMap[i] == nFieldId) |
1659 | 0 | { |
1660 | 0 | return m_poRelTable->GetNativeFieldType(i); |
1661 | 0 | } |
1662 | 0 | } |
1663 | | |
1664 | 0 | return TABFUnknown; |
1665 | 0 | } |
1666 | | |
1667 | | /********************************************************************** |
1668 | | * TABRelation::AddFieldNative() |
1669 | | * |
1670 | | * Create a new field using a native mapinfo data type... this is an |
1671 | | * alternative to defining fields through the OGR interface. |
1672 | | * This function should be called after creating a new dataset, but before |
1673 | | * writing the first feature. |
1674 | | * |
1675 | | * This function will build/update the OGRFeatureDefn that will have to be |
1676 | | * used when writing features to this dataset. |
1677 | | * |
1678 | | * A reference to the OGRFeatureDefn can be obtained using GetLayerDefn(). |
1679 | | * |
1680 | | * Returns 0 on success, -1 on error. |
1681 | | **********************************************************************/ |
1682 | | int TABRelation::AddFieldNative(const char *pszName, TABFieldType eMapInfoType, |
1683 | | int nWidth /*=0*/, int nPrecision /*=0*/, |
1684 | | GBool bIndexed /*=FALSE*/, |
1685 | | GBool bUnique /*=FALSE*/, int bApproxOK) |
1686 | 0 | { |
1687 | 0 | if (m_poMainTable == nullptr || m_poRelTable == nullptr || |
1688 | 0 | m_panMainTableFieldMap == nullptr || m_panRelTableFieldMap == nullptr) |
1689 | 0 | return -1; |
1690 | | |
1691 | 0 | if (!bUnique) |
1692 | 0 | { |
1693 | | /*------------------------------------------------------------- |
1694 | | * Add field to poMainTable and to m_poDefn |
1695 | | *------------------------------------------------------------*/ |
1696 | 0 | if (m_poMainTable->AddFieldNative(pszName, eMapInfoType, nWidth, |
1697 | 0 | nPrecision, bIndexed, bUnique, |
1698 | 0 | bApproxOK) != 0) |
1699 | 0 | return -1; |
1700 | | |
1701 | 0 | const OGRFeatureDefn *poMainDefn = m_poMainTable->GetLayerDefn(); |
1702 | |
|
1703 | 0 | m_panMainTableFieldMap = static_cast<int *>(CPLRealloc( |
1704 | 0 | m_panMainTableFieldMap, poMainDefn->GetFieldCount() * sizeof(int))); |
1705 | |
|
1706 | 0 | m_poDefn->AddFieldDefn( |
1707 | 0 | poMainDefn->GetFieldDefn(poMainDefn->GetFieldCount() - 1)); |
1708 | |
|
1709 | 0 | m_panMainTableFieldMap[poMainDefn->GetFieldCount() - 1] = |
1710 | 0 | m_poDefn->GetFieldCount() - 1; |
1711 | 0 | } |
1712 | 0 | else |
1713 | 0 | { |
1714 | | /*------------------------------------------------------------- |
1715 | | * Add field to poRelTable and to m_poDefn |
1716 | | *------------------------------------------------------------*/ |
1717 | 0 | if (m_poRelTable->AddFieldNative(pszName, eMapInfoType, nWidth, |
1718 | 0 | nPrecision, bIndexed, bUnique, |
1719 | 0 | bApproxOK) != 0) |
1720 | 0 | return -1; |
1721 | | |
1722 | 0 | const OGRFeatureDefn *poRelDefn = m_poRelTable->GetLayerDefn(); |
1723 | |
|
1724 | 0 | m_panRelTableFieldMap = static_cast<int *>(CPLRealloc( |
1725 | 0 | m_panRelTableFieldMap, poRelDefn->GetFieldCount() * sizeof(int))); |
1726 | |
|
1727 | 0 | m_poDefn->AddFieldDefn( |
1728 | 0 | poRelDefn->GetFieldDefn(poRelDefn->GetFieldCount() - 1)); |
1729 | |
|
1730 | 0 | m_panRelTableFieldMap[poRelDefn->GetFieldCount() - 1] = |
1731 | 0 | m_poDefn->GetFieldCount() - 1; |
1732 | | |
1733 | | // The first field in this table must be indexed. |
1734 | 0 | if (poRelDefn->GetFieldCount() == 1) |
1735 | 0 | m_poRelTable->SetFieldIndexed(0); |
1736 | 0 | } |
1737 | | |
1738 | 0 | return 0; |
1739 | 0 | } |
1740 | | |
1741 | | /********************************************************************** |
1742 | | * TABRelation::IsFieldIndexed() |
1743 | | * |
1744 | | * Returns TRUE is specified field is indexed. |
1745 | | * |
1746 | | * Note that field ids are positive and start at 0. |
1747 | | **********************************************************************/ |
1748 | | GBool TABRelation::IsFieldIndexed(int nFieldId) |
1749 | 0 | { |
1750 | 0 | int i, numFields; |
1751 | |
|
1752 | 0 | if (m_poMainTable == nullptr || m_poRelTable == nullptr || |
1753 | 0 | m_panMainTableFieldMap == nullptr || m_panRelTableFieldMap == nullptr) |
1754 | 0 | return FALSE; |
1755 | | |
1756 | | /*----------------------------------------------------------------- |
1757 | | * Look for nFieldId in the field maps and call the corresponding |
1758 | | * TAB file's GetNativeFieldType() |
1759 | | *----------------------------------------------------------------*/ |
1760 | 0 | numFields = m_poMainTable->GetLayerDefn()->GetFieldCount(); |
1761 | 0 | for (i = 0; i < numFields; i++) |
1762 | 0 | { |
1763 | 0 | if (m_panMainTableFieldMap[i] == nFieldId) |
1764 | 0 | { |
1765 | 0 | return m_poMainTable->IsFieldIndexed(i); |
1766 | 0 | } |
1767 | 0 | } |
1768 | | |
1769 | 0 | numFields = m_poRelTable->GetLayerDefn()->GetFieldCount(); |
1770 | 0 | for (i = 0; i < numFields; i++) |
1771 | 0 | { |
1772 | 0 | if (m_panRelTableFieldMap[i] == nFieldId) |
1773 | 0 | { |
1774 | 0 | return m_poRelTable->IsFieldIndexed(i); |
1775 | 0 | } |
1776 | 0 | } |
1777 | | |
1778 | 0 | return FALSE; |
1779 | 0 | } |
1780 | | |
1781 | | /********************************************************************** |
1782 | | * TABRelation::SetFieldIndexed() |
1783 | | * |
1784 | | * Request that the specified field be indexed. This will create the .IND |
1785 | | * file, etc. |
1786 | | * |
1787 | | * Note that field ids are positive and start at 0. |
1788 | | * |
1789 | | * Returns 0 on success, -1 on error. |
1790 | | **********************************************************************/ |
1791 | | int TABRelation::SetFieldIndexed(int nFieldId) |
1792 | 0 | { |
1793 | 0 | int i, numFields; |
1794 | |
|
1795 | 0 | if (m_poMainTable == nullptr || m_poRelTable == nullptr || |
1796 | 0 | m_panMainTableFieldMap == nullptr || m_panRelTableFieldMap == nullptr) |
1797 | 0 | return -1; |
1798 | | |
1799 | | /*----------------------------------------------------------------- |
1800 | | * Look for nFieldId in the field maps and call the corresponding |
1801 | | * TAB file's GetNativeFieldType() |
1802 | | *----------------------------------------------------------------*/ |
1803 | 0 | numFields = m_poMainTable->GetLayerDefn()->GetFieldCount(); |
1804 | 0 | for (i = 0; i < numFields; i++) |
1805 | 0 | { |
1806 | 0 | if (m_panMainTableFieldMap[i] == nFieldId) |
1807 | 0 | { |
1808 | 0 | return m_poMainTable->SetFieldIndexed(i); |
1809 | 0 | } |
1810 | 0 | } |
1811 | | |
1812 | 0 | numFields = m_poRelTable->GetLayerDefn()->GetFieldCount(); |
1813 | 0 | for (i = 0; i < numFields; i++) |
1814 | 0 | { |
1815 | 0 | if (m_panRelTableFieldMap[i] == nFieldId) |
1816 | 0 | { |
1817 | 0 | return m_poRelTable->SetFieldIndexed(i); |
1818 | 0 | } |
1819 | 0 | } |
1820 | | |
1821 | 0 | return -1; |
1822 | 0 | } |
1823 | | |
1824 | | /********************************************************************** |
1825 | | * TABRelation::IsFieldUnique() |
1826 | | * |
1827 | | * Returns TRUE is specified field is part of the unique table (poRelTable). |
1828 | | * |
1829 | | * Note that field ids are positive and start at 0. |
1830 | | **********************************************************************/ |
1831 | | GBool TABRelation::IsFieldUnique(int nFieldId) |
1832 | 0 | { |
1833 | 0 | int i, numFields; |
1834 | |
|
1835 | 0 | if (m_poMainTable == nullptr || m_poRelTable == nullptr || |
1836 | 0 | m_panMainTableFieldMap == nullptr || m_panRelTableFieldMap == nullptr) |
1837 | 0 | return FALSE; |
1838 | | |
1839 | | /*----------------------------------------------------------------- |
1840 | | * Look for nFieldId in the poRelTable field map |
1841 | | *----------------------------------------------------------------*/ |
1842 | 0 | numFields = m_poRelTable->GetLayerDefn()->GetFieldCount(); |
1843 | 0 | for (i = 0; i < numFields; i++) |
1844 | 0 | { |
1845 | 0 | if (m_panRelTableFieldMap[i] == nFieldId) |
1846 | 0 | { |
1847 | 0 | return TRUE; // If it is here then it is unique! |
1848 | 0 | } |
1849 | 0 | } |
1850 | | |
1851 | 0 | return FALSE; |
1852 | 0 | } |
1853 | | |
1854 | | /********************************************************************** |
1855 | | * TABRelation::WriteFeature() |
1856 | | * |
1857 | | * Write a feature to this dataset. |
1858 | | * |
1859 | | * For now only sequential writes are supported (i.e. with nFeatureId=-1) |
1860 | | * but eventually we should be able to do random access by specifying |
1861 | | * a value through nFeatureId. |
1862 | | * |
1863 | | * Returns the new featureId (> 0) on success, or -1 if an |
1864 | | * error happened in which case, CPLError() will have been called to |
1865 | | * report the reason of the failure. |
1866 | | **********************************************************************/ |
1867 | | int TABRelation::WriteFeature(TABFeature *poFeature, int nFeatureId /*=-1*/) |
1868 | 0 | { |
1869 | 0 | TABFeature *poMainFeature = nullptr; |
1870 | |
|
1871 | 0 | if (nFeatureId != -1) |
1872 | 0 | { |
1873 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
1874 | 0 | "WriteFeature(): random access not implemented yet."); |
1875 | 0 | return -1; |
1876 | 0 | } |
1877 | | |
1878 | 0 | CPLAssert(m_poMainTable && m_poRelTable); |
1879 | | |
1880 | | // We'll need the feature Defn later... |
1881 | 0 | const OGRFeatureDefn *poMainDefn = m_poMainTable->GetLayerDefn(); |
1882 | 0 | const OGRFeatureDefn *poRelDefn = m_poRelTable->GetLayerDefn(); |
1883 | | |
1884 | | /*----------------------------------------------------------------- |
1885 | | * Create one feature for each table |
1886 | | * Copy the geometry only to the feature from the main table |
1887 | | *----------------------------------------------------------------*/ |
1888 | 0 | poMainFeature = poFeature->CloneTABFeature(poMainDefn); |
1889 | |
|
1890 | 0 | if (poFeature->GetFeatureClass() != TABFCNoGeomFeature) |
1891 | 0 | { |
1892 | 0 | OGRGeometry *poGeom = poFeature->GetGeometryRef(); |
1893 | 0 | poMainFeature->SetGeometry(poGeom); |
1894 | 0 | } |
1895 | | |
1896 | | /*----------------------------------------------------------------- |
1897 | | * Copy fields to poMainFeature |
1898 | | *----------------------------------------------------------------*/ |
1899 | 0 | for (int i = 0; i < poMainDefn->GetFieldCount(); i++) |
1900 | 0 | { |
1901 | 0 | if (m_panMainTableFieldMap[i] != -1) |
1902 | 0 | { |
1903 | 0 | poMainFeature->SetField( |
1904 | 0 | i, poFeature->GetRawFieldRef(m_panMainTableFieldMap[i])); |
1905 | 0 | } |
1906 | 0 | } |
1907 | | |
1908 | | /*----------------------------------------------------------------- |
1909 | | * Look for a record id for the unique fields, and write a new |
1910 | | * record if necessary |
1911 | | *----------------------------------------------------------------*/ |
1912 | 0 | int nRecordNo = 0; |
1913 | 0 | int nUniqueIndexNo = -1; |
1914 | 0 | if (m_panMainTableFieldMap[0] != -1) |
1915 | 0 | nUniqueIndexNo = m_poRelTable->GetFieldIndexNumber(0); |
1916 | |
|
1917 | 0 | if (nUniqueIndexNo > 0) |
1918 | 0 | { |
1919 | 0 | GByte *pKey = BuildFieldKey( |
1920 | 0 | poFeature, 0, m_poRelTable->GetNativeFieldType(0), nUniqueIndexNo); |
1921 | |
|
1922 | 0 | if (!pKey || (nRecordNo = m_poRelINDFileRef->FindFirst(nUniqueIndexNo, |
1923 | 0 | pKey)) == -1) |
1924 | 0 | return -1; |
1925 | | |
1926 | 0 | if (nRecordNo == 0) |
1927 | 0 | { |
1928 | | /*--------------------------------------------------------- |
1929 | | * No record in poRelTable yet for this unique value... |
1930 | | * add one now... |
1931 | | *--------------------------------------------------------*/ |
1932 | 0 | TABFeature *poRelFeature = new TABFeature(poRelDefn); |
1933 | |
|
1934 | 0 | for (int i = 0; i < poRelDefn->GetFieldCount(); i++) |
1935 | 0 | { |
1936 | 0 | if (m_panRelTableFieldMap[i] != -1) |
1937 | 0 | { |
1938 | 0 | poRelFeature->SetField( |
1939 | 0 | i, poFeature->GetRawFieldRef(m_panRelTableFieldMap[i])); |
1940 | 0 | } |
1941 | 0 | } |
1942 | |
|
1943 | 0 | nRecordNo = ++m_nUniqueRecordNo; |
1944 | |
|
1945 | 0 | poRelFeature->SetField(m_nRelFieldNo, nRecordNo); |
1946 | |
|
1947 | 0 | if (m_poRelTable->CreateFeature(poRelFeature) == OGRERR_NONE) |
1948 | 0 | return -1; |
1949 | | |
1950 | 0 | delete poRelFeature; |
1951 | 0 | } |
1952 | 0 | } |
1953 | | |
1954 | | /*----------------------------------------------------------------- |
1955 | | * Write poMainFeature to the main table |
1956 | | *----------------------------------------------------------------*/ |
1957 | 0 | poMainFeature->SetField(m_nMainFieldNo, nRecordNo); |
1958 | |
|
1959 | 0 | if (m_poMainTable->CreateFeature(poMainFeature) != OGRERR_NONE) |
1960 | 0 | nFeatureId = static_cast<int>(poMainFeature->GetFID()); |
1961 | 0 | else |
1962 | 0 | nFeatureId = -1; |
1963 | |
|
1964 | 0 | delete poMainFeature; |
1965 | |
|
1966 | 0 | return nFeatureId; |
1967 | 0 | } |
1968 | | |
1969 | | /********************************************************************** |
1970 | | * TABFile::SetFeatureDefn() |
1971 | | * |
1972 | | * NOT FULLY IMPLEMENTED YET... |
1973 | | * |
1974 | | * Returns 0 on success, -1 on error. |
1975 | | **********************************************************************/ |
1976 | | int TABRelation::SetFeatureDefn( |
1977 | | OGRFeatureDefn *poFeatureDefn, |
1978 | | CPL_UNUSED TABFieldType *paeMapInfoNativeFieldTypes /* =NULL */) |
1979 | 0 | { |
1980 | 0 | if (m_poDefn && m_poDefn->GetFieldCount() > 0) |
1981 | 0 | { |
1982 | 0 | CPLAssert(m_poDefn == nullptr); |
1983 | 0 | return -1; |
1984 | 0 | } |
1985 | | |
1986 | | /*----------------------------------------------------------------- |
1987 | | * Keep a reference to the OGRFeatureDefn... we'll have to take the |
1988 | | * reference count into account when we are done with it. |
1989 | | *----------------------------------------------------------------*/ |
1990 | 0 | if (m_poDefn) |
1991 | 0 | m_poDefn->Release(); |
1992 | 0 | m_poDefn = poFeatureDefn; |
1993 | 0 | m_poDefn->Reference(); |
1994 | |
|
1995 | 0 | return 0; |
1996 | 0 | } |