Skip to content

Commit ff390db

Browse files
authored
Add support for GeoPackage 1.2 format (#9)
1 parent fcafde9 commit ff390db

9 files changed

Lines changed: 99 additions & 3 deletions

File tree

readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ The library is able to recognize:
297297
- `Shapefile`
298298
- `Shapefile` inside `zip` archive
299299
- `GeoTIFF`
300+
- `GeoPackage` format version 1.2 (can contain both vector and raster data, but will be reported as vector)
300301
- Styled Layer Descriptor `SLD` files for layer styles in XML format
301302

302303
You can check if a file is supported using
@@ -350,6 +351,10 @@ docker-compose -f ./tests/docker-compose.yml up -d
350351
vendor/bin/phpunit
351352
```
352353

354+
**Notes on testing files**
355+
356+
- The GeoPackage testing file was copied from [github.com/ngageoint/geopackage-js](https://github.com/ngageoint/geopackage-js/blob/master/test/fixtures/rivers.gpkg)
357+
353358
## Contributing
354359

355360
Hey, we're accepting Pull Requests. Please target your pull request to the `master` branch.

src/GeoFormat.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ final class GeoFormat
1111
const SHAPEFILE_ZIP = "shapefile_zip";
1212
const GEOTIFF = "geotiff";
1313
const SLD = "SLD";
14+
const GEOPACKAGE = "geopackage";
1415
}

src/Http/InteractsWithHttp.php

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,7 @@ private function checkResponseError(ResponseInterface $response)
4242
$responseBody = $response->getBody();
4343
$contentTypeHeader = $response->getHeader('Content-Type');
4444
$contentType = !empty($contentTypeHeader) ? $contentTypeHeader[0] : '';
45-
4645
if ($response->getStatusCode() !== 200 && $response->getStatusCode() !== 201 && $response->getStatusCode() !== 204) {
47-
// dump($response);
4846
if ($response->getStatusCode() === 500 && strpos($contentType, 'text/html')!== false) {
4947
$reason = substr((string)$responseBody, 0, 500);
5048

src/Support/BinaryReader.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,25 @@ public static function isGeoTiff($path)
114114
return $hasGeoKeyDirectory;
115115
}
116116

117+
public static function isGeoPackage($path)
118+
{
119+
$handle = self::openFileBinary($path);
120+
$sqliteMagic = self::getString(fread($handle, 16), 15);
121+
fseek($handle, 68);
122+
$gpkgMagic = self::getString(fread($handle, 4), 4);
123+
self::closeFile($handle);
124+
125+
if($sqliteMagic !== 'SQLite format 3'){
126+
return false;
127+
}
128+
129+
if($gpkgMagic !== 'GPKG'){
130+
return false;
131+
}
132+
133+
return true;
134+
}
135+
117136
private static function getBytes($data, $length, $offset = 0, $big_endian = true)
118137
{
119138
if ($length <= 2) {
@@ -124,4 +143,19 @@ private static function getBytes($data, $length, $offset = 0, $big_endian = true
124143
// unsigned short 16bit current(unpack($big_endian ? 'n' : 'v', $tiffHeader, 4));
125144
// unsigned long 32bit current(unpack($big_endian ? 'N' : 'V', $tiffHeader, 4));
126145
}
146+
147+
private static function getString($data, $length, $offset = 0)
148+
{
149+
try{
150+
$chars = [];
151+
152+
for ($i=$offset; $i < $length; $i++) {
153+
$chars[] = current(unpack('a', $data, $i));
154+
}
155+
156+
return implode('', $chars);
157+
}catch(Exception $ex){
158+
return '';
159+
}
160+
}
127161
}

src/Support/TypeResolver.php

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ final class TypeResolver
1212
GeoFormat::SHAPEFILE => 'application/octet-stream', // shapefile
1313
GeoFormat::SHAPEFILE_ZIP => 'application/zip', // shapefile in ZIP container
1414
GeoFormat::GEOTIFF => 'image/tiff', // geotiff
15-
GeoFormat::SLD => 'application/vnd.ogc.sld+xml', // geotiff
15+
GeoFormat::SLD => 'application/vnd.ogc.sld+xml',
16+
GeoFormat::GEOPACKAGE => 'application/geopackage+sqlite3',
1617
];
1718

1819
protected static $mimeTypeToFormat = [];
@@ -21,10 +22,12 @@ final class TypeResolver
2122
GeoFormat::SHAPEFILE => GeoType::VECTOR,
2223
GeoFormat::SHAPEFILE_ZIP => GeoType::VECTOR,
2324
GeoFormat::GEOTIFF => GeoType::RASTER,
25+
GeoFormat::GEOPACKAGE => GeoType::VECTOR,
2426

2527
GeoType::VECTOR => [
2628
GeoFormat::SHAPEFILE,
2729
GeoFormat::SHAPEFILE_ZIP,
30+
GeoFormat::GEOPACKAGE,
2831
],
2932
GeoType::RASTER => [
3033
GeoFormat::GEOTIFF,
@@ -38,6 +41,7 @@ final class TypeResolver
3841
GeoFormat::SHAPEFILE => 'shp',
3942
GeoFormat::SHAPEFILE_ZIP => 'shp',
4043
GeoFormat::GEOTIFF => 'geotiff',
44+
GeoFormat::GEOPACKAGE => 'gpkg',
4145
];
4246

4347
/**
@@ -89,6 +93,9 @@ public static function identify($path)
8993
$format = GeoFormat::SLD;
9094
$mimeType = self::$mimeTypes[GeoFormat::SLD];
9195
}
96+
} elseif (BinaryReader::isGeoPackage($path)) {
97+
$format = GeoFormat::GEOPACKAGE;
98+
$mimeType = self::$mimeTypes[GeoFormat::GEOPACKAGE];
9299
}
93100

94101
$type = self::convertFormatToType($format);

tests/Integration/GeoServerDataStoresTest.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,4 +190,41 @@ public function test_shapefile_in_zip_archive_can_be_renamed_during_upload_and_d
190190

191191
$this->assertFalse($this->geoserver->exist($data), "Data still exists after remove");
192192
}
193+
194+
195+
public function test_geopackage_can_be_uploaded_and_deleted()
196+
{
197+
$datastoreName = 'rivers';
198+
$data = GeoFile::from(__DIR__ . '/../fixtures/rivers.gpkg')->name($datastoreName);
199+
200+
$feature = $this->geoserver->upload($data);
201+
202+
$this->assertInstanceOf(Feature::class, $feature);
203+
$this->assertEquals(GeoType::VECTOR, $feature->type());
204+
$this->assertEquals($datastoreName, $feature->name);
205+
$this->assertEquals($datastoreName, $feature->title);
206+
$this->assertEquals($datastoreName, $feature->nativeName);
207+
$this->assertEquals("EPSG:3857", $feature->srs);
208+
$this->assertTrue($feature->enabled);
209+
$this->assertFalse($feature->overridingServiceSRS);
210+
$this->assertFalse($feature->skipNumberMatched);
211+
$this->assertFalse($feature->circularArcPresent);
212+
$this->assertNotNull($feature->store);
213+
$this->assertNotNull($feature->keywords);
214+
$this->assertNotNull($feature->nativeBoundingBox);
215+
$this->assertNotNull($feature->boundingBox);
216+
$this->assertEquals("EPSG:4326", $feature->boundingBox->crs);
217+
$this->assertEquals(-165.18163033675, $feature->boundingBox->minX);
218+
$this->assertEquals(-50.786361166949, $feature->boundingBox->minY);
219+
$this->assertEquals(176.5891535779, $feature->boundingBox->maxX);
220+
$this->assertEquals(73.578794485117, $feature->boundingBox->maxY);
221+
222+
$this->assertTrue($this->geoserver->exist($data), "Data not existing after upload");
223+
224+
$deleteResult = $this->geoserver->remove($data);
225+
226+
$this->assertTrue($deleteResult, "GeoFile not deleted");
227+
228+
$this->assertFalse($this->geoserver->exist($data), "Data still exists after remove");
229+
}
193230
}

tests/Unit/GeoFileTest.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public function supported_files()
1414
[__DIR__ . '/../fixtures/shapefile.shp'],
1515
[__DIR__ . '/../fixtures/shapefile.zip'],
1616
[__DIR__ . '/../fixtures/geotiff.tiff'],
17+
[__DIR__ . '/../fixtures/empty.gpkg'],
1718
];
1819
}
1920

@@ -87,6 +88,19 @@ public function test_geotiff_is_recognized()
8788
$this->assertEquals($file->originalName, $file->name);
8889
}
8990

91+
public function test_geopackage_is_recognized()
92+
{
93+
$file = GeoFile::from(__DIR__ . '/../fixtures/empty.gpkg');
94+
95+
$this->assertInstanceOf(GeoFile::class, $file);
96+
$this->assertEquals(GeoFormat::GEOPACKAGE, $file->format);
97+
$this->assertEquals(GeoType::VECTOR, $file->type);
98+
$this->assertEquals('application/geopackage+sqlite3', $file->mimeType);
99+
$this->assertEquals('gpkg', $file->extension);
100+
$this->assertEquals('empty.gpkg', $file->name);
101+
$this->assertEquals($file->originalName, $file->name);
102+
}
103+
90104
public function test_copy_to_temporary()
91105
{
92106
$file = GeoFile::from(__DIR__ . '/../fixtures/buildings.zip');

tests/fixtures/empty.gpkg

25 KB
Binary file not shown.

tests/fixtures/rivers.gpkg

8.45 MB
Binary file not shown.

0 commit comments

Comments
 (0)