diff --git a/tileserver.php b/tileserver.php index 54103aa..46890ee 100644 --- a/tileserver.php +++ b/tileserver.php @@ -4,7 +4,7 @@ * TileServer.php project * ====================== * https://github.com/klokantech/tileserver-php/ - * Copyright (C) 2014 - Klokan Technologies GmbH + * Copyright (C) 2016 - Klokan Technologies GmbH */ global $config; @@ -143,7 +143,7 @@ class Server { } /** - * + * Get metadata from metadataJson * @param string $jsonFileName * @return array */ @@ -166,7 +166,7 @@ class Server { $resultdata = $result->fetchAll(); foreach ($resultdata as $r) { - $value = preg_replace('/(\\n)+/','',$r['value']); + $value = preg_replace('/(\\n)+/', '', $r['value']); $metadata[$r['name']] = addslashes($value); } if (!array_key_exists('minzoom', $metadata) @@ -175,10 +175,12 @@ class Server { // autodetect minzoom and maxzoom $result = $this->db->query('select min(zoom_level) as min, max(zoom_level) as max from tiles'); $resultdata = $result->fetchAll(); - if (!array_key_exists('minzoom', $metadata)) + if (!array_key_exists('minzoom', $metadata)){ $metadata['minzoom'] = $resultdata[0]['min']; - if (!array_key_exists('maxzoom', $metadata)) + } + if (!array_key_exists('maxzoom', $metadata)){ $metadata['maxzoom'] = $resultdata[0]['max']; + } } // autodetect format using JPEG magic number FFD8 if (!array_key_exists('format', $metadata)) { @@ -192,10 +194,10 @@ class Server { if (!array_key_exists('bounds', $metadata)) { $result = $this->db->query('select min(tile_column) as w, max(tile_column) as e, min(tile_row) as s, max(tile_row) as n from tiles where zoom_level='.$metadata['maxzoom']); $resultdata = $result->fetchAll(); - $w = -180 + 360 * ($resultdata[0]['w'] / pow(2,$metadata['maxzoom'])); - $e = -180 + 360 * ((1+$resultdata[0]['e']) / pow(2,$metadata['maxzoom'])); + $w = -180 + 360 * ($resultdata[0]['w'] / pow(2, $metadata['maxzoom'])); + $e = -180 + 360 * ((1 + $resultdata[0]['e']) / pow(2, $metadata['maxzoom'])); $n = $this->row2lat($resultdata[0]['n'], $metadata['maxzoom']); - $s = $this->row2lat($resultdata[0]['s']-1, $metadata['maxzoom']); + $s = $this->row2lat($resultdata[0]['s'] - 1, $metadata['maxzoom']); $metadata['bounds'] = implode(',', array($w, $s, $e, $n)); } $metadata = $this->metadataValidation($metadata); @@ -211,8 +213,8 @@ class Server { * @return integer */ public function row2lat($r, $zoom) { - $y = $r / pow(2,$zoom-1) - 1; - return rad2deg(2.0 * atan(exp(3.191459196*$y)) - 1.57079632679489661922); + $y = $r / pow(2, $zoom - 1 ) - 1; + return rad2deg(2.0 * atan(exp(3.191459196 * $y)) - 1.57079632679489661922); } /** @@ -221,26 +223,33 @@ class Server { * @return object */ public function metadataValidation($metadata) { - if (array_key_exists('bounds', $metadata)) { + if (!array_key_exists('bounds', $metadata)) { + $metadata['bounds'] = array(-180, -85.06, 180, 85.06); + } elseif (!is_array($metadata['bounds'])) { $metadata['bounds'] = array_map('floatval', explode(',', $metadata['bounds'])); - } else { - $metadata['bounds'] = array(-180, -85.051128779807, 180, 85.051128779807); } if (!array_key_exists('profile', $metadata)) { $metadata['profile'] = 'mercator'; } -// TODO: detect thumb / SQL for mbtiles - if (array_key_exists('minzoom', $metadata)) + if (array_key_exists('minzoom', $metadata)){ $metadata['minzoom'] = intval($metadata['minzoom']); - else + }else{ $metadata['minzoom'] = 0; - if (array_key_exists('maxzoom', $metadata)) + } + if (array_key_exists('maxzoom', $metadata)){ $metadata['maxzoom'] = intval($metadata['maxzoom']); - else + }else{ $metadata['maxzoom'] = 18; + } if (!array_key_exists('format', $metadata)) { $metadata['format'] = 'png'; } + if (!array_key_exists('scale', $metadata)) { + $metadata['scale'] = 1; + } + + // TODO: detect thumb / SQL for mbtiles + return $metadata; } @@ -272,8 +281,8 @@ class Server { $filename = $filename . '.mbtiles'; $lastModifiedTime = filemtime($filename); $eTag = md5($lastModifiedTime); - header("Last-Modified: " . gmdate("D, d M Y H:i:s", $lastModifiedTime) . " GMT"); - header("Etag:" . $eTag); + header('Last-Modified: ' . gmdate('D, d M Y H:i:s', $lastModifiedTime) . ' GMT'); + header('Etag:' . $eTag); if (@strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModifiedTime || @trim($_SERVER['HTTP_IF_NONE_MATCH']) == $eTag) { return TRUE; @@ -351,7 +360,7 @@ class Server { die; } else { //scale of tile (for retina tiles) - $meta = json_decode(file_get_contents($tileset.'/metadata.json')); + $meta = json_decode(file_get_contents($tileset . '/metadata.json')); if(!isset($meta->scale)){ $meta->scale = 1; } @@ -359,7 +368,7 @@ class Server { $this->getCleanTile($meta->scale, $ext); } else { header('HTTP/1.1 404 Not Found'); - echo 'Server: Unknown or not specified dataset "'.$tileset.'"'; + echo 'Server: Unknown or not specified dataset "' . $tileset . '"'; die; } } @@ -478,7 +487,7 @@ class Server { . $this->config['baseUrls'][0] . '/' . $map['basename'] . '.json
'; echo 'Bounds: ' . $extend . '

