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