Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Different utility functions used in FileGDB OGR driver.
5 : * Author: Ragi Yaser Burhum, ragi@burhum.com
6 : * Paul Ramsey, pramsey at cleverelephant.ca
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2010, Ragi Yaser Burhum
10 : * Copyright (c) 2011, Paul Ramsey <pramsey at cleverelephant.ca>
11 : * Copyright (c) 2011-2014, Even Rouault <even dot rouault at spatialys.com>
12 : *
13 : * Permission is hereby granted, free of charge, to any person obtaining a
14 : * copy of this software and associated documentation files (the "Software"),
15 : * to deal in the Software without restriction, including without limitation
16 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
17 : * and/or sell copies of the Software, and to permit persons to whom the
18 : * Software is furnished to do so, subject to the following conditions:
19 : *
20 : * The above copyright notice and this permission notice shall be included
21 : * in all copies or substantial portions of the Software.
22 : *
23 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
24 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
26 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
28 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
29 : * DEALINGS IN THE SOFTWARE.
30 : ****************************************************************************/
31 :
32 : #include "FGdbUtils.h"
33 : #include <algorithm>
34 :
35 : #include "ogr_api.h"
36 : #include "ogrpgeogeometry.h"
37 :
38 : using std::string;
39 :
40 : /*************************************************************************/
41 : /* StringToWString() */
42 : /*************************************************************************/
43 :
44 20779 : std::wstring StringToWString(const std::string &utf8string)
45 : {
46 : wchar_t *pszUTF16 =
47 20779 : CPLRecodeToWChar(utf8string.c_str(), CPL_ENC_UTF8, CPL_ENC_UCS2);
48 20779 : std::wstring utf16string = pszUTF16;
49 20779 : CPLFree(pszUTF16);
50 20779 : return utf16string;
51 : }
52 :
53 : /*************************************************************************/
54 : /* WStringToString() */
55 : /*************************************************************************/
56 :
57 10428 : std::string WStringToString(const std::wstring &utf16string)
58 : {
59 : char *pszUTF8 =
60 10428 : CPLRecodeFromWChar(utf16string.c_str(), CPL_ENC_UCS2, CPL_ENC_UTF8);
61 10428 : std::string utf8string = pszUTF8;
62 10428 : CPLFree(pszUTF8);
63 10428 : return utf8string;
64 : }
65 :
66 : /*************************************************************************/
67 : /* GDBErr() */
68 : /*************************************************************************/
69 :
70 26 : bool GDBErr(long int hr, const std::string &desc, CPLErr errType,
71 : const char *pszAddMsg)
72 : {
73 26 : std::wstring fgdb_error_desc_w;
74 : fgdbError er;
75 26 : er = FileGDBAPI::ErrorInfo::GetErrorDescription(static_cast<fgdbError>(hr),
76 : fgdb_error_desc_w);
77 26 : if (er == S_OK)
78 : {
79 52 : std::string fgdb_error_desc = WStringToString(fgdb_error_desc_w);
80 26 : CPLError(errType, CPLE_AppDefined, "%s (%s)%s", desc.c_str(),
81 : fgdb_error_desc.c_str(), pszAddMsg);
82 : }
83 : else
84 : {
85 0 : CPLError(errType, CPLE_AppDefined, "Error (%ld): %s%s", hr,
86 : desc.c_str(), pszAddMsg);
87 : }
88 : // FIXME? EvenR: not sure if ClearErrors() is really necessary, but as it,
89 : // it causes crashes in case of repeated errors
90 : // FileGDBAPI::ErrorInfo::ClearErrors();
91 :
92 52 : return false;
93 : }
94 :
95 : /*************************************************************************/
96 : /* GDBDebug() */
97 : /*************************************************************************/
98 :
99 0 : bool GDBDebug(long int hr, const std::string &desc)
100 : {
101 0 : std::wstring fgdb_error_desc_w;
102 : fgdbError er;
103 0 : er = FileGDBAPI::ErrorInfo::GetErrorDescription(static_cast<fgdbError>(hr),
104 : fgdb_error_desc_w);
105 0 : if (er == S_OK)
106 : {
107 0 : std::string fgdb_error_desc = WStringToString(fgdb_error_desc_w);
108 0 : CPLDebug("FGDB", "%s (%s)", desc.c_str(), fgdb_error_desc.c_str());
109 : }
110 : else
111 : {
112 0 : CPLDebug("FGDB", "%s", desc.c_str());
113 : }
114 : // FIXME? EvenR: not sure if ClearErrors() is really necessary, but as it,
115 : // it causes crashes in case of repeated errors
116 : // FileGDBAPI::ErrorInfo::ClearErrors();
117 :
118 0 : return false;
119 : }
120 :
121 : /*************************************************************************/
122 : /* GDBToOGRGeometry() */
123 : /*************************************************************************/
124 :
125 370 : bool GDBToOGRGeometry(const std::string &geoType, bool hasZ, bool hasM,
126 : OGRwkbGeometryType *pOut)
127 : {
128 370 : if (geoType == "esriGeometryPoint")
129 : {
130 88 : *pOut = wkbPoint;
131 : }
132 282 : else if (geoType == "esriGeometryMultipoint")
133 : {
134 35 : *pOut = wkbMultiPoint;
135 : }
136 247 : else if (geoType == "esriGeometryLine")
137 : {
138 0 : *pOut = wkbLineString;
139 : }
140 247 : else if (geoType == "esriGeometryPolyline")
141 : {
142 80 : *pOut = wkbMultiLineString;
143 : }
144 201 : else if (geoType == "esriGeometryPolygon" ||
145 34 : geoType == "esriGeometryMultiPatch")
146 : {
147 167 : *pOut = wkbMultiPolygon; // no mapping to single polygon
148 : }
149 : else
150 : {
151 0 : CPLError(CE_Failure, CPLE_AppDefined,
152 : "Cannot map esriGeometryType(%s) to OGRwkbGeometryType",
153 : geoType.c_str());
154 0 : return false;
155 : }
156 370 : if (hasZ)
157 145 : *pOut = wkbSetZ(*pOut);
158 370 : if (hasM)
159 7 : *pOut = wkbSetM(*pOut);
160 :
161 370 : return true;
162 : }
163 :
164 : /*************************************************************************/
165 : /* OGRGeometryToGDB() */
166 : /*************************************************************************/
167 :
168 137 : bool OGRGeometryToGDB(OGRwkbGeometryType ogrType, std::string *gdbType,
169 : bool *hasZ, bool *hasM)
170 : {
171 137 : *hasZ = wkbHasZ(ogrType);
172 137 : *hasM = wkbHasM(ogrType);
173 137 : switch (wkbFlatten(ogrType))
174 : {
175 42 : case wkbPoint:
176 : {
177 42 : *gdbType = "esriGeometryPoint";
178 42 : break;
179 : }
180 :
181 13 : case wkbMultiPoint:
182 : {
183 13 : *gdbType = "esriGeometryMultipoint";
184 13 : break;
185 : }
186 :
187 27 : case wkbLineString:
188 : case wkbMultiLineString:
189 : {
190 27 : *gdbType = "esriGeometryPolyline";
191 27 : break;
192 : }
193 :
194 49 : case wkbPolygon:
195 : case wkbMultiPolygon:
196 : {
197 49 : *gdbType = "esriGeometryPolygon";
198 49 : break;
199 : }
200 :
201 6 : case wkbTIN:
202 : case wkbPolyhedralSurface:
203 : {
204 6 : *gdbType = "esriGeometryMultiPatch";
205 6 : break;
206 : }
207 :
208 0 : default:
209 : {
210 0 : CPLError(CE_Failure, CPLE_AppDefined,
211 : "Cannot map OGRwkbGeometryType (%s) to ESRI type",
212 : OGRGeometryTypeToName(ogrType));
213 0 : return false;
214 : }
215 : }
216 137 : return true;
217 : }
218 :
219 : /*************************************************************************/
220 : /* OGRToGDBFieldType() */
221 : /*************************************************************************/
222 :
223 1375 : bool OGRToGDBFieldType(OGRFieldType ogrType, OGRFieldSubType eSubType,
224 : std::string *gdbType)
225 : {
226 1375 : switch (ogrType)
227 : {
228 426 : case OFTInteger:
229 : {
230 426 : if (eSubType == OFSTInt16)
231 104 : *gdbType = "esriFieldTypeSmallInteger";
232 : else
233 322 : *gdbType = "esriFieldTypeInteger";
234 426 : break;
235 : }
236 321 : case OFTReal:
237 : case OFTInteger64:
238 : {
239 321 : if (eSubType == OFSTFloat32)
240 102 : *gdbType = "esriFieldTypeSingle";
241 : else
242 219 : *gdbType = "esriFieldTypeDouble";
243 321 : break;
244 : }
245 320 : case OFTString:
246 : {
247 320 : *gdbType = "esriFieldTypeString";
248 320 : break;
249 : }
250 204 : case OFTBinary:
251 : {
252 204 : *gdbType = "esriFieldTypeBlob";
253 204 : break;
254 : }
255 104 : case OFTDate:
256 : case OFTDateTime:
257 : {
258 104 : *gdbType = "esriFieldTypeDate";
259 104 : break;
260 : }
261 0 : default:
262 : {
263 0 : CPLError(CE_Warning, CPLE_AppDefined,
264 : "Cannot map OGR field type (%s)",
265 : OGR_GetFieldTypeName(ogrType));
266 0 : return false;
267 : }
268 : }
269 :
270 1375 : return true;
271 : }
272 :
273 : /*************************************************************************/
274 : /* GDBFieldTypeToLengthInBytes() */
275 : /*************************************************************************/
276 :
277 1375 : bool GDBFieldTypeToLengthInBytes(const std::string &gdbType, int &lengthOut)
278 : {
279 : /* Length based on
280 : * FileGDB_API/samples/XMLsamples/OneOfEachFieldType.xml */
281 : /* Length is in bytes per doc of FileGDB_API/xmlResources/FileGDBAPI.xsd */
282 1375 : if (gdbType == "esriFieldTypeSmallInteger")
283 : {
284 206 : lengthOut = 2;
285 : }
286 1169 : else if (gdbType == "esriFieldTypeInteger")
287 : {
288 220 : lengthOut = 4;
289 : }
290 949 : else if (gdbType == "esriFieldTypeSingle")
291 : {
292 204 : lengthOut = 4;
293 : }
294 745 : else if (gdbType == "esriFieldTypeDouble")
295 : {
296 117 : lengthOut = 8;
297 : }
298 628 : else if (gdbType == "esriFieldTypeString" || gdbType == "esriFieldTypeXML")
299 : {
300 217 : lengthOut = atoi(CPLGetConfigOption("FGDB_STRING_WIDTH", "65536"));
301 : }
302 411 : else if (gdbType == "esriFieldTypeDate")
303 : {
304 104 : lengthOut = 8;
305 : }
306 307 : else if (gdbType == "esriFieldTypeOID")
307 : {
308 0 : lengthOut = 4;
309 : }
310 307 : else if (gdbType == "esriFieldTypeGUID")
311 : {
312 102 : lengthOut = 16;
313 : }
314 205 : else if (gdbType == "esriFieldTypeBlob")
315 : {
316 204 : lengthOut = 0;
317 : }
318 1 : else if (gdbType == "esriFieldTypeGlobalID")
319 : {
320 1 : lengthOut = 38;
321 : }
322 : else
323 : {
324 0 : CPLError(CE_Warning, CPLE_AppDefined, "Cannot map ESRI field type (%s)",
325 : gdbType.c_str());
326 0 : return false;
327 : }
328 :
329 1375 : return true;
330 : }
331 :
332 : /*************************************************************************/
333 : /* GDBGeometryToOGRGeometry() */
334 : /*************************************************************************/
335 :
336 1832 : bool GDBGeometryToOGRGeometry(bool forceMulti,
337 : FileGDBAPI::ShapeBuffer *pGdbGeometry,
338 : OGRSpatialReference *pOGRSR,
339 : OGRGeometry **ppOutGeometry)
340 : {
341 :
342 1832 : OGRGeometry *pOGRGeometry = nullptr;
343 :
344 : OGRErr eErr =
345 3664 : OGRCreateFromShapeBin(pGdbGeometry->shapeBuffer, &pOGRGeometry,
346 1832 : static_cast<int>(pGdbGeometry->inUseLength));
347 :
348 : // OGRErr eErr =
349 : // OGRGeometryFactory::createFromWkb(pGdbGeometry->shapeBuffer, pOGRSR,
350 : // &pOGRGeometry, pGdbGeometry->inUseLength );
351 :
352 1832 : if (eErr != OGRERR_NONE)
353 : {
354 0 : CPLError(CE_Failure, CPLE_AppDefined,
355 : "Failed attempting to import GDB WKB Geometry. "
356 : "OGRGeometryFactory err:%d",
357 : eErr);
358 0 : return false;
359 : }
360 :
361 1832 : if (pOGRGeometry != nullptr)
362 : {
363 : // force geometries to multi if requested
364 :
365 : // If it is a polygon, force to MultiPolygon since we always produce
366 : // multipolygons
367 : OGRwkbGeometryType eFlattenType =
368 1832 : wkbFlatten(pOGRGeometry->getGeometryType());
369 1832 : if (eFlattenType == wkbPolygon)
370 : {
371 561 : pOGRGeometry =
372 561 : OGRGeometryFactory::forceToMultiPolygon(pOGRGeometry);
373 : }
374 1271 : else if (eFlattenType == wkbCurvePolygon)
375 : {
376 12 : OGRMultiSurface *poMS = new OGRMultiSurface();
377 12 : poMS->addGeometryDirectly(pOGRGeometry);
378 12 : pOGRGeometry = poMS;
379 : }
380 1259 : else if (forceMulti)
381 : {
382 688 : if (eFlattenType == wkbLineString)
383 : {
384 342 : pOGRGeometry =
385 342 : OGRGeometryFactory::forceToMultiLineString(pOGRGeometry);
386 : }
387 346 : else if (eFlattenType == wkbCompoundCurve)
388 : {
389 9 : OGRMultiCurve *poMC = new OGRMultiCurve();
390 9 : poMC->addGeometryDirectly(pOGRGeometry);
391 9 : pOGRGeometry = poMC;
392 : }
393 337 : else if (eFlattenType == wkbPoint)
394 : {
395 0 : pOGRGeometry =
396 0 : OGRGeometryFactory::forceToMultiPoint(pOGRGeometry);
397 : }
398 : }
399 :
400 1832 : if (pOGRGeometry)
401 1832 : pOGRGeometry->assignSpatialReference(pOGRSR);
402 : }
403 :
404 1832 : *ppOutGeometry = pOGRGeometry;
405 :
406 1832 : return true;
407 : }
408 :
409 : /*************************************************************************/
410 : /* GDBToOGRSpatialReference() */
411 : /*************************************************************************/
412 :
413 7 : bool GDBToOGRSpatialReference(const string &wkt, OGRSpatialReference **ppSR)
414 : {
415 7 : if (wkt.empty())
416 : {
417 0 : CPLError(CE_Warning, CPLE_AppDefined, "ESRI Spatial Reference is NULL");
418 0 : return false;
419 : }
420 :
421 7 : *ppSR = new OGRSpatialReference(wkt.c_str());
422 7 : (*ppSR)->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
423 :
424 7 : OGRErr result = (*ppSR)->morphFromESRI();
425 :
426 7 : if (result == OGRERR_NONE)
427 : {
428 7 : if (CPLTestBool(CPLGetConfigOption("USE_OSR_FIND_MATCHES", "YES")))
429 : {
430 7 : int nEntries = 0;
431 7 : int *panConfidence = nullptr;
432 : OGRSpatialReferenceH *pahSRS =
433 7 : (*ppSR)->FindMatches(nullptr, &nEntries, &panConfidence);
434 7 : if (nEntries == 1 && panConfidence[0] == 100)
435 : {
436 0 : (*ppSR)->Release();
437 0 : (*ppSR) = reinterpret_cast<OGRSpatialReference *>(pahSRS[0]);
438 0 : (*ppSR)->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
439 0 : CPLFree(pahSRS);
440 : }
441 : else
442 : {
443 7 : OSRFreeSRSArray(pahSRS);
444 : }
445 7 : CPLFree(panConfidence);
446 : }
447 : else
448 : {
449 0 : (*ppSR)->AutoIdentifyEPSG();
450 : }
451 :
452 7 : return true;
453 : }
454 : else
455 : {
456 0 : delete *ppSR;
457 0 : *ppSR = nullptr;
458 :
459 0 : CPLError(CE_Failure, CPLE_AppDefined,
460 : "Failed morphing from ESRI Geometry: %s", wkt.c_str());
461 :
462 0 : return false;
463 : }
464 : }
465 :
466 : /*************************************************************************/
467 : /* FGDB_CPLAddXMLAttribute() */
468 : /*************************************************************************/
469 :
470 : /* Utility method for attributing nodes */
471 7696 : void FGDB_CPLAddXMLAttribute(CPLXMLNode *node, const char *attrname,
472 : const char *attrvalue)
473 : {
474 7696 : if (!node)
475 0 : return;
476 7696 : CPLCreateXMLNode(CPLCreateXMLNode(node, CXT_Attribute, attrname), CXT_Text,
477 : attrvalue);
478 : }
479 :
480 : /*************************************************************************/
481 : /* FGDBLaunderName() */
482 : /*************************************************************************/
483 :
484 1523 : std::wstring FGDBLaunderName(const std::wstring &name)
485 : {
486 1523 : std::wstring newName = name;
487 :
488 : // https://support.esri.com/en/technical-article/000005588
489 :
490 : // "Do not start field or table names with an underscore or a number."
491 : // But we can see in the wild table names starting with underscore...
492 : // (cf https://github.com/OSGeo/gdal/issues/4112)
493 1523 : if (!newName.empty() && newName[0] >= '0' && newName[0] <= '9')
494 : {
495 2 : newName = StringToWString("_") + newName;
496 : }
497 :
498 : // "Essentially, eliminate anything that is not alphanumeric or an
499 : // underscore." Note: alphanumeric unicode is supported
500 10725 : for (size_t i = 0; i < newName.size(); i++)
501 : {
502 17307 : if (!(newName[i] == '_' || (newName[i] >= '0' && newName[i] <= '9') ||
503 8105 : (newName[i] >= 'a' && newName[i] <= 'z') ||
504 312 : (newName[i] >= 'A' && newName[i] <= 'Z') || newName[i] >= 128))
505 : {
506 20 : newName[i] = '_';
507 : }
508 : }
509 :
510 1523 : return newName;
511 : }
512 :
513 : /*************************************************************************/
514 : /* FGDBEscapeUnsupportedPrefixes() */
515 : /*************************************************************************/
516 :
517 148 : std::wstring FGDBEscapeUnsupportedPrefixes(const std::wstring &className)
518 : {
519 148 : std::wstring newName = className;
520 : // From ESRI docs
521 : // Feature classes starting with these strings are unsupported.
522 : static const char *const UNSUPPORTED_PREFIXES[] = {"sde_", "gdb_", "delta_",
523 : nullptr};
524 :
525 589 : for (int i = 0; UNSUPPORTED_PREFIXES[i] != nullptr; i++)
526 : {
527 : // cppcheck-suppress stlIfStrFind
528 442 : if (newName.find(StringToWString(UNSUPPORTED_PREFIXES[i])) == 0)
529 : {
530 : // Normally table names shouldn't start with underscore, but
531 : // there are such in the wild (cf
532 : // https://github.com/OSGeo/gdal/issues/4112)
533 1 : newName = StringToWString("_") + newName;
534 1 : break;
535 : }
536 : }
537 :
538 148 : return newName;
539 : }
540 :
541 : /*************************************************************************/
542 : /* FGDBEscapeReservedKeywords() */
543 : /*************************************************************************/
544 :
545 1523 : std::wstring FGDBEscapeReservedKeywords(const std::wstring &name)
546 : {
547 3046 : std::string newName = WStringToString(name);
548 3046 : std::string upperName = CPLString(newName).toupper();
549 :
550 : // From ESRI docs
551 : static const char *const RESERVED_WORDS[] = {
552 : FGDB_OID_NAME, "ADD", "ALTER", "AND", "AS", "ASC",
553 : "BETWEEN", "BY", "COLUMN", "CREATE", "DATE", "DELETE",
554 : "DESC", "DROP", "EXISTS", "FOR", "FROM", "IN",
555 : "INSERT", "INTO", "IS", "LIKE", "NOT", "NULL",
556 : "OR", "ORDER", "SELECT", "SET", "TABLE", "UPDATE",
557 : "VALUES", "WHERE", nullptr};
558 :
559 : // Append an underscore to any FGDB reserved words used as field names
560 : // This is the same behavior ArcCatalog follows.
561 50227 : for (int i = 0; RESERVED_WORDS[i] != nullptr; i++)
562 : {
563 48706 : const char *w = RESERVED_WORDS[i];
564 48706 : if (upperName == w)
565 : {
566 2 : newName += '_';
567 2 : break;
568 : }
569 : }
570 :
571 3046 : return StringToWString(newName);
572 : }
|