Line data Source code
1 : /**********************************************************************
2 : *
3 : * Name: mitab_utils.cpp
4 : * Project: MapInfo TAB Read/Write library
5 : * Language: C++
6 : * Purpose: Misc. util. functions for the library
7 : * Author: Daniel Morissette, dmorissette@dmsolutions.ca
8 : *
9 : **********************************************************************
10 : * Copyright (c) 1999-2001, Daniel Morissette
11 : *
12 : * Permission is hereby granted, free of charge, to any person obtaining a
13 : * copy of this software and associated documentation files (the "Software"),
14 : * to deal in the Software without restriction, including without limitation
15 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
16 : * and/or sell copies of the Software, and to permit persons to whom the
17 : * Software is furnished to do so, subject to the following conditions:
18 : *
19 : * The above copyright notice and this permission notice shall be included
20 : * in all copies or substantial portions of the Software.
21 : *
22 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 : * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
25 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
27 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28 : * DEALINGS IN THE SOFTWARE.
29 : **********************************************************************/
30 :
31 : #include "cpl_port.h"
32 : #include "mitab_utils.h"
33 :
34 : #include <cctype>
35 : #include <climits>
36 : #include <cmath>
37 : #include <cstring>
38 : #include <limits>
39 :
40 : #include "mitab.h"
41 : #include "cpl_conv.h"
42 : #include "cpl_error.h"
43 : #include "cpl_string.h"
44 : #include "cpl_vsi.h"
45 :
46 : /**********************************************************************
47 : * TABGenerateArc()
48 : *
49 : * Generate the coordinates for an arc and ADD the coordinates to the
50 : * geometry object. If the geometry already contains some points then
51 : * these won't be lost.
52 : *
53 : * poLine can be a OGRLineString or one of its derived classes, such as
54 : * OGRLinearRing
55 : * numPoints is the number of points to generate.
56 : * Angles are specified in radians, valid values are in the range [0..2*PI]
57 : *
58 : * Arcs are always generated counterclockwise, even if StartAngle > EndAngle
59 : *
60 : * Returns 0 on success, -1 on error.
61 : **********************************************************************/
62 2793 : int TABGenerateArc(OGRLineString *poLine, int numPoints, double dCenterX,
63 : double dCenterY, double dXRadius, double dYRadius,
64 : double dStartAngle, double dEndAngle)
65 : {
66 : // Adjust angles to go counterclockwise
67 2793 : if (dEndAngle < dStartAngle)
68 0 : dEndAngle += 2.0 * M_PI;
69 :
70 2793 : const double dAngleStep = (dEndAngle - dStartAngle) / (numPoints - 1.0);
71 :
72 2793 : double dAngle = 0.0;
73 268981 : for (int i = 0; i < numPoints; i++)
74 : {
75 266188 : dAngle = dStartAngle + i * dAngleStep;
76 266188 : const double dX = dCenterX + dXRadius * cos(dAngle);
77 266188 : const double dY = dCenterY + dYRadius * sin(dAngle);
78 266188 : poLine->addPoint(dX, dY);
79 : }
80 :
81 : // Complete the arc with the last EndAngle, to make sure that
82 : // the arc is correctly closed.
83 2793 : const double dX = dCenterX + dXRadius * cos(dAngle);
84 2793 : const double dY = dCenterY + dYRadius * sin(dAngle);
85 2793 : poLine->addPoint(dX, dY);
86 :
87 2793 : return 0;
88 : }
89 :
90 : /**********************************************************************
91 : * TABCloseRing()
92 : *
93 : * Check if a ring is closed, and add a point to close it if necessary.
94 : *
95 : * Returns 0 on success, -1 on error.
96 : **********************************************************************/
97 829 : int TABCloseRing(OGRLineString *poRing)
98 : {
99 829 : if (poRing->getNumPoints() > 0 && !poRing->get_IsClosed())
100 : {
101 829 : poRing->addPoint(poRing->getX(0), poRing->getY(0));
102 : }
103 :
104 829 : return 0;
105 : }
106 :
107 : /**********************************************************************
108 : * TABAdjustCaseSensitiveFilename()
109 : *
110 : * Scan a filename and its path, adjust uppercase/lowercases if
111 : * necessary.
112 : *
113 : * Returns TRUE if file found, or FALSE if it could not be located with
114 : * a case-insensitive search.
115 : *
116 : * This function works on the original buffer and returns a reference to it.
117 : * It does nothing on Windows systems where filenames are not case sensitive.
118 : **********************************************************************/
119 : #ifdef _WIN32
120 : static bool TABAdjustCaseSensitiveFilename(char * /* pszFname */)
121 : {
122 : // Nothing to do on Windows.
123 : return true;
124 : }
125 : #else
126 : // Unix case.
127 658 : static bool TABAdjustCaseSensitiveFilename(char *pszFname)
128 : {
129 : VSIStatBufL sStatBuf;
130 :
131 : // First check if the filename is OK as is.
132 658 : if (VSIStatL(pszFname, &sStatBuf) == 0)
133 : {
134 0 : return true;
135 : }
136 :
137 : // File either does not exist or has the wrong cases.
138 : // Go backwards until we find a portion of the path that is valid.
139 658 : char *pszTmpPath = CPLStrdup(pszFname);
140 658 : const int nTotalLen = static_cast<int>(strlen(pszTmpPath));
141 658 : int iTmpPtr = nTotalLen;
142 658 : bool bValidPath = false;
143 :
144 1316 : while (iTmpPtr > 0 && !bValidPath)
145 : {
146 : // Move back to the previous '/' separator.
147 658 : pszTmpPath[--iTmpPtr] = '\0';
148 8874 : while (iTmpPtr > 0 && pszTmpPath[iTmpPtr - 1] != '/')
149 : {
150 8216 : pszTmpPath[--iTmpPtr] = '\0';
151 : }
152 :
153 658 : if (iTmpPtr > 0 && VSIStatL(pszTmpPath, &sStatBuf) == 0)
154 641 : bValidPath = true;
155 : }
156 :
157 658 : CPLAssert(iTmpPtr >= 0);
158 :
159 : // Assume that CWD is valid. Therefore an empty path is a valid.
160 658 : if (iTmpPtr == 0)
161 17 : bValidPath = true;
162 :
163 : // Now that we have a valid base, reconstruct the whole path
164 : // by scanning all the sub-directories.
165 : // If we get to a point where a path component does not exist then
166 : // we simply return the rest of the path as is.
167 1316 : while (bValidPath && static_cast<int>(strlen(pszTmpPath)) < nTotalLen)
168 : {
169 658 : int iLastPartStart = iTmpPtr;
170 658 : char **papszDir = VSIReadDir(pszTmpPath);
171 :
172 : // Add one component to the current path.
173 658 : pszTmpPath[iTmpPtr] = pszFname[iTmpPtr];
174 658 : iTmpPtr++;
175 8874 : for (; pszFname[iTmpPtr] != '\0' && pszFname[iTmpPtr] != '/'; iTmpPtr++)
176 : {
177 8216 : pszTmpPath[iTmpPtr] = pszFname[iTmpPtr];
178 : }
179 :
180 658 : while (iLastPartStart < iTmpPtr && pszTmpPath[iLastPartStart] == '/')
181 0 : iLastPartStart++;
182 :
183 : // And do a case insensitive search in the current dir.
184 4120 : for (int iEntry = 0; papszDir && papszDir[iEntry]; iEntry++)
185 : {
186 3462 : if (EQUAL(pszTmpPath + iLastPartStart, papszDir[iEntry]))
187 : {
188 : // Fount it.
189 0 : strcpy(pszTmpPath + iLastPartStart, papszDir[iEntry]);
190 0 : break;
191 : }
192 : }
193 :
194 658 : if (iTmpPtr > 0 && VSIStatL(pszTmpPath, &sStatBuf) != 0)
195 658 : bValidPath = false;
196 :
197 658 : CSLDestroy(papszDir);
198 : }
199 :
200 : // We reached the last valid path component... just copy the rest
201 : // of the path as is.
202 658 : if (iTmpPtr < nTotalLen - 1)
203 : {
204 0 : strncpy(pszTmpPath + iTmpPtr, pszFname + iTmpPtr, nTotalLen - iTmpPtr);
205 : }
206 :
207 : // Update the source buffer and return.
208 658 : strcpy(pszFname, pszTmpPath);
209 658 : CPLFree(pszTmpPath);
210 :
211 658 : return bValidPath;
212 : }
213 : #endif // Not win32.
214 :
215 : /**********************************************************************
216 : * TABAdjustFilenameExtension()
217 : *
218 : * Because Unix filenames are case sensitive and MapInfo datasets often have
219 : * mixed cases filenames, we use this function to find the right filename
220 : * to use to open a specific file.
221 : *
222 : * This function works directly on the source string, so the filename it
223 : * contains at the end of the call is the one that should be used.
224 : *
225 : * Returns TRUE if one of the extensions worked, and FALSE otherwise.
226 : * If none of the extensions worked then the original extension will NOT be
227 : * restored.
228 : **********************************************************************/
229 8142 : GBool TABAdjustFilenameExtension(char *pszFname)
230 : {
231 : VSIStatBufL sStatBuf;
232 :
233 : // First try using filename as provided
234 8142 : if (VSIStatL(pszFname, &sStatBuf) == 0)
235 : {
236 7484 : return TRUE;
237 : }
238 :
239 : // Try using uppercase extension (we assume that fname contains a '.')
240 658 : for (int i = static_cast<int>(strlen(pszFname)) - 1;
241 2516 : i >= 0 && pszFname[i] != '.'; i--)
242 : {
243 1858 : pszFname[i] = static_cast<char>(
244 1858 : CPLToupper(static_cast<unsigned char>(pszFname[i])));
245 : }
246 :
247 658 : if (VSIStatL(pszFname, &sStatBuf) == 0)
248 : {
249 0 : return TRUE;
250 : }
251 :
252 : // Try using lowercase extension.
253 658 : for (int i = static_cast<int>(strlen(pszFname)) - 1;
254 2516 : i >= 0 && pszFname[i] != '.'; i--)
255 : {
256 1858 : pszFname[i] = static_cast<char>(
257 1858 : CPLTolower(static_cast<unsigned char>(pszFname[i])));
258 : }
259 :
260 658 : if (VSIStatL(pszFname, &sStatBuf) == 0)
261 : {
262 0 : return TRUE;
263 : }
264 :
265 : // None of the extensions worked.
266 : // Try adjusting cases in the whole path and filename.
267 658 : return TABAdjustCaseSensitiveFilename(pszFname);
268 : }
269 :
270 : /**********************************************************************
271 : * TABGetBasename()
272 : *
273 : * Extract the basename part of a complete file path.
274 : *
275 : * Returns a newly allocated string without the leading path (dirs) and
276 : * the extension. The returned string should be freed using CPLFree().
277 : **********************************************************************/
278 2373 : char *TABGetBasename(const char *pszFname)
279 : {
280 : // Skip leading path or use whole name if no path dividers are encountered.
281 2373 : const char *pszTmp = pszFname + strlen(pszFname) - 1;
282 39165 : while (pszTmp != pszFname && *pszTmp != '/' && *pszTmp != '\\')
283 36792 : pszTmp--;
284 :
285 2373 : if (pszTmp != pszFname)
286 2367 : pszTmp++;
287 :
288 : // Now allocate our own copy and remove extension.
289 2373 : char *pszBasename = CPLStrdup(pszTmp);
290 9492 : for (int i = static_cast<int>(strlen(pszBasename)) - 1; i >= 0; i--)
291 : {
292 9492 : if (pszBasename[i] == '.')
293 : {
294 2373 : pszBasename[i] = '\0';
295 2373 : break;
296 : }
297 : }
298 :
299 2373 : return pszBasename;
300 : }
301 :
302 : /**********************************************************************
303 : * TAB_CSLLoad()
304 : *
305 : * Same as CSLLoad(), but does not produce an error if it fails... it
306 : * just returns NULL silently instead.
307 : *
308 : * Load a test file into a stringlist.
309 : *
310 : * Lines are limited in length by the size of the CPLReadLine() buffer.
311 : **********************************************************************/
312 1304 : char **TAB_CSLLoad(const char *pszFname)
313 : {
314 2608 : CPLStringList oList;
315 :
316 1304 : VSILFILE *fp = VSIFOpenL(pszFname, "rt");
317 :
318 1304 : if (fp)
319 : {
320 12044 : while (!VSIFEofL(fp))
321 : {
322 10740 : const char *pszLine = nullptr;
323 10740 : if ((pszLine = CPLReadLineL(fp)) != nullptr)
324 : {
325 10740 : oList.AddString(pszLine);
326 : }
327 : }
328 :
329 1304 : VSIFCloseL(fp);
330 : }
331 :
332 2608 : return oList.StealList();
333 : }
334 :
335 : /**********************************************************************
336 : * TABUnEscapeString()
337 : *
338 : * Convert a string that can possibly contain escaped "\n" chars in
339 : * into into a new one with binary newlines in it.
340 : *
341 : * Tries to work on the original buffer unless bSrcIsConst=TRUE, in
342 : * which case the original is always untouched and a copy is allocated
343 : * ONLY IF NECESSARY. This means that the caller should compare the
344 : * return value and the source (pszString) to see if a copy was returned,
345 : * in which case the caller becomes responsible of freeing both the
346 : * source and the copy.
347 : **********************************************************************/
348 297 : char *TABUnEscapeString(char *pszString, GBool bSrcIsConst)
349 : {
350 : // First check if we need to do any replacement.
351 297 : if (pszString == nullptr || strstr(pszString, "\\n") == nullptr)
352 : {
353 297 : return pszString;
354 : }
355 :
356 : // Yes, we need to replace at least one "\n".
357 : // We try to work on the original buffer unless we have bSrcIsConst=TRUE.
358 : //
359 : // Note that we do not worry about freeing the source buffer when we
360 : // return a copy. It is up to the caller to decide if the source needs
361 : // to be freed based on context and by comparing pszString with
362 : // the returned pointer (pszWorkString) to see if they are identical.
363 0 : char *pszWorkString = nullptr;
364 0 : if (bSrcIsConst)
365 : {
366 : // We have to create a copy to work on.
367 : pszWorkString = static_cast<char *>(
368 0 : CPLMalloc(sizeof(char) * (strlen(pszString) + 1)));
369 : }
370 : else
371 : {
372 : // Work on the original.
373 0 : pszWorkString = pszString;
374 : }
375 :
376 0 : int i = 0;
377 0 : int j = 0;
378 0 : while (pszString[i])
379 : {
380 0 : if (pszString[i] == '\\' && pszString[i + 1] == 'n')
381 : {
382 0 : pszWorkString[j++] = '\n';
383 0 : i += 2;
384 : }
385 0 : else if (pszString[i] == '\\' && pszString[i + 1] == '\\')
386 : {
387 0 : pszWorkString[j++] = '\\';
388 0 : i += 2;
389 : }
390 : else
391 : {
392 0 : pszWorkString[j++] = pszString[i++];
393 : }
394 : }
395 0 : pszWorkString[j++] = '\0';
396 :
397 0 : return pszWorkString;
398 : }
399 :
400 : /**********************************************************************
401 : * TABEscapeString()
402 : *
403 : * Convert a string that can possibly contain binary "\n" chars in
404 : * into into a new one with escaped newlines ("\\" + "n") in it.
405 : *
406 : * The function returns the original string pointer if it did not need to
407 : * be modified, or a copy that has to be freed by the caller if the
408 : * string had to be modified.
409 : *
410 : * It is up to the caller to decide if the returned string needs to be
411 : * freed by comparing the source (pszString) pointer with the returned
412 : * pointer (pszWorkString) to see if they are identical.
413 : **********************************************************************/
414 0 : char *TABEscapeString(char *pszString)
415 : {
416 : // First check if we need to do any replacement
417 0 : if (pszString == nullptr || strchr(pszString, '\n') == nullptr)
418 : {
419 0 : return pszString;
420 : }
421 :
422 : // Need to do some replacements. Alloc a copy big enough
423 : // to hold the worst possible case.
424 : char *pszWorkString = static_cast<char *>(
425 0 : CPLMalloc(2 * sizeof(char) * (strlen(pszString) + 1)));
426 :
427 0 : int i = 0;
428 0 : int j = 0;
429 :
430 0 : while (pszString[i])
431 : {
432 0 : if (pszString[i] == '\n')
433 : {
434 0 : pszWorkString[j++] = '\\';
435 0 : pszWorkString[j++] = 'n';
436 0 : i++;
437 : }
438 0 : else if (pszString[i] == '\\')
439 : {
440 0 : pszWorkString[j++] = '\\';
441 0 : pszWorkString[j++] = '\\';
442 0 : i++;
443 : }
444 : else
445 : {
446 0 : pszWorkString[j++] = pszString[i++];
447 : }
448 : }
449 0 : pszWorkString[j++] = '\0';
450 :
451 0 : return pszWorkString;
452 : }
453 :
454 : /**********************************************************************
455 : * TABCleanFieldName()
456 : *
457 : * Return a copy of pszSrcName that contains only valid characters for a
458 : * TAB field name. All invalid characters are replaced by '_'.
459 : *
460 : * The returned string should be freed by the caller.
461 : **********************************************************************/
462 392 : char *TABCleanFieldName(const char *pszSrcName)
463 : {
464 392 : char *pszNewName = CPLStrdup(pszSrcName);
465 392 : if (strlen(pszNewName) > 31)
466 : {
467 0 : pszNewName[31] = '\0';
468 0 : CPLError(CE_Warning,
469 : static_cast<CPLErrorNum>(TAB_WarningInvalidFieldName),
470 : "Field name '%s' is longer than the max of 31 characters. "
471 : "'%s' will be used instead.",
472 : pszSrcName, pszNewName);
473 : }
474 :
475 : // According to the MapInfo User's Guide (p. 240, v5.5).
476 : // New Table Command:
477 : // Name:
478 : // Displays the field name in the name box. You can also enter new field
479 : // names here. Defaults are Field1, Field2, etc. A field name can contain
480 : // up to 31 alphanumeric characters. Use letters, numbers, and the
481 : // underscore. Do not use spaces; instead, use the underscore character
482 : // (_) to separate words in a field name. Use upper and lower case for
483 : // legibility, but MapInfo is not case-sensitive.
484 : //
485 : // It was also verified that extended chars with accents are also
486 : // accepted.
487 392 : int numInvalidChars = 0;
488 2277 : for (int i = 0; pszSrcName && pszSrcName[i] != '\0'; i++)
489 : {
490 1885 : if (pszSrcName[i] == '#')
491 : {
492 0 : if (i == 0)
493 : {
494 0 : pszNewName[i] = '_';
495 0 : numInvalidChars++;
496 : }
497 : }
498 1885 : else if (!(pszSrcName[i] == '_' ||
499 1479 : (i != 0 && pszSrcName[i] >= '0' && pszSrcName[i] <= '9') ||
500 1692 : (pszSrcName[i] >= 'a' && pszSrcName[i] <= 'z') ||
501 463 : (pszSrcName[i] >= 'A' && pszSrcName[i] <= 'Z') ||
502 187 : static_cast<GByte>(pszSrcName[i]) >= 192))
503 : {
504 1 : pszNewName[i] = '_';
505 1 : numInvalidChars++;
506 : }
507 : }
508 :
509 392 : if (numInvalidChars > 0)
510 : {
511 1 : CPLError(CE_Warning,
512 : static_cast<CPLErrorNum>(TAB_WarningInvalidFieldName),
513 : "Field name '%s' contains invalid characters. "
514 : "'%s' will be used instead.",
515 : pszSrcName, pszNewName);
516 : }
517 :
518 392 : return pszNewName;
519 : }
520 :
521 : /**********************************************************************
522 : * MapInfo Units string to numeric ID conversion
523 : **********************************************************************/
524 : typedef struct
525 : {
526 : int nUnitId;
527 : const char *pszAbbrev;
528 : } MapInfoUnitsInfo;
529 :
530 : static const MapInfoUnitsInfo gasUnitsList[] = {
531 : {0, "mi"}, {1, "km"}, {2, "in"}, {3, "ft"},
532 : {4, "yd"}, {5, "mm"}, {6, "cm"}, {7, "m"},
533 : {8, "survey ft"}, {8, "survey foot"}, // alternate
534 : {13, nullptr}, {9, "nmi"}, {30, "li"}, {31, "ch"},
535 : {32, "rd"}, {-1, nullptr}};
536 :
537 : /**********************************************************************
538 : * TABUnitIdToString()
539 : *
540 : * Return the MIF units name for specified units id.
541 : * Return "" if no match found.
542 : *
543 : * The returned string should not be freed by the caller.
544 : **********************************************************************/
545 81 : const char *TABUnitIdToString(int nId)
546 : {
547 81 : const MapInfoUnitsInfo *psList = gasUnitsList;
548 :
549 674 : while (psList->nUnitId != -1)
550 : {
551 674 : if (psList->nUnitId == nId)
552 81 : return psList->pszAbbrev;
553 593 : psList++;
554 : }
555 :
556 0 : return "";
557 : }
558 :
559 : /**********************************************************************
560 : * TABUnitIdFromString()
561 : *
562 : * Return the units ID for specified MIF units name
563 : *
564 : * Returns -1 if no match found.
565 : **********************************************************************/
566 166 : int TABUnitIdFromString(const char *pszName)
567 : {
568 166 : if (pszName == nullptr)
569 0 : return 13;
570 :
571 166 : const MapInfoUnitsInfo *psList = gasUnitsList;
572 :
573 1314 : while (psList->nUnitId != -1)
574 : {
575 1314 : if (psList->pszAbbrev != nullptr && EQUAL(psList->pszAbbrev, pszName))
576 166 : return psList->nUnitId;
577 1148 : psList++;
578 : }
579 :
580 0 : return -1;
581 : }
582 :
583 : /**********************************************************************
584 : * TABSaturatedAdd()
585 : ***********************************************************************/
586 :
587 70184 : void TABSaturatedAdd(GInt32 &nVal, GInt32 nAdd)
588 : {
589 70184 : const GInt32 int_max = std::numeric_limits<GInt32>::max();
590 70184 : const GInt32 int_min = std::numeric_limits<GInt32>::min();
591 :
592 70184 : if (nAdd >= 0 && nVal > int_max - nAdd)
593 0 : nVal = int_max;
594 70184 : else if (nAdd == int_min && nVal < 0)
595 0 : nVal = int_min;
596 70184 : else if (nAdd != int_min && nAdd < 0 && nVal < int_min - nAdd)
597 0 : nVal = int_min;
598 : else
599 70184 : nVal += nAdd;
600 70184 : }
601 :
602 : /**********************************************************************
603 : * TABInt16Diff()
604 : **********************************************************************/
605 :
606 10316 : GInt16 TABInt16Diff(int a, int b)
607 : {
608 10316 : GIntBig nDiff = static_cast<GIntBig>(a) - b;
609 : // Maybe we should error out instead of saturating ???
610 10316 : if (nDiff < -32768)
611 0 : return -32768;
612 10316 : if (nDiff > 32767)
613 0 : return 32767;
614 10316 : return static_cast<GInt16>(nDiff);
615 : }
|