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