/src/gdal/ogr/ogrsf_frmts/gpkg/ogrgeopackageutility.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: GeoPackage Translator |
4 | | * Purpose: Utility functions for OGR GeoPackage driver. |
5 | | * Author: Paul Ramsey, pramsey@boundlessgeo.com |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2013, Paul Ramsey <pramsey@boundlessgeo.com> |
9 | | * |
10 | | * SPDX-License-Identifier: MIT |
11 | | ****************************************************************************/ |
12 | | |
13 | | #include "ogrgeopackageutility.h" |
14 | | #include "ogr_p.h" |
15 | | #include "ogr_wkb.h" |
16 | | #include "sqlite/ogrsqlitebase.h" |
17 | | #include <limits> |
18 | | |
19 | | /* Requirement 20: A GeoPackage SHALL store feature table geometries */ |
20 | | /* with the basic simple feature geometry types (Geometry, Point, */ |
21 | | /* LineString, Polygon, MultiPoint, MultiLineString, MultiPolygon, */ |
22 | | /* GeomCollection) */ |
23 | | /* http://opengis.github.io/geopackage/#geometry_types */ |
24 | | OGRwkbGeometryType GPkgGeometryTypeToWKB(const char *pszGpkgType, bool bHasZ, |
25 | | bool bHasM) |
26 | 5.58k | { |
27 | 5.58k | OGRwkbGeometryType oType; |
28 | | |
29 | 5.58k | if (EQUAL("Geometry", pszGpkgType)) |
30 | 366 | oType = wkbUnknown; |
31 | | /* The 1.0 spec is not completely clear on what should be used... */ |
32 | 5.22k | else if (EQUAL("GeomCollection", pszGpkgType) || |
33 | 5.22k | EQUAL("GeometryCollection", pszGpkgType)) |
34 | 0 | oType = wkbGeometryCollection; |
35 | 5.22k | else |
36 | 5.22k | { |
37 | 5.22k | oType = OGRFromOGCGeomType(pszGpkgType); |
38 | 5.22k | if (oType == wkbUnknown) |
39 | 3.47k | oType = wkbNone; |
40 | 5.22k | } |
41 | | |
42 | 5.58k | if ((oType != wkbNone) && bHasZ) |
43 | 313 | { |
44 | 313 | oType = wkbSetZ(oType); |
45 | 313 | } |
46 | 5.58k | if ((oType != wkbNone) && bHasM) |
47 | 528 | { |
48 | 528 | oType = wkbSetM(oType); |
49 | 528 | } |
50 | | |
51 | 5.58k | return oType; |
52 | 5.58k | } |
53 | | |
54 | | /* Requirement 5: The columns of tables in a GeoPackage SHALL only be */ |
55 | | /* declared using one of the data types specified in table GeoPackage */ |
56 | | /* Data Types. */ |
57 | | /* http://opengis.github.io/geopackage/#table_column_data_types */ |
58 | | // return a OGRFieldType value or OFTMaxType + 1 |
59 | | int GPkgFieldToOGR(const char *pszGpkgType, OGRFieldSubType &eSubType, |
60 | | int &nMaxWidth) |
61 | 5.04k | { |
62 | 5.04k | eSubType = OFSTNone; |
63 | 5.04k | nMaxWidth = 0; |
64 | | |
65 | | /* Integer types */ |
66 | 5.04k | if (STRNCASECMP("INT", pszGpkgType, 3) == 0) |
67 | 939 | { |
68 | 939 | if (!EQUAL("INT", pszGpkgType) && !EQUAL("INTEGER", pszGpkgType)) |
69 | 461 | { |
70 | 461 | CPLError(CE_Warning, CPLE_AppDefined, |
71 | 461 | "Field format '%s' not supported. " |
72 | 461 | "Interpreted as INT", |
73 | 461 | pszGpkgType); |
74 | 461 | } |
75 | 939 | return OFTInteger64; |
76 | 939 | } |
77 | 4.11k | else if (EQUAL("MEDIUMINT", pszGpkgType)) |
78 | 8 | return OFTInteger; |
79 | 4.10k | else if (EQUAL("SMALLINT", pszGpkgType)) |
80 | 0 | { |
81 | 0 | eSubType = OFSTInt16; |
82 | 0 | return OFTInteger; |
83 | 0 | } |
84 | 4.10k | else if (EQUAL("TINYINT", pszGpkgType)) |
85 | 0 | return OFTInteger; // [-128, 127] |
86 | 4.10k | else if (EQUAL("BOOLEAN", pszGpkgType)) |
87 | 0 | { |
88 | 0 | eSubType = OFSTBoolean; |
89 | 0 | return OFTInteger; |
90 | 0 | } |
91 | | |
92 | | /* Real types */ |
93 | 4.10k | else if (EQUAL("FLOAT", pszGpkgType)) |
94 | 24 | { |
95 | 24 | eSubType = OFSTFloat32; |
96 | 24 | return OFTReal; |
97 | 24 | } |
98 | 4.07k | else if (EQUAL("DOUBLE", pszGpkgType)) |
99 | 0 | return OFTReal; |
100 | 4.07k | else if (EQUAL("REAL", pszGpkgType)) |
101 | 316 | return OFTReal; |
102 | | |
103 | | // Only used normally in gpkg_data_column_constraints table, and we |
104 | | // need this only is reading it through ExecuteSQL() |
105 | 3.76k | else if (EQUAL("NUMERIC", pszGpkgType)) |
106 | 0 | return OFTReal; |
107 | | |
108 | | /* String/binary types */ |
109 | 3.76k | else if (STRNCASECMP("TEXT", pszGpkgType, 4) == 0) |
110 | 117 | { |
111 | 117 | if (pszGpkgType[4] == '(') |
112 | 1 | nMaxWidth = atoi(pszGpkgType + 5); |
113 | 116 | else if (pszGpkgType[4] != '\0') |
114 | 0 | { |
115 | 0 | CPLError(CE_Warning, CPLE_AppDefined, |
116 | 0 | "Field format '%s' not supported. " |
117 | 0 | "Interpreted as TEXT", |
118 | 0 | pszGpkgType); |
119 | 0 | } |
120 | 117 | return OFTString; |
121 | 117 | } |
122 | | |
123 | 3.64k | else if (STRNCASECMP("BLOB", pszGpkgType, 4) == 0) |
124 | 69 | { |
125 | 69 | if (pszGpkgType[4] != '(' && pszGpkgType[4] != '\0') |
126 | 11 | { |
127 | 11 | CPLError(CE_Warning, CPLE_AppDefined, |
128 | 11 | "Field format '%s' not supported. " |
129 | 11 | "Interpreted as BLOB", |
130 | 11 | pszGpkgType); |
131 | 11 | } |
132 | 69 | return OFTBinary; |
133 | 69 | } |
134 | | |
135 | | /* Date types */ |
136 | 3.57k | else if (EQUAL("DATE", pszGpkgType)) |
137 | 0 | return OFTDate; |
138 | 3.57k | else if (EQUAL("DATETIME", pszGpkgType)) |
139 | 5 | return OFTDateTime; |
140 | | |
141 | | /* Illegal! */ |
142 | 3.57k | else |
143 | 3.57k | { |
144 | 3.57k | if (GPkgGeometryTypeToWKB(pszGpkgType, false, false) == wkbNone) |
145 | 2.98k | { |
146 | 2.98k | CPLError(CE_Warning, CPLE_AppDefined, |
147 | 2.98k | "Field format '%s' not supported", pszGpkgType); |
148 | 2.98k | } |
149 | 3.57k | return OFTMaxType + 1; |
150 | 3.57k | } |
151 | 5.04k | } |
152 | | |
153 | | /* Requirement 5: The columns of tables in a GeoPackage SHALL only be */ |
154 | | /* declared using one of the data types specified in table GeoPackage */ |
155 | | /* Data Types. */ |
156 | | /* http://opengis.github.io/geopackage/#table_column_data_types */ |
157 | | const char *GPkgFieldFromOGR(OGRFieldType eType, OGRFieldSubType eSubType, |
158 | | int nMaxWidth) |
159 | 28.0k | { |
160 | 28.0k | switch (eType) |
161 | 28.0k | { |
162 | 160 | case OFTInteger: |
163 | 160 | { |
164 | 160 | if (eSubType == OFSTBoolean) |
165 | 37 | return "BOOLEAN"; |
166 | 123 | else if (eSubType == OFSTInt16) |
167 | 0 | return "SMALLINT"; |
168 | 123 | else |
169 | 123 | return "MEDIUMINT"; |
170 | 160 | } |
171 | 32 | case OFTInteger64: |
172 | 32 | return "INTEGER"; |
173 | 82 | case OFTReal: |
174 | 82 | { |
175 | 82 | if (eSubType == OFSTFloat32) |
176 | 0 | return "FLOAT"; |
177 | 82 | else |
178 | 82 | return "REAL"; |
179 | 82 | } |
180 | 27.7k | case OFTString: |
181 | 27.7k | { |
182 | 27.7k | if (nMaxWidth > 0) |
183 | 169 | return CPLSPrintf("TEXT(%d)", nMaxWidth); |
184 | 27.5k | else |
185 | 27.5k | return "TEXT"; |
186 | 27.7k | } |
187 | 0 | case OFTBinary: |
188 | 0 | return "BLOB"; |
189 | 0 | case OFTDate: |
190 | 0 | return "DATE"; |
191 | 0 | case OFTDateTime: |
192 | 0 | return "DATETIME"; |
193 | 0 | default: |
194 | 0 | return "TEXT"; |
195 | 28.0k | } |
196 | 28.0k | } |
197 | | |
198 | | /* Requirement 19: A GeoPackage SHALL store feature table geometries |
199 | | * with or without optional elevation (Z) and/or measure (M) values in SQL |
200 | | * BLOBs using the Standard GeoPackageBinary format specified in table |
201 | | * GeoPackage SQL Geometry Binary Format and clause Geometry Encoding. |
202 | | * |
203 | | * http://opengis.github.io/geopackage/#gpb_format |
204 | | * |
205 | | * GeoPackageBinaryHeader { |
206 | | * byte[2] magic = 0x4750; |
207 | | * byte version; |
208 | | * byte flags; |
209 | | * int32 srs_id; |
210 | | * double[] envelope; |
211 | | * } |
212 | | * |
213 | | * StandardGeoPackageBinary { |
214 | | * GeoPackageBinaryHeader header; |
215 | | * WKBGeometry geometry; |
216 | | * } |
217 | | * |
218 | | * Flags byte contents: |
219 | | * Bit 7: Reserved for future |
220 | | * Bit 6: Reserved for future |
221 | | * Bit 5: Using Extended GPKG Binary? |
222 | | * Bit 4: Geometry is Empty? |
223 | | * Bit 3,2,1: Envelope contents (0 none, 1=X/Y, 2=X/Y/Z, 3=X/Y/M, 4=X/Y/Z/M) |
224 | | * Bit 0: Byte order of header (0=big/XDR, 1=little/NDR) |
225 | | * |
226 | | */ |
227 | | |
228 | | GByte *GPkgGeometryFromOGR(const OGRGeometry *poGeometry, int iSrsId, |
229 | | const OGRGeomCoordinateBinaryPrecision *psPrecision, |
230 | | size_t *pnWkbLen) |
231 | 28.5k | { |
232 | 28.5k | CPLAssert(poGeometry != nullptr); |
233 | | |
234 | 28.5k | GByte byFlags = 0; |
235 | 28.5k | GByte byEnv = 1; |
236 | 28.5k | OGRwkbExportOptions wkbExportOptions; |
237 | 28.5k | if (psPrecision) |
238 | 28.5k | wkbExportOptions.sPrecision = *psPrecision; |
239 | 28.5k | wkbExportOptions.eByteOrder = static_cast<OGRwkbByteOrder>(CPL_IS_LSB); |
240 | 28.5k | OGRErr err; |
241 | 28.5k | bool bPoint = (wkbFlatten(poGeometry->getGeometryType()) == wkbPoint); |
242 | 28.5k | bool bEmpty = poGeometry->IsEmpty(); |
243 | | /* We voluntarily use getCoordinateDimension() so as to get only 2 for |
244 | | * XY/XYM */ |
245 | | /* and 3 for XYZ/XYZM as we currently don't write envelopes with M extent. |
246 | | */ |
247 | 28.5k | int iDims = poGeometry->getCoordinateDimension(); |
248 | | |
249 | | /* Header has 8 bytes for sure, and optional extra space for bounds */ |
250 | 28.5k | size_t nHeaderLen = 2 + 1 + 1 + 4; |
251 | 28.5k | if (!bPoint && !bEmpty) |
252 | 3.99k | { |
253 | 3.99k | nHeaderLen += 8 * 2 * iDims; |
254 | 3.99k | } |
255 | | |
256 | | /* Total BLOB size is header + WKB size */ |
257 | 28.5k | size_t nWkbLen = nHeaderLen + poGeometry->WkbSize(); |
258 | 28.5k | if (nWkbLen > static_cast<size_t>(std::numeric_limits<int>::max())) |
259 | 0 | { |
260 | 0 | CPLError(CE_Failure, CPLE_NotSupported, "too big geometry blob"); |
261 | 0 | return nullptr; |
262 | 0 | } |
263 | 28.5k | GByte *pabyWkb = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nWkbLen)); |
264 | 28.5k | if (!pabyWkb) |
265 | 0 | return nullptr; |
266 | 28.5k | if (pnWkbLen) |
267 | 28.5k | *pnWkbLen = nWkbLen; |
268 | | |
269 | | /* Header Magic */ |
270 | 28.5k | pabyWkb[0] = 0x47; |
271 | 28.5k | pabyWkb[1] = 0x50; |
272 | | |
273 | | /* GPKG BLOB Version */ |
274 | 28.5k | pabyWkb[2] = 0; |
275 | | |
276 | | /* Extended? No. */ |
277 | | |
278 | | /* Envelope dimensionality? */ |
279 | | |
280 | | /* Don't write envelope for point type */ |
281 | 28.5k | if (bPoint) |
282 | 11.6k | byEnv = 0; |
283 | 16.9k | else |
284 | | /* 3D envelope for 3D data */ |
285 | 16.9k | if (iDims == 3) |
286 | 1.61k | byEnv = 2; |
287 | | /* 2D envelope otherwise */ |
288 | 15.2k | else |
289 | 15.2k | byEnv = 1; |
290 | | |
291 | | /* Empty? No envelope then. */ |
292 | 28.5k | if (bEmpty) |
293 | 13.1k | { |
294 | 13.1k | byEnv = 0; |
295 | | /* Set empty flag */ |
296 | 13.1k | byFlags |= (1 << 4); |
297 | 13.1k | } |
298 | | |
299 | | /* Set envelope flags */ |
300 | 28.5k | byFlags |= (byEnv << 1); |
301 | | |
302 | | /* Byte order of header? */ |
303 | | /* Use native endianness */ |
304 | 28.5k | byFlags |= wkbExportOptions.eByteOrder; |
305 | | |
306 | | /* Write flags byte */ |
307 | 28.5k | pabyWkb[3] = byFlags; |
308 | | |
309 | | /* Write srs_id */ |
310 | 28.5k | memcpy(pabyWkb + 4, &iSrsId, 4); |
311 | | |
312 | | /* Write envelope */ |
313 | 28.5k | if (!bEmpty && !bPoint) |
314 | 3.99k | { |
315 | 3.99k | double *padPtr = reinterpret_cast<double *>(pabyWkb + 8); |
316 | 3.99k | if (iDims == 3) |
317 | 553 | { |
318 | 553 | OGREnvelope3D oEnv3d; |
319 | 553 | poGeometry->getEnvelope(&oEnv3d); |
320 | 553 | padPtr[0] = oEnv3d.MinX; |
321 | 553 | padPtr[1] = oEnv3d.MaxX; |
322 | 553 | padPtr[2] = oEnv3d.MinY; |
323 | 553 | padPtr[3] = oEnv3d.MaxY; |
324 | 553 | padPtr[4] = oEnv3d.MinZ; |
325 | 553 | padPtr[5] = oEnv3d.MaxZ; |
326 | 553 | } |
327 | 3.44k | else |
328 | 3.44k | { |
329 | 3.44k | OGREnvelope oEnv; |
330 | 3.44k | poGeometry->getEnvelope(&oEnv); |
331 | 3.44k | padPtr[0] = oEnv.MinX; |
332 | 3.44k | padPtr[1] = oEnv.MaxX; |
333 | 3.44k | padPtr[2] = oEnv.MinY; |
334 | 3.44k | padPtr[3] = oEnv.MaxY; |
335 | 3.44k | } |
336 | 3.99k | } |
337 | | |
338 | 28.5k | GByte *pabyPtr = pabyWkb + nHeaderLen; |
339 | | |
340 | | /* Use the wkbVariantIso for ISO SQL/MM output (differs for 3d geometry) */ |
341 | 28.5k | wkbExportOptions.eWkbVariant = wkbVariantIso; |
342 | 28.5k | err = poGeometry->exportToWkb(pabyPtr, &wkbExportOptions); |
343 | 28.5k | if (err != OGRERR_NONE) |
344 | 0 | { |
345 | 0 | CPLFree(pabyWkb); |
346 | 0 | return nullptr; |
347 | 0 | } |
348 | | |
349 | 28.5k | return pabyWkb; |
350 | 28.5k | } |
351 | | |
352 | | OGRErr GPkgHeaderFromWKB(const GByte *pabyGpkg, size_t nGpkgLen, |
353 | | GPkgHeader *poHeader) |
354 | 76.4k | { |
355 | 76.4k | CPLAssert(pabyGpkg != nullptr); |
356 | 76.4k | CPLAssert(poHeader != nullptr); |
357 | | |
358 | | /* Magic (match required) */ |
359 | 76.4k | if (nGpkgLen < 8 || pabyGpkg[0] != 0x47 || pabyGpkg[1] != 0x50 || |
360 | 75.4k | pabyGpkg[2] != 0) /* Version (only 0 supported at this time)*/ |
361 | 2.15k | { |
362 | 2.15k | memset(poHeader, 0, sizeof(*poHeader)); |
363 | 2.15k | return OGRERR_FAILURE; |
364 | 2.15k | } |
365 | | |
366 | | /* Flags */ |
367 | 74.3k | GByte byFlags = pabyGpkg[3]; |
368 | 74.3k | poHeader->bEmpty = (byFlags & (0x01 << 4)) >> 4; |
369 | 74.3k | poHeader->bExtended = (byFlags & (0x01 << 5)) >> 5; |
370 | 74.3k | poHeader->eByteOrder = static_cast<OGRwkbByteOrder>(byFlags & 0x01); |
371 | 74.3k | poHeader->bExtentHasXY = false; |
372 | 74.3k | poHeader->bExtentHasZ = false; |
373 | | #ifdef notdef |
374 | | poHeader->bExtentHasM = false; |
375 | | #endif |
376 | 74.3k | bool bSwap = OGR_SWAP(poHeader->eByteOrder); |
377 | | |
378 | | /* Envelope */ |
379 | 74.3k | int iEnvelope = (byFlags & (0x07 << 1)) >> 1; |
380 | 74.3k | int nEnvelopeDim = 0; |
381 | 74.3k | if (iEnvelope) |
382 | 21.4k | { |
383 | 21.4k | poHeader->bExtentHasXY = true; |
384 | 21.4k | if (iEnvelope == 1) |
385 | 18.0k | { |
386 | 18.0k | nEnvelopeDim = 2; /* 2D envelope */ |
387 | 18.0k | } |
388 | 3.38k | else if (iEnvelope == 2) |
389 | 2.77k | { |
390 | 2.77k | poHeader->bExtentHasZ = true; |
391 | 2.77k | nEnvelopeDim = 3; /* 2D+Z envelope */ |
392 | 2.77k | } |
393 | 615 | else if (iEnvelope == 3) |
394 | 0 | { |
395 | | #ifdef notdef |
396 | | poHeader->bExtentHasM = true; |
397 | | #endif |
398 | 0 | nEnvelopeDim = 3; /* 2D+M envelope */ |
399 | 0 | } |
400 | 615 | else if (iEnvelope == 4) |
401 | 615 | { |
402 | 615 | poHeader->bExtentHasZ = true; |
403 | | #ifdef notdef |
404 | | poHeader->bExtentHasM = true; |
405 | | #endif |
406 | 615 | nEnvelopeDim = 4; /* 2D+ZM envelope */ |
407 | 615 | } |
408 | 0 | else |
409 | 0 | { |
410 | 0 | return OGRERR_FAILURE; |
411 | 0 | } |
412 | 21.4k | } |
413 | | |
414 | | /* SrsId */ |
415 | 74.3k | int iSrsId = 0; |
416 | 74.3k | memcpy(&iSrsId, pabyGpkg + 4, 4); |
417 | 74.3k | if (bSwap) |
418 | 530 | { |
419 | 530 | iSrsId = CPL_SWAP32(iSrsId); |
420 | 530 | } |
421 | 74.3k | poHeader->iSrsId = iSrsId; |
422 | | |
423 | 74.3k | if (nGpkgLen < static_cast<size_t>(8 + 8 * 2 * nEnvelopeDim)) |
424 | 1.47k | { |
425 | | // Not enough bytes |
426 | 1.47k | return OGRERR_FAILURE; |
427 | 1.47k | } |
428 | | |
429 | | /* Envelope */ |
430 | 72.8k | const double *padPtr = reinterpret_cast<const double *>(pabyGpkg + 8); |
431 | 72.8k | if (poHeader->bExtentHasXY) |
432 | 19.9k | { |
433 | 19.9k | poHeader->MinX = padPtr[0]; |
434 | 19.9k | poHeader->MaxX = padPtr[1]; |
435 | 19.9k | poHeader->MinY = padPtr[2]; |
436 | 19.9k | poHeader->MaxY = padPtr[3]; |
437 | 19.9k | if (bSwap) |
438 | 0 | { |
439 | 0 | CPL_SWAPDOUBLE(&(poHeader->MinX)); |
440 | 0 | CPL_SWAPDOUBLE(&(poHeader->MaxX)); |
441 | 0 | CPL_SWAPDOUBLE(&(poHeader->MinY)); |
442 | 0 | CPL_SWAPDOUBLE(&(poHeader->MaxY)); |
443 | 0 | } |
444 | 19.9k | } |
445 | 72.8k | if (poHeader->bExtentHasZ) |
446 | 2.76k | { |
447 | 2.76k | poHeader->MinZ = padPtr[4]; |
448 | 2.76k | poHeader->MaxZ = padPtr[5]; |
449 | 2.76k | if (bSwap) |
450 | 0 | { |
451 | 0 | CPL_SWAPDOUBLE(&(poHeader->MinZ)); |
452 | 0 | CPL_SWAPDOUBLE(&(poHeader->MaxZ)); |
453 | 0 | } |
454 | 2.76k | } |
455 | | #ifdef notdef |
456 | | if (poHeader->bExtentHasM) |
457 | | { |
458 | | poHeader->MinM = padPtr[(poHeader->bExtentHasZ) ? 6 : 4]; |
459 | | poHeader->MaxM = padPtr[(poHeader->bExtentHasZ) ? 7 : 5]; |
460 | | if (bSwap) |
461 | | { |
462 | | CPL_SWAPDOUBLE(&(poHeader->MinM)); |
463 | | CPL_SWAPDOUBLE(&(poHeader->MaxM)); |
464 | | } |
465 | | } |
466 | | #endif |
467 | | |
468 | | /* Header size in byte stream */ |
469 | 72.8k | poHeader->nHeaderLen = 8 + 8 * 2 * nEnvelopeDim; |
470 | | |
471 | | #ifdef DEBUG_VERBOSE |
472 | | std::string s; |
473 | | for (size_t i = poHeader->nHeaderLen; i < nGpkgLen; ++i) |
474 | | { |
475 | | s += CPLSPrintf("%02X ", pabyGpkg[i]); |
476 | | } |
477 | | CPLDebug("GPKG", "Bytes after GPKG header: %s", s.c_str()); |
478 | | #endif |
479 | | |
480 | | // Workaround for a Spatialite bug. The CastToXYZ() function, when |
481 | | // called on an empty geometry, and EnableGpkgMode() is enabled, |
482 | | // returns an empty geometry, not tagged as such, but with an invalid |
483 | | // bounding box. |
484 | | // Cf https://github.com/OSGeo/gdal/issues/13557 |
485 | 72.8k | if (!poHeader->bEmpty && poHeader->bExtentHasXY && |
486 | 19.9k | poHeader->nHeaderLen == 40 && |
487 | 17.2k | poHeader->MinX == std::numeric_limits<double>::max() && |
488 | 0 | poHeader->MaxX == -std::numeric_limits<double>::max() && |
489 | 0 | poHeader->MinY == std::numeric_limits<double>::max() && |
490 | 0 | poHeader->MaxY == -std::numeric_limits<double>::max() && |
491 | | // CastToXYZ(POLYGON EMPTY) returns just the GPKG header for some reason |
492 | 0 | (nGpkgLen == 40 || |
493 | | // CastToXYZ(LINESTRING EMPTY) returns a LINESTRING Z EMPTY |
494 | 0 | (nGpkgLen == 49 && |
495 | 0 | memcmp(pabyGpkg + 40, "\x01\xEA\x03\x00\x00\x00\x00\x00\x00", 9) == |
496 | 0 | 0) || |
497 | | // CastToXYZ(POINT EMPTY) returns a Point Z (NaN NaN 0) |
498 | | // CastToXYZ(POINT Z EMPTY) returns a Point Z (NaN NaN NaN) |
499 | 0 | (nGpkgLen == 69 && memcmp(pabyGpkg + 40, |
500 | 0 | "\x01" |
501 | 0 | "\xE9\x03\x00\x00" |
502 | 0 | "\x00\x00\x00\x00\x00\x00\xF8\x7F" |
503 | 0 | "\x00\x00\x00\x00\x00\x00\xF8\x7F", |
504 | 0 | 21) == 0))) |
505 | 0 | { |
506 | 0 | CPLDebugOnce("GPKG", |
507 | 0 | "Work arounding a Spatialite bug with empty geometries"); |
508 | 0 | poHeader->bEmpty = true; |
509 | 0 | poHeader->bExtentHasXY = false; |
510 | 0 | } |
511 | | |
512 | 72.8k | return OGRERR_NONE; |
513 | 74.3k | } |
514 | | |
515 | | bool GPkgUpdateHeader(GByte *pabyGpkg, size_t nGpkgLen, int nSrsId, double MinX, |
516 | | double MaxX, double MinY, double MaxY, double MinZ, |
517 | | double MaxZ) |
518 | 0 | { |
519 | 0 | CPLAssert(nGpkgLen >= 8); |
520 | | |
521 | | /* Flags */ |
522 | 0 | const GByte byFlags = pabyGpkg[3]; |
523 | 0 | const auto eByteOrder = static_cast<OGRwkbByteOrder>(byFlags & 0x01); |
524 | 0 | const bool bSwap = OGR_SWAP(eByteOrder); |
525 | | |
526 | | /* SrsId */ |
527 | 0 | if (bSwap) |
528 | 0 | { |
529 | 0 | nSrsId = CPL_SWAP32(nSrsId); |
530 | 0 | } |
531 | 0 | memcpy(pabyGpkg + 4, &nSrsId, 4); |
532 | | |
533 | | /* Envelope */ |
534 | 0 | const int iEnvelope = (byFlags & (0x07 << 1)) >> 1; |
535 | 0 | int nEnvelopeDim = 0; |
536 | 0 | if (iEnvelope) |
537 | 0 | { |
538 | 0 | if (iEnvelope == 1) |
539 | 0 | { |
540 | 0 | nEnvelopeDim = 2; /* 2D envelope */ |
541 | 0 | } |
542 | 0 | else if (iEnvelope == 2) |
543 | 0 | { |
544 | 0 | nEnvelopeDim = 3; /* 2D+Z envelope */ |
545 | 0 | } |
546 | 0 | else if (iEnvelope == 3) |
547 | 0 | { |
548 | 0 | nEnvelopeDim = 3; /* 2D+M envelope */ |
549 | 0 | } |
550 | 0 | else if (iEnvelope == 4) |
551 | 0 | { |
552 | 0 | nEnvelopeDim = 4; /* 2D+ZM envelope */ |
553 | 0 | } |
554 | 0 | else |
555 | 0 | { |
556 | 0 | return false; |
557 | 0 | } |
558 | 0 | } |
559 | 0 | else |
560 | 0 | { |
561 | 0 | return true; |
562 | 0 | } |
563 | | |
564 | 0 | if (nGpkgLen < static_cast<size_t>(8 + 8 * 2 * nEnvelopeDim)) |
565 | 0 | { |
566 | | // Not enough bytes |
567 | 0 | return false; |
568 | 0 | } |
569 | | |
570 | | /* Envelope */ |
571 | 0 | if (bSwap) |
572 | 0 | { |
573 | 0 | CPL_SWAPDOUBLE(&(MinX)); |
574 | 0 | CPL_SWAPDOUBLE(&(MaxX)); |
575 | 0 | CPL_SWAPDOUBLE(&(MinY)); |
576 | 0 | CPL_SWAPDOUBLE(&(MaxY)); |
577 | 0 | CPL_SWAPDOUBLE(&(MinZ)); |
578 | 0 | CPL_SWAPDOUBLE(&(MaxZ)); |
579 | 0 | } |
580 | |
|
581 | 0 | double *padPtr = reinterpret_cast<double *>(pabyGpkg + 8); |
582 | 0 | memcpy(&padPtr[0], &MinX, sizeof(double)); |
583 | 0 | memcpy(&padPtr[1], &MaxX, sizeof(double)); |
584 | 0 | memcpy(&padPtr[2], &MinY, sizeof(double)); |
585 | 0 | memcpy(&padPtr[3], &MaxY, sizeof(double)); |
586 | |
|
587 | 0 | if (iEnvelope == 2 || iEnvelope == 4) |
588 | 0 | { |
589 | 0 | memcpy(&padPtr[4], &MinZ, sizeof(double)); |
590 | 0 | memcpy(&padPtr[5], &MaxZ, sizeof(double)); |
591 | 0 | } |
592 | |
|
593 | 0 | return true; |
594 | 0 | } |
595 | | |
596 | | OGRGeometry *GPkgGeometryToOGR(const GByte *pabyGpkg, size_t nGpkgLen, |
597 | | OGRSpatialReference *poSrs) |
598 | 7.40k | { |
599 | 7.40k | CPLAssert(pabyGpkg != nullptr); |
600 | | |
601 | 7.40k | GPkgHeader oHeader; |
602 | | |
603 | | /* Read header */ |
604 | 7.40k | OGRErr err = GPkgHeaderFromWKB(pabyGpkg, nGpkgLen, &oHeader); |
605 | 7.40k | if (err != OGRERR_NONE) |
606 | 2.51k | return nullptr; |
607 | | |
608 | | /* WKB pointer */ |
609 | 4.89k | const GByte *pabyWkb = pabyGpkg + oHeader.nHeaderLen; |
610 | 4.89k | size_t nWkbLen = nGpkgLen - oHeader.nHeaderLen; |
611 | | |
612 | | /* Parse WKB */ |
613 | 4.89k | OGRGeometry *poGeom = nullptr; |
614 | 4.89k | err = OGRGeometryFactory::createFromWkb(pabyWkb, poSrs, &poGeom, |
615 | 4.89k | static_cast<int>(nWkbLen)); |
616 | 4.89k | if (err != OGRERR_NONE) |
617 | 219 | return nullptr; |
618 | | |
619 | 4.67k | return poGeom; |
620 | 4.89k | } |
621 | | |
622 | | /************************************************************************/ |
623 | | /* OGRGeoPackageGetHeader() */ |
624 | | /************************************************************************/ |
625 | | |
626 | | bool OGRGeoPackageGetHeader(sqlite3_context * /*pContext*/, int /*argc*/, |
627 | | sqlite3_value **argv, GPkgHeader *psHeader, |
628 | | bool bNeedExtent, bool bNeedExtent3D, int iGeomIdx) |
629 | 69.0k | { |
630 | | |
631 | | // Extent3D implies extent |
632 | 69.0k | const bool bNeedAnyExtent{bNeedExtent || bNeedExtent3D}; |
633 | | |
634 | 69.0k | if (sqlite3_value_type(argv[iGeomIdx]) != SQLITE_BLOB) |
635 | 0 | { |
636 | 0 | memset(psHeader, 0, sizeof(*psHeader)); |
637 | 0 | return false; |
638 | 0 | } |
639 | 69.0k | const int nBLOBLen = sqlite3_value_bytes(argv[iGeomIdx]); |
640 | 69.0k | const GByte *pabyBLOB = |
641 | 69.0k | reinterpret_cast<const GByte *>(sqlite3_value_blob(argv[iGeomIdx])); |
642 | | |
643 | 69.0k | if (nBLOBLen < 8) |
644 | 0 | { |
645 | 0 | memset(psHeader, 0, sizeof(*psHeader)); |
646 | 0 | return false; |
647 | 0 | } |
648 | 69.0k | else if (GPkgHeaderFromWKB(pabyBLOB, nBLOBLen, psHeader) != OGRERR_NONE) |
649 | 1.11k | { |
650 | 1.11k | bool bEmpty = false; |
651 | 1.11k | memset(psHeader, 0, sizeof(*psHeader)); |
652 | 1.11k | if (OGRSQLiteGetSpatialiteGeometryHeader( |
653 | 1.11k | pabyBLOB, nBLOBLen, &(psHeader->iSrsId), nullptr, &bEmpty, |
654 | 1.11k | &(psHeader->MinX), &(psHeader->MinY), &(psHeader->MaxX), |
655 | 1.11k | &(psHeader->MaxY)) == OGRERR_NONE) |
656 | 0 | { |
657 | 0 | psHeader->bEmpty = bEmpty; |
658 | 0 | psHeader->bExtentHasXY = !bEmpty; |
659 | 0 | if (!bNeedExtent3D && !(bEmpty && bNeedAnyExtent)) |
660 | 0 | return true; |
661 | 0 | } |
662 | | |
663 | 1.11k | return false; |
664 | 1.11k | } |
665 | | |
666 | 67.9k | if (psHeader->bEmpty && bNeedAnyExtent) |
667 | 0 | { |
668 | 0 | return false; |
669 | 0 | } |
670 | 67.9k | else if (!psHeader->bExtentHasXY && bNeedExtent && !bNeedExtent3D) |
671 | 28.0k | { |
672 | 28.0k | OGREnvelope sEnvelope; |
673 | 28.0k | if (OGRWKBGetBoundingBox(pabyBLOB + psHeader->nHeaderLen, |
674 | 28.0k | static_cast<size_t>(nBLOBLen) - |
675 | 28.0k | psHeader->nHeaderLen, |
676 | 28.0k | sEnvelope)) |
677 | 28.0k | { |
678 | 28.0k | psHeader->MinX = sEnvelope.MinX; |
679 | 28.0k | psHeader->MaxX = sEnvelope.MaxX; |
680 | 28.0k | psHeader->MinY = sEnvelope.MinY; |
681 | 28.0k | psHeader->MaxY = sEnvelope.MaxY; |
682 | 28.0k | return true; |
683 | 28.0k | } |
684 | 0 | return false; |
685 | 28.0k | } |
686 | 39.9k | else if (!psHeader->bExtentHasZ && bNeedExtent3D) |
687 | 0 | { |
688 | 0 | OGREnvelope3D sEnvelope3D; |
689 | 0 | if (OGRWKBGetBoundingBox(pabyBLOB + psHeader->nHeaderLen, |
690 | 0 | static_cast<size_t>(nBLOBLen) - |
691 | 0 | psHeader->nHeaderLen, |
692 | 0 | sEnvelope3D)) |
693 | 0 | { |
694 | 0 | psHeader->MinX = sEnvelope3D.MinX; |
695 | 0 | psHeader->MaxX = sEnvelope3D.MaxX; |
696 | 0 | psHeader->MinY = sEnvelope3D.MinY; |
697 | 0 | psHeader->MaxY = sEnvelope3D.MaxY; |
698 | 0 | psHeader->MinZ = sEnvelope3D.MinZ; |
699 | 0 | psHeader->MaxZ = sEnvelope3D.MaxZ; |
700 | 0 | return true; |
701 | 0 | } |
702 | 0 | return false; |
703 | 0 | } |
704 | 39.9k | return true; |
705 | 67.9k | } |