Skip to content

Instantly share code, notes, and snippets.

@taviroquai
Created May 8, 2016 17:58
Show Gist options
  • Save taviroquai/858b9519becf2703687045a04fb87421 to your computer and use it in GitHub Desktop.
Save taviroquai/858b9519becf2703687045a04fb87421 to your computer and use it in GitHub Desktop.
PHP parse GeoPackage
<?php
/**
* Parse GeoPackageBinaryHeader
*
* References
*
* http://www.geopackage.org/spec/#gpb_spec
*
* https://en.wikipedia.org/wiki/Well-known_text
* http://php.net/manual/en/function.unpack.php
* http://ngageoint.github.io/geopackage-js/ (NodeJS + SQL.js Demo)
*
* @param string $filename
*/
protected function parseGeoPackageGeometry($filename)
{
// Default values
$header = [
'magic' => '',
'version' => 0,
'flags' => 0,
'srs_id' => 0,
'envelope' => []
];
$wkb = '';
// Open binary
$h = fopen($filename, 'rb');
if (!$h) {
throw new \Exception('Could not open stream (geometry data)');
}
// Get stream stats
$fstat = fstat($h);
$total = $fstat['size'];
$read = 0;
// Parse header
$bytes = unpack('A2magic/c1version/c1flags', fread($h, 4));
$read += 4;
$header['magic'] = $bytes['magic'];
$header['version'] = $bytes['version'];
$header['flags'] = $bytes['flags'];
$header['envelop_flag'] = ($header['flags'] >> 1) & 7;
$header['byte_order'] = $header['flags'] & 1;
// Parse SRID
$unpack_op = $header['byte_order'] ? 'V' : 'N';
$bytes = array_values(unpack($unpack_op, fread($h, 4)));
$read += 4;
$header['srs_id'] = $bytes[0];
switch ($header['envelop_flag']) {
case 1: // 32 bytes envelop
$unpack_op = $header['byte_order'] ? 'f*' : 'f*';
$bytes = array_values(unpack('f*', fread($h, 32)));
$header['envelope'] = [
'minx' => $bytes[0],
'miny' => $bytes[1],
'maxx' => $bytes[2],
'maxy' => $bytes[3],
'minz' => false,
'maxz' => false,
'minm' => false,
'maxm' => false
];
$read += 32;
break;
case 2: // 48 bytes envelop
$unpack_op = $header['byte_order'] ? 'f*' : 'f*';
$bytes = array_values(unpack('f*', fread($h, 48)));
$header['envelope'] = [
'minx' => $bytes[0],
'miny' => $bytes[1],
'maxx' => $bytes[2],
'maxy' => $bytes[3],
'minz' => $bytes[4],
'maxz' => $bytes[5],
'minm' => false,
'maxm' => false
];
$read += 48;
break;
case 3: // 48 bytes envelop
$unpack_op = $header['byte_order'] ? 'f*' : 'f*';
$bytes = array_values(unpack('f*', fread($h, 48)));
$header['envelope'] = [
'minx' => $bytes[0],
'miny' => $bytes[1],
'maxx' => $bytes[2],
'maxy' => $bytes[3],
'minz' => false,
'maxz' => false,
'minm' => $bytes[4],
'maxm' => $bytes[5]
];
$read += 48;
break;
default: ;// 0 envelop
}
// Get WKB from bytes left
$wkb = fread($h, $total - $read);
// Close handler
fclose($h);
return [$header, $wkb];
}
@taviroquai
Copy link
Author

When passing the WKB binary to wkx.js using Buffer, it does not recognize the geometry and throws an error.

NOTES:

  1. Using sample file http://www.geopackage.org/data/simple_sewer_features.gpkg from http://www.geopackage.org/#sampledata
  2. Using in browser wkx.js from https://github.com/cschwarz/wkx

Steps to reproduce:

  1. Open sample file .gpkg in PHP with SQLite PDO (OK)
  2. Query: SELECT * FROM s_manhole WHERE the_geom IS NOT NULL (OK)
  3. Foreach geometry call PHP function parseGeoPackageGeometry (OK)
  4. Save WKB result as binary file (OK)
  5. Open WKB in browser with wkx library (ERROR: GeometryType 963 not supported)

@danielbarela
Copy link

danielbarela commented May 8, 2016

Appears you are reading floats for minx miny etc, but all of those are doubles (8 bytes).

@judgej
Copy link

judgej commented Apr 16, 2020

This is great, parsing the geod blobs nicely from the https://gadm.org/ data. Thank you.

The only change I needed to make was to replace $filename with $filestream, leaving it up to the caller to option the stream. In my case the stream is created from a string that is selected directly from the GeoPackage SQLite tables. No files are involved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment