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