'; } - echo '

Copyright (C) 2014 - Klokan Technologies GmbH

'; + echo '

Copyright (C) 2016 - Klokan Technologies GmbH

'; echo ''; } @@ -638,7 +647,7 @@ class Json extends Server { public function getJson() { parent::setDatasets(); header('Access-Control-Allow-Origin: *'); - header("Content-Type: application/json; charset=utf-8"); + header('Content-Type: application/json; charset=utf-8'); if ($this->callback !== 'grid') { echo $this->callback . '(' . $this->createJson($this->layer) . ');'; die; } else { @@ -652,7 +661,7 @@ class Json extends Server { public function getJsonp() { parent::setDatasets(); header('Access-Control-Allow-Origin: *'); - header("Content-Type: application/javascript; charset=utf-8"); + header('Content-Type: application/javascript; charset=utf-8'); echo $this->callback . '(' . $this->createJson($this->layer) . ');'; } @@ -719,11 +728,190 @@ class Wmts extends Server { } } + /** + * Validates tilematrixset, calculates missing params + * @param Obrject $tileMatrix + * @return Object + */ + public function parseTileMatrix($layer, $tileMatrix){ + + //process projection + if(isset($layer['proj4'])){ + preg_match_all("/([^+= ]+)=([^= ]+)/", $layer['proj4'], $res); + $proj4 = array_combine($res[1], $res[2]); + } + + for($i = 0; $i < count($tileMatrix); $i++){ + + if(!isset($tileMatrix[$i]['id'])){ + $tileMatrix[$i]['id'] = (string) $i; + } + if (!isset($tileMatrix[$i]['extent']) && isset($layer['extent'])) { + $tileMatrix[$i]['extent'] = $layer['extent']; + } + if (!isset($tileMatrix[$i]['matrix_size'])) { + $tileExtent = $this->tilesOfExtent( + $tileMatrix[$i]['extent'], + $tileMatrix[$i]['origin'], + $tileMatrix[$i]['pixel_size'], + $tileMatrix[$i]['tile_size'] + ); + $tileMatrix[$i]['matrix_size'] = array( + $tileExtent[2] + 1, + $tileExtent[1] + 1 + ); + } + if(!isset($tileMatrix[$i]['origin']) && isset($tileMatrix[$i]['extent'])){ + $tileMatrix[$i]['origin'] = array( + $tileMatrix[$i]['extent'][0], $tileMatrix[$i]['extent'][3] + ); + } + // Origins of geographic coordinate systems are setting in opposite order + if (isset($proj4) && $proj4['proj'] === 'longlat') { + $tileMatrix[$i]['origin'] = array_reverse($tileMatrix[$i]['origin']); + } + if(!isset($tileMatrix[$i]['scale_denominator'])){ + $tileMatrix[$i]['scale_denominator'] = count($tileMatrix) - $i; + } + if(!isset($tileMatrix[$i]['tile_size'])){ + $tileSize = 256 * (int) $layer['scale']; + $tileMatrix[$i]['tile_size'] = array($tileSize, $tileSize); + } + } + + return $tileMatrix; + } + + /** + * Calculates corners of tilematrix + * @param array $extent + * @param array $origin + * @param array $pixel_size + * @param array $tile_size + * @return array + */ + public function tilesOfExtent($extent, $origin, $pixel_size, $tile_size) { + $tiles = array( + $this->minsample($extent[0] - $origin[0], $pixel_size[0] * $tile_size[0]), + $this->minsample($extent[1] - $origin[1], $pixel_size[1] * $tile_size[1]), + $this->maxsample($extent[2] - $origin[0], $pixel_size[0] * $tile_size[0]), + $this->maxsample($extent[3] - $origin[1], $pixel_size[1] * $tile_size[1]), + ); + return $tiles; + } + + private function minsample($x, $f){ + return $f > 0 ? floor($x / $f) : ceil(($x / $f) - 1); + } + + private function maxsample($x, $f){ + return $f < 0 ? floor($x / $f) : ceil(($x / $f) - 1); + } + + /** + * Default TileMetrixSet for Pseudo Mercator projection 3857 + * @return string TileMatrixSet xml + */ + public function getMercatorTileMatrixSet(){ + $denominatorBase = 559082264.0287178; + $extent = array(-20037508.34,-20037508.34,20037508.34,20037508.34); + $tileMatrixSet = array(); + + for($i = 0; $i <= 18; $i++){ + $matrixSize = pow(2, $i); + $tileMatrixSet[] = array( + 'extent' => $extent, + 'id' => (string) $i, + 'matrix_size' => array($matrixSize, $matrixSize), + 'origin' => array($extent[0], $extent[3]), + 'scale_denominator' => $denominatorBase / pow(2, $i), + 'tile_size' => array(256, 256) + ); + } + + return $this->getTileMatrixSet('GoogleMapsCompatible', $tileMatrixSet, 'EPSG:3857'); + } + + /** + * Default TileMetrixSet for WGS84 projection 4326 + * @return string Xml + */ + public function getWGS84TileMatrixSet(){ + $extent = array(-180.000000, -90.000000, 180.000000, 90.000000); + $scaleDenominators = array(279541132.01435887813568115234, 139770566.00717943906784057617, + 69885283.00358971953392028809, 34942641.50179485976696014404, 17471320.75089742988348007202, + 8735660.37544871494174003601, 4367830.18772435747087001801, 2183915.09386217873543500900, + 1091957.54693108936771750450, 545978.77346554468385875225, 272989.38673277234192937613, + 136494.69336638617096468806, 68247.34668319308548234403, 34123.67334159654274117202, + 17061.83667079825318069197, 8530.91833539912659034599, 4265.45916769956329517299, + 2132.72958384978574031265); + $tileMatrixSet = array(); + + for($i = 0; $i <= count($scaleDenominators); $i++){ + $matrixSize = pow(2, $i); + $tileMatrixSet[] = array( + 'extent' => $extent, + 'id' => (string) $i, + 'matrix_size' => array($matrixSize * 2, $matrixSize), + 'origin' => array($extent[3], $extent[0]), + 'scale_denominator' => $scaleDenominators[$i], + 'tile_size' => array(256, 256) + ); + } + + return $this->getTileMatrixSet('WGS84', $tileMatrixSet, 'EPSG:4326'); + } + + /** + * Prints WMTS TileMatrixSet + * @param string $name + * @param array $tileMatrixSet Array of levels + * @param string $crs Code of crs eg: EPSG:3857 + * @return string TileMatrixSet xml + */ + public function getTileMatrixSet($name, $tileMatrixSet, $crs = 'EPSG:3857'){ + $srs = explode(':', $crs); + $TileMatrixSet = ' + ' . $name . ' + ' . $name . ' '. $crs .' + ' . $name . ' + urn:ogc:def:crs:'.$srs[0].'::'.$srs[1].''; + // urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible; + foreach($tileMatrixSet as $level){ + $TileMatrixSet .= ' + + ' . $level['id'] . ' + ' . $level['scale_denominator'] . ' + '. $level['origin'][0] . ' ' . $level['origin'][1] .' + ' . $level['tile_size'][0] . ' + ' . $level['tile_size'][1] . ' + ' . $level['matrix_size'][0] . ' + ' . $level['matrix_size'][1] . ' + '; + } + $TileMatrixSet .= ''; + + return $TileMatrixSet; + } + /** * Returns tilesets getCapabilities */ public function getCapabilities() { - header("Content-type: application/xml"); + + $layers = array_merge($this->fileLayer, $this->dbLayer); + + //if TileMatrixSet is provided validate it + for($i = 0; $i < count($layers); $i++){ + if($layers[$i]['profile'] == 'custom'){ + $layers[$i]['tile_matrix'] = $this->parseTileMatrix( + $layers[$i], + $layers[$i]['tile_matrix'] + ); + } + } + + header('Content-type: application/xml'); echo ' @@ -777,23 +965,33 @@ class Wmts extends Server { '; - $maps = array_merge($this->fileLayer, $this->dbLayer); + + $customtileMatrixSets = ''; + + //layers $mercator = new GlobalMercator(); - foreach ($maps as $m) { - if (strpos($m['basename'], '.') !== false) { - $basename = explode('.', $m['basename']); - } else { - $basename = $m['basename']; - } + foreach ($layers as $m) { + + $basename = $m['basename']; $title = (array_key_exists('name', $m)) ? $m['name'] : $basename; $profile = $m['profile']; $bounds = $m['bounds']; $format = $m['format'] == 'hybrid' ? 'jpgpng' : $m['format']; $mime = ($format == 'jpg') ? 'image/jpeg' : 'image/' . $format; + if ($profile == 'geodetic') { - $tileMatrixSet = "WGS84"; + $tileMatrixSet = 'WGS84'; + }elseif ($m['profile'] == 'custom') { + $crs = explode(':', $m['crs']); + $tileMatrixSet = 'custom' . $crs[1] . $m['basename']; + $customtileMatrixSets .= $this->getTileMatrixSet( + $tileMatrixSet, + $m['tile_matrix'], + $m['crs'] + ); } else { - $tileMatrixSet = "GoogleMapsCompatible"; + $tileMatrixSet = 'GoogleMapsCompatible'; + list( $minx, $miny ) = $mercator->LatLonToMeters($bounds[1], $bounds[0]); list( $maxx, $maxy ) = $mercator->LatLonToMeters($bounds[3], $bounds[2]); $bounds3857 = array($minx, $miny, $maxx, $maxy); @@ -821,358 +1019,19 @@ class Wmts extends Server { '; } - echo ' - - GoogleMapsCompatible - the wellknown \'GoogleMapsCompatible\' tile matrix set defined by OGC WMTS specification - GoogleMapsCompatible - urn:ogc:def:crs:EPSG:6.18:3:3857 - urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible - - 0 - 559082264.0287178 - -20037508.34278925 20037508.34278925 - 256 - 256 - 1 - 1 - - - 1 - 279541132.0143589 - -20037508.34278925 20037508.34278925 - 256 - 256 - 2 - 2 - - - 2 - 139770566.0071794 - -20037508.34278925 20037508.34278925 - 256 - 256 - 4 - 4 - - - 3 - 69885283.00358972 - -20037508.34278925 20037508.34278925 - 256 - 256 - 8 - 8 - - - 4 - 34942641.50179486 - -20037508.34278925 20037508.34278925 - 256 - 256 - 16 - 16 - - - 5 - 17471320.75089743 - -20037508.34278925 20037508.34278925 - 256 - 256 - 32 - 32 - - - 6 - 8735660.375448715 - -20037508.34278925 20037508.34278925 - 256 - 256 - 64 - 64 - - - 7 - 4367830.187724357 - -20037508.34278925 20037508.34278925 - 256 - 256 - 128 - 128 - - - 8 - 2183915.093862179 - -20037508.34278925 20037508.34278925 - 256 - 256 - 256 - 256 - - - 9 - 1091957.546931089 - -20037508.34278925 20037508.34278925 - 256 - 256 - 512 - 512 - - - 10 - 545978.7734655447 - -20037508.34278925 20037508.34278925 - 256 - 256 - 1024 - 1024 - - - 11 - 272989.3867327723 - -20037508.34278925 20037508.34278925 - 256 - 256 - 2048 - 2048 - - - 12 - 136494.6933663862 - -20037508.34278925 20037508.34278925 - 256 - 256 - 4096 - 4096 - - - 13 - 68247.34668319309 - -20037508.34278925 20037508.34278925 - 256 - 256 - 8192 - 8192 - - - 14 - 34123.67334159654 - -20037508.34278925 20037508.34278925 - 256 - 256 - 16384 - 16384 - - - 15 - 17061.83667079827 - -20037508.34278925 20037508.34278925 - 256 - 256 - 32768 - 32768 - - - 16 - 8530.918335399136 - -20037508.34278925 20037508.34278925 - 256 - 256 - 65536 - 65536 - - - 17 - 4265.459167699568 - -20037508.34278925 20037508.34278925 - 256 - 256 - 131072 - 131072 - - - 18 - 2132.729583849784 - -20037508.34278925 20037508.34278925 - 256 - 256 - 262144 - 262144 - - - - WGS84 - GoogleCRS84Quad - urn:ogc:def:crs:EPSG:6.3:4326 - - -180.000000 -90.000000 - 180.000000 90.000000 - - urn:ogc:def:wkss:OGC:1.0:GoogleCRS84Quad - - 0 - 279541132.01435887813568115234 - 90.000000 -180.000000 - 256 - 256 - 2 - 1 - - - 1 - 139770566.00717943906784057617 - 90.000000 -180.000000 - 256 - 256 - 4 - 2 - - - 2 - 69885283.00358971953392028809 - 90.000000 -180.000000 - 256 - 256 - 8 - 4 - - - 3 - 34942641.50179485976696014404 - 90.000000 -180.000000 - 256 - 256 - 16 - 8 - - - 4 - 17471320.75089742988348007202 - 90.000000 -180.000000 - 256 - 256 - 32 - 16 - - - 5 - 8735660.37544871494174003601 - 90.000000 -180.000000 - 256 - 256 - 64 - 32 - - - 6 - 4367830.18772435747087001801 - 90.000000 -180.000000 - 256 - 256 - 128 - 64 - - - 7 - 2183915.09386217873543500900 - 90.000000 -180.000000 - 256 - 256 - 256 - 128 - - - 8 - 1091957.54693108936771750450 - 90.000000 -180.000000 - 256 - 256 - 512 - 256 - - - 9 - 545978.77346554468385875225 - 90.000000 -180.000000 - 256 - 256 - 1024 - 512 - - - 10 - 272989.38673277234192937613 - 90.000000 -180.000000 - 256 - 256 - 2048 - 1024 - - - 11 - 136494.69336638617096468806 - 90.000000 -180.000000 - 256 - 256 - 4096 - 2048 - - - 12 - 68247.34668319308548234403 - 90.000000 -180.000000 - 256 - 256 - 8192 - 4096 - - - 13 - 34123.67334159654274117202 - 90.000000 -180.000000 - 256 - 256 - 16384 - 8192 - - - 14 - 17061.83667079825318069197 - 90.000000 -180.000000 - 256 - 256 - 32768 - 16384 - - - 15 - 8530.91833539912659034599 - 90.000000 -180.000000 - 256 - 256 - 65536 - 32768 - - - 16 - 4265.45916769956329517299 - 90.000000 -180.000000 - 256 - 256 - 131072 - 65536 - - - 17 - 2132.72958384978574031265 - 90.000000 -180.000000 - 256 - 256 - 262144 - 131072 - - - + + // Print custom TileMatrixSets + if (strlen($customtileMatrixSets) > 0) { + echo $customtileMatrixSets; + } + + // Print PseudoMercator TileMatrixSet + echo $this->getMercatorTileMatrixSet(); + + // Print WGS84 TileMatrixSet + echo $this->getWGS84TileMatrixSet(); + + echo ' '; } @@ -1248,16 +1107,16 @@ class Tms extends Server { public function getCapabilities() { parent::setDatasets(); $maps = array_merge($this->fileLayer, $this->dbLayer); - header("Content-type: application/xml"); + header('Content-type: application/xml'); echo''; foreach ($maps as $m) { $basename = $m['basename']; $title = (array_key_exists('name', $m) ) ? $m['name'] : $basename; $profile = $m['profile']; if ($profile == 'geodetic') { - $srs = "EPSG:4326"; + $srs = 'EPSG:4326'; } else { - $srs = "EPSG:3857"; + $srs = 'EPSG:3857'; echo ''; @@ -1282,12 +1141,12 @@ class Tms extends Server { $description = (array_key_exists('description', $m)) ? $m['description'] : ""; $bounds = $m['bounds']; if ($m['profile'] == 'geodetic') { - $srs = "EPSG:4326"; + $srs = 'EPSG:4326'; $originx = -180.0; $originy = -90.0; $initialResolution = 0.703125; } else { - $srs = "EPSG:3857"; + $srs = 'EPSG:3857'; $originx = -20037508.342789; $originy = -20037508.342789; $mercator = new GlobalMercator();