/src/gdal/ogr/ogrutils.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: OpenGIS Simple Features Reference Implementation |
4 | | * Purpose: Utility functions for OGR classes, including some related to |
5 | | * parsing well known text format vectors. |
6 | | * Author: Frank Warmerdam, warmerdam@pobox.com |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 1999, Frank Warmerdam |
10 | | * Copyright (c) 2008-2014, Even Rouault <even dot rouault at spatialys.com> |
11 | | * |
12 | | * SPDX-License-Identifier: MIT |
13 | | ****************************************************************************/ |
14 | | |
15 | | #include "cpl_port.h" |
16 | | #include "ogr_geometry.h" |
17 | | #include "ogr_p.h" |
18 | | |
19 | | #include <cassert> |
20 | | #include <cmath> |
21 | | #include <cstddef> |
22 | | #include <cstdio> |
23 | | #include <cstdlib> |
24 | | #include <cstring> |
25 | | #include <cctype> |
26 | | #include <limits> |
27 | | #include <sstream> |
28 | | #include <iomanip> |
29 | | |
30 | | #include "cpl_conv.h" |
31 | | #include "cpl_error.h" |
32 | | #include "cpl_string.h" |
33 | | #include "cpl_time.h" |
34 | | #include "cpl_vsi.h" |
35 | | #include "gdal.h" |
36 | | #include "ogr_core.h" |
37 | | #include "ogr_geometry.h" |
38 | | #include "ogrsf_frmts.h" |
39 | | |
40 | | // Returns whether a double fits within an int. |
41 | | // Unable to put this in cpl_port.h as include limit breaks grib. |
42 | | inline bool CPLIsDoubleAnInt(double d) |
43 | 0 | { |
44 | | // Write it this way to detect NaN |
45 | 0 | if (!(d >= std::numeric_limits<int>::min() && |
46 | 0 | d <= std::numeric_limits<int>::max())) |
47 | 0 | { |
48 | 0 | return false; |
49 | 0 | } |
50 | 0 | return d == static_cast<double>(static_cast<int>(d)); |
51 | 0 | } |
52 | | |
53 | | namespace |
54 | | { |
55 | | |
56 | | // Remove trailing zeros except the last one. |
57 | | void removeTrailingZeros(std::string &s) |
58 | 0 | { |
59 | 0 | auto pos = s.find('.'); |
60 | 0 | if (pos == std::string::npos) |
61 | 0 | return; |
62 | | |
63 | | // Remove zeros at the end. We know this won't be npos because we |
64 | | // have a decimal point. |
65 | 0 | auto nzpos = s.find_last_not_of('0'); |
66 | 0 | s = s.substr(0, nzpos + 1); |
67 | | |
68 | | // Make sure there is one 0 after the decimal point. |
69 | 0 | if (s.back() == '.') |
70 | 0 | s += '0'; |
71 | 0 | } |
72 | | |
73 | | // Round a string representing a number by 1 in the least significant digit. |
74 | | void roundup(std::string &s) |
75 | 0 | { |
76 | | // Remove a negative sign if it exists to make processing |
77 | | // more straigtforward. |
78 | 0 | bool negative(false); |
79 | 0 | if (s[0] == '-') |
80 | 0 | { |
81 | 0 | negative = true; |
82 | 0 | s = s.substr(1); |
83 | 0 | } |
84 | | |
85 | | // Go from the back to the front. If we increment a digit other than |
86 | | // a '9', we're done. If we increment a '9', set it to a '0' and move |
87 | | // to the next (more significant) digit. If we get to the front of the |
88 | | // string, add a '1' to the front of the string. |
89 | 0 | for (int pos = static_cast<int>(s.size() - 1); pos >= 0; pos--) |
90 | 0 | { |
91 | 0 | if (s[pos] == '.') |
92 | 0 | continue; |
93 | 0 | s[pos]++; |
94 | | |
95 | | // Incrementing past 9 gets you a colon in ASCII. |
96 | 0 | if (s[pos] != ':') |
97 | 0 | break; |
98 | 0 | else |
99 | 0 | s[pos] = '0'; |
100 | 0 | if (pos == 0) |
101 | 0 | s = '1' + s; |
102 | 0 | } |
103 | 0 | if (negative) |
104 | 0 | s = '-' + s; |
105 | 0 | } |
106 | | |
107 | | // This attempts to eliminate what is likely binary -> decimal representation |
108 | | // error or the result of low-order rounding with calculations. The result |
109 | | // may be more visually pleasing and takes up fewer places. |
110 | | void intelliround(std::string &s) |
111 | 0 | { |
112 | 0 | const size_t len = s.size(); |
113 | | |
114 | | // If we don't have ten characters (a bit arbitrary), don't do anything. |
115 | 0 | constexpr size_t MIN_THRESHOLD_FOR_INELLIROUND = 10; |
116 | 0 | if (len <= MIN_THRESHOLD_FOR_INELLIROUND) |
117 | 0 | return; |
118 | | |
119 | | // If there is no decimal point, just return. |
120 | 0 | size_t iDotPos = std::string::npos; |
121 | 0 | size_t i = 0; |
122 | 0 | for (; i < len; ++i) |
123 | 0 | { |
124 | 0 | if (s[i] == '.') |
125 | 0 | { |
126 | 0 | iDotPos = i; |
127 | 0 | break; |
128 | 0 | } |
129 | 0 | } |
130 | 0 | if (iDotPos == std::string::npos) |
131 | 0 | return; |
132 | 0 | for (; i < len; ++i) |
133 | 0 | { |
134 | 0 | if (s[i] == 'e' || s[i] == 'E') |
135 | 0 | { |
136 | | // Don't mess with exponential formatting. |
137 | 0 | return; |
138 | 0 | } |
139 | 0 | } |
140 | | |
141 | 0 | size_t nCountBeforeDot = iDotPos - 1; |
142 | 0 | if (s[0] == '-') |
143 | 0 | nCountBeforeDot--; |
144 | | |
145 | | /* -------------------------------------------------------------------- */ |
146 | | /* Trim trailing 00000x's as they are likely roundoff error. */ |
147 | | /* -------------------------------------------------------------------- */ |
148 | 0 | if (s[len - 2] == '0' && s[len - 3] == '0' && s[len - 4] == '0' && |
149 | 0 | s[len - 5] == '0' && s[len - 6] == '0') |
150 | 0 | { |
151 | 0 | s.pop_back(); |
152 | 0 | } |
153 | | // I don't understand this case exactly. It's like saying if the |
154 | | // value is large enough and there are sufficient sig digits before |
155 | | // a bunch of zeros, remove the zeros and any digits at the end that |
156 | | // may be nonzero. Perhaps if we can't exactly explain in words what |
157 | | // we're doing here, we shouldn't do it? Perhaps it should |
158 | | // be generalized? |
159 | | // The value "12345.000000011" invokes this case, if anyone |
160 | | // is interested. |
161 | 0 | else if (iDotPos < len - 8 && (nCountBeforeDot >= 4 || s[len - 3] == '0') && |
162 | 0 | (nCountBeforeDot >= 5 || s[len - 4] == '0') && |
163 | 0 | (nCountBeforeDot >= 6 || s[len - 5] == '0') && |
164 | 0 | (nCountBeforeDot >= 7 || s[len - 6] == '0') && |
165 | 0 | (nCountBeforeDot >= 8 || s[len - 7] == '0') && s[len - 8] == '0' && |
166 | 0 | s[len - 9] == '0') |
167 | 0 | { |
168 | 0 | s.resize(s.size() - 8); |
169 | 0 | } |
170 | | /* -------------------------------------------------------------------- */ |
171 | | /* Trim trailing 99999x's as they are likely roundoff error. */ |
172 | | /* -------------------------------------------------------------------- */ |
173 | 0 | else if (s[len - 2] == '9' && s[len - 3] == '9' && s[len - 4] == '9' && |
174 | 0 | s[len - 5] == '9' && s[len - 6] == '9') |
175 | 0 | { |
176 | 0 | s.resize(len - 6); |
177 | 0 | roundup(s); |
178 | 0 | } |
179 | 0 | else if (iDotPos < len - 9 && (nCountBeforeDot >= 4 || s[len - 3] == '9') && |
180 | 0 | (nCountBeforeDot >= 5 || s[len - 4] == '9') && |
181 | 0 | (nCountBeforeDot >= 6 || s[len - 5] == '9') && |
182 | 0 | (nCountBeforeDot >= 7 || s[len - 6] == '9') && |
183 | 0 | (nCountBeforeDot >= 8 || s[len - 7] == '9') && s[len - 8] == '9' && |
184 | 0 | s[len - 9] == '9') |
185 | 0 | { |
186 | 0 | s.resize(len - 9); |
187 | 0 | roundup(s); |
188 | 0 | } |
189 | 0 | } |
190 | | |
191 | | } // unnamed namespace |
192 | | |
193 | | /************************************************************************/ |
194 | | /* OGRFormatDouble() */ |
195 | | /************************************************************************/ |
196 | | |
197 | | void OGRFormatDouble(char *pszBuffer, int nBufferLen, double dfVal, |
198 | | char chDecimalSep, int nPrecision, |
199 | | char chConversionSpecifier) |
200 | 0 | { |
201 | 0 | OGRWktOptions opts(nPrecision, OGRWktOptions::getDefaultRound()); |
202 | 0 | opts.format = (chConversionSpecifier == 'g' || chConversionSpecifier == 'G') |
203 | 0 | ? OGRWktFormat::G |
204 | 0 | : OGRWktFormat::F; |
205 | |
|
206 | 0 | std::string s = OGRFormatDouble(dfVal, opts, 1); |
207 | 0 | if (chDecimalSep != '\0' && chDecimalSep != '.') |
208 | 0 | { |
209 | 0 | auto pos = s.find('.'); |
210 | 0 | if (pos != std::string::npos) |
211 | 0 | s.replace(pos, 1, std::string(1, chDecimalSep)); |
212 | 0 | } |
213 | 0 | if (s.size() + 1 > static_cast<size_t>(nBufferLen)) |
214 | 0 | { |
215 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
216 | 0 | "Truncated double value %s to " |
217 | 0 | "%s.", |
218 | 0 | s.data(), s.substr(0, nBufferLen - 1).data()); |
219 | 0 | s.resize(nBufferLen - 1); |
220 | 0 | } |
221 | 0 | strcpy(pszBuffer, s.data()); |
222 | 0 | } |
223 | | |
224 | | /// Simplified OGRFormatDouble that can be made to adhere to provided |
225 | | /// options. |
226 | | std::string OGRFormatDouble(double val, const OGRWktOptions &opts, int nDimIdx) |
227 | 0 | { |
228 | | // So to have identical cross platform representation. |
229 | 0 | if (std::isinf(val)) |
230 | 0 | return (val > 0) ? "inf" : "-inf"; |
231 | 0 | if (std::isnan(val)) |
232 | 0 | return "nan"; |
233 | | |
234 | 0 | static thread_local std::locale classic_locale = []() |
235 | 0 | { return std::locale::classic(); }(); |
236 | 0 | std::ostringstream oss; |
237 | 0 | oss.imbue(classic_locale); // Make sure we output decimal points. |
238 | 0 | bool l_round(opts.round); |
239 | 0 | if (opts.format == OGRWktFormat::F || |
240 | 0 | (opts.format == OGRWktFormat::Default && fabs(val) < 1)) |
241 | 0 | oss << std::fixed; |
242 | 0 | else |
243 | 0 | { |
244 | | // Uppercase because OGC spec says capital 'E'. |
245 | 0 | oss << std::uppercase; |
246 | 0 | l_round = false; |
247 | 0 | } |
248 | 0 | oss << std::setprecision(nDimIdx < 3 ? opts.xyPrecision |
249 | 0 | : nDimIdx == 3 ? opts.zPrecision |
250 | 0 | : opts.mPrecision); |
251 | 0 | oss << val; |
252 | |
|
253 | 0 | std::string sval = oss.str(); |
254 | |
|
255 | 0 | if (l_round) |
256 | 0 | intelliround(sval); |
257 | 0 | removeTrailingZeros(sval); |
258 | 0 | return sval; |
259 | 0 | } |
260 | | |
261 | | /************************************************************************/ |
262 | | /* OGRMakeWktCoordinate() */ |
263 | | /* */ |
264 | | /* Format a well known text coordinate, trying to keep the */ |
265 | | /* ASCII representation compact, but accurate. These rules */ |
266 | | /* will have to tighten up in the future. */ |
267 | | /* */ |
268 | | /* Currently a new point should require no more than 64 */ |
269 | | /* characters barring the X or Y value being extremely large. */ |
270 | | /************************************************************************/ |
271 | | |
272 | | void OGRMakeWktCoordinate(char *pszTarget, double x, double y, double z, |
273 | | int nDimension) |
274 | | |
275 | 0 | { |
276 | 0 | std::string wkt = |
277 | 0 | OGRMakeWktCoordinate(x, y, z, nDimension, OGRWktOptions()); |
278 | 0 | memcpy(pszTarget, wkt.data(), wkt.size() + 1); |
279 | 0 | } |
280 | | |
281 | | static bool isInteger(const std::string &s) |
282 | 0 | { |
283 | 0 | return s.find_first_not_of("0123456789") == std::string::npos; |
284 | 0 | } |
285 | | |
286 | | std::string OGRMakeWktCoordinate(double x, double y, double z, int nDimension, |
287 | | const OGRWktOptions &opts) |
288 | 0 | { |
289 | 0 | std::string wkt; |
290 | | |
291 | | // Why do we do this? Seems especially strange since we're ADDING |
292 | | // ".0" onto values in the case below. The "&&" here also seems strange. |
293 | 0 | if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(x) && |
294 | 0 | CPLIsDoubleAnInt(y)) |
295 | 0 | { |
296 | 0 | wkt = std::to_string(static_cast<int>(x)); |
297 | 0 | wkt += ' '; |
298 | 0 | wkt += std::to_string(static_cast<int>(y)); |
299 | 0 | } |
300 | 0 | else |
301 | 0 | { |
302 | 0 | wkt = OGRFormatDouble(x, opts, 1); |
303 | | // ABELL - Why do we do special formatting? |
304 | 0 | if (isInteger(wkt)) |
305 | 0 | wkt += ".0"; |
306 | 0 | wkt += ' '; |
307 | |
|
308 | 0 | std::string yval = OGRFormatDouble(y, opts, 2); |
309 | 0 | if (isInteger(yval)) |
310 | 0 | yval += ".0"; |
311 | 0 | wkt += yval; |
312 | 0 | } |
313 | |
|
314 | 0 | if (nDimension == 3) |
315 | 0 | { |
316 | 0 | wkt += ' '; |
317 | 0 | if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z)) |
318 | 0 | wkt += std::to_string(static_cast<int>(z)); |
319 | 0 | else |
320 | 0 | { |
321 | 0 | wkt += OGRFormatDouble(z, opts, 3); |
322 | 0 | } |
323 | 0 | } |
324 | 0 | return wkt; |
325 | 0 | } |
326 | | |
327 | | /************************************************************************/ |
328 | | /* OGRMakeWktCoordinateM() */ |
329 | | /* */ |
330 | | /* Format a well known text coordinate, trying to keep the */ |
331 | | /* ASCII representation compact, but accurate. These rules */ |
332 | | /* will have to tighten up in the future. */ |
333 | | /* */ |
334 | | /* Currently a new point should require no more than 64 */ |
335 | | /* characters barring the X or Y value being extremely large. */ |
336 | | /************************************************************************/ |
337 | | |
338 | | void OGRMakeWktCoordinateM(char *pszTarget, double x, double y, double z, |
339 | | double m, OGRBoolean hasZ, OGRBoolean hasM) |
340 | | |
341 | 0 | { |
342 | 0 | std::string wkt = |
343 | 0 | OGRMakeWktCoordinateM(x, y, z, m, hasZ, hasM, OGRWktOptions()); |
344 | 0 | memcpy(pszTarget, wkt.data(), wkt.size() + 1); |
345 | 0 | } |
346 | | |
347 | | std::string OGRMakeWktCoordinateM(double x, double y, double z, double m, |
348 | | OGRBoolean hasZ, OGRBoolean hasM, |
349 | | const OGRWktOptions &opts) |
350 | 0 | { |
351 | 0 | std::string wkt; |
352 | 0 | if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(x) && |
353 | 0 | CPLIsDoubleAnInt(y)) |
354 | 0 | { |
355 | 0 | wkt = std::to_string(static_cast<int>(x)); |
356 | 0 | wkt += ' '; |
357 | 0 | wkt += std::to_string(static_cast<int>(y)); |
358 | 0 | } |
359 | 0 | else |
360 | 0 | { |
361 | 0 | wkt = OGRFormatDouble(x, opts, 1); |
362 | 0 | if (isInteger(wkt)) |
363 | 0 | wkt += ".0"; |
364 | 0 | wkt += ' '; |
365 | |
|
366 | 0 | std::string yval = OGRFormatDouble(y, opts, 2); |
367 | 0 | if (isInteger(yval)) |
368 | 0 | yval += ".0"; |
369 | 0 | wkt += yval; |
370 | 0 | } |
371 | |
|
372 | 0 | if (hasZ) |
373 | 0 | { |
374 | 0 | wkt += ' '; |
375 | 0 | if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(z)) |
376 | 0 | wkt += std::to_string(static_cast<int>(z)); |
377 | 0 | else |
378 | 0 | wkt += OGRFormatDouble(z, opts, 3); |
379 | 0 | } |
380 | |
|
381 | 0 | if (hasM) |
382 | 0 | { |
383 | 0 | wkt += ' '; |
384 | 0 | if (opts.format == OGRWktFormat::Default && CPLIsDoubleAnInt(m)) |
385 | 0 | wkt += std::to_string(static_cast<int>(m)); |
386 | 0 | else |
387 | 0 | wkt += OGRFormatDouble(m, opts, 4); |
388 | 0 | } |
389 | 0 | return wkt; |
390 | 0 | } |
391 | | |
392 | | /************************************************************************/ |
393 | | /* OGRWktReadToken() */ |
394 | | /* */ |
395 | | /* Read one token or delimiter and put into token buffer. Pre */ |
396 | | /* and post white space is swallowed. */ |
397 | | /************************************************************************/ |
398 | | |
399 | | const char *OGRWktReadToken(const char *pszInput, char *pszToken) |
400 | | |
401 | 189k | { |
402 | 189k | if (pszInput == nullptr) |
403 | 0 | return nullptr; |
404 | | |
405 | | /* -------------------------------------------------------------------- */ |
406 | | /* Swallow pre-white space. */ |
407 | | /* -------------------------------------------------------------------- */ |
408 | 193k | while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == '\n' || |
409 | 193k | *pszInput == '\r') |
410 | 4.03k | ++pszInput; |
411 | | |
412 | | /* -------------------------------------------------------------------- */ |
413 | | /* If this is a delimiter, read just one character. */ |
414 | | /* -------------------------------------------------------------------- */ |
415 | 189k | if (*pszInput == '(' || *pszInput == ')' || *pszInput == ',') |
416 | 72.3k | { |
417 | 72.3k | pszToken[0] = *pszInput; |
418 | 72.3k | pszToken[1] = '\0'; |
419 | | |
420 | 72.3k | ++pszInput; |
421 | 72.3k | } |
422 | | |
423 | | /* -------------------------------------------------------------------- */ |
424 | | /* Or if it alpha numeric read till we reach non-alpha numeric */ |
425 | | /* text. */ |
426 | | /* -------------------------------------------------------------------- */ |
427 | 117k | else |
428 | 117k | { |
429 | 117k | int iChar = 0; |
430 | | |
431 | 1.01M | while (iChar < OGR_WKT_TOKEN_MAX - 1 && |
432 | 1.01M | ((*pszInput >= 'a' && *pszInput <= 'z') || |
433 | 1.01M | (*pszInput >= 'A' && *pszInput <= 'Z') || |
434 | 1.01M | (*pszInput >= '0' && *pszInput <= '9') || *pszInput == '.' || |
435 | 1.01M | *pszInput == '+' || *pszInput == '-')) |
436 | 894k | { |
437 | 894k | pszToken[iChar++] = *(pszInput++); |
438 | 894k | } |
439 | | |
440 | 117k | pszToken[iChar++] = '\0'; |
441 | 117k | } |
442 | | |
443 | | /* -------------------------------------------------------------------- */ |
444 | | /* Eat any trailing white space. */ |
445 | | /* -------------------------------------------------------------------- */ |
446 | 314k | while (*pszInput == ' ' || *pszInput == '\t' || *pszInput == '\n' || |
447 | 314k | *pszInput == '\r') |
448 | 124k | ++pszInput; |
449 | | |
450 | 189k | return pszInput; |
451 | 189k | } |
452 | | |
453 | | /************************************************************************/ |
454 | | /* OGRWktReadPoints() */ |
455 | | /* */ |
456 | | /* Read a point string. The point list must be contained in */ |
457 | | /* brackets and each point pair separated by a comma. */ |
458 | | /************************************************************************/ |
459 | | |
460 | | const char *OGRWktReadPoints(const char *pszInput, OGRRawPoint **ppaoPoints, |
461 | | double **ppadfZ, int *pnMaxPoints, |
462 | | int *pnPointsRead) |
463 | | |
464 | 0 | { |
465 | 0 | const char *pszOrigInput = pszInput; |
466 | 0 | *pnPointsRead = 0; |
467 | |
|
468 | 0 | if (pszInput == nullptr) |
469 | 0 | return nullptr; |
470 | | |
471 | | /* -------------------------------------------------------------------- */ |
472 | | /* Eat any leading white space. */ |
473 | | /* -------------------------------------------------------------------- */ |
474 | 0 | while (*pszInput == ' ' || *pszInput == '\t') |
475 | 0 | ++pszInput; |
476 | | |
477 | | /* -------------------------------------------------------------------- */ |
478 | | /* If this isn't an opening bracket then we have a problem. */ |
479 | | /* -------------------------------------------------------------------- */ |
480 | 0 | if (*pszInput != '(') |
481 | 0 | { |
482 | 0 | CPLDebug("OGR", "Expected '(', but got %s in OGRWktReadPoints().", |
483 | 0 | pszInput); |
484 | |
|
485 | 0 | return pszInput; |
486 | 0 | } |
487 | | |
488 | 0 | ++pszInput; |
489 | | |
490 | | /* ==================================================================== */ |
491 | | /* This loop reads a single point. It will continue till we */ |
492 | | /* run out of well formed points, or a closing bracket is */ |
493 | | /* encountered. */ |
494 | | /* ==================================================================== */ |
495 | 0 | char szDelim[OGR_WKT_TOKEN_MAX] = {}; |
496 | |
|
497 | 0 | do |
498 | 0 | { |
499 | | /* -------------------------------------------------------------------- |
500 | | */ |
501 | | /* Read the X and Y values, verify they are numeric. */ |
502 | | /* -------------------------------------------------------------------- |
503 | | */ |
504 | 0 | char szTokenX[OGR_WKT_TOKEN_MAX] = {}; |
505 | 0 | char szTokenY[OGR_WKT_TOKEN_MAX] = {}; |
506 | |
|
507 | 0 | pszInput = OGRWktReadToken(pszInput, szTokenX); |
508 | 0 | pszInput = OGRWktReadToken(pszInput, szTokenY); |
509 | |
|
510 | 0 | if ((!isdigit(static_cast<unsigned char>(szTokenX[0])) && |
511 | 0 | szTokenX[0] != '-' && szTokenX[0] != '.') || |
512 | 0 | (!isdigit(static_cast<unsigned char>(szTokenY[0])) && |
513 | 0 | szTokenY[0] != '-' && szTokenY[0] != '.')) |
514 | 0 | return nullptr; |
515 | | |
516 | | /* -------------------------------------------------------------------- |
517 | | */ |
518 | | /* Do we need to grow the point list to hold this point? */ |
519 | | /* -------------------------------------------------------------------- |
520 | | */ |
521 | 0 | if (*pnPointsRead == *pnMaxPoints) |
522 | 0 | { |
523 | 0 | *pnMaxPoints = *pnMaxPoints * 2 + 10; |
524 | 0 | *ppaoPoints = static_cast<OGRRawPoint *>( |
525 | 0 | CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints)); |
526 | |
|
527 | 0 | if (*ppadfZ != nullptr) |
528 | 0 | { |
529 | 0 | *ppadfZ = static_cast<double *>( |
530 | 0 | CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints)); |
531 | 0 | } |
532 | 0 | } |
533 | | |
534 | | /* -------------------------------------------------------------------- |
535 | | */ |
536 | | /* Add point to list. */ |
537 | | /* -------------------------------------------------------------------- |
538 | | */ |
539 | 0 | (*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX); |
540 | 0 | (*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY); |
541 | | |
542 | | /* -------------------------------------------------------------------- |
543 | | */ |
544 | | /* Do we have a Z coordinate? */ |
545 | | /* -------------------------------------------------------------------- |
546 | | */ |
547 | 0 | pszInput = OGRWktReadToken(pszInput, szDelim); |
548 | |
|
549 | 0 | if (isdigit(static_cast<unsigned char>(szDelim[0])) || |
550 | 0 | szDelim[0] == '-' || szDelim[0] == '.') |
551 | 0 | { |
552 | 0 | if (*ppadfZ == nullptr) |
553 | 0 | { |
554 | 0 | *ppadfZ = static_cast<double *>( |
555 | 0 | CPLCalloc(sizeof(double), *pnMaxPoints)); |
556 | 0 | } |
557 | |
|
558 | 0 | (*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim); |
559 | |
|
560 | 0 | pszInput = OGRWktReadToken(pszInput, szDelim); |
561 | 0 | } |
562 | 0 | else if (*ppadfZ != nullptr) |
563 | 0 | { |
564 | 0 | (*ppadfZ)[*pnPointsRead] = 0.0; |
565 | 0 | } |
566 | |
|
567 | 0 | ++(*pnPointsRead); |
568 | | |
569 | | /* -------------------------------------------------------------------- |
570 | | */ |
571 | | /* Do we have a M coordinate? */ |
572 | | /* If we do, just skip it. */ |
573 | | /* -------------------------------------------------------------------- |
574 | | */ |
575 | 0 | if (isdigit(static_cast<unsigned char>(szDelim[0])) || |
576 | 0 | szDelim[0] == '-' || szDelim[0] == '.') |
577 | 0 | { |
578 | 0 | pszInput = OGRWktReadToken(pszInput, szDelim); |
579 | 0 | } |
580 | | |
581 | | /* -------------------------------------------------------------------- |
582 | | */ |
583 | | /* Read next delimiter ... it should be a comma if there are */ |
584 | | /* more points. */ |
585 | | /* -------------------------------------------------------------------- |
586 | | */ |
587 | 0 | if (szDelim[0] != ')' && szDelim[0] != ',') |
588 | 0 | { |
589 | 0 | CPLDebug("OGR", |
590 | 0 | "Corrupt input in OGRWktReadPoints(). " |
591 | 0 | "Got `%s' when expecting `,' or `)', near `%s' in %s.", |
592 | 0 | szDelim, pszInput, pszOrigInput); |
593 | 0 | return nullptr; |
594 | 0 | } |
595 | 0 | } while (szDelim[0] == ','); |
596 | | |
597 | 0 | return pszInput; |
598 | 0 | } |
599 | | |
600 | | /************************************************************************/ |
601 | | /* OGRWktReadPointsM() */ |
602 | | /* */ |
603 | | /* Read a point string. The point list must be contained in */ |
604 | | /* brackets and each point pair separated by a comma. */ |
605 | | /************************************************************************/ |
606 | | |
607 | | const char *OGRWktReadPointsM(const char *pszInput, OGRRawPoint **ppaoPoints, |
608 | | double **ppadfZ, double **ppadfM, int *flags, |
609 | | int *pnMaxPoints, int *pnPointsRead) |
610 | | |
611 | 9.42k | { |
612 | 9.42k | const char *pszOrigInput = pszInput; |
613 | 9.42k | const bool bNoFlags = !(*flags & OGRGeometry::OGR_G_3D) && |
614 | 9.42k | !(*flags & OGRGeometry::OGR_G_MEASURED); |
615 | 9.42k | *pnPointsRead = 0; |
616 | | |
617 | 9.42k | if (pszInput == nullptr) |
618 | 0 | return nullptr; |
619 | | |
620 | | /* -------------------------------------------------------------------- */ |
621 | | /* Eat any leading white space. */ |
622 | | /* -------------------------------------------------------------------- */ |
623 | 9.42k | while (*pszInput == ' ' || *pszInput == '\t') |
624 | 0 | ++pszInput; |
625 | | |
626 | | /* -------------------------------------------------------------------- */ |
627 | | /* If this isn't an opening bracket then we have a problem. */ |
628 | | /* -------------------------------------------------------------------- */ |
629 | 9.42k | if (*pszInput != '(') |
630 | 24 | { |
631 | 24 | CPLDebug("OGR", "Expected '(', but got %s in OGRWktReadPointsM().", |
632 | 24 | pszInput); |
633 | | |
634 | 24 | return pszInput; |
635 | 24 | } |
636 | | |
637 | 9.40k | ++pszInput; |
638 | | |
639 | | /* ==================================================================== */ |
640 | | /* This loop reads a single point. It will continue till we */ |
641 | | /* run out of well formed points, or a closing bracket is */ |
642 | | /* encountered. */ |
643 | | /* ==================================================================== */ |
644 | 9.40k | char szDelim[OGR_WKT_TOKEN_MAX] = {}; |
645 | | |
646 | 9.40k | do |
647 | 40.2k | { |
648 | | /* -------------------------------------------------------------------- |
649 | | */ |
650 | | /* Read the X and Y values, verify they are numeric. */ |
651 | | /* -------------------------------------------------------------------- |
652 | | */ |
653 | 40.2k | char szTokenX[OGR_WKT_TOKEN_MAX] = {}; |
654 | 40.2k | char szTokenY[OGR_WKT_TOKEN_MAX] = {}; |
655 | | |
656 | 40.2k | pszInput = OGRWktReadToken(pszInput, szTokenX); |
657 | 40.2k | pszInput = OGRWktReadToken(pszInput, szTokenY); |
658 | | |
659 | 40.2k | if ((!isdigit(static_cast<unsigned char>(szTokenX[0])) && |
660 | 40.2k | szTokenX[0] != '-' && szTokenX[0] != '.' && |
661 | 40.2k | !EQUAL(szTokenX, "nan")) || |
662 | 40.2k | (!isdigit(static_cast<unsigned char>(szTokenY[0])) && |
663 | 40.1k | szTokenY[0] != '-' && szTokenY[0] != '.' && |
664 | 40.1k | !EQUAL(szTokenY, "nan"))) |
665 | 205 | return nullptr; |
666 | | |
667 | | /* -------------------------------------------------------------------- |
668 | | */ |
669 | | /* Do we need to grow the point list to hold this point? */ |
670 | | /* -------------------------------------------------------------------- |
671 | | */ |
672 | 39.9k | if (*pnPointsRead == *pnMaxPoints) |
673 | 4.50k | { |
674 | 4.50k | *pnMaxPoints = *pnMaxPoints * 2 + 10; |
675 | 4.50k | *ppaoPoints = static_cast<OGRRawPoint *>( |
676 | 4.50k | CPLRealloc(*ppaoPoints, sizeof(OGRRawPoint) * *pnMaxPoints)); |
677 | | |
678 | 4.50k | if (*ppadfZ != nullptr) |
679 | 646 | { |
680 | 646 | *ppadfZ = static_cast<double *>( |
681 | 646 | CPLRealloc(*ppadfZ, sizeof(double) * *pnMaxPoints)); |
682 | 646 | } |
683 | | |
684 | 4.50k | if (*ppadfM != nullptr) |
685 | 492 | { |
686 | 492 | *ppadfM = static_cast<double *>( |
687 | 492 | CPLRealloc(*ppadfM, sizeof(double) * *pnMaxPoints)); |
688 | 492 | } |
689 | 4.50k | } |
690 | | |
691 | | /* -------------------------------------------------------------------- |
692 | | */ |
693 | | /* Add point to list. */ |
694 | | /* -------------------------------------------------------------------- |
695 | | */ |
696 | 39.9k | (*ppaoPoints)[*pnPointsRead].x = CPLAtof(szTokenX); |
697 | 39.9k | (*ppaoPoints)[*pnPointsRead].y = CPLAtof(szTokenY); |
698 | | |
699 | | /* -------------------------------------------------------------------- |
700 | | */ |
701 | | /* Read the next token. */ |
702 | | /* -------------------------------------------------------------------- |
703 | | */ |
704 | 39.9k | pszInput = OGRWktReadToken(pszInput, szDelim); |
705 | | |
706 | | /* -------------------------------------------------------------------- |
707 | | */ |
708 | | /* If there are unexpectedly more coordinates, they are Z. */ |
709 | | /* -------------------------------------------------------------------- |
710 | | */ |
711 | | |
712 | 39.9k | if (!(*flags & OGRGeometry::OGR_G_3D) && |
713 | 39.9k | !(*flags & OGRGeometry::OGR_G_MEASURED) && |
714 | 39.9k | (isdigit(static_cast<unsigned char>(szDelim[0])) || |
715 | 6.35k | szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan"))) |
716 | 2.97k | { |
717 | 2.97k | *flags |= OGRGeometry::OGR_G_3D; |
718 | 2.97k | } |
719 | | |
720 | | /* -------------------------------------------------------------------- |
721 | | */ |
722 | | /* Get Z if flag says so. */ |
723 | | /* Zero out possible remains from earlier strings. */ |
724 | | /* -------------------------------------------------------------------- |
725 | | */ |
726 | | |
727 | 39.9k | if (*flags & OGRGeometry::OGR_G_3D) |
728 | 28.0k | { |
729 | 28.0k | if (*ppadfZ == nullptr) |
730 | 2.27k | { |
731 | 2.27k | *ppadfZ = static_cast<double *>( |
732 | 2.27k | CPLCalloc(sizeof(double), *pnMaxPoints)); |
733 | 2.27k | } |
734 | 28.0k | if (isdigit(static_cast<unsigned char>(szDelim[0])) || |
735 | 28.0k | szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan")) |
736 | 14.1k | { |
737 | 14.1k | (*ppadfZ)[*pnPointsRead] = CPLAtof(szDelim); |
738 | 14.1k | pszInput = OGRWktReadToken(pszInput, szDelim); |
739 | 14.1k | } |
740 | 13.9k | else |
741 | 13.9k | { |
742 | 13.9k | (*ppadfZ)[*pnPointsRead] = 0.0; |
743 | 13.9k | } |
744 | 28.0k | } |
745 | 11.9k | else if (*ppadfZ != nullptr) |
746 | 6.89k | { |
747 | 6.89k | (*ppadfZ)[*pnPointsRead] = 0.0; |
748 | 6.89k | } |
749 | | |
750 | | /* -------------------------------------------------------------------- |
751 | | */ |
752 | | /* If there are unexpectedly even more coordinates, */ |
753 | | /* they are discarded unless there were no flags originally. */ |
754 | | /* This is for backwards compatibility. Should this be an error? */ |
755 | | /* -------------------------------------------------------------------- |
756 | | */ |
757 | | |
758 | 39.9k | if (!(*flags & OGRGeometry::OGR_G_MEASURED) && |
759 | 39.9k | (isdigit(static_cast<unsigned char>(szDelim[0])) || |
760 | 16.6k | szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan"))) |
761 | 2.00k | { |
762 | 2.00k | if (bNoFlags) |
763 | 1.31k | { |
764 | 1.31k | *flags |= OGRGeometry::OGR_G_MEASURED; |
765 | 1.31k | } |
766 | 691 | else |
767 | 691 | { |
768 | 691 | pszInput = OGRWktReadToken(pszInput, szDelim); |
769 | 691 | } |
770 | 2.00k | } |
771 | | |
772 | | /* -------------------------------------------------------------------- |
773 | | */ |
774 | | /* Get M if flag says so. */ |
775 | | /* Zero out possible remains from earlier strings. */ |
776 | | /* -------------------------------------------------------------------- |
777 | | */ |
778 | | |
779 | 39.9k | if (*flags & OGRGeometry::OGR_G_MEASURED) |
780 | 24.6k | { |
781 | 24.6k | if (*ppadfM == nullptr) |
782 | 4.43k | { |
783 | 4.43k | *ppadfM = static_cast<double *>( |
784 | 4.43k | CPLCalloc(sizeof(double), *pnMaxPoints)); |
785 | 4.43k | } |
786 | 24.6k | if (isdigit(static_cast<unsigned char>(szDelim[0])) || |
787 | 24.6k | szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan")) |
788 | 6.32k | { |
789 | 6.32k | (*ppadfM)[*pnPointsRead] = CPLAtof(szDelim); |
790 | 6.32k | pszInput = OGRWktReadToken(pszInput, szDelim); |
791 | 6.32k | } |
792 | 18.3k | else |
793 | 18.3k | { |
794 | 18.3k | (*ppadfM)[*pnPointsRead] = 0.0; |
795 | 18.3k | } |
796 | 24.6k | } |
797 | 15.3k | else if (*ppadfM != nullptr) |
798 | 0 | { |
799 | 0 | (*ppadfM)[*pnPointsRead] = 0.0; |
800 | 0 | } |
801 | | |
802 | | /* -------------------------------------------------------------------- |
803 | | */ |
804 | | /* If there are still more coordinates and we do not have Z */ |
805 | | /* then we have a case of flags == M and four coordinates. */ |
806 | | /* This is allowed in BNF. */ |
807 | | /* -------------------------------------------------------------------- |
808 | | */ |
809 | | |
810 | 39.9k | if (!(*flags & OGRGeometry::OGR_G_3D) && |
811 | 39.9k | (isdigit(static_cast<unsigned char>(szDelim[0])) || |
812 | 11.9k | szDelim[0] == '-' || szDelim[0] == '.' || EQUAL(szDelim, "nan"))) |
813 | 747 | { |
814 | 747 | *flags |= OGRGeometry::OGR_G_3D; |
815 | 747 | if (*ppadfZ == nullptr) |
816 | 394 | { |
817 | 394 | *ppadfZ = static_cast<double *>( |
818 | 394 | CPLCalloc(sizeof(double), *pnMaxPoints)); |
819 | 394 | } |
820 | 747 | (*ppadfZ)[*pnPointsRead] = (*ppadfM)[*pnPointsRead]; |
821 | 747 | (*ppadfM)[*pnPointsRead] = CPLAtof(szDelim); |
822 | 747 | pszInput = OGRWktReadToken(pszInput, szDelim); |
823 | 747 | } |
824 | | |
825 | | /* -------------------------------------------------------------------- |
826 | | */ |
827 | | /* Increase points index. */ |
828 | | /* -------------------------------------------------------------------- |
829 | | */ |
830 | 39.9k | ++(*pnPointsRead); |
831 | | |
832 | | /* -------------------------------------------------------------------- |
833 | | */ |
834 | | /* The next delimiter should be a comma or an ending bracket. */ |
835 | | /* -------------------------------------------------------------------- |
836 | | */ |
837 | 39.9k | if (szDelim[0] != ')' && szDelim[0] != ',') |
838 | 398 | { |
839 | 398 | CPLDebug("OGR", |
840 | 398 | "Corrupt input in OGRWktReadPointsM() " |
841 | 398 | "Got `%s' when expecting `,' or `)', near `%s' in %s.", |
842 | 398 | szDelim, pszInput, pszOrigInput); |
843 | 398 | return nullptr; |
844 | 398 | } |
845 | 39.9k | } while (szDelim[0] == ','); |
846 | | |
847 | 8.79k | return pszInput; |
848 | 9.40k | } |
849 | | |
850 | | /************************************************************************/ |
851 | | /* OGRMalloc() */ |
852 | | /* */ |
853 | | /* Cover for CPLMalloc() */ |
854 | | /************************************************************************/ |
855 | | |
856 | | void *OGRMalloc(size_t size) |
857 | | |
858 | 0 | { |
859 | 0 | return CPLMalloc(size); |
860 | 0 | } |
861 | | |
862 | | /************************************************************************/ |
863 | | /* OGRCalloc() */ |
864 | | /* */ |
865 | | /* Cover for CPLCalloc() */ |
866 | | /************************************************************************/ |
867 | | |
868 | | void *OGRCalloc(size_t count, size_t size) |
869 | | |
870 | 0 | { |
871 | 0 | return CPLCalloc(count, size); |
872 | 0 | } |
873 | | |
874 | | /************************************************************************/ |
875 | | /* OGRRealloc() */ |
876 | | /* */ |
877 | | /* Cover for CPLRealloc() */ |
878 | | /************************************************************************/ |
879 | | |
880 | | void *OGRRealloc(void *pOld, size_t size) |
881 | | |
882 | 0 | { |
883 | 0 | return CPLRealloc(pOld, size); |
884 | 0 | } |
885 | | |
886 | | /************************************************************************/ |
887 | | /* OGRFree() */ |
888 | | /* */ |
889 | | /* Cover for CPLFree(). */ |
890 | | /************************************************************************/ |
891 | | |
892 | | void OGRFree(void *pMemory) |
893 | | |
894 | 0 | { |
895 | 0 | CPLFree(pMemory); |
896 | 0 | } |
897 | | |
898 | | /** |
899 | | * \fn OGRGeneralCmdLineProcessor(int, char***, int) |
900 | | * General utility option processing. |
901 | | * |
902 | | * This function is intended to provide a variety of generic commandline |
903 | | * options for all OGR commandline utilities. It takes care of the following |
904 | | * commandline options: |
905 | | * |
906 | | * \--version: report version of GDAL in use. |
907 | | * \--license: report GDAL license info. |
908 | | * \--format [format]: report details of one format driver. |
909 | | * \--formats: report all format drivers configured. |
910 | | * \--optfile filename: expand an option file into the argument list. |
911 | | * \--config key value: set system configuration option. |
912 | | * \--debug [on/off/value]: set debug level. |
913 | | * \--pause: Pause for user input (allows time to attach debugger) |
914 | | * \--locale [locale]: Install a locale using setlocale() (debugging) |
915 | | * \--help-general: report detailed help on general options. |
916 | | * |
917 | | * The argument array is replaced "in place" and should be freed with |
918 | | * CSLDestroy() when no longer needed. The typical usage looks something |
919 | | * like the following. Note that the formats should be registered so that |
920 | | * the \--formats option will work properly. |
921 | | * |
922 | | * int main( int argc, char ** argv ) |
923 | | * { |
924 | | * OGRRegisterAll(); |
925 | | * |
926 | | * argc = OGRGeneralCmdLineProcessor( argc, &argv, 0 ); |
927 | | * if( argc < 1 ) |
928 | | * exit( -argc ); |
929 | | * |
930 | | * @param nArgc number of values in the argument list. |
931 | | * @param ppapszArgv pointer to the argument list array (will be updated in |
932 | | * place). |
933 | | * @param nOptions unused. |
934 | | * |
935 | | * @return updated nArgc argument count. Return of 0 requests terminate |
936 | | * without error, return of -1 requests exit with error code. |
937 | | */ |
938 | | |
939 | | int OGRGeneralCmdLineProcessor(int nArgc, char ***ppapszArgv, |
940 | | CPL_UNUSED int nOptions) |
941 | | |
942 | 0 | { |
943 | 0 | return GDALGeneralCmdLineProcessor(nArgc, ppapszArgv, GDAL_OF_VECTOR); |
944 | 0 | } |
945 | | |
946 | | /************************************************************************/ |
947 | | /* OGRTimezoneToTZFlag() */ |
948 | | /************************************************************************/ |
949 | | |
950 | | /** \brief Converts a text timezone into OGR TZFlag integer representation. |
951 | | * |
952 | | * @param pszTZ "UTC", "Etc/UTC", or "(+/-)[0-9][0-9](:?)[0-9][0-9]" |
953 | | * @param bEmitErrorIfUnhandledFormat Whether to emit an error if pszTZ is |
954 | | * a non-empty string with unrecognized |
955 | | * format. |
956 | | */ |
957 | | int OGRTimezoneToTZFlag(const char *pszTZ, bool bEmitErrorIfUnhandledFormat) |
958 | 0 | { |
959 | 0 | int nTZFlag = OGR_TZFLAG_UNKNOWN; |
960 | 0 | const size_t nTZLen = strlen(pszTZ); |
961 | 0 | if (strcmp(pszTZ, "UTC") == 0 || strcmp(pszTZ, "Etc/UTC") == 0) |
962 | 0 | { |
963 | 0 | nTZFlag = OGR_TZFLAG_UTC; |
964 | 0 | } |
965 | 0 | else if ((pszTZ[0] == '+' || pszTZ[0] == '-') && |
966 | 0 | ((nTZLen == 6 && pszTZ[3] == ':') || |
967 | 0 | (nTZLen == 5 && pszTZ[3] >= '0' && pszTZ[3] <= '9'))) |
968 | 0 | { |
969 | 0 | int nTZHour = atoi(pszTZ + 1); |
970 | 0 | int nTZMin = atoi(pszTZ + (nTZLen == 6 ? 4 : 3)); |
971 | 0 | if (nTZHour >= 0 && nTZHour <= 14 && nTZMin >= 0 && nTZMin < 60 && |
972 | 0 | (nTZMin % 15) == 0) |
973 | 0 | { |
974 | 0 | nTZFlag = (nTZHour * 4) + (nTZMin / 15); |
975 | 0 | if (pszTZ[0] == '+') |
976 | 0 | { |
977 | 0 | nTZFlag = OGR_TZFLAG_UTC + nTZFlag; |
978 | 0 | } |
979 | 0 | else |
980 | 0 | { |
981 | 0 | nTZFlag = OGR_TZFLAG_UTC - nTZFlag; |
982 | 0 | } |
983 | 0 | } |
984 | 0 | } |
985 | 0 | else if (pszTZ[0] != 0 && bEmitErrorIfUnhandledFormat) |
986 | 0 | { |
987 | 0 | CPLError(CE_Failure, CPLE_AppDefined, "Unrecognized timezone: '%s'", |
988 | 0 | pszTZ); |
989 | 0 | } |
990 | 0 | return nTZFlag; |
991 | 0 | } |
992 | | |
993 | | /************************************************************************/ |
994 | | /* OGRTZFlagToTimezone() */ |
995 | | /************************************************************************/ |
996 | | |
997 | | /** \brief Converts a OGR TZFlag integer representation into a string |
998 | | * |
999 | | * @param nTZFlag OGR TZFlag integer (only ones with |
1000 | | * value > OGR_TZFLAG_MIXED_TZ will yield a non-empty output) |
1001 | | * @param pszUTCRepresentation String to return if nTZFlag == OGR_TZFLAG_UTC. |
1002 | | * Typically "UTC", "Z", or "+00:00" |
1003 | | */ |
1004 | | std::string OGRTZFlagToTimezone(int nTZFlag, const char *pszUTCRepresentation) |
1005 | 0 | { |
1006 | 0 | if (nTZFlag == OGR_TZFLAG_UTC) |
1007 | 0 | { |
1008 | 0 | return pszUTCRepresentation; |
1009 | 0 | } |
1010 | 0 | else if (nTZFlag > OGR_TZFLAG_MIXED_TZ) |
1011 | 0 | { |
1012 | 0 | char chSign; |
1013 | 0 | const int nOffset = (nTZFlag - OGR_TZFLAG_UTC) * 15; |
1014 | 0 | int nHours = static_cast<int>(nOffset / 60); // Round towards zero. |
1015 | 0 | const int nMinutes = std::abs(nOffset - nHours * 60); |
1016 | |
|
1017 | 0 | if (nOffset < 0) |
1018 | 0 | { |
1019 | 0 | chSign = '-'; |
1020 | 0 | nHours = std::abs(nHours); |
1021 | 0 | } |
1022 | 0 | else |
1023 | 0 | { |
1024 | 0 | chSign = '+'; |
1025 | 0 | } |
1026 | 0 | return CPLSPrintf("%c%02d:%02d", chSign, nHours, nMinutes); |
1027 | 0 | } |
1028 | 0 | else |
1029 | 0 | { |
1030 | 0 | return std::string(); |
1031 | 0 | } |
1032 | 0 | } |
1033 | | |
1034 | | /************************************************************************/ |
1035 | | /* OGRParseDate() */ |
1036 | | /* */ |
1037 | | /* Parse a variety of text date formats into an OGRField. */ |
1038 | | /************************************************************************/ |
1039 | | |
1040 | | /** |
1041 | | * Parse date string. |
1042 | | * |
1043 | | * This function attempts to parse a date string in a variety of formats |
1044 | | * into the OGRField.Date format suitable for use with OGR. Generally |
1045 | | * speaking this function is expecting values like: |
1046 | | * |
1047 | | * YYYY-MM-DD HH:MM:SS(.sss)?+nn |
1048 | | * or YYYY-MM-DDTHH:MM:SS(.sss)?Z (ISO 8601 format) |
1049 | | * or YYYY-MM-DDZ |
1050 | | * or THH:MM(:SS(.sss)?)?Z? (ISO 8601 extended format) |
1051 | | * or THHMM(SS(.sss)?)?Z? (ISO 8601 basic format) |
1052 | | * |
1053 | | * The seconds may also have a decimal portion (parsed as milliseconds). And |
1054 | | * just dates (YYYY-MM-DD) or just times (HH:MM:SS[.sss]) are also supported. |
1055 | | * The date may also be in YYYY/MM/DD format. If the year is less than 100 |
1056 | | * and greater than 30 a "1900" century value will be set. If it is less than |
1057 | | * 30 and greater than -1 then a "2000" century value will be set. In |
1058 | | * the future this function may be generalized, and additional control |
1059 | | * provided through nOptions, but an nOptions value of "0" should always do |
1060 | | * a reasonable default form of processing. |
1061 | | * |
1062 | | * The value of psField will be indeterminate if the function fails (returns |
1063 | | * FALSE). |
1064 | | * |
1065 | | * @param pszInput the input date string. |
1066 | | * @param psField the OGRField that will be updated with the parsed result. |
1067 | | * @param nOptions parsing options. 0 or OGRPARSEDATE_OPTION_LAX |
1068 | | * |
1069 | | * @return TRUE if apparently successful or FALSE on failure. |
1070 | | */ |
1071 | | |
1072 | | int OGRParseDate(const char *pszInput, OGRField *psField, int nOptions) |
1073 | 0 | { |
1074 | 0 | psField->Date.Year = 0; |
1075 | 0 | psField->Date.Month = 0; |
1076 | 0 | psField->Date.Day = 0; |
1077 | 0 | psField->Date.Hour = 0; |
1078 | 0 | psField->Date.Minute = 0; |
1079 | 0 | psField->Date.Second = 0; |
1080 | 0 | psField->Date.TZFlag = 0; |
1081 | 0 | psField->Date.Reserved = 0; |
1082 | | |
1083 | | /* -------------------------------------------------------------------- */ |
1084 | | /* Do we have a date? */ |
1085 | | /* -------------------------------------------------------------------- */ |
1086 | 0 | for (int i = 0; i < 256 && *pszInput == ' '; ++i) |
1087 | 0 | ++pszInput; |
1088 | |
|
1089 | 0 | bool bGotSomething = false; |
1090 | 0 | bool bTFound = false; |
1091 | 0 | if (strchr(pszInput, '-') || strchr(pszInput, '/')) |
1092 | 0 | { |
1093 | 0 | if (!(*pszInput == '-' || *pszInput == '+' || |
1094 | 0 | (*pszInput >= '0' && *pszInput <= '9'))) |
1095 | 0 | return FALSE; |
1096 | 0 | int nYear = atoi(pszInput); |
1097 | 0 | if (nYear > std::numeric_limits<GInt16>::max() || |
1098 | 0 | nYear < std::numeric_limits<GInt16>::min()) |
1099 | 0 | { |
1100 | 0 | CPLError(CE_Failure, CPLE_NotSupported, |
1101 | 0 | "Years < %d or > %d are not supported", |
1102 | 0 | std::numeric_limits<GInt16>::min(), |
1103 | 0 | std::numeric_limits<GInt16>::max()); |
1104 | 0 | return FALSE; |
1105 | 0 | } |
1106 | 0 | psField->Date.Year = static_cast<GInt16>(nYear); |
1107 | 0 | if ((pszInput[1] == '-' || pszInput[1] == '/') || |
1108 | 0 | (pszInput[1] != '\0' && (pszInput[2] == '-' || pszInput[2] == '/'))) |
1109 | 0 | { |
1110 | 0 | if (psField->Date.Year < 100 && psField->Date.Year >= 30) |
1111 | 0 | psField->Date.Year += 1900; |
1112 | 0 | else if (psField->Date.Year < 30 && psField->Date.Year >= 0) |
1113 | 0 | psField->Date.Year += 2000; |
1114 | 0 | } |
1115 | |
|
1116 | 0 | if (*pszInput == '-') |
1117 | 0 | ++pszInput; |
1118 | 0 | for (int i = 0; i < 5 && *pszInput >= '0' && *pszInput <= '9'; ++i) |
1119 | 0 | ++pszInput; |
1120 | 0 | if (*pszInput != '-' && *pszInput != '/') |
1121 | 0 | return FALSE; |
1122 | 0 | else |
1123 | 0 | ++pszInput; |
1124 | | |
1125 | 0 | if (!(*pszInput >= '0' && *pszInput <= '9')) |
1126 | 0 | return FALSE; |
1127 | 0 | if (!(pszInput[1] >= '0' && pszInput[1] <= '9')) |
1128 | 0 | { |
1129 | 0 | if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0) |
1130 | 0 | return FALSE; |
1131 | 0 | const int nMonth = (pszInput[0] - '0'); |
1132 | 0 | if (nMonth == 0) |
1133 | 0 | return FALSE; |
1134 | 0 | psField->Date.Month = static_cast<GByte>(nMonth); |
1135 | 0 | ++pszInput; |
1136 | 0 | } |
1137 | 0 | else |
1138 | 0 | { |
1139 | 0 | const int nMonth = (pszInput[0] - '0') * 10 + (pszInput[1] - '0'); |
1140 | 0 | if (nMonth == 0 || nMonth > 12) |
1141 | 0 | return FALSE; |
1142 | 0 | psField->Date.Month = static_cast<GByte>(nMonth); |
1143 | |
|
1144 | 0 | pszInput += 2; |
1145 | 0 | } |
1146 | 0 | if (*pszInput != '-' && *pszInput != '/') |
1147 | 0 | return FALSE; |
1148 | 0 | else |
1149 | 0 | ++pszInput; |
1150 | | |
1151 | 0 | if (!(*pszInput >= '0' && *pszInput <= '9')) |
1152 | 0 | return FALSE; |
1153 | 0 | if (!(pszInput[1] >= '0' && pszInput[1] <= '9')) |
1154 | 0 | { |
1155 | 0 | if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0) |
1156 | 0 | return FALSE; |
1157 | 0 | const int nDay = (pszInput[0] - '0'); |
1158 | 0 | if (nDay == 0) |
1159 | 0 | return FALSE; |
1160 | 0 | psField->Date.Day = static_cast<GByte>(nDay); |
1161 | 0 | ++pszInput; |
1162 | 0 | } |
1163 | 0 | else |
1164 | 0 | { |
1165 | 0 | const int nDay = (pszInput[0] - '0') * 10 + (pszInput[1] - '0'); |
1166 | 0 | if (nDay == 0 || nDay > 31) |
1167 | 0 | return FALSE; |
1168 | 0 | psField->Date.Day = static_cast<GByte>(nDay); |
1169 | |
|
1170 | 0 | pszInput += 2; |
1171 | 0 | } |
1172 | 0 | if (*pszInput == '\0') |
1173 | 0 | return TRUE; |
1174 | | |
1175 | 0 | bGotSomething = true; |
1176 | | |
1177 | | // If ISO 8601 format. |
1178 | 0 | if (*pszInput == 'T') |
1179 | 0 | { |
1180 | 0 | bTFound = true; |
1181 | 0 | ++pszInput; |
1182 | 0 | } |
1183 | 0 | else if (*pszInput == 'Z') |
1184 | 0 | return TRUE; |
1185 | 0 | else if (*pszInput != ' ') |
1186 | 0 | return FALSE; |
1187 | 0 | } |
1188 | | |
1189 | | /* -------------------------------------------------------------------- */ |
1190 | | /* Do we have a time? */ |
1191 | | /* -------------------------------------------------------------------- */ |
1192 | 0 | for (int i = 0; i < 256 && *pszInput == ' '; ++i) |
1193 | 0 | ++pszInput; |
1194 | 0 | if (*pszInput == 'T') |
1195 | 0 | { |
1196 | 0 | bTFound = true; |
1197 | 0 | ++pszInput; |
1198 | 0 | } |
1199 | |
|
1200 | 0 | if (bTFound || strchr(pszInput, ':')) |
1201 | 0 | { |
1202 | 0 | if (!(*pszInput >= '0' && *pszInput <= '9')) |
1203 | 0 | return FALSE; |
1204 | 0 | if (!(pszInput[1] >= '0' && pszInput[1] <= '9')) |
1205 | 0 | { |
1206 | 0 | if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0) |
1207 | 0 | return FALSE; |
1208 | | |
1209 | 0 | if (!((bTFound || pszInput[1] == ':'))) |
1210 | 0 | return FALSE; |
1211 | 0 | const int nHour = (pszInput[0] - '0'); |
1212 | 0 | psField->Date.Hour = static_cast<GByte>(nHour); |
1213 | |
|
1214 | 0 | pszInput++; |
1215 | 0 | } |
1216 | 0 | else |
1217 | 0 | { |
1218 | 0 | if (!((bTFound || pszInput[2] == ':'))) |
1219 | 0 | return FALSE; |
1220 | 0 | const int nHour = (pszInput[0] - '0') * 10 + (pszInput[1] - '0'); |
1221 | 0 | if (nHour > 23) |
1222 | 0 | return FALSE; |
1223 | 0 | psField->Date.Hour = static_cast<GByte>(nHour); |
1224 | |
|
1225 | 0 | pszInput += 2; |
1226 | 0 | } |
1227 | 0 | if (*pszInput == ':') |
1228 | 0 | ++pszInput; |
1229 | |
|
1230 | 0 | if (!(*pszInput >= '0' && *pszInput <= '9')) |
1231 | 0 | return FALSE; |
1232 | 0 | if (!(pszInput[1] >= '0' && pszInput[1] <= '9')) |
1233 | 0 | { |
1234 | 0 | if ((nOptions & OGRPARSEDATE_OPTION_LAX) == 0) |
1235 | 0 | return FALSE; |
1236 | | |
1237 | 0 | const int nMinute = (pszInput[0] - '0'); |
1238 | 0 | psField->Date.Minute = static_cast<GByte>(nMinute); |
1239 | |
|
1240 | 0 | pszInput++; |
1241 | 0 | } |
1242 | 0 | else |
1243 | 0 | { |
1244 | 0 | const int nMinute = (pszInput[0] - '0') * 10 + (pszInput[1] - '0'); |
1245 | 0 | if (nMinute > 59) |
1246 | 0 | return FALSE; |
1247 | 0 | psField->Date.Minute = static_cast<GByte>(nMinute); |
1248 | |
|
1249 | 0 | pszInput += 2; |
1250 | 0 | } |
1251 | | |
1252 | 0 | if ((bTFound && *pszInput >= '0' && *pszInput <= '9') || |
1253 | 0 | *pszInput == ':') |
1254 | 0 | { |
1255 | 0 | if (*pszInput == ':') |
1256 | 0 | ++pszInput; |
1257 | |
|
1258 | 0 | if (!(*pszInput >= '0' && *pszInput <= '9' && |
1259 | 0 | (((nOptions & OGRPARSEDATE_OPTION_LAX) != 0) || |
1260 | 0 | (pszInput[1] >= '0' && pszInput[1] <= '9')))) |
1261 | 0 | return FALSE; |
1262 | 0 | const double dfSeconds = CPLAtof(pszInput); |
1263 | | // We accept second=60 for leap seconds |
1264 | 0 | if (dfSeconds > 60.0) |
1265 | 0 | return FALSE; |
1266 | 0 | psField->Date.Second = static_cast<float>(dfSeconds); |
1267 | |
|
1268 | 0 | pszInput += 2; |
1269 | 0 | if (*pszInput == '.') |
1270 | 0 | { |
1271 | 0 | ++pszInput; |
1272 | 0 | while (*pszInput >= '0' && *pszInput <= '9') |
1273 | 0 | { |
1274 | 0 | ++pszInput; |
1275 | 0 | } |
1276 | 0 | } |
1277 | | |
1278 | | // If ISO 8601 format. |
1279 | 0 | if (*pszInput == 'Z') |
1280 | 0 | { |
1281 | 0 | psField->Date.TZFlag = 100; |
1282 | 0 | } |
1283 | 0 | } |
1284 | | |
1285 | 0 | bGotSomething = true; |
1286 | 0 | } |
1287 | 0 | else if (bGotSomething && *pszInput != '\0') |
1288 | 0 | return FALSE; |
1289 | | |
1290 | | // No date or time! |
1291 | 0 | if (!bGotSomething) |
1292 | 0 | return FALSE; |
1293 | | |
1294 | | /* -------------------------------------------------------------------- */ |
1295 | | /* Do we have a timezone? */ |
1296 | | /* -------------------------------------------------------------------- */ |
1297 | 0 | while (*pszInput == ' ') |
1298 | 0 | ++pszInput; |
1299 | |
|
1300 | 0 | if (*pszInput == '-' || *pszInput == '+') |
1301 | 0 | { |
1302 | | // +HH integral offset |
1303 | 0 | if (strlen(pszInput) <= 3) |
1304 | 0 | { |
1305 | 0 | psField->Date.TZFlag = static_cast<GByte>(100 + atoi(pszInput) * 4); |
1306 | 0 | } |
1307 | 0 | else if (pszInput[3] == ':' // +HH:MM offset |
1308 | 0 | && atoi(pszInput + 4) % 15 == 0) |
1309 | 0 | { |
1310 | 0 | psField->Date.TZFlag = static_cast<GByte>( |
1311 | 0 | 100 + atoi(pszInput + 1) * 4 + (atoi(pszInput + 4) / 15)); |
1312 | |
|
1313 | 0 | if (pszInput[0] == '-') |
1314 | 0 | psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100; |
1315 | 0 | } |
1316 | 0 | else if (isdigit(static_cast<unsigned char>(pszInput[3])) && |
1317 | 0 | isdigit( |
1318 | 0 | static_cast<unsigned char>(pszInput[4])) // +HHMM offset |
1319 | 0 | && atoi(pszInput + 3) % 15 == 0) |
1320 | 0 | { |
1321 | 0 | psField->Date.TZFlag = static_cast<GByte>( |
1322 | 0 | 100 + static_cast<GByte>(CPLScanLong(pszInput + 1, 2)) * 4 + |
1323 | 0 | (atoi(pszInput + 3) / 15)); |
1324 | |
|
1325 | 0 | if (pszInput[0] == '-') |
1326 | 0 | psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100; |
1327 | 0 | } |
1328 | 0 | else if (isdigit(static_cast<unsigned char>(pszInput[3])) && |
1329 | 0 | pszInput[4] == '\0' // +HMM offset |
1330 | 0 | && atoi(pszInput + 2) % 15 == 0) |
1331 | 0 | { |
1332 | 0 | psField->Date.TZFlag = static_cast<GByte>( |
1333 | 0 | 100 + static_cast<GByte>(CPLScanLong(pszInput + 1, 1)) * 4 + |
1334 | 0 | (atoi(pszInput + 2) / 15)); |
1335 | |
|
1336 | 0 | if (pszInput[0] == '-') |
1337 | 0 | psField->Date.TZFlag = -1 * (psField->Date.TZFlag - 100) + 100; |
1338 | 0 | } |
1339 | | // otherwise ignore any timezone info. |
1340 | 0 | } |
1341 | |
|
1342 | 0 | return TRUE; |
1343 | 0 | } |
1344 | | |
1345 | | /************************************************************************/ |
1346 | | /* OGRParseDateTimeYYYYMMDDTHHMMZ() */ |
1347 | | /************************************************************************/ |
1348 | | |
1349 | | bool OGRParseDateTimeYYYYMMDDTHHMMZ(std::string_view sInput, OGRField *psField) |
1350 | 0 | { |
1351 | | // Detect "YYYY-MM-DDTHH:MM[Z]" (16 or 17 characters) |
1352 | 0 | if ((sInput.size() == 16 || (sInput.size() == 17 && sInput[16] == 'Z')) && |
1353 | 0 | sInput[4] == '-' && sInput[7] == '-' && sInput[10] == 'T' && |
1354 | 0 | sInput[13] == ':' && static_cast<unsigned>(sInput[0] - '0') <= 9 && |
1355 | 0 | static_cast<unsigned>(sInput[1] - '0') <= 9 && |
1356 | 0 | static_cast<unsigned>(sInput[2] - '0') <= 9 && |
1357 | 0 | static_cast<unsigned>(sInput[3] - '0') <= 9 && |
1358 | 0 | static_cast<unsigned>(sInput[5] - '0') <= 9 && |
1359 | 0 | static_cast<unsigned>(sInput[6] - '0') <= 9 && |
1360 | 0 | static_cast<unsigned>(sInput[8] - '0') <= 9 && |
1361 | 0 | static_cast<unsigned>(sInput[9] - '0') <= 9 && |
1362 | 0 | static_cast<unsigned>(sInput[11] - '0') <= 9 && |
1363 | 0 | static_cast<unsigned>(sInput[12] - '0') <= 9 && |
1364 | 0 | static_cast<unsigned>(sInput[14] - '0') <= 9 && |
1365 | 0 | static_cast<unsigned>(sInput[15] - '0') <= 9) |
1366 | 0 | { |
1367 | 0 | psField->Date.Year = static_cast<GInt16>( |
1368 | 0 | ((((sInput[0] - '0') * 10 + (sInput[1] - '0')) * 10) + |
1369 | 0 | (sInput[2] - '0')) * |
1370 | 0 | 10 + |
1371 | 0 | (sInput[3] - '0')); |
1372 | 0 | psField->Date.Month = |
1373 | 0 | static_cast<GByte>((sInput[5] - '0') * 10 + (sInput[6] - '0')); |
1374 | 0 | psField->Date.Day = |
1375 | 0 | static_cast<GByte>((sInput[8] - '0') * 10 + (sInput[9] - '0')); |
1376 | 0 | psField->Date.Hour = |
1377 | 0 | static_cast<GByte>((sInput[11] - '0') * 10 + (sInput[12] - '0')); |
1378 | 0 | psField->Date.Minute = |
1379 | 0 | static_cast<GByte>((sInput[14] - '0') * 10 + (sInput[15] - '0')); |
1380 | 0 | psField->Date.Second = 0.0f; |
1381 | 0 | psField->Date.TZFlag = sInput.size() == 16 ? 0 : 100; |
1382 | 0 | psField->Date.Reserved = 0; |
1383 | 0 | if (psField->Date.Month == 0 || psField->Date.Month > 12 || |
1384 | 0 | psField->Date.Day == 0 || psField->Date.Day > 31 || |
1385 | 0 | psField->Date.Hour > 23 || psField->Date.Minute > 59) |
1386 | 0 | { |
1387 | 0 | return false; |
1388 | 0 | } |
1389 | 0 | return true; |
1390 | 0 | } |
1391 | | |
1392 | 0 | return false; |
1393 | 0 | } |
1394 | | |
1395 | | /************************************************************************/ |
1396 | | /* OGRParseDateTimeYYYYMMDDTHHMMSSZ() */ |
1397 | | /************************************************************************/ |
1398 | | |
1399 | | bool OGRParseDateTimeYYYYMMDDTHHMMSSZ(std::string_view sInput, |
1400 | | OGRField *psField) |
1401 | 0 | { |
1402 | | // Detect "YYYY-MM-DDTHH:MM:SS[Z]" (19 or 20 characters) |
1403 | 0 | if ((sInput.size() == 19 || (sInput.size() == 20 && sInput[19] == 'Z')) && |
1404 | 0 | sInput[4] == '-' && sInput[7] == '-' && sInput[10] == 'T' && |
1405 | 0 | sInput[13] == ':' && sInput[16] == ':' && |
1406 | 0 | static_cast<unsigned>(sInput[0] - '0') <= 9 && |
1407 | 0 | static_cast<unsigned>(sInput[1] - '0') <= 9 && |
1408 | 0 | static_cast<unsigned>(sInput[2] - '0') <= 9 && |
1409 | 0 | static_cast<unsigned>(sInput[3] - '0') <= 9 && |
1410 | 0 | static_cast<unsigned>(sInput[5] - '0') <= 9 && |
1411 | 0 | static_cast<unsigned>(sInput[6] - '0') <= 9 && |
1412 | 0 | static_cast<unsigned>(sInput[8] - '0') <= 9 && |
1413 | 0 | static_cast<unsigned>(sInput[9] - '0') <= 9 && |
1414 | 0 | static_cast<unsigned>(sInput[11] - '0') <= 9 && |
1415 | 0 | static_cast<unsigned>(sInput[12] - '0') <= 9 && |
1416 | 0 | static_cast<unsigned>(sInput[14] - '0') <= 9 && |
1417 | 0 | static_cast<unsigned>(sInput[15] - '0') <= 9 && |
1418 | 0 | static_cast<unsigned>(sInput[17] - '0') <= 9 && |
1419 | 0 | static_cast<unsigned>(sInput[18] - '0') <= 9) |
1420 | 0 | { |
1421 | 0 | psField->Date.Year = static_cast<GInt16>( |
1422 | 0 | ((((sInput[0] - '0') * 10 + (sInput[1] - '0')) * 10) + |
1423 | 0 | (sInput[2] - '0')) * |
1424 | 0 | 10 + |
1425 | 0 | (sInput[3] - '0')); |
1426 | 0 | psField->Date.Month = |
1427 | 0 | static_cast<GByte>((sInput[5] - '0') * 10 + (sInput[6] - '0')); |
1428 | 0 | psField->Date.Day = |
1429 | 0 | static_cast<GByte>((sInput[8] - '0') * 10 + (sInput[9] - '0')); |
1430 | 0 | psField->Date.Hour = |
1431 | 0 | static_cast<GByte>((sInput[11] - '0') * 10 + (sInput[12] - '0')); |
1432 | 0 | psField->Date.Minute = |
1433 | 0 | static_cast<GByte>((sInput[14] - '0') * 10 + (sInput[15] - '0')); |
1434 | 0 | psField->Date.Second = |
1435 | 0 | static_cast<float>(((sInput[17] - '0') * 10 + (sInput[18] - '0'))); |
1436 | 0 | psField->Date.TZFlag = sInput.size() == 19 ? 0 : 100; |
1437 | 0 | psField->Date.Reserved = 0; |
1438 | 0 | if (psField->Date.Month == 0 || psField->Date.Month > 12 || |
1439 | 0 | psField->Date.Day == 0 || psField->Date.Day > 31 || |
1440 | 0 | psField->Date.Hour > 23 || psField->Date.Minute > 59 || |
1441 | 0 | psField->Date.Second >= 61.0f) |
1442 | 0 | { |
1443 | 0 | return false; |
1444 | 0 | } |
1445 | 0 | return true; |
1446 | 0 | } |
1447 | | |
1448 | 0 | return false; |
1449 | 0 | } |
1450 | | |
1451 | | /************************************************************************/ |
1452 | | /* OGRParseDateTimeYYYYMMDDTHHMMSSsssZ() */ |
1453 | | /************************************************************************/ |
1454 | | |
1455 | | bool OGRParseDateTimeYYYYMMDDTHHMMSSsssZ(std::string_view sInput, |
1456 | | OGRField *psField) |
1457 | 0 | { |
1458 | | // Detect "YYYY-MM-DDTHH:MM:SS.SSS[Z]" (23 or 24 characters) |
1459 | 0 | if ((sInput.size() == 23 || (sInput.size() == 24 && sInput[23] == 'Z')) && |
1460 | 0 | sInput[4] == '-' && sInput[7] == '-' && sInput[10] == 'T' && |
1461 | 0 | sInput[13] == ':' && sInput[16] == ':' && sInput[19] == '.' && |
1462 | 0 | static_cast<unsigned>(sInput[0] - '0') <= 9 && |
1463 | 0 | static_cast<unsigned>(sInput[1] - '0') <= 9 && |
1464 | 0 | static_cast<unsigned>(sInput[2] - '0') <= 9 && |
1465 | 0 | static_cast<unsigned>(sInput[3] - '0') <= 9 && |
1466 | 0 | static_cast<unsigned>(sInput[5] - '0') <= 9 && |
1467 | 0 | static_cast<unsigned>(sInput[6] - '0') <= 9 && |
1468 | 0 | static_cast<unsigned>(sInput[8] - '0') <= 9 && |
1469 | 0 | static_cast<unsigned>(sInput[9] - '0') <= 9 && |
1470 | 0 | static_cast<unsigned>(sInput[11] - '0') <= 9 && |
1471 | 0 | static_cast<unsigned>(sInput[12] - '0') <= 9 && |
1472 | 0 | static_cast<unsigned>(sInput[14] - '0') <= 9 && |
1473 | 0 | static_cast<unsigned>(sInput[15] - '0') <= 9 && |
1474 | 0 | static_cast<unsigned>(sInput[17] - '0') <= 9 && |
1475 | 0 | static_cast<unsigned>(sInput[18] - '0') <= 9 && |
1476 | 0 | static_cast<unsigned>(sInput[20] - '0') <= 9 && |
1477 | 0 | static_cast<unsigned>(sInput[21] - '0') <= 9 && |
1478 | 0 | static_cast<unsigned>(sInput[22] - '0') <= 9) |
1479 | 0 | { |
1480 | 0 | psField->Date.Year = static_cast<GInt16>( |
1481 | 0 | ((((sInput[0] - '0') * 10 + (sInput[1] - '0')) * 10) + |
1482 | 0 | (sInput[2] - '0')) * |
1483 | 0 | 10 + |
1484 | 0 | (sInput[3] - '0')); |
1485 | 0 | psField->Date.Month = |
1486 | 0 | static_cast<GByte>((sInput[5] - '0') * 10 + (sInput[6] - '0')); |
1487 | 0 | psField->Date.Day = |
1488 | 0 | static_cast<GByte>((sInput[8] - '0') * 10 + (sInput[9] - '0')); |
1489 | 0 | psField->Date.Hour = |
1490 | 0 | static_cast<GByte>((sInput[11] - '0') * 10 + (sInput[12] - '0')); |
1491 | 0 | psField->Date.Minute = |
1492 | 0 | static_cast<GByte>((sInput[14] - '0') * 10 + (sInput[15] - '0')); |
1493 | 0 | psField->Date.Second = |
1494 | 0 | static_cast<float>(((sInput[17] - '0') * 10 + (sInput[18] - '0')) + |
1495 | 0 | ((sInput[20] - '0') * 100 + |
1496 | 0 | (sInput[21] - '0') * 10 + (sInput[22] - '0')) / |
1497 | 0 | 1000.0); |
1498 | 0 | psField->Date.TZFlag = sInput.size() == 23 ? 0 : 100; |
1499 | 0 | psField->Date.Reserved = 0; |
1500 | 0 | if (psField->Date.Month == 0 || psField->Date.Month > 12 || |
1501 | 0 | psField->Date.Day == 0 || psField->Date.Day > 31 || |
1502 | 0 | psField->Date.Hour > 23 || psField->Date.Minute > 59 || |
1503 | 0 | psField->Date.Second >= 61.0f) |
1504 | 0 | { |
1505 | 0 | return false; |
1506 | 0 | } |
1507 | 0 | return true; |
1508 | 0 | } |
1509 | | |
1510 | 0 | return false; |
1511 | 0 | } |
1512 | | |
1513 | | /************************************************************************/ |
1514 | | /* OGRParseXMLDateTime() */ |
1515 | | /************************************************************************/ |
1516 | | |
1517 | | int OGRParseXMLDateTime(const char *pszXMLDateTime, OGRField *psField) |
1518 | 0 | { |
1519 | 0 | int year = 0; |
1520 | 0 | int month = 0; |
1521 | 0 | int day = 0; |
1522 | 0 | int hour = 0; |
1523 | 0 | int minute = 0; |
1524 | 0 | int TZHour = 0; |
1525 | 0 | int TZMinute = 0; |
1526 | 0 | float second = 0; |
1527 | 0 | char c = '\0'; |
1528 | 0 | int TZ = 0; |
1529 | 0 | bool bRet = false; |
1530 | | |
1531 | | // Date is expressed as a UTC date. |
1532 | 0 | if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c", &year, &month, |
1533 | 0 | &day, &hour, &minute, &second, &c) == 7 && |
1534 | 0 | c == 'Z') |
1535 | 0 | { |
1536 | 0 | TZ = 100; |
1537 | 0 | bRet = true; |
1538 | 0 | } |
1539 | | // Date is expressed as a UTC date, with a timezone. |
1540 | 0 | else if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f%c%02d:%02d", |
1541 | 0 | &year, &month, &day, &hour, &minute, &second, &c, &TZHour, |
1542 | 0 | &TZMinute) == 9 && |
1543 | 0 | (c == '+' || c == '-')) |
1544 | 0 | { |
1545 | 0 | TZ = 100 + ((c == '+') ? 1 : -1) * ((TZHour * 60 + TZMinute) / 15); |
1546 | 0 | bRet = true; |
1547 | 0 | } |
1548 | | // Date is expressed into an unknown timezone. |
1549 | 0 | else if (sscanf(pszXMLDateTime, "%04d-%02d-%02dT%02d:%02d:%f", &year, |
1550 | 0 | &month, &day, &hour, &minute, &second) == 6) |
1551 | 0 | { |
1552 | 0 | TZ = 0; |
1553 | 0 | bRet = true; |
1554 | 0 | } |
1555 | | // Date is expressed as a UTC date with only year:month:day. |
1556 | 0 | else if (sscanf(pszXMLDateTime, "%04d-%02d-%02d", &year, &month, &day) == 3) |
1557 | 0 | { |
1558 | 0 | TZ = 0; |
1559 | 0 | bRet = true; |
1560 | 0 | } |
1561 | | // Date is expressed as a UTC date with only year:month. |
1562 | 0 | else if (sscanf(pszXMLDateTime, "%04d-%02d", &year, &month) == 2) |
1563 | 0 | { |
1564 | 0 | TZ = 0; |
1565 | 0 | bRet = true; |
1566 | 0 | day = 1; |
1567 | 0 | } |
1568 | |
|
1569 | 0 | if (!bRet) |
1570 | 0 | return FALSE; |
1571 | | |
1572 | 0 | psField->Date.Year = static_cast<GInt16>(year); |
1573 | 0 | psField->Date.Month = static_cast<GByte>(month); |
1574 | 0 | psField->Date.Day = static_cast<GByte>(day); |
1575 | 0 | psField->Date.Hour = static_cast<GByte>(hour); |
1576 | 0 | psField->Date.Minute = static_cast<GByte>(minute); |
1577 | 0 | psField->Date.Second = second; |
1578 | 0 | psField->Date.TZFlag = static_cast<GByte>(TZ); |
1579 | 0 | psField->Date.Reserved = 0; |
1580 | |
|
1581 | 0 | return TRUE; |
1582 | 0 | } |
1583 | | |
1584 | | /************************************************************************/ |
1585 | | /* OGRParseRFC822DateTime() */ |
1586 | | /************************************************************************/ |
1587 | | |
1588 | | static const char *const aszMonthStr[] = {"Jan", "Feb", "Mar", "Apr", |
1589 | | "May", "Jun", "Jul", "Aug", |
1590 | | "Sep", "Oct", "Nov", "Dec"}; |
1591 | | |
1592 | | int OGRParseRFC822DateTime(const char *pszRFC822DateTime, OGRField *psField) |
1593 | 0 | { |
1594 | 0 | int nYear, nMonth, nDay, nHour, nMinute, nSecond, nTZFlag; |
1595 | 0 | if (!CPLParseRFC822DateTime(pszRFC822DateTime, &nYear, &nMonth, &nDay, |
1596 | 0 | &nHour, &nMinute, &nSecond, &nTZFlag, nullptr)) |
1597 | 0 | { |
1598 | 0 | return false; |
1599 | 0 | } |
1600 | | |
1601 | 0 | psField->Date.Year = static_cast<GInt16>(nYear); |
1602 | 0 | psField->Date.Month = static_cast<GByte>(nMonth); |
1603 | 0 | psField->Date.Day = static_cast<GByte>(nDay); |
1604 | 0 | psField->Date.Hour = static_cast<GByte>(nHour); |
1605 | 0 | psField->Date.Minute = static_cast<GByte>(nMinute); |
1606 | 0 | psField->Date.Second = (nSecond < 0) ? 0.0f : static_cast<float>(nSecond); |
1607 | 0 | psField->Date.TZFlag = static_cast<GByte>(nTZFlag); |
1608 | 0 | psField->Date.Reserved = 0; |
1609 | |
|
1610 | 0 | return true; |
1611 | 0 | } |
1612 | | |
1613 | | /** |
1614 | | * Returns the day of the week in Gregorian calendar |
1615 | | * |
1616 | | * @param day : day of the month, between 1 and 31 |
1617 | | * @param month : month of the year, between 1 (Jan) and 12 (Dec) |
1618 | | * @param year : year |
1619 | | |
1620 | | * @return day of the week : 0 for Monday, ... 6 for Sunday |
1621 | | */ |
1622 | | |
1623 | | int OGRGetDayOfWeek(int day, int month, int year) |
1624 | 0 | { |
1625 | | // Reference: Zeller's congruence. |
1626 | 0 | const int q = day; |
1627 | 0 | int m = month; |
1628 | 0 | if (month >= 3) |
1629 | 0 | { |
1630 | | // m = month; |
1631 | 0 | } |
1632 | 0 | else |
1633 | 0 | { |
1634 | 0 | m = month + 12; |
1635 | 0 | year--; |
1636 | 0 | } |
1637 | 0 | const int K = year % 100; |
1638 | 0 | const int J = year / 100; |
1639 | 0 | const int h = (q + (((m + 1) * 26) / 10) + K + K / 4 + J / 4 + 5 * J) % 7; |
1640 | 0 | return (h + 5) % 7; |
1641 | 0 | } |
1642 | | |
1643 | | /************************************************************************/ |
1644 | | /* OGRGetRFC822DateTime() */ |
1645 | | /************************************************************************/ |
1646 | | |
1647 | | char *OGRGetRFC822DateTime(const OGRField *psField) |
1648 | 0 | { |
1649 | 0 | char *pszTZ = nullptr; |
1650 | 0 | const char *const aszDayOfWeek[] = {"Mon", "Tue", "Wed", "Thu", |
1651 | 0 | "Fri", "Sat", "Sun"}; |
1652 | |
|
1653 | 0 | int dayofweek = OGRGetDayOfWeek(psField->Date.Day, psField->Date.Month, |
1654 | 0 | psField->Date.Year); |
1655 | |
|
1656 | 0 | int month = psField->Date.Month; |
1657 | 0 | if (month < 1 || month > 12) |
1658 | 0 | month = 1; |
1659 | |
|
1660 | 0 | int TZFlag = psField->Date.TZFlag; |
1661 | 0 | if (TZFlag == 0 || TZFlag == 100) |
1662 | 0 | { |
1663 | 0 | pszTZ = CPLStrdup("GMT"); |
1664 | 0 | } |
1665 | 0 | else |
1666 | 0 | { |
1667 | 0 | int TZOffset = std::abs(TZFlag - 100) * 15; |
1668 | 0 | int TZHour = TZOffset / 60; |
1669 | 0 | int TZMinute = TZOffset - TZHour * 60; |
1670 | 0 | pszTZ = CPLStrdup(CPLSPrintf("%c%02d%02d", TZFlag > 100 ? '+' : '-', |
1671 | 0 | TZHour, TZMinute)); |
1672 | 0 | } |
1673 | 0 | char *pszRet = CPLStrdup(CPLSPrintf( |
1674 | 0 | "%s, %02d %s %04d %02d:%02d:%02d %s", aszDayOfWeek[dayofweek], |
1675 | 0 | psField->Date.Day, aszMonthStr[month - 1], psField->Date.Year, |
1676 | 0 | psField->Date.Hour, psField->Date.Minute, |
1677 | 0 | static_cast<int>(psField->Date.Second), pszTZ)); |
1678 | 0 | CPLFree(pszTZ); |
1679 | 0 | return pszRet; |
1680 | 0 | } |
1681 | | |
1682 | | /************************************************************************/ |
1683 | | /* OGRGetXMLDateTime() */ |
1684 | | /************************************************************************/ |
1685 | | |
1686 | | char *OGRGetXMLDateTime(const OGRField *psField) |
1687 | 0 | { |
1688 | 0 | char *pszRet = |
1689 | 0 | static_cast<char *>(CPLMalloc(OGR_SIZEOF_ISO8601_DATETIME_BUFFER)); |
1690 | 0 | OGRGetISO8601DateTime(psField, false, pszRet); |
1691 | 0 | return pszRet; |
1692 | 0 | } |
1693 | | |
1694 | | char *OGRGetXMLDateTime(const OGRField *psField, bool bAlwaysMillisecond) |
1695 | 0 | { |
1696 | 0 | char *pszRet = |
1697 | 0 | static_cast<char *>(CPLMalloc(OGR_SIZEOF_ISO8601_DATETIME_BUFFER)); |
1698 | 0 | OGRGetISO8601DateTime(psField, bAlwaysMillisecond, pszRet); |
1699 | 0 | return pszRet; |
1700 | 0 | } |
1701 | | |
1702 | | int OGRGetISO8601DateTime(const OGRField *psField, bool bAlwaysMillisecond, |
1703 | | char szBuffer[OGR_SIZEOF_ISO8601_DATETIME_BUFFER]) |
1704 | 0 | { |
1705 | 0 | OGRISO8601Format sFormat; |
1706 | 0 | sFormat.ePrecision = bAlwaysMillisecond ? OGRISO8601Precision::MILLISECOND |
1707 | 0 | : OGRISO8601Precision::AUTO; |
1708 | 0 | return OGRGetISO8601DateTime(psField, sFormat, szBuffer); |
1709 | 0 | } |
1710 | | |
1711 | | int OGRGetISO8601DateTime(const OGRField *psField, |
1712 | | const OGRISO8601Format &sFormat, |
1713 | | char szBuffer[OGR_SIZEOF_ISO8601_DATETIME_BUFFER]) |
1714 | 0 | { |
1715 | 0 | const GInt16 year = psField->Date.Year; |
1716 | 0 | const GByte month = psField->Date.Month; |
1717 | 0 | const GByte day = psField->Date.Day; |
1718 | 0 | const GByte hour = psField->Date.Hour; |
1719 | 0 | const GByte minute = psField->Date.Minute; |
1720 | 0 | const float second = psField->Date.Second; |
1721 | 0 | const GByte TZFlag = psField->Date.TZFlag; |
1722 | |
|
1723 | 0 | if (year < 0 || year >= 10000) |
1724 | 0 | { |
1725 | 0 | CPLError(CE_Failure, CPLE_AppDefined, |
1726 | 0 | "OGRGetISO8601DateTime(): year %d unsupported ", year); |
1727 | 0 | szBuffer[0] = 0; |
1728 | 0 | return 0; |
1729 | 0 | } |
1730 | | |
1731 | 0 | int nYear = year; |
1732 | 0 | szBuffer[3] = (nYear % 10) + '0'; |
1733 | 0 | nYear /= 10; |
1734 | 0 | szBuffer[2] = (nYear % 10) + '0'; |
1735 | 0 | nYear /= 10; |
1736 | 0 | szBuffer[1] = (nYear % 10) + '0'; |
1737 | 0 | nYear /= 10; |
1738 | 0 | szBuffer[0] = static_cast<char>(nYear /*% 10*/ + '0'); |
1739 | 0 | szBuffer[4] = '-'; |
1740 | 0 | szBuffer[5] = ((month / 10) % 10) + '0'; |
1741 | 0 | szBuffer[6] = (month % 10) + '0'; |
1742 | 0 | szBuffer[7] = '-'; |
1743 | 0 | szBuffer[8] = ((day / 10) % 10) + '0'; |
1744 | 0 | szBuffer[9] = (day % 10) + '0'; |
1745 | 0 | szBuffer[10] = 'T'; |
1746 | 0 | szBuffer[11] = ((hour / 10) % 10) + '0'; |
1747 | 0 | szBuffer[12] = (hour % 10) + '0'; |
1748 | 0 | szBuffer[13] = ':'; |
1749 | 0 | szBuffer[14] = ((minute / 10) % 10) + '0'; |
1750 | 0 | szBuffer[15] = (minute % 10) + '0'; |
1751 | 0 | int nPos; |
1752 | 0 | if (sFormat.ePrecision == OGRISO8601Precision::MINUTE) |
1753 | 0 | { |
1754 | 0 | nPos = 16; |
1755 | 0 | } |
1756 | 0 | else |
1757 | 0 | { |
1758 | 0 | szBuffer[16] = ':'; |
1759 | |
|
1760 | 0 | if (sFormat.ePrecision == OGRISO8601Precision::MILLISECOND || |
1761 | 0 | (sFormat.ePrecision == OGRISO8601Precision::AUTO && |
1762 | 0 | OGR_GET_MS(second))) |
1763 | 0 | { |
1764 | | /* Below is equivalent of the below snprintf(), but hand-made for |
1765 | | * faster execution. */ |
1766 | | /* snprintf(szBuffer, nMaxSize, |
1767 | | "%04d-%02u-%02uT%02u:%02u:%06.3f%s", |
1768 | | year, month, day, hour, minute, second, |
1769 | | szTimeZone); |
1770 | | */ |
1771 | 0 | int nMilliSecond = static_cast<int>(second * 1000.0f + 0.5f); |
1772 | 0 | szBuffer[22] = (nMilliSecond % 10) + '0'; |
1773 | 0 | nMilliSecond /= 10; |
1774 | 0 | szBuffer[21] = (nMilliSecond % 10) + '0'; |
1775 | 0 | nMilliSecond /= 10; |
1776 | 0 | szBuffer[20] = (nMilliSecond % 10) + '0'; |
1777 | 0 | nMilliSecond /= 10; |
1778 | 0 | szBuffer[19] = '.'; |
1779 | 0 | szBuffer[18] = (nMilliSecond % 10) + '0'; |
1780 | 0 | nMilliSecond /= 10; |
1781 | 0 | szBuffer[17] = (nMilliSecond % 10) + '0'; |
1782 | 0 | nPos = 23; |
1783 | 0 | } |
1784 | 0 | else |
1785 | 0 | { |
1786 | | /* Below is equivalent of the below snprintf(), but hand-made for |
1787 | | * faster execution. */ |
1788 | | /* snprintf(szBuffer, nMaxSize, |
1789 | | "%04d-%02u-%02uT%02u:%02u:%02u%s", |
1790 | | year, month, day, hour, minute, |
1791 | | static_cast<GByte>(second), szTimeZone); |
1792 | | */ |
1793 | 0 | int nSecond = static_cast<int>(second + 0.5f); |
1794 | 0 | szBuffer[17] = ((nSecond / 10) % 10) + '0'; |
1795 | 0 | szBuffer[18] = (nSecond % 10) + '0'; |
1796 | 0 | nPos = 19; |
1797 | 0 | } |
1798 | 0 | } |
1799 | |
|
1800 | 0 | switch (TZFlag) |
1801 | 0 | { |
1802 | 0 | case 0: // Unknown time zone |
1803 | 0 | case 1: // Local time zone (not specified) |
1804 | 0 | break; |
1805 | | |
1806 | 0 | case 100: // GMT |
1807 | 0 | szBuffer[nPos++] = 'Z'; |
1808 | 0 | break; |
1809 | | |
1810 | 0 | default: // Offset (in quarter-hour units) from GMT |
1811 | 0 | const int TZOffset = std::abs(TZFlag - 100) * 15; |
1812 | 0 | const int TZHour = TZOffset / 60; |
1813 | 0 | const int TZMinute = TZOffset % 60; |
1814 | |
|
1815 | 0 | szBuffer[nPos++] = (TZFlag > 100) ? '+' : '-'; |
1816 | 0 | szBuffer[nPos++] = ((TZHour / 10) % 10) + '0'; |
1817 | 0 | szBuffer[nPos++] = (TZHour % 10) + '0'; |
1818 | 0 | szBuffer[nPos++] = ':'; |
1819 | 0 | szBuffer[nPos++] = ((TZMinute / 10) % 10) + '0'; |
1820 | 0 | szBuffer[nPos++] = (TZMinute % 10) + '0'; |
1821 | 0 | } |
1822 | | |
1823 | 0 | szBuffer[nPos] = 0; |
1824 | |
|
1825 | 0 | return nPos; |
1826 | 0 | } |
1827 | | |
1828 | | /************************************************************************/ |
1829 | | /* OGRGetXML_UTF8_EscapedString() */ |
1830 | | /************************************************************************/ |
1831 | | |
1832 | | char *OGRGetXML_UTF8_EscapedString(const char *pszString) |
1833 | 0 | { |
1834 | 0 | char *pszEscaped = nullptr; |
1835 | 0 | if (!CPLIsUTF8(pszString, -1) && |
1836 | 0 | CPLTestBool(CPLGetConfigOption("OGR_FORCE_ASCII", "YES"))) |
1837 | 0 | { |
1838 | 0 | static bool bFirstTime = true; |
1839 | 0 | if (bFirstTime) |
1840 | 0 | { |
1841 | 0 | bFirstTime = false; |
1842 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
1843 | 0 | "%s is not a valid UTF-8 string. Forcing it to ASCII. " |
1844 | 0 | "If you still want the original string and change the XML " |
1845 | 0 | "file encoding afterwards, you can define " |
1846 | 0 | "OGR_FORCE_ASCII=NO as configuration option. " |
1847 | 0 | "This warning won't be issued anymore", |
1848 | 0 | pszString); |
1849 | 0 | } |
1850 | 0 | else |
1851 | 0 | { |
1852 | 0 | CPLDebug("OGR", |
1853 | 0 | "%s is not a valid UTF-8 string. Forcing it to ASCII", |
1854 | 0 | pszString); |
1855 | 0 | } |
1856 | 0 | char *pszTemp = CPLForceToASCII(pszString, -1, '?'); |
1857 | 0 | pszEscaped = CPLEscapeString(pszTemp, -1, CPLES_XML); |
1858 | 0 | CPLFree(pszTemp); |
1859 | 0 | } |
1860 | 0 | else |
1861 | 0 | pszEscaped = CPLEscapeString(pszString, -1, CPLES_XML); |
1862 | 0 | return pszEscaped; |
1863 | 0 | } |
1864 | | |
1865 | | /************************************************************************/ |
1866 | | /* OGRCompareDate() */ |
1867 | | /************************************************************************/ |
1868 | | |
1869 | | int OGRCompareDate(const OGRField *psFirstTuple, const OGRField *psSecondTuple) |
1870 | 0 | { |
1871 | | // TODO: We ignore TZFlag. |
1872 | |
|
1873 | 0 | if (psFirstTuple->Date.Year < psSecondTuple->Date.Year) |
1874 | 0 | return -1; |
1875 | 0 | else if (psFirstTuple->Date.Year > psSecondTuple->Date.Year) |
1876 | 0 | return 1; |
1877 | | |
1878 | 0 | if (psFirstTuple->Date.Month < psSecondTuple->Date.Month) |
1879 | 0 | return -1; |
1880 | 0 | else if (psFirstTuple->Date.Month > psSecondTuple->Date.Month) |
1881 | 0 | return 1; |
1882 | | |
1883 | 0 | if (psFirstTuple->Date.Day < psSecondTuple->Date.Day) |
1884 | 0 | return -1; |
1885 | 0 | else if (psFirstTuple->Date.Day > psSecondTuple->Date.Day) |
1886 | 0 | return 1; |
1887 | | |
1888 | 0 | if (psFirstTuple->Date.Hour < psSecondTuple->Date.Hour) |
1889 | 0 | return -1; |
1890 | 0 | else if (psFirstTuple->Date.Hour > psSecondTuple->Date.Hour) |
1891 | 0 | return 1; |
1892 | | |
1893 | 0 | if (psFirstTuple->Date.Minute < psSecondTuple->Date.Minute) |
1894 | 0 | return -1; |
1895 | 0 | else if (psFirstTuple->Date.Minute > psSecondTuple->Date.Minute) |
1896 | 0 | return 1; |
1897 | | |
1898 | 0 | if (psFirstTuple->Date.Second < psSecondTuple->Date.Second) |
1899 | 0 | return -1; |
1900 | 0 | else if (psFirstTuple->Date.Second > psSecondTuple->Date.Second) |
1901 | 0 | return 1; |
1902 | | |
1903 | 0 | return 0; |
1904 | 0 | } |
1905 | | |
1906 | | /************************************************************************/ |
1907 | | /* OGRFastAtof() */ |
1908 | | /************************************************************************/ |
1909 | | |
1910 | | // On Windows, CPLAtof() is very slow if the number is followed by other long |
1911 | | // content. Just extract the number into a short string before calling |
1912 | | // CPLAtof() on it. |
1913 | | static double OGRCallAtofOnShortString(const char *pszStr) |
1914 | 0 | { |
1915 | 0 | const char *p = pszStr; |
1916 | 0 | while (*p == ' ' || *p == '\t') |
1917 | 0 | ++p; |
1918 | |
|
1919 | 0 | char szTemp[128] = {}; |
1920 | 0 | int nCounter = 0; |
1921 | 0 | while (*p == '+' || *p == '-' || (*p >= '0' && *p <= '9') || *p == '.' || |
1922 | 0 | (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D')) |
1923 | 0 | { |
1924 | 0 | szTemp[nCounter++] = *(p++); |
1925 | 0 | if (nCounter == 127) |
1926 | 0 | return CPLAtof(pszStr); |
1927 | 0 | } |
1928 | 0 | szTemp[nCounter] = '\0'; |
1929 | 0 | return CPLAtof(szTemp); |
1930 | 0 | } |
1931 | | |
1932 | | /** Same contract as CPLAtof, except than it doesn't always call the |
1933 | | * system CPLAtof() that may be slow on some platforms. For simple but |
1934 | | * common strings, it'll use a faster implementation (up to 20x faster |
1935 | | * than CPLAtof() on MS runtime libraries) that has no guaranty to return |
1936 | | * exactly the same floating point number. |
1937 | | */ |
1938 | | |
1939 | | double OGRFastAtof(const char *pszStr) |
1940 | 0 | { |
1941 | 0 | double dfVal = 0; |
1942 | 0 | double dfSign = 1.0; |
1943 | 0 | const char *p = pszStr; |
1944 | |
|
1945 | 0 | constexpr double adfTenPower[] = { |
1946 | 0 | 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, 1e10, |
1947 | 0 | 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, 1e20, 1e21, |
1948 | 0 | 1e22, 1e23, 1e24, 1e25, 1e26, 1e27, 1e28, 1e29, 1e30, 1e31}; |
1949 | |
|
1950 | 0 | while (*p == ' ' || *p == '\t') |
1951 | 0 | ++p; |
1952 | |
|
1953 | 0 | if (*p == '+') |
1954 | 0 | ++p; |
1955 | 0 | else if (*p == '-') |
1956 | 0 | { |
1957 | 0 | dfSign = -1.0; |
1958 | 0 | ++p; |
1959 | 0 | } |
1960 | |
|
1961 | 0 | while (true) |
1962 | 0 | { |
1963 | 0 | if (*p >= '0' && *p <= '9') |
1964 | 0 | { |
1965 | 0 | dfVal = dfVal * 10.0 + (*p - '0'); |
1966 | 0 | ++p; |
1967 | 0 | } |
1968 | 0 | else if (*p == '.') |
1969 | 0 | { |
1970 | 0 | ++p; |
1971 | 0 | break; |
1972 | 0 | } |
1973 | 0 | else if (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D') |
1974 | 0 | return OGRCallAtofOnShortString(pszStr); |
1975 | 0 | else |
1976 | 0 | return dfSign * dfVal; |
1977 | 0 | } |
1978 | | |
1979 | 0 | unsigned int countFractionnal = 0; |
1980 | 0 | while (true) |
1981 | 0 | { |
1982 | 0 | if (*p >= '0' && *p <= '9') |
1983 | 0 | { |
1984 | 0 | dfVal = dfVal * 10.0 + (*p - '0'); |
1985 | 0 | ++countFractionnal; |
1986 | 0 | ++p; |
1987 | 0 | } |
1988 | 0 | else if (*p == 'e' || *p == 'E' || *p == 'd' || *p == 'D') |
1989 | 0 | return OGRCallAtofOnShortString(pszStr); |
1990 | 0 | else |
1991 | 0 | { |
1992 | 0 | if (countFractionnal < CPL_ARRAYSIZE(adfTenPower)) |
1993 | 0 | return dfSign * (dfVal / adfTenPower[countFractionnal]); |
1994 | 0 | else |
1995 | 0 | return OGRCallAtofOnShortString(pszStr); |
1996 | 0 | } |
1997 | 0 | } |
1998 | 0 | } |
1999 | | |
2000 | | /** |
2001 | | * Check that panPermutation is a permutation of [0, nSize-1]. |
2002 | | * @param panPermutation an array of nSize elements. |
2003 | | * @param nSize size of the array. |
2004 | | * @return OGRERR_NONE if panPermutation is a permutation of [0, nSize - 1]. |
2005 | | * @since OGR 1.9.0 |
2006 | | */ |
2007 | | OGRErr OGRCheckPermutation(const int *panPermutation, int nSize) |
2008 | 0 | { |
2009 | 0 | OGRErr eErr = OGRERR_NONE; |
2010 | 0 | int *panCheck = static_cast<int *>(CPLCalloc(nSize, sizeof(int))); |
2011 | 0 | for (int i = 0; i < nSize; ++i) |
2012 | 0 | { |
2013 | 0 | if (panPermutation[i] < 0 || panPermutation[i] >= nSize) |
2014 | 0 | { |
2015 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, "Bad value for element %d", |
2016 | 0 | i); |
2017 | 0 | eErr = OGRERR_FAILURE; |
2018 | 0 | break; |
2019 | 0 | } |
2020 | 0 | if (panCheck[panPermutation[i]] != 0) |
2021 | 0 | { |
2022 | 0 | CPLError(CE_Failure, CPLE_IllegalArg, |
2023 | 0 | "Array is not a permutation of [0,%d]", nSize - 1); |
2024 | 0 | eErr = OGRERR_FAILURE; |
2025 | 0 | break; |
2026 | 0 | } |
2027 | 0 | panCheck[panPermutation[i]] = 1; |
2028 | 0 | } |
2029 | 0 | CPLFree(panCheck); |
2030 | 0 | return eErr; |
2031 | 0 | } |
2032 | | |
2033 | | OGRErr OGRReadWKBGeometryType(const unsigned char *pabyData, |
2034 | | OGRwkbVariant eWkbVariant, |
2035 | | OGRwkbGeometryType *peGeometryType) |
2036 | 0 | { |
2037 | 0 | if (!peGeometryType) |
2038 | 0 | return OGRERR_FAILURE; |
2039 | | |
2040 | | /* -------------------------------------------------------------------- */ |
2041 | | /* Get the byte order byte. */ |
2042 | | /* -------------------------------------------------------------------- */ |
2043 | 0 | int nByteOrder = DB2_V72_FIX_BYTE_ORDER(*pabyData); |
2044 | 0 | if (!(nByteOrder == wkbXDR || nByteOrder == wkbNDR)) |
2045 | 0 | return OGRERR_CORRUPT_DATA; |
2046 | 0 | OGRwkbByteOrder eByteOrder = static_cast<OGRwkbByteOrder>(nByteOrder); |
2047 | | |
2048 | | /* -------------------------------------------------------------------- */ |
2049 | | /* Get the geometry type. */ |
2050 | | /* -------------------------------------------------------------------- */ |
2051 | 0 | bool bIsOldStyle3D = false; |
2052 | 0 | bool bIsOldStyleMeasured = false; |
2053 | 0 | int iRawType = 0; |
2054 | |
|
2055 | 0 | memcpy(&iRawType, pabyData + 1, 4); |
2056 | 0 | if (OGR_SWAP(eByteOrder)) |
2057 | 0 | { |
2058 | 0 | CPL_SWAP32PTR(&iRawType); |
2059 | 0 | } |
2060 | | |
2061 | | // Test for M bit in PostGIS WKB, see ogrgeometry.cpp:4956. |
2062 | 0 | if (0x40000000 & iRawType) |
2063 | 0 | { |
2064 | 0 | iRawType &= ~0x40000000; |
2065 | 0 | bIsOldStyleMeasured = true; |
2066 | 0 | } |
2067 | | // Old-style OGC z-bit is flipped? Tests also Z bit in PostGIS WKB. |
2068 | 0 | if (wkb25DBitInternalUse & iRawType) |
2069 | 0 | { |
2070 | | // Clean off top 3 bytes. |
2071 | 0 | iRawType &= 0x000000FF; |
2072 | 0 | bIsOldStyle3D = true; |
2073 | 0 | } |
2074 | | |
2075 | | // ISO SQL/MM Part3 draft -> Deprecated. |
2076 | | // See http://jtc1sc32.org/doc/N1101-1150/32N1107-WD13249-3--spatial.pdf |
2077 | 0 | if (iRawType == 1000001) |
2078 | 0 | iRawType = wkbCircularString; |
2079 | 0 | else if (iRawType == 1000002) |
2080 | 0 | iRawType = wkbCompoundCurve; |
2081 | 0 | else if (iRawType == 1000003) |
2082 | 0 | iRawType = wkbCurvePolygon; |
2083 | 0 | else if (iRawType == 1000004) |
2084 | 0 | iRawType = wkbMultiCurve; |
2085 | 0 | else if (iRawType == 1000005) |
2086 | 0 | iRawType = wkbMultiSurface; |
2087 | 0 | else if (iRawType == 2000001) |
2088 | 0 | iRawType = wkbPointZM; |
2089 | 0 | else if (iRawType == 2000002) |
2090 | 0 | iRawType = wkbLineStringZM; |
2091 | 0 | else if (iRawType == 2000003) |
2092 | 0 | iRawType = wkbCircularStringZM; |
2093 | 0 | else if (iRawType == 2000004) |
2094 | 0 | iRawType = wkbCompoundCurveZM; |
2095 | 0 | else if (iRawType == 2000005) |
2096 | 0 | iRawType = wkbPolygonZM; |
2097 | 0 | else if (iRawType == 2000006) |
2098 | 0 | iRawType = wkbCurvePolygonZM; |
2099 | 0 | else if (iRawType == 2000007) |
2100 | 0 | iRawType = wkbMultiPointZM; |
2101 | 0 | else if (iRawType == 2000008) |
2102 | 0 | iRawType = wkbMultiCurveZM; |
2103 | 0 | else if (iRawType == 2000009) |
2104 | 0 | iRawType = wkbMultiLineStringZM; |
2105 | 0 | else if (iRawType == 2000010) |
2106 | 0 | iRawType = wkbMultiSurfaceZM; |
2107 | 0 | else if (iRawType == 2000011) |
2108 | 0 | iRawType = wkbMultiPolygonZM; |
2109 | 0 | else if (iRawType == 2000012) |
2110 | 0 | iRawType = wkbGeometryCollectionZM; |
2111 | 0 | else if (iRawType == 3000001) |
2112 | 0 | iRawType = wkbPoint25D; |
2113 | 0 | else if (iRawType == 3000002) |
2114 | 0 | iRawType = wkbLineString25D; |
2115 | 0 | else if (iRawType == 3000003) |
2116 | 0 | iRawType = wkbCircularStringZ; |
2117 | 0 | else if (iRawType == 3000004) |
2118 | 0 | iRawType = wkbCompoundCurveZ; |
2119 | 0 | else if (iRawType == 3000005) |
2120 | 0 | iRawType = wkbPolygon25D; |
2121 | 0 | else if (iRawType == 3000006) |
2122 | 0 | iRawType = wkbCurvePolygonZ; |
2123 | 0 | else if (iRawType == 3000007) |
2124 | 0 | iRawType = wkbMultiPoint25D; |
2125 | 0 | else if (iRawType == 3000008) |
2126 | 0 | iRawType = wkbMultiCurveZ; |
2127 | 0 | else if (iRawType == 3000009) |
2128 | 0 | iRawType = wkbMultiLineString25D; |
2129 | 0 | else if (iRawType == 3000010) |
2130 | 0 | iRawType = wkbMultiSurfaceZ; |
2131 | 0 | else if (iRawType == 3000011) |
2132 | 0 | iRawType = wkbMultiPolygon25D; |
2133 | 0 | else if (iRawType == 3000012) |
2134 | 0 | iRawType = wkbGeometryCollection25D; |
2135 | 0 | else if (iRawType == 4000001) |
2136 | 0 | iRawType = wkbPointM; |
2137 | 0 | else if (iRawType == 4000002) |
2138 | 0 | iRawType = wkbLineStringM; |
2139 | 0 | else if (iRawType == 4000003) |
2140 | 0 | iRawType = wkbCircularStringM; |
2141 | 0 | else if (iRawType == 4000004) |
2142 | 0 | iRawType = wkbCompoundCurveM; |
2143 | 0 | else if (iRawType == 4000005) |
2144 | 0 | iRawType = wkbPolygonM; |
2145 | 0 | else if (iRawType == 4000006) |
2146 | 0 | iRawType = wkbCurvePolygonM; |
2147 | 0 | else if (iRawType == 4000007) |
2148 | 0 | iRawType = wkbMultiPointM; |
2149 | 0 | else if (iRawType == 4000008) |
2150 | 0 | iRawType = wkbMultiCurveM; |
2151 | 0 | else if (iRawType == 4000009) |
2152 | 0 | iRawType = wkbMultiLineStringM; |
2153 | 0 | else if (iRawType == 4000010) |
2154 | 0 | iRawType = wkbMultiSurfaceM; |
2155 | 0 | else if (iRawType == 4000011) |
2156 | 0 | iRawType = wkbMultiPolygonM; |
2157 | 0 | else if (iRawType == 4000012) |
2158 | 0 | iRawType = wkbGeometryCollectionM; |
2159 | | |
2160 | | // Sometimes the Z flag is in the 2nd byte? |
2161 | 0 | if (iRawType & (wkb25DBitInternalUse >> 16)) |
2162 | 0 | { |
2163 | | // Clean off top 3 bytes. |
2164 | 0 | iRawType &= 0x000000FF; |
2165 | 0 | bIsOldStyle3D = true; |
2166 | 0 | } |
2167 | |
|
2168 | 0 | if (eWkbVariant == wkbVariantPostGIS1) |
2169 | 0 | { |
2170 | 0 | if (iRawType == POSTGIS15_CURVEPOLYGON) |
2171 | 0 | iRawType = wkbCurvePolygon; |
2172 | 0 | else if (iRawType == POSTGIS15_MULTICURVE) |
2173 | 0 | iRawType = wkbMultiCurve; |
2174 | 0 | else if (iRawType == POSTGIS15_MULTISURFACE) |
2175 | 0 | iRawType = wkbMultiSurface; |
2176 | 0 | } |
2177 | | |
2178 | | // ISO SQL/MM style types are between 1-17, 1001-1017, 2001-2017, and |
2179 | | // 3001-3017. |
2180 | 0 | if (!((iRawType > 0 && iRawType <= 17) || |
2181 | 0 | (iRawType > 1000 && iRawType <= 1017) || |
2182 | 0 | (iRawType > 2000 && iRawType <= 2017) || |
2183 | 0 | (iRawType > 3000 && iRawType <= 3017))) |
2184 | 0 | { |
2185 | 0 | CPLError(CE_Failure, CPLE_NotSupported, "Unsupported WKB type %d", |
2186 | 0 | iRawType); |
2187 | 0 | return OGRERR_UNSUPPORTED_GEOMETRY_TYPE; |
2188 | 0 | } |
2189 | | |
2190 | 0 | if (bIsOldStyle3D) |
2191 | 0 | { |
2192 | 0 | iRawType += 1000; |
2193 | 0 | } |
2194 | 0 | if (bIsOldStyleMeasured) |
2195 | 0 | { |
2196 | 0 | iRawType += 2000; |
2197 | 0 | } |
2198 | | |
2199 | | // Convert to OGRwkbGeometryType value. |
2200 | 0 | if (iRawType >= 1001 && iRawType <= 1007) |
2201 | 0 | { |
2202 | 0 | iRawType -= 1000; |
2203 | 0 | iRawType |= wkb25DBitInternalUse; |
2204 | 0 | } |
2205 | |
|
2206 | 0 | *peGeometryType = static_cast<OGRwkbGeometryType>(iRawType); |
2207 | |
|
2208 | 0 | return OGRERR_NONE; |
2209 | 0 | } |
2210 | | |
2211 | | /************************************************************************/ |
2212 | | /* OGRReadWKTGeometryType() */ |
2213 | | /************************************************************************/ |
2214 | | |
2215 | | OGRErr OGRReadWKTGeometryType(const char *pszWKT, |
2216 | | OGRwkbGeometryType *peGeometryType) |
2217 | 0 | { |
2218 | 0 | if (!peGeometryType) |
2219 | 0 | return OGRERR_FAILURE; |
2220 | | |
2221 | 0 | OGRwkbGeometryType eGeomType = wkbUnknown; |
2222 | 0 | if (STARTS_WITH_CI(pszWKT, "POINT")) |
2223 | 0 | eGeomType = wkbPoint; |
2224 | 0 | else if (STARTS_WITH_CI(pszWKT, "LINESTRING")) |
2225 | 0 | eGeomType = wkbLineString; |
2226 | 0 | else if (STARTS_WITH_CI(pszWKT, "POLYGON")) |
2227 | 0 | eGeomType = wkbPolygon; |
2228 | 0 | else if (STARTS_WITH_CI(pszWKT, "MULTIPOINT")) |
2229 | 0 | eGeomType = wkbMultiPoint; |
2230 | 0 | else if (STARTS_WITH_CI(pszWKT, "MULTILINESTRING")) |
2231 | 0 | eGeomType = wkbMultiLineString; |
2232 | 0 | else if (STARTS_WITH_CI(pszWKT, "MULTIPOLYGON")) |
2233 | 0 | eGeomType = wkbMultiPolygon; |
2234 | 0 | else if (STARTS_WITH_CI(pszWKT, "GEOMETRYCOLLECTION")) |
2235 | 0 | eGeomType = wkbGeometryCollection; |
2236 | 0 | else if (STARTS_WITH_CI(pszWKT, "CIRCULARSTRING")) |
2237 | 0 | eGeomType = wkbCircularString; |
2238 | 0 | else if (STARTS_WITH_CI(pszWKT, "COMPOUNDCURVE")) |
2239 | 0 | eGeomType = wkbCompoundCurve; |
2240 | 0 | else if (STARTS_WITH_CI(pszWKT, "CURVEPOLYGON")) |
2241 | 0 | eGeomType = wkbCurvePolygon; |
2242 | 0 | else if (STARTS_WITH_CI(pszWKT, "MULTICURVE")) |
2243 | 0 | eGeomType = wkbMultiCurve; |
2244 | 0 | else if (STARTS_WITH_CI(pszWKT, "MULTISURFACE")) |
2245 | 0 | eGeomType = wkbMultiSurface; |
2246 | 0 | else if (STARTS_WITH_CI(pszWKT, "POLYHEDRALSURFACE")) |
2247 | 0 | eGeomType = wkbPolyhedralSurface; |
2248 | 0 | else if (STARTS_WITH_CI(pszWKT, "TIN")) |
2249 | 0 | eGeomType = wkbTIN; |
2250 | 0 | else |
2251 | 0 | return OGRERR_UNSUPPORTED_GEOMETRY_TYPE; |
2252 | | |
2253 | 0 | if (strstr(pszWKT, " ZM")) |
2254 | 0 | eGeomType = OGR_GT_SetModifier(eGeomType, true, true); |
2255 | 0 | else if (strstr(pszWKT, " Z")) |
2256 | 0 | eGeomType = OGR_GT_SetModifier(eGeomType, true, false); |
2257 | 0 | else if (strstr(pszWKT, " M")) |
2258 | 0 | eGeomType = OGR_GT_SetModifier(eGeomType, false, true); |
2259 | |
|
2260 | 0 | *peGeometryType = eGeomType; |
2261 | |
|
2262 | 0 | return OGRERR_NONE; |
2263 | 0 | } |
2264 | | |
2265 | | /************************************************************************/ |
2266 | | /* OGRFormatFloat() */ |
2267 | | /************************************************************************/ |
2268 | | |
2269 | | int OGRFormatFloat(char *pszBuffer, int nBufferLen, float fVal, int nPrecision, |
2270 | | char chConversionSpecifier) |
2271 | 0 | { |
2272 | | // So to have identical cross platform representation. |
2273 | 0 | if (std::isinf(fVal)) |
2274 | 0 | return CPLsnprintf(pszBuffer, nBufferLen, (fVal > 0) ? "inf" : "-inf"); |
2275 | 0 | if (std::isnan(fVal)) |
2276 | 0 | return CPLsnprintf(pszBuffer, nBufferLen, "nan"); |
2277 | | |
2278 | 0 | int nSize = 0; |
2279 | 0 | char szFormatting[32] = {}; |
2280 | 0 | constexpr int MAX_SIGNIFICANT_DIGITS_FLOAT32 = 8; |
2281 | 0 | const int nInitialSignificantFigures = |
2282 | 0 | nPrecision >= 0 ? nPrecision : MAX_SIGNIFICANT_DIGITS_FLOAT32; |
2283 | |
|
2284 | 0 | CPLsnprintf(szFormatting, sizeof(szFormatting), "%%.%d%c", |
2285 | 0 | nInitialSignificantFigures, chConversionSpecifier); |
2286 | 0 | nSize = CPLsnprintf(pszBuffer, nBufferLen, szFormatting, fVal); |
2287 | 0 | const char *pszDot = strchr(pszBuffer, '.'); |
2288 | | |
2289 | | // Try to avoid 0.34999999 or 0.15000001 rounding issues by |
2290 | | // decreasing a bit precision. |
2291 | 0 | if (nInitialSignificantFigures >= 8 && pszDot != nullptr && |
2292 | 0 | (strstr(pszDot, "99999") != nullptr || |
2293 | 0 | strstr(pszDot, "00000") != nullptr)) |
2294 | 0 | { |
2295 | 0 | const CPLString osOriBuffer(pszBuffer, nSize); |
2296 | |
|
2297 | 0 | bool bOK = false; |
2298 | 0 | for (int i = 1; i <= 3; i++) |
2299 | 0 | { |
2300 | 0 | CPLsnprintf(szFormatting, sizeof(szFormatting), "%%.%d%c", |
2301 | 0 | nInitialSignificantFigures - i, chConversionSpecifier); |
2302 | 0 | nSize = CPLsnprintf(pszBuffer, nBufferLen, szFormatting, fVal); |
2303 | 0 | pszDot = strchr(pszBuffer, '.'); |
2304 | 0 | if (pszDot != nullptr && strstr(pszDot, "99999") == nullptr && |
2305 | 0 | strstr(pszDot, "00000") == nullptr && |
2306 | 0 | static_cast<float>(CPLAtof(pszBuffer)) == fVal) |
2307 | 0 | { |
2308 | 0 | bOK = true; |
2309 | 0 | break; |
2310 | 0 | } |
2311 | 0 | } |
2312 | 0 | if (!bOK) |
2313 | 0 | { |
2314 | 0 | memcpy(pszBuffer, osOriBuffer.c_str(), osOriBuffer.size() + 1); |
2315 | 0 | nSize = static_cast<int>(osOriBuffer.size()); |
2316 | 0 | } |
2317 | 0 | } |
2318 | |
|
2319 | 0 | if (nSize + 2 < static_cast<int>(nBufferLen) && |
2320 | 0 | strchr(pszBuffer, '.') == nullptr && strchr(pszBuffer, 'e') == nullptr) |
2321 | 0 | { |
2322 | 0 | nSize += CPLsnprintf(pszBuffer + nSize, nBufferLen - nSize, ".0"); |
2323 | 0 | } |
2324 | |
|
2325 | 0 | return nSize; |
2326 | 0 | } |
2327 | | |
2328 | | int OGR_GET_MS(float fSec) |
2329 | 0 | { |
2330 | 0 | if (std::isnan(fSec)) |
2331 | 0 | return 0; |
2332 | 0 | if (fSec >= 999) |
2333 | 0 | return 999; |
2334 | 0 | if (fSec <= 0) |
2335 | 0 | return 0; |
2336 | 0 | const float fValue = (fSec - static_cast<int>(fSec)) * 1000 + 0.5f; |
2337 | 0 | return static_cast<int>(fValue); |
2338 | 0 | } |
2339 | | |
2340 | | /************************************************************************/ |
2341 | | /* OGRDuplicateCharacter() */ |
2342 | | /************************************************************************/ |
2343 | | |
2344 | | std::string OGRDuplicateCharacter(const std::string &osStr, char ch) |
2345 | 0 | { |
2346 | 0 | char aszReplacement[] = {ch, ch, 0}; |
2347 | 0 | return CPLString(osStr).replaceAll(ch, aszReplacement); |
2348 | 0 | } |