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