EVOLUTION-MANAGER
Edit File: ogrgeopackageutility.cpp
/****************************************************************************** * $Id$ * * Project: GeoPackage Translator * Purpose: Utility functions for OGR GeoPackage driver. * Author: Paul Ramsey, pramsey@boundlessgeo.com * ****************************************************************************** * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com> * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. ****************************************************************************/ #include "ogrgeopackageutility.h" #include "ogr_p.h" /* Runs a SQL command and ignores the result (good for INSERT/UPDATE/CREATE) */ OGRErr SQLCommand(sqlite3 * poDb, const char * pszSQL) { CPLAssert( poDb != NULL ); CPLAssert( pszSQL != NULL ); char *pszErrMsg = NULL; int rc = sqlite3_exec(poDb, pszSQL, NULL, NULL, &pszErrMsg); if ( rc != SQLITE_OK ) { CPLError( CE_Failure, CPLE_AppDefined, "sqlite3_exec(%s) failed: %s", pszSQL, pszErrMsg ); return OGRERR_FAILURE; } return OGRERR_NONE; } OGRErr SQLResultInit(SQLResult * poResult) { poResult->papszResult = NULL; poResult->pszErrMsg = NULL; poResult->nRowCount = 0; poResult->nColCount = 0; poResult->rc = 0; return OGRERR_NONE; } OGRErr SQLQuery(sqlite3 * poDb, const char * pszSQL, SQLResult * poResult) { CPLAssert( poDb != NULL ); CPLAssert( pszSQL != NULL ); CPLAssert( poResult != NULL ); SQLResultInit(poResult); poResult->rc = sqlite3_get_table( poDb, pszSQL, &(poResult->papszResult), &(poResult->nRowCount), &(poResult->nColCount), &(poResult->pszErrMsg) ); if( poResult->rc != SQLITE_OK ) { CPLError( CE_Failure, CPLE_AppDefined, "sqlite3_get_table(%s) failed: %s", pszSQL, poResult->pszErrMsg ); return OGRERR_FAILURE; } return OGRERR_NONE; } OGRErr SQLResultFree(SQLResult * poResult) { if ( poResult->papszResult ) sqlite3_free_table(poResult->papszResult); if ( poResult->pszErrMsg ) sqlite3_free(poResult->pszErrMsg); return OGRERR_NONE; } const char* SQLResultGetColumn(const SQLResult * poResult, int iColNum) { if ( ! poResult ) return NULL; if ( iColNum < 0 || iColNum >= poResult->nColCount ) return NULL; return poResult->papszResult[iColNum]; } const char* SQLResultGetValue(const SQLResult * poResult, int iColNum, int iRowNum) { if ( ! poResult ) return NULL; int nCols = poResult->nColCount; int nRows = poResult->nRowCount; if ( iColNum < 0 || iColNum >= nCols ) return NULL; if ( iRowNum < 0 || iRowNum >= nRows ) return NULL; return poResult->papszResult[ nCols + iRowNum * nCols + iColNum ]; } int SQLResultGetValueAsInteger(const SQLResult * poResult, int iColNum, int iRowNum) { if ( ! poResult ) return 0; int nCols = poResult->nColCount; int nRows = poResult->nRowCount; if ( iColNum < 0 || iColNum >= nCols ) return 0; if ( iRowNum < 0 || iRowNum >= nRows ) return 0; char *pszValue = poResult->papszResult[ nCols + iRowNum * nCols + iColNum ]; if ( ! pszValue ) return 0; return atoi(pszValue); } /* Returns the first row of first column of SQL as integer */ int SQLGetInteger(sqlite3 * poDb, const char * pszSQL, OGRErr *err) { CPLAssert( poDb != NULL ); sqlite3_stmt *poStmt; int rc, i; /* Prepare the SQL */ rc = sqlite3_prepare_v2(poDb, pszSQL, strlen(pszSQL), &poStmt, NULL); if ( rc != SQLITE_OK ) { CPLError( CE_Failure, CPLE_AppDefined, "sqlite3_prepare_v2(%s) failed: %s", pszSQL, sqlite3_errmsg( poDb ) ); if ( err ) *err = OGRERR_FAILURE; return 0; } /* Execute and fetch first row */ rc = sqlite3_step(poStmt); if ( rc != SQLITE_ROW ) { if ( err ) *err = OGRERR_FAILURE; return 0; } /* Read the integer from the row */ i = sqlite3_column_int(poStmt, 0); sqlite3_finalize(poStmt); if ( err ) *err = OGRERR_NONE; return i; } /* Requirement 20: A GeoPackage SHALL store feature table geometries */ /* with the basic simple feature geometry types (Geometry, Point, */ /* LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, */ /* GeomCollection) */ /* http://opengis.github.io/geopackage/#geometry_types */ OGRwkbGeometryType GPkgGeometryTypeToWKB(const char *pszGpkgType, int bHasZ) { OGRwkbGeometryType oType; if ( EQUAL("Geometry", pszGpkgType) ) oType = wkbUnknown; else if ( EQUAL("Point", pszGpkgType) ) oType = wkbPoint; else if ( EQUAL("LineString", pszGpkgType) ) oType = wkbLineString; else if ( EQUAL("Polygon", pszGpkgType) ) oType = wkbPolygon; else if ( EQUAL("MultiPoint", pszGpkgType) ) oType = wkbMultiPoint; else if ( EQUAL("MultiLineString", pszGpkgType) ) oType = wkbMultiLineString; else if ( EQUAL("MultiPolygon", pszGpkgType) ) oType = wkbMultiPolygon; /* The 1.0 spec is not completely clear on what should be used... */ else if ( EQUAL("GeomCollection", pszGpkgType) || EQUAL("GeometryCollection", pszGpkgType) ) oType = wkbGeometryCollection; else oType = wkbNone; if ( (oType != wkbNone) && bHasZ ) { unsigned int oi = oType; oi &= wkb25DBit; oType = (OGRwkbGeometryType)oi; } return oType; } /* Requirement 20: A GeoPackage SHALL store feature table geometries */ /* with the basic simple feature geometry types (Geometry, Point, */ /* LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, */ /* GeomCollection) */ /* http://opengis.github.io/geopackage/#geometry_types */ const char* GPkgGeometryTypeFromWKB(OGRwkbGeometryType oType) { oType = wkbFlatten(oType); switch(oType) { case wkbPoint: return "point"; case wkbLineString: return "linestring"; case wkbPolygon: return "polygon"; case wkbMultiPoint: return "multipoint"; case wkbMultiLineString: return "multilinestring"; case wkbMultiPolygon: return "multipolygon"; case wkbGeometryCollection: return "geometrycollection"; default: return NULL; } } /* Requirement 5: The columns of tables in a GeoPackage SHALL only be */ /* declared using one of the data types specified in table GeoPackage */ /* Data Types. */ /* http://opengis.github.io/geopackage/#table_column_data_types */ OGRFieldType GPkgFieldToOGR(const char *pszGpkgType) { /* Integer types */ if ( STRNCASECMP("INTEGER", pszGpkgType, 7) == 0 ) return OFTInteger; else if ( STRNCASECMP("INT", pszGpkgType, 3) == 0 ) return OFTInteger; else if ( STRNCASECMP("MEDIUMINT", pszGpkgType, 9) == 0 ) return OFTInteger; else if ( STRNCASECMP("SMALLINT", pszGpkgType, 8) == 0 ) return OFTInteger; else if ( STRNCASECMP("TINYINT", pszGpkgType, 7) == 0 ) return OFTInteger; else if ( STRNCASECMP("BOOLEAN", pszGpkgType, 7) == 0 ) return OFTInteger; /* Real types */ else if ( STRNCASECMP("FLOAT", pszGpkgType, 5) == 0 ) return OFTReal; else if ( STRNCASECMP("DOUBLE", pszGpkgType, 6) == 0 ) return OFTReal; else if ( STRNCASECMP("REAL", pszGpkgType, 4) == 0 ) return OFTReal; /* String/binary types */ else if ( STRNCASECMP("TEXT", pszGpkgType, 4) == 0 ) return OFTString; else if ( STRNCASECMP("BLOB", pszGpkgType, 4) == 0 ) return OFTBinary; /* Date types */ else if ( STRNCASECMP("DATE", pszGpkgType, 4) == 0 ) return OFTDate; else if ( STRNCASECMP("DATETIME", pszGpkgType, 8) == 0 ) return OFTDateTime; /* Illegal! */ else return (OGRFieldType)(OFTMaxType + 1); } /* Requirement 5: The columns of tables in a GeoPackage SHALL only be */ /* declared using one of the data types specified in table GeoPackage */ /* Data Types. */ /* http://opengis.github.io/geopackage/#table_column_data_types */ const char* GPkgFieldFromOGR(OGRFieldType nType) { switch(nType) { case OFTInteger: return "INTEGER"; case OFTReal: return "REAL"; case OFTString: return "TEXT"; case OFTBinary: return "BLOB"; case OFTDate: return "DATE"; case OFTDateTime: return "DATETIME"; default: return NULL; } } int SQLiteFieldFromOGR(OGRFieldType nType) { switch(nType) { case OFTInteger: return SQLITE_INTEGER; case OFTReal: return SQLITE_FLOAT; case OFTString: return SQLITE_TEXT; case OFTBinary: return SQLITE_BLOB; case OFTDate: return SQLITE_TEXT; case OFTDateTime: return SQLITE_TEXT; default: return 0; } } /* Requirement 19: A GeoPackage SHALL store feature table geometries * with or without optional elevation (Z) and/or measure (M) values in SQL * BLOBs using the Standard GeoPackageBinary format specified in table GeoPackage * SQL Geometry Binary Format and clause Geometry Encoding. * * http://opengis.github.io/geopackage/#gpb_format * * GeoPackageBinaryHeader { * byte[2] magic = 0x4750; * byte version; * byte flags; * int32 srs_id; * double[] envelope; * } * * StandardGeoPackageBinary { * GeoPackageBinaryHeader header; * WKBGeometry geometry; * } * * Flags byte contents: * Bit 7: Reserved for future * Bit 6: Reserved for future * Bit 5: Using Extended GPKG Binary? * Bit 4: Geometry is Empty? * Bit 3,2,1: Envelope contents (0 none, 1=X/Y, 2=X/Y/Z, 3=X/Y/M, 4=X/Y/Z/M) * Bit 0: Byte order of header (0=big/XDR, 1=little/NDR) * */ GByte* GPkgGeometryFromOGR(const OGRGeometry *poGeometry, int iSrsId, size_t *pszWkb) { CPLAssert( poGeometry != NULL ); GByte *pabyPtr; GByte byFlags = 0; GByte byEnv = 1; OGRwkbByteOrder eByteOrder = (OGRwkbByteOrder)CPL_IS_LSB; OGRErr err; OGRBoolean bPoint = (wkbFlatten(poGeometry->getGeometryType()) == wkbPoint); OGRBoolean bEmpty = poGeometry->IsEmpty(); int iDims = poGeometry->getCoordinateDimension(); /* Header has 8 bytes for sure, and optional extra space for bounds */ size_t szHeader = 2+1+1+4; if ( ! bPoint && ! bEmpty ) { szHeader += 8*2*iDims; } /* Total BLOB size is header + WKB size */ size_t szWkb = szHeader + poGeometry->WkbSize(); GByte *pabyWkb = (GByte *)CPLMalloc(szWkb); if (pszWkb) *pszWkb = szWkb; /* Header Magic */ pabyWkb[0] = 0x47; pabyWkb[1] = 0x50; /* GPKG BLOB Version */ pabyWkb[2] = 0; /* Extended? No. */ /* Envelope dimensionality? */ /* Don't write envelope for point type */ if ( bPoint ) byEnv = 0; else /* 3D envelope for 3D data */ if ( iDims == 3 ) byEnv = 2; /* 2D envelope otherwise */ else byEnv = 1; /* Empty? No envelope then. */ if ( bEmpty ) { byEnv = 0; /* Set empty flag */ byFlags |= (1 << 4); } /* Set envelope flags */ byFlags |= (byEnv << 1); /* Byte order of header? */ /* Use native endianness */ byFlags |= eByteOrder; /* Write flags byte */ pabyWkb[3] = byFlags; /* Write srs_id */ memcpy(pabyWkb+4, &iSrsId, 4); /* Write envelope */ if ( ! bEmpty && ! bPoint ) { double *padPtr = (double*)(pabyWkb+8); if ( iDims == 3 ) { OGREnvelope3D oEnv3d; poGeometry->getEnvelope(&oEnv3d); padPtr[0] = oEnv3d.MinX; padPtr[1] = oEnv3d.MaxX; padPtr[2] = oEnv3d.MinY; padPtr[3] = oEnv3d.MaxY; padPtr[4] = oEnv3d.MinZ; padPtr[5] = oEnv3d.MaxZ; } else { OGREnvelope oEnv; poGeometry->getEnvelope(&oEnv); padPtr[0] = oEnv.MinX; padPtr[1] = oEnv.MaxX; padPtr[2] = oEnv.MinY; padPtr[3] = oEnv.MaxY; } } pabyPtr = pabyWkb + szHeader; /* Use the wkbVariantIso for ISO SQL/MM output (differs for 3d geometry) */ err = poGeometry->exportToWkb(eByteOrder, pabyPtr, wkbVariantIso); if ( err != OGRERR_NONE ) { CPLFree(pabyWkb); return NULL; } return pabyWkb; } OGRErr GPkgHeaderFromWKB(const GByte *pabyGpkg, GPkgHeader *poHeader) { CPLAssert( pabyGpkg != NULL ); CPLAssert( poHeader != NULL ); /* Magic (match required) */ if ( pabyGpkg[0] != 0x47 || pabyGpkg[1] != 0x50 || pabyGpkg[2] != 0 ) /* Version (only 0 supported at this time)*/ { return OGRERR_FAILURE; } /* Flags */ GByte byFlags = pabyGpkg[3]; poHeader->bEmpty = (byFlags & (0x01 << 4)) >> 4; poHeader->bExtended = (byFlags & (0x01 << 5)) >> 5; poHeader->eByteOrder = (OGRwkbByteOrder)(byFlags & 0x01); OGRBoolean bSwap = OGR_SWAP(poHeader->eByteOrder); /* Envelope */ int iEnvelope = (byFlags & (0x07 << 1)) >> 1; if ( iEnvelope == 1 ) poHeader->iDims = 2; /* 2D envelope */ else if ( iEnvelope == 2 ) poHeader->iDims = 3; /* 3D envelope */ else poHeader->iDims = 0; /* No envelope */ /* SrsId */ int iSrsId; memcpy(&iSrsId, pabyGpkg+4, 4); if ( bSwap ) { iSrsId = CPL_SWAP32(iSrsId); } poHeader->iSrsId = iSrsId; /* Envelope */ double *padPtr = (double*)(pabyGpkg+8); if ( poHeader->iDims == 2 ) { poHeader->MinX = padPtr[0]; poHeader->MaxX = padPtr[1]; poHeader->MinY = padPtr[2]; poHeader->MaxY = padPtr[3]; if ( bSwap ) { CPL_SWAPDOUBLE(&(poHeader->MinX)); CPL_SWAPDOUBLE(&(poHeader->MaxX)); CPL_SWAPDOUBLE(&(poHeader->MinY)); CPL_SWAPDOUBLE(&(poHeader->MaxY)); } } if ( poHeader->iDims == 3 ) { poHeader->MinZ = padPtr[4]; poHeader->MaxZ = padPtr[5]; if ( bSwap ) { CPL_SWAPDOUBLE(&(poHeader->MinZ)); CPL_SWAPDOUBLE(&(poHeader->MaxZ)); } } /* Header size in byte stream */ poHeader->szHeader = 8 + 8*2*(poHeader->iDims); return OGRERR_NONE; } OGRGeometry* GPkgGeometryToOGR(GByte *pabyGpkg, size_t szGpkg, OGRSpatialReference *poSrs) { CPLAssert( pabyGpkg != NULL ); GPkgHeader oHeader; OGRGeometry *poGeom; /* Read header */ OGRErr err = GPkgHeaderFromWKB(pabyGpkg, &oHeader); if ( err != OGRERR_NONE ) return NULL; /* WKB pointer */ GByte *pabyWkb = pabyGpkg + oHeader.szHeader; size_t szWkb = szGpkg - oHeader.szHeader; /* Parse WKB */ err = OGRGeometryFactory::createFromWkb(pabyWkb, poSrs, &poGeom, szWkb); if ( err != OGRERR_NONE ) return NULL; return poGeom; } OGRErr GPkgEnvelopeToOGR(GByte *pabyGpkg, CPL_UNUSED size_t szGpkg, OGREnvelope *poEnv) { CPLAssert( poEnv != NULL ); CPLAssert( pabyGpkg != NULL ); GPkgHeader oHeader; /* Read header */ OGRErr err = GPkgHeaderFromWKB(pabyGpkg, &oHeader); if ( err != OGRERR_NONE ) return err; if ( oHeader.bEmpty || oHeader.iDims == 0 ) { return OGRERR_FAILURE; } poEnv->MinX = oHeader.MinX; poEnv->MaxX = oHeader.MaxX; poEnv->MinY = oHeader.MinY; poEnv->MaxY = oHeader.MaxY; // if ( oHeader.iDims == 3 ) // { // poEnv->MinZ = oHeader.MinZ; // poEnv->MaxZ = oHeader.MaxZ; // } return OGRERR_NONE; }