diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..dd8f16a --- /dev/null +++ b/.travis.yml @@ -0,0 +1,24 @@ +language: php +php: + - 5.4 + - 5.5 + - 5.6 + - 7.0 + - 7.1 + - hhvm +script: + - php tileserver.php +after_success: + - wget https://github.com/klokantech/tileserver-php/releases/download/v0.1/grandcanyon.mbtiles + - git config --global user.email "travis@localhost.localdomain" + - git config --global user.name "Travis CI" + - git add --all + - git commit -am "Travis change" +deploy: + provider: openshift + user: osm2vectortiles@klokantech.com + password: + secure: hiWKBaqh/CMdnQ2qxsRSKYAnk4tP/q2J50TaO+2sH09x+0Q85ygfICCDrEx457xqmGW0e4zZPsL83mMPvGt5KJO6g9DIncj6BFhpZA0ysnRJ3X1fczTiVN5hQfqNpa+/YIrQ0whu1Ur/IfdYTtvArYhuAPeigCloumGk9gNgSIQ= + domain: tileserver + app: php + skip_cleanup: true diff --git a/README.md b/README.md index 37923f1..89e756d 100644 --- a/README.md +++ b/README.md @@ -1,36 +1,26 @@ TileServer PHP: MapTiler and MBTiles maps via WMTS ================================================== -This server distributes maps to desktop, web, and mobile applications from -a standard Apache+PHP web hosting. +[![Build Status](https://travis-ci.org/klokantech/tileserver-php.svg)](https://travis-ci.org/klokantech/tileserver-php) +[![Docker Hub](https://img.shields.io/badge/docker-hub-blue.svg)](https://hub.docker.com/r/klokantech/tileserver-php/) -Try a live demo at: http://tileserver.maptiler.com/ +This server distributes maps to desktop, web, and mobile applications from a standard Apache+PHP web hosting. -It is a free and open-source project implementing OGC WMTS standard for -pre-rendered map tiles made with [MapTiler](http://www.maptiler.com/), GDAL2Tiles, -or available as MBTiles files. +It is a free and open-source project implementing the OGC WMTS standard for pre-rendered map tiles made with any [map tiling software](https://www.maptiler.com/desktop/) like MapTiler Desktop, GDAL2Tiles, or any other MBTiles file. -It is the easiest and cheapest way how to serve zoomable maps in a -standardized way - practically from any ordinary web hosting. +It is the easiest and cheapest way how to serve zoomable maps in a standardized way - practically from any ordinary web hosting. -It is easy to install - just copy the project files to a PHP-enabled -directory along with your map data. +It is easy to install - copy the project files to a PHP-enabled directory along with your map data. -It comes with an online interface showing the list of the maps and step-by-step guides for online mapping libraries (Google Maps API, Leaflet, OpenLayers, OL3, MapBox JS, ArcGIS JS) and various desktop GIS software: +It comes with an online interface showing the list of the maps and step-by-step guides for online mapping libraries (Google Maps API, Leaflet, OpenLayers, OL3, MapLibre GL JS, ArcGIS JS) and various desktop GIS software: ![tileserver-screenshot](https://f.cloud.github.com/assets/59284/1041807/a040160c-0fdb-11e3-8941-ab367b2a648d.png) -This project is developed in PHP, not because it is the best language for -development of web applications, but because it maximally simplify the -deployment on large number of web hosting providers including various free -web hostings. +This project is developed in PHP, not because it is the best language for the development of web applications, but because it maximally simplifies the deployment on a large number of web hostings, including various free web hostings providers. -Tiles are served directly by Apache with mod_rewrite rules as static files -and therefore are very fast and with correct HTTP caching headers. -Only XML metadata are delivered via PHP. -MBTiles are served via PHP, and are therfore slower, unless they are unpacked with mbutil. +Tiles are served directly by Apache with mod_rewrite rules as static files and therefore are very fast and with correct HTTP caching headers. Only XML metadata is delivered via PHP. MBTiles are served via PHP and are therefore slower unless they are unpacked with mbutil. -[MapTiler](http://www.maptiler.com/) can render GeoTIFF, ECW, MrSID, GeoPDF into compatible map tiles. JPEG, PNG, GIF and TIFF with scanned maps or images without geolocation can be turned into standard map layers with the visual georeferencing functionality (http://youtu.be/eJxdCe9CNYg). +[MapTiler](http://www.maptiler.com/) can render GeoTIFF, ECW, MrSID, GeoPDF into compatible map tiles. JPEG, PNG, GIF, and TIFF with scanned maps or images without geolocation can be turned into standard map layers with the visual georeferencing functionality (http://youtu.be/eJxdCe9CNYg). [![MapTiler - mapping tiles](https://cloud.githubusercontent.com/assets/59284/3037911/583d7810-e0c6-11e3-877c-6a7747b80dd3.jpg)](http://www.maptiler.com/) @@ -38,44 +28,36 @@ Requirements: ------------- - Apache webserver (with mod_rewrite / .htaccess supported) -- PHP 5.2+ +- PHP 5.6+ with SQLite module (php5-sqlite) -(or anther webserver implementing mod_rewrite rules and PHP) +(or another webserver implementing mod_rewrite rules and PHP) Installation: ------------- Download the project files as a [zip archive](https://github.com/klokantech/tileserver-php/archive/master.zip) or source code from GitHub and unpack it into a web-hosting of your choice. -If you access the web address relevant to the installation directory, -the TileServer.php Server should display you a welcome message and further -instructions. +If you access the web address relevant to the installation directory, the TileServer.php Server should display you a welcome message and further instructions. -Then you can upload to the web hosting your mapping data - a directory with -tiles rendered with [MapTiler](http://www.maptiler.com/). +Then you can upload to the web hosting your mapping data - a directory with tiles rendered with [MapTiler](http://www.maptiler.com/). -Tiles produced by open-source GDAL2Tiles or MapTiler and tiles in .mbtiles -files can be easily converted to required structure (XYZ with top-left origin -and metadata.json file). The open-source utility [mbutil](https://github.com/mapbox/mbutil) produces -exactly the required format. +Tiles produced by open-source GDAL2Tiles or MapTiler Desktop and tiles in .mbtiles format can be easily converted to the required structure (XYZ with top-left origin and metadata.json file). The open-source utility [mbutil](https://github.com/mapbox/mbutil) produces exactly the required format. -Direct reading of .mbtiles files is supported, but with decreased performance -compared to the static files in a directory. The advantage is easier data management, -especially upload over FTP or similar protocols. +Direct reading of .mbtiles files is supported but with decreased performance compared to the static files in a directory. The advantage is easier data management, especially upload over FTP or similar protocols. Supported protocols: -------------------- - OpenGIS WMTS 1.0.0 - + The Open Geospatial Consortium (OGC) Web Map Tile Service (WMTS) Both KVP and RESTful version 1.0.0: http://www.opengeospatial.org/standards/wmts/ - + Target is maximal compliance to the standard. - + Exposed at http://[...]/wmts - + - OSGeo TMS 1.0.0 The OSGeo Tile Maps Service, but with inverted y-coordinates: @@ -87,32 +69,40 @@ Supported protocols: flipped y-axis. Exposed at http://[...]/tms - -- TileJSON.js + +- TileJSON Metadata about the individual maps in a ready to use form for web clients following the standard http://mapbox.com/developers/tilejson/ and with support for JSONP access. - Exposed at http://[...]/layer.jsonp - + Exposed at http://[...]/layer.json or .jsonp + - Direct access with XYZ tile requests (to existing tiles in a directory or to .mbtiles) Compatible with Google Maps API / Bing SDK / OpenStreetMap clients. - + Exposed at http://[...]/layer/z/x/y.ext - -- MapBox UTFgrid request (for existing tiles in .mbtiles with UTFgrid support). Callback is supported + +- MapBox UTFgrid request (for existing tiles in .mbtiles with UTFgrid support). Callback is supported Example https://www.mapbox.com/demo/visiblemap/ Specification https://github.com/mapbox/utfgrid-spec - + Exposed at http://[...]/layer/z/x/y.grid.json - -To use the OGC WMTS standard point your client (desktop or web) to the URL -of 'directory' where you installed tileserver.php project with suffix "wmts". +- MapBox Vector Tiles (for MBTiles generated by [MapBox Studio Classic](https://www.mapbox.com/mapbox-studio-classic/) or by [OSM2VectorTiles](http://osm2vectortiles.org/) project). + + Example http://osm2vectortiles.tileserver.com/ + TileJSON can be used in MapBox Studio Classic, MapBox SDKs/APIs, OpenLayers, etc. + + Exposed at http://[...]/layer/z/x/y.pbf + +- Retina / HighDPI routing with 512 tiles +Use @2x suffix in url for JSONs and tiles. For example http://tileserver.maptiler.com/grandcanyon@2x.json + +To use the OGC WMTS standard, point your client (desktop or web) to the URL of 'directory' where you installed tileserver.php project with suffix "wmts". For example: http://www.example.com/directory/wmts If you have installed the project into a root directory of a domain, then the address is: http://www.example.com/wmts @@ -120,54 +110,46 @@ If you have installed the project into a root directory of a domain, then the ad The supported WMTS requests includes: GetCapabilities RESTful/KVP: - + http://[...]/1.0.0/WMTSCapabilities.xml http://[...]?service=wmts&request=getcapabilities&version=1.0.0 - + GetTile RESTful/KVP: - + http://[...]/layer/[ANYTHING-OPTIONAL][z]/[x]/[y].[ext] http://[...]?service=wmts&request=getTile&layer=[layer]&tilematrix=[z]&tilerow=[y]&tilecol=[y]&format=[ext] - + Other example requests are mentioned in the .htaccess. +TileServer-PHP supports all coordinates systems. You have to define it with tilejson with specification on https://github.com/klokantech/tilejson-spec/tree/custom-projection/2.2.0 +Or use MapTiler to produce datasets with this specification. + Performance from the web clients -------------------------------- It is highly recommended to map several domain names to the service, such as: + http://a.example.com/, http://b.example.com/, http://c.example.com/. -This can be done with DNS CNAME records pointing to your hosting. -The reason for this is that traditionally browsers will not send more then two -simultaneous http request to the same domain - with multiple domains for the -same server you can better saturate the network and receive the maps faster. + +This can be done with DNS CNAME records pointing to your hosting. The reason for this is that traditionally browsers will not send more than two simultaneous HTTP requests to the same domain - with multiple domains for the same server, you can better saturate the network and receive the maps faster. Performance ----------- -In case the data are available in a form of directory with XYZ tiles, then -Apache webserver is serving these files directly as WMTS RESTful or KVP. +In case the data are available in the form of a directory with XYZ tiles, then the Apache webserver is serving these files directly as WMTS RESTful or KVP. -This means performance is excellent, maps are delivered very fast and large -number of concurrent visitors can be handled even with quite a low-end -hardware or cheap/free web hosting providers. +This means performance is excellent, maps are delivered very fast, and a large number of concurrent visitors can be handled even with quite low-end hardware or cheap/free web hosting providers. -Mod_rewrite rules are utilized to ensure the HTTP requests defined in the OCG -WMTS standard are served, and Apache preserve standard caching headers & eTag. +Mod_rewrite rules are utilized to ensure the HTTP requests defined in the OCG WMTS standard are served, and Apache preserves standard caching headers & eTag. -The performance should be significantly better then performance of any other -tile caching project (such as TileCache.org or GeoWebCache). +The performance should be significantly better than any other tile caching project (such as TileCache.org or GeoWebCache). -Performance graph for "apache static" comparing other tile caching projects -is available online at: -http://code.google.com/p/mod-geocache/wiki/PreliminaryBenchmark +Performance graph for "apache static" comparing other tile caching projects is available online at http://code.google.com/p/mod-geocache/wiki/PreliminaryBenchmark Limits of actual implementation ------------------------------- -With intention, in this moment the project supports only: -- Mercator tiles (a la OpenStreetMap) and Geodetic tiles (WGS84 unprojected) - with known and described tiling scheme. -- All tiles must be 256x256 pixels. +With intention, at this moment, the project supports only: - We enforce and require XYZ (top-left origin) tiling schema (even for TMS). Password protection @@ -198,22 +180,19 @@ TileServer.php can run without any problems over HTTPS, if required. Microsoft Windows web-hosting ----------------------------- -The TileServer.php should run on Windows-powered webservers with Apache -installation if PHP 5.2+ and mod_rewrite are available. +The TileServer.php should run on Windows-powered webservers with Apache installation if PHP 5.2+ and mod_rewrite are available. -With the IIS webserver hosting, you may need PHP and IIRF module -(http://iirf.codeplex.com/) and alter appropriately the rewrite rules. +With the IIS webserver hosting, you may need PHP and IIRF module (http://iirf.codeplex.com/) and alter appropriately the rewrite rules. Credits / Contributors ---------------------- -Project developed initially by Klokan Technologies GmbH, Switzerland in -cooperation with National Oceanic and Atmospheric Administration - NOAA, USA. +Project developed initially by Klokan Technologies GmbH, Switzerland, in cooperation with National Oceanic and Atmospheric Administration - NOAA, USA. -- Petr Pridal - Klokan Technologies GmbH +- Petr Pridal - Klokan Technologies GmbH - Jason Woolard - NOAA - Jon Sellars - NOAA -- Dalibor Janak - Klokan Technologies GmbH +- Dalibor Janak - Klokan Technologies GmbH Tested WMTS/TMS clients ----------------------- @@ -232,21 +211,26 @@ Tested WMTS/TMS clients http://www.thecarbonproject.com/gaia.php - MapBox.js - the loading of maps via TileJSON, interaction layer supported https://www.mapbox.com/mapbox.js + +Alternative +----------- + +If you need [map server with commercial support](https://www.maptiler.com/server/), explore the possibilities provided by the MapTiler Server. BSD License ----------- -Copyright (C) 2015 Klokan Technologies GmbH (http://www.klokantech.com/) +Copyright (C) 2020 MapTiler AG (https://www.maptiler.com/) All rights reserved. Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: +modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. + list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. + and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED diff --git a/tileserver.php b/tileserver.php old mode 100644 new mode 100755 index b124db1..d4e12d4 --- a/tileserver.php +++ b/tileserver.php @@ -1,32 +1,35 @@ 'Server:getHtml', - '/test' => 'Server:getInfo', + '/maps' => 'Server:getInfo', '/html' => 'Server:getHtml', + '/:alpha/:number/:number/:number.grid.json' => 'Json:getUTFGrid', '/:alpha.json' => 'Json:getJson', '/:alpha.jsonp' => 'Json:getJsonp', - '/:alpha/:number/:number/:number.:alpha.json' => 'Json:getUTFGrid', '/wmts' => 'Wmts:get', '/wmts/1.0.0/WMTSCapabilities.xml' => 'Wmts:get', - '/wmts/:alpha/:number/:number/:number.:alpha' => 'Wmts:getTile', - '/wmts/:alpha/:alpha/:number/:number/:number.:alpha' => 'Wmts:getTile', - '/wmts/:alpha/:alpha/:alpha/:number/:number/:number.:alpha' => 'Wmts:getTile', - '/:alpha/:number/:number/:number.:alpha' => 'Wmts:getTile', + '/wmts/:alpha/:number/:number/:alpha' => 'Wmts:getTile', + '/wmts/:alpha/:alpha/:number/:number/:alpha' => 'Wmts:getTile', + '/wmts/:alpha/:alpha/:alpha/:number/:number/:alpha' => 'Wmts:getTile', + '/:alpha/:number/:number/:alpha' => 'Wmts:getTile', '/tms' => 'Tms:getCapabilities', '/tms/:alpha' => 'Tms:getLayerCapabilities', -)); +]); /** * Server base @@ -35,25 +38,25 @@ class Server { /** * Configuration of TileServer [baseUrls, serverTitle] - * @var array + * @var array */ public $config; /** * Datasets stored in file structure - * @var array + * @var array */ - public $fileLayer = array(); + public $fileLayer = []; /** * Datasets stored in database - * @var array + * @var array */ - public $dbLayer = array(); + public $dbLayer = []; /** * PDO database connection - * @var object + * @var object */ public $db; @@ -62,6 +65,26 @@ class Server { */ public function __construct() { $this->config = $GLOBALS['config']; + + if($this->config['dataRoot'] != '' + && substr($this->config['dataRoot'], -1) != '/' ){ + $this->config['dataRoot'] .= '/'; + } + + //Get config from enviroment + $envServerTitle = getenv('serverTitle'); + if($envServerTitle !== false){ + $this->config['serverTitle'] = $envServerTitle; + } + $envBaseUrls = getenv('baseUrls'); + if($envBaseUrls !== false){ + $this->config['baseUrls'] = is_array($envBaseUrls) ? + $envBaseUrls : explode(',', $envBaseUrls); + } + $envTemplate = getenv('template'); + if($envBaseUrls !== false){ + $this->config['template'] = $envTemplate; + } } /** @@ -69,7 +92,7 @@ public function __construct() { */ public function setDatasets() { $mjs = glob('*/metadata.json'); - $mbts = glob('*.mbtiles'); + $mbts = glob($this->config['dataRoot'] . '*.mbtiles'); if ($mjs) { foreach (array_filter($mjs, 'is_readable') as $mj) { $layer = $this->metadataFromMetadataJson($mj); @@ -92,13 +115,12 @@ public function setParams($params) { $this->layer = $params[1]; } $params = array_reverse($params); - if (isset($params[3])) { - $this->z = $params[3]; - $this->x = $params[2]; - $this->y = $params[1]; - } - if (isset($params[0])) { - $this->ext = $params[0]; + if (isset($params[2])) { + $this->z = $params[2]; + $this->x = $params[1]; + $file = explode('.', $params[0]); + $this->y = $file[0]; + $this->ext = isset($file[1]) ? $file[1] : null; } } @@ -114,7 +136,7 @@ public function getGlobal($isKey) { return $value; } } - return FALSE; + return false; } /** @@ -123,10 +145,10 @@ public function getGlobal($isKey) { * @return boolean */ public function isDBLayer($layer) { - if (is_file($layer . '.mbtiles')) { - return TRUE; + if (is_file($this->config['dataRoot'] . $layer . '.mbtiles')) { + return true; } else { - return FALSE; + return false; } } @@ -137,22 +159,21 @@ public function isDBLayer($layer) { */ public function isFileLayer($layer) { if (is_dir($layer)) { - return TRUE; + return true; } else { - return FALSE; + return false; } } /** - * + * Get metadata from metadataJson * @param string $jsonFileName * @return array */ public function metadataFromMetadataJson($jsonFileName) { $metadata = json_decode(file_get_contents($jsonFileName), true); - $metadata = $this->metadataValidation($metadata); $metadata['basename'] = str_replace('/metadata.json', '', $jsonFileName); - return $metadata; + return $this->metadataValidation($metadata); } /** @@ -161,13 +182,13 @@ public function metadataFromMetadataJson($jsonFileName) { * @return object */ public function metadataFromMbtiles($mbt) { - $metadata = array(); + $metadata = []; $this->DBconnect($mbt); $result = $this->db->query('select * from metadata'); $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) @@ -176,10 +197,12 @@ public function metadataFromMbtiles($mbt) { // 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)) { @@ -193,18 +216,18 @@ public function metadataFromMbtiles($mbt) { 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']); - $metadata['bounds'] = implode(',', array($w, $s, $e, $n)); + $s = $this->row2lat($resultdata[0]['s'] - 1, $metadata['maxzoom']); + $metadata['bounds'] = implode(',', [$w, $s, $e, $n]); } - $metadata = $this->metadataValidation($metadata); $mbt = explode('.', $mbt); $metadata['basename'] = $mbt[0]; + $metadata = $this->metadataValidation($metadata); return $metadata; } - + /** * Convert row number to latitude of the top of the row * @param integer $r @@ -212,8 +235,8 @@ public function metadataFromMbtiles($mbt) { * @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); } /** @@ -222,25 +245,48 @@ public function row2lat($r, $zoom) { * @return object */ public function metadataValidation($metadata) { - if (array_key_exists('bounds', $metadata)) { + if (!array_key_exists('bounds', $metadata)) { + $metadata['bounds'] = [-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('tiles', $metadata)){ + $pos = strrpos($metadata['tiles'][0], '.'); + $metadata['format'] = trim(substr($metadata['tiles'][0], $pos + 1)); + } + } + $formats = $this->config['availableFormats']; + if(!in_array(strtolower($metadata['format']), $formats)){ + $metadata['format'] = 'png'; + } + if (!array_key_exists('scale', $metadata)) { + $metadata['scale'] = 1; + } + if(!array_key_exists('tiles', $metadata)){ + $tiles = []; + foreach ($this->config['baseUrls'] as $url) { + $url = '' . $this->config['protocol'] . '://' . $url . '/' . + $metadata['basename'] . '/{z}/{x}/{y}'; + if(strlen($metadata['format']) <= 4){ + $url .= '.' . $metadata['format']; + } + $tiles[] = $url; + } + $metadata['tiles'] = $tiles; } return $metadata; } @@ -251,7 +297,7 @@ public function metadataValidation($metadata) { */ public function DBconnect($tileset) { try { - $this->db = new PDO('sqlite:' . $tileset, '', '', array(PDO::ATTR_PERSISTENT => true)); + $this->db = new PDO('sqlite:' . $tileset, '', '', [PDO::ATTR_PERSISTENT => true]); } catch (Exception $exc) { echo $exc->getTraceAsString(); die; @@ -270,16 +316,16 @@ public function DBconnect($tileset) { * @return boolean */ public function isModified($filename) { - $filename = $filename . '.mbtiles'; + $filename = $this->config['dataRoot'] . $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; + return true; } else { - return FALSE; + return false; } } @@ -293,12 +339,12 @@ public function isModified($filename) { */ public function renderTile($tileset, $z, $y, $x, $ext) { if ($this->isDBLayer($tileset)) { - if ($this->isModified($tileset) == TRUE) { + if ($this->isModified($tileset) == true) { header('Access-Control-Allow-Origin: *'); header('HTTP/1.1 304 Not Modified'); die; } - $this->DBconnect($tileset . '.mbtiles'); + $this->DBconnect($this->config['dataRoot'] . $tileset . '.mbtiles'); $z = floatval($z); $y = floatval($y); $x = floatval($x); @@ -308,16 +354,17 @@ public function renderTile($tileset, $z, $y, $x, $ext) { } $result = $this->db->query('select tile_data as t from tiles where zoom_level=' . $z . ' and tile_column=' . $x . ' and tile_row=' . $y); $data = $result->fetchColumn(); - if (!isset($data) || $data === FALSE) { - //scale of tile (for retina tiles) + if (!isset($data) || $data === false) { + //if tile doesn't exist + //select scale of tile (for retina tiles) $result = $this->db->query('select value from metadata where name="scale"'); $resultdata = $result->fetchColumn(); - $scale = isset($resultdata) && $resultdata !== FALSE ? $resultdata : 1; - $this->getCleanTile($scale); + $scale = isset($resultdata) && $resultdata !== false ? $resultdata : 1; + $this->getCleanTile($scale, $ext); } else { $result = $this->db->query('select value from metadata where name="format"'); $resultdata = $result->fetchColumn(); - $format = isset($resultdata) && $resultdata !== FALSE ? $resultdata : 'png'; + $format = isset($resultdata) && $resultdata !== false ? $resultdata : 'png'; if ($format == 'jpg') { $format = 'jpeg'; } @@ -331,30 +378,35 @@ public function renderTile($tileset, $z, $y, $x, $ext) { echo $data; } } elseif ($this->isFileLayer($tileset)) { - $name = './' . $tileset . '/' . $z . '/' . $x . '/' . $y . '.' . $ext; + $name = './' . $tileset . '/' . $z . '/' . $x . '/' . $y; + $mime = 'image/'; + if($ext != null){ + $name .= '.' . $ext; + } if ($fp = @fopen($name, 'rb')) { + if($ext != null){ + $mime .= $ext; + }else{ + //detect image type from file + $mimetypes = ['gif', 'jpeg', 'png']; + $mime .= $mimetypes[exif_imagetype($name) - 1]; + } header('Access-Control-Allow-Origin: *'); - header('Content-Type: image/' . $ext); + header('Content-Type: ' . $mime); header('Content-Length: ' . filesize($name)); fpassthru($fp); 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; } - if ($ext == 'pbf') { - header('HTTP/1.1 404 Not Found'); - header('Content-Type: application/json; charset=utf-8'); - echo '{"message":"Tile does not exist"}'; - die; - } - $this->getCleanTile($meta->scale); } + $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; } } @@ -363,15 +415,31 @@ public function renderTile($tileset, $z, $y, $x, $ext) { * Returns clean tile * @param integer $scale Default 1 */ - public function getCleanTile($scale = 1) { - $tileSize = 256 * $scale; - $png = imagecreatetruecolor($tileSize, $tileSize); - imagesavealpha($png, true); - $trans_colour = imagecolorallocatealpha($png, 0, 0, 0, 127); - imagefill($png, 0, 0, $trans_colour); - header('Access-Control-Allow-Origin: *'); - header('Content-type: image/png'); - imagepng($png); + public function getCleanTile($scale = 1, $format = 'png') { + switch ($format) { + case 'pbf': + header('Access-Control-Allow-Origin: *'); + header('HTTP/1.1 204 No Content'); + header('Content-Type: application/json; charset=utf-8'); + break; + case 'webp': + header('Access-Control-Allow-Origin: *'); + header('Content-type: image/webp'); + echo base64_decode('UklGRhIAAABXRUJQVlA4TAYAAAAvQWxvAGs='); + break; + case 'jpg': + header('Access-Control-Allow-Origin: *'); + header('Content-type: image/jpg'); + echo base64_decode('/9j/2wBDAAMCAgICAgMCAgIDAwMDBAYEBAQEBAgGBgUGCQgKCgkICQkKDA8MCgsOCwkJDRENDg8QEBEQCgwSExIQEw8QEBD/yQALCAABAAEBAREA/8wABgAQEAX/2gAIAQEAAD8A0s8g/9k='); + break; + case 'png': + default: + header('Access-Control-Allow-Origin: *'); + header('Content-type: image/png'); + // 256x256 transparent optimised png tile + echo pack('H*', '89504e470d0a1a0a0000000d494844520000010000000100010300000066bc3a2500000003504c5445000000a77a3dda0000000174524e530040e6d8660000001f494441541819edc1010d000000c220fba77e0e37600000000000000000e70221000001f5a2bd040000000049454e44ae426082'); + break; + } die; } @@ -382,30 +450,32 @@ public function getCleanTile($scale = 1) { * @param integer $y * @param integer $x */ - public function renderUTFGrid($tileset, $z, $y, $x, $flip = TRUE) { + public function renderUTFGrid($tileset, $z, $y, $x, $flip = true) { if ($this->isDBLayer($tileset)) { - if ($this->isModified($tileset) == TRUE) { + if ($this->isModified($tileset) == true) { header('HTTP/1.1 304 Not Modified'); } if ($flip) { $y = pow(2, $z) - 1 - $y; } try { - $this->DBconnect($tileset . '.mbtiles'); - $result = $this->db->query('SELECT grid FROM grids WHERE tile_column = ' . $x . ' AND tile_row = ' . $y . ' AND zoom_level = ' . $z); - if (!isset($result) || $result === FALSE) { - header('Access-Control-Allow-Origin: *'); - echo '{}'; - die; - } else { - $data = $result->fetchColumn(); + $this->DBconnect($this->config['dataRoot'] . $tileset . '.mbtiles'); - $grid = gzuncompress($data); + $query = 'SELECT grid FROM grids WHERE tile_column = ' . $x . ' AND ' + . 'tile_row = ' . $y . ' AND zoom_level = ' . $z; + $result = $this->db->query($query); + $data = $result->fetch(PDO::FETCH_ASSOC); + + if ($data !== false) { + $grid = gzuncompress($data['grid']); $grid = substr(trim($grid), 0, -1); //adds legend (data) to output $grid .= ',"data":{'; - $result = $this->db->query('SELECT key_name as key, key_json as json FROM grid_data WHERE zoom_level=' . $z . ' and tile_column=' . $x . ' and tile_row=' . $y); + $kquery = 'SELECT key_name as key, key_json as json FROM grid_data ' + . 'WHERE zoom_level=' . $z . ' and ' + . 'tile_column=' . $x . ' and tile_row=' . $y; + $result = $this->db->query($kquery); while ($r = $result->fetch(PDO::FETCH_ASSOC)) { $grid .= '"' . $r['key'] . '":' . $r['json'] . ','; } @@ -413,14 +483,18 @@ public function renderUTFGrid($tileset, $z, $y, $x, $flip = TRUE) { header('Access-Control-Allow-Origin: *'); if (isset($_GET['callback']) && !empty($_GET['callback'])) { - header("Content-Type:text/javascript charset=utf-8"); + header('Content-Type:text/javascript charset=utf-8'); echo $_GET['callback'] . '(' . $grid . ');'; } else { - header("Content-Type:text/javascript; charset=utf-8"); + header('Content-Type:text/javascript; charset=utf-8'); echo $grid; } + } else { + header('Access-Control-Allow-Origin: *'); + echo '{}'; + die; } - } catch (PDOException $e) { + } catch (Exception $e) { header('Content-type: text/plain'); print 'Error querying the database: ' . $e->getMessage(); } @@ -434,31 +508,24 @@ public function renderUTFGrid($tileset, $z, $y, $x, $flip = TRUE) { * Returns server info */ public function getInfo() { -// echo $this->config['baseUrls'][0];die; $this->setDatasets(); $maps = array_merge($this->fileLayer, $this->dbLayer); header('Content-Type: text/html;charset=UTF-8'); - echo '' . $this->config['serverTitle'] . ''; - echo '

' . $this->config['serverTitle'] . '

'; - echo 'TileJSON service: ' . $this->config['baseUrls'][0] . '/index.json
'; - echo 'WMTS service: ' . $this->config['baseUrls'][0] . '/wmts
'; - echo 'TMS service: ' . $this->config['baseUrls'][0] . '/tms'; + echo '' . $this->config['serverTitle'] . '' . + '

' . $this->config['serverTitle'] . '

' . + 'TileJSON service: ' . $this->config['baseUrls'][0] . '/index.json
' . + 'WMTS service: ' . $this->config['baseUrls'][0] . '/wmts
' . + 'TMS service: ' . $this->config['baseUrls'][0] . '/tms'; foreach ($maps as $map) { - $extend = '['; - foreach ($map['bounds'] as $ext) { - $extend = $extend . ' ' . $ext; - } - $extend = $extend . ' ]'; - if (strpos($map['basename'], 'mbtiles') !== false) { - echo '

Available MBtiles tileset: ' . $map['basename'] . '
'; - } else { - echo '

Available file tileset: ' . $map['basename'] . '
'; - } - echo 'Metadata: ' - . $this->config['baseUrls'][0] . '/' . $map['basename'] . '.json
'; - echo 'Bounds: ' . $extend . '

'; + $extend = '[' . implode($map['bounds'], ', ') . ']'; + echo '

Tileset: ' . $map['basename'] . '
' . + 'Metadata: ' . + $this->config['baseUrls'][0] . '/' . $map['basename'] . '.json
' . + 'Bounds: ' . $extend ; + if(isset($map['crs'])){echo '
CRS: ' . $map['crs'];} + echo '

'; } - echo '

Copyright (C) 2014 - Klokan Technologies GmbH

'; + echo '

Copyright (C) 2016 - Klokan Technologies GmbH

'; echo ''; } @@ -468,28 +535,34 @@ public function getInfo() { public function getHtml() { $this->setDatasets(); $maps = array_merge($this->fileLayer, $this->dbLayer); - header('Content-Type: text/html;charset=UTF-8'); - echo '' . $this->config['serverTitle'] . ''; - echo ' - - -

Welcome to ' . $this->config['serverTitle'] . '

-

This server distributes maps to desktop, web, and mobile applications.

-

The mapping data are available as OpenGIS Web Map Tiling Service (OGC WMTS), OSGEO Tile Map Service (TMS), and popular XYZ urls described with TileJSON metadata.

'; - if (!isset($maps)) { - echo '

No maps available yet

-

- Ready to go - just upload some maps into directory:' . getcwd() . '/ on this server.

-

Note: The maps can be a directory with tiles in XYZ format with metadata.json file.
- You can easily convert existing geodata (GeoTIFF, ECW, MrSID, etc) to this tile structure with MapTiler Cluster or open-source projects such as GDAL2Tiles or MapTiler or simply upload any maps in MBTiles format made by TileMill. Helpful is also the mbutil tool. Serving directly from .mbtiles files is supported, but with decreased performance.

'; + if (isset($this->config['template']) && file_exists($this->config['template'])) { + $baseUrls = $this->config['baseUrls']; + $serverTitle = $this->config['serverTitle']; + include_once $this->config['template']; } else { - echo '
    '; - foreach ($maps as $map) { - echo "
  • " . $map['name'] . '
  • '; + header('Content-Type: text/html;charset=UTF-8'); + echo '' . $this->config['serverTitle'] . ''; + echo ' + + +

    Welcome to ' . $this->config['serverTitle'] . '

    +

    This server distributes maps to desktop, web, and mobile applications.

    +

    The mapping data are available as OpenGIS Web Map Tiling Service (OGC WMTS), OSGEO Tile Map Service (TMS), and popular XYZ urls described with TileJSON metadata.

    '; + if (!isset($maps)) { + echo '

    No maps available yet

    +

    + Ready to go - just upload some maps into directory:' . getcwd() . '/ on this server.

    +

    Note: The maps can be a directory with tiles in XYZ format with metadata.json file.
    + You can easily convert existing geodata (GeoTIFF, ECW, MrSID, etc) to this tile structure with MapTiler Cluster or open-source projects such as GDAL2Tiles or MapTiler or simply upload any maps in MBTiles format made by TileMill. Helpful is also the mbutil tool. Serving directly from .mbtiles files is supported, but with decreased performance.

    '; + } else { + echo '
      '; + foreach ($maps as $map) { + echo "
    • " . $map['name'] . '
    • '; + } + echo '
    '; } - echo '
'; + echo ''; } - echo ''; } } @@ -501,7 +574,7 @@ class Json extends Server { /** * Callback for JSONP default grid - * @var string + * @var string */ private $callback = 'grid'; @@ -511,27 +584,27 @@ class Json extends Server { public $layer = 'index'; /** - * @var integer + * @var integer */ public $z; /** - * @var integer + * @var integer */ public $y; /** - * @var integer + * @var integer */ public $x; /** - * @var string + * @var string */ public $ext; /** - * + * * @param array $params */ public function __construct($params) { @@ -550,13 +623,8 @@ public function __construct($params) { public function metadataTileJson($metadata) { $metadata['tilejson'] = '2.0.0'; $metadata['scheme'] = 'xyz'; - $tiles = array(); - foreach ($this->config['baseUrls'] as $url) { - $tiles[] = '' . $this->config['protocol'] . '://' . $url . '/' . $metadata['basename'] . '/{z}/{x}/{y}.' . $metadata['format']; - } - $metadata['tiles'] = $tiles; if ($this->isDBLayer($metadata['basename'])) { - $this->DBconnect($metadata['basename'] . '.mbtiles'); + $this->DBconnect($this->config['dataRoot'] . $metadata['basename'] . '.mbtiles'); $res = $this->db->query('SELECT name FROM sqlite_master WHERE name="grids";'); if ($res) { foreach ($this->config['baseUrls'] as $url) { @@ -568,7 +636,9 @@ public function metadataTileJson($metadata) { if (array_key_exists('json', $metadata)) { $mjson = json_decode(stripslashes($metadata['json'])); foreach ($mjson as $key => $value) { - $metadata[$key] = $value; + if ($key != 'Layer'){ + $metadata[$key] = $value; + } } unset($metadata['json']); } @@ -613,7 +683,7 @@ private function createJson($basename) { 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 { @@ -627,7 +697,7 @@ public function getJson() { 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) . ');'; } @@ -651,27 +721,27 @@ class Wmts extends Server { public $layer; /** - * @var integer + * @var integer */ public $z; /** - * @var integer + * @var integer */ public $y; /** - * @var integer + * @var integer */ public $x; /** - * @var string + * @var string */ public $ext; /** - * + * * @param array $params */ public function __construct($params) { @@ -686,7 +756,7 @@ public function __construct($params) { */ public function get() { $request = $this->getGlobal('Request'); - if ($request !== FALSE && $request == 'gettile') { + if ($request !== false && $request == 'gettile') { $this->getTile(); } else { parent::setDatasets(); @@ -695,10 +765,190 @@ public function get() { } /** - * Returns tilesets getCapabilities + * Validates tilematrixset, calculates missing params + * @param Object $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'] = [ + $tileExtent[2] + 1, + $tileExtent[1] + 1 + ]; + } + if(!isset($tileMatrix[$i]['origin']) && isset($tileMatrix[$i]['extent'])){ + $tileMatrix[$i]['origin'] = [ + $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'] = [$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 = [ + $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 + * @param ?number $maxZoom + * @return string TileMatrixSet xml + */ + public function getMercatorTileMatrixSet($maxZoom = 18){ + $denominatorBase = 559082264.0287178; + $extent = [-20037508.34,-20037508.34,20037508.34,20037508.34]; + $tileMatrixSet = []; + + for($i = 0; $i <= $maxZoom; $i++){ + $matrixSize = pow(2, $i); + $tileMatrixSet[] = [ + 'extent' => $extent, + 'id' => (string) $i, + 'matrix_size' => [$matrixSize, $matrixSize], + 'origin' => [$extent[0], $extent[3]], + 'scale_denominator' => $denominatorBase / pow(2, $i), + 'tile_size' => [256, 256] + ]; + } + + return $this->getTileMatrixSet('GoogleMapsCompatible', $tileMatrixSet, 'EPSG:3857'); + } + + /** + * Default TileMetrixSet for WGS84 projection 4326 + * @return string Xml + */ + public function getWGS84TileMatrixSet(){ + $extent = [-180.000000, -90.000000, 180.000000, 90.000000]; + $scaleDenominators = [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 = []; + + for($i = 0; $i <= 17; $i++){ + $matrixSize = pow(2, $i); + $tileMatrixSet[] = [ + 'extent' => $extent, + 'id' => (string) $i, + 'matrix_size' => [$matrixSize * 2, $matrixSize], + 'origin' => [$extent[3], $extent[0]], + 'scale_denominator' => $scaleDenominators[$i], + 'tile_size' => [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 ' @@ -752,27 +1002,42 @@ public function getCapabilities() { '; - $maps = array_merge($this->fileLayer, $this->dbLayer); - $mercator = new GlobalMercator(); - foreach ($maps as $m) { - if (strpos($m['basename'], '.') !== false) { - $basename = explode('.', $m['basename']); - } else { - $basename = $m['basename']; - } + + $customtileMatrixSets = ''; + $maxMercatorZoom = 18; + + //layers + 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']; - $mime = ($format == 'jpg') ? 'image/jpeg' : 'image/png'; + $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"; - list( $minx, $miny ) = $mercator->LatLonToMeters($bounds[1], $bounds[0]); - list( $maxx, $maxy ) = $mercator->LatLonToMeters($bounds[3], $bounds[2]); - $bounds3857 = array($minx, $miny, $maxx, $maxy); + $tileMatrixSet = 'GoogleMapsCompatible'; + $maxMercatorZoom = max($maxMercatorZoom, $m['maxzoom']); + } + + $wmtsHost = substr($m['tiles'][0], 0, strrpos($m['tiles'][0], $m['basename'])); + $resourceUrlTemplate = $wmtsHost . $basename + . '/{TileMatrix}/{TileCol}/{TileRow}'; + if(strlen($format) <= 4){ + $resourceUrlTemplate .= '.' . $format; } + echo' ' . $title . ' @@ -788,362 +1053,22 @@ public function getCapabilities() { ' . $tileMatrixSet . ' - + '; } - 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($maxMercatorZoom); + + // Print WGS84 TileMatrixSet + echo $this->getWGS84TileMatrixSet(); + + echo ' '; } @@ -1154,13 +1079,19 @@ public function getCapabilities() { public function getTile() { $request = $this->getGlobal('Request'); if ($request) { - if (strpos('/', $_GET['Format']) !== FALSE) { + if (strpos('/', $_GET['Format']) !== false) { $format = explode('/', $_GET['Format']); $format = $format[1]; } else { $format = $this->getGlobal('Format'); } - parent::renderTile($this->getGlobal('Layer'), $this->getGlobal('TileMatrix'), $this->getGlobal('TileRow'), $this->getGlobal('TileCol'), $format); + parent::renderTile( + $this->getGlobal('Layer'), + $this->getGlobal('TileMatrix'), + $this->getGlobal('TileRow'), + $this->getGlobal('TileCol'), + $format + ); } else { parent::renderTile($this->layer, $this->z, $this->y, $this->x, $this->ext); } @@ -1179,27 +1110,27 @@ class Tms extends Server { public $layer; /** - * @var integer + * @var integer */ public $z; /** - * @var integer + * @var integer */ public $y; /** - * @var integer + * @var integer */ public $x; /** - * @var string + * @var string */ public $ext; /** - * + * * @param array $params */ public function __construct($params) { @@ -1213,20 +1144,22 @@ public function __construct($params) { 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"; - echo ''; + $srs = 'EPSG:3857'; } + $url = $this->config['protocol'] . '://' . $this->config['baseUrls'][0] + . '/tms/' . $basename; + echo ''; } echo ''; } @@ -1247,32 +1180,36 @@ public function getLayerCapabilities() { $description = (array_key_exists('description', $m)) ? $m['description'] : ""; $bounds = $m['bounds']; if ($m['profile'] == 'geodetic') { - $srs = "EPSG:4326"; - $originx = -180.0; - $originy = -90.0; - $initialResolution = 0.703125; + $srs = 'EPSG:4326'; + $initRes = 0.703125; + } elseif ($m['profile'] == 'custom') { + $srs = $m['crs']; + $bounds = $m['extent']; + if(isset($m['tile_matrix'][0]['pixel_size'][0])){ + $initRes = $m['tile_matrix'][0]['pixel_size'][0]; + }else{ + $initRes = 1; + } } else { - $srs = "EPSG:3857"; - $originx = -20037508.342789; - $originy = -20037508.342789; - $mercator = new GlobalMercator(); - list( $minx, $miny ) = $mercator->LatLonToMeters($bounds[1], $bounds[0]); - list( $maxx, $maxy ) = $mercator->LatLonToMeters($bounds[3], $bounds[2]); - $bounds = array($minx, $miny, $maxx, $maxy); - $initialResolution = 156543.03392804062; + $srs = 'EPSG:3857'; + $bounds = [-20037508.34,-20037508.34,20037508.34,20037508.34]; + $initRes = 156543.03392804062; } $mime = ($m['format'] == 'jpg') ? 'image/jpeg' : 'image/png'; header("Content-type: application/xml"); - echo ' + $serviceUrl = $this->config['protocol'] . '://' . $this->config['baseUrls'][0] . '/' . $m['basename']; + echo ' ' . htmlspecialchars($title) . ' ' . htmlspecialchars($description) . ' ' . $srs . ' - + '; for ($zoom = $m['minzoom']; $zoom < $m['maxzoom'] + 1; $zoom++) { - echo ''; + $res = $initRes / pow(2, $zoom); + $url = $this->config['protocol'] . '://' . $this->config['baseUrls'][0] . '/' . $m['basename'] . '/' . $zoom; + echo ''; } echo''; } @@ -1283,135 +1220,6 @@ public function getLayerCapabilities() { public function getTile() { parent::renderTile($this->layer, $this->z, $this->y, $this->x, $this->ext); } - -} - -/* - GlobalMapTiles - part of Aggregate Map Tools - Version 1.0 - Copyright (c) 2009 The Bivings Group - All rights reserved. - Author: John Bafford - - http://www.bivings.com/ - http://bafford.com/softare/aggregate-map-tools/ - - Based on GDAL2Tiles / globalmaptiles.py - Original python version Copyright (c) 2008 Klokan Petr Pridal. All rights reserved. - http://www.klokan.cz/projects/gdal2tiles/ - - 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, sublic ense, - 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 abov - e 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. - */ - -class GlobalMercator { - - var $tileSize; - var $initialResolution; - var $originShift; - -//Initialize the TMS Global Mercator pyramid - function __construct($tileSize = 256) { - $this->tileSize = $tileSize; - $this->initialResolution = 2 * M_PI * 6378137 / $this->tileSize; -# 156543.03392804062 for tileSize 256 Pixels - $this->originShift = 2 * M_PI * 6378137 / 2.0; -# 20037508.342789244 - } - -//Converts given lat/lon in WGS84 Datum to XY in Spherical Mercator EPSG:900913 - function LatLonToMeters($lat, $lon) { - $mx = $lon * $this->originShift / 180.0; - $my = log(tan((90 + $lat) * M_PI / 360.0)) / (M_PI / 180.0); - - $my *= $this->originShift / 180.0; - - return array($mx, $my); - } - -//Converts XY point from Spherical Mercator EPSG:900913 to lat/lon in WGS84 Datum - function MetersToLatLon($mx, $my) { - $lon = ($mx / $this->originShift) * 180.0; - $lat = ($my / $this->originShift) * 180.0; - - $lat = 180 / M_PI * (2 * atan(exp($lat * M_PI / 180.0)) - M_PI / 2.0); - - return array($lat, $lon); - } - -//Converts pixel coordinates in given zoom level of pyramid to EPSG:900913 - function PixelsToMeters($px, $py, $zoom) { - $res = $this->Resolution($zoom); - $mx = $px * $res - $this->originShift; - $my = $py * $res - $this->originShift; - - return array($mx, $my); - } - -//Converts EPSG:900913 to pyramid pixel coordinates in given zoom level - function MetersToPixels($mx, $my, $zoom) { - $res = $this->Resolution($zoom); - - $px = ($mx + $this->originShift) / $res; - $py = ($my + $this->originShift) / $res; - - return array($px, $py); - } - -//Returns a tile covering region in given pixel coordinates - function PixelsToTile($px, $py) { - $tx = ceil($px / $this->tileSize) - 1; - $ty = ceil($py / $this->tileSize) - 1; - - return array($tx, $ty); - } - -//Returns tile for given mercator coordinates - function MetersToTile($mx, $my, $zoom) { - list($px, $py) = $this->MetersToPixels($mx, $my, $zoom); - - return $this->PixelsToTile($px, $py); - } - -//Returns bounds of the given tile in EPSG:900913 coordinates - function TileBounds($tx, $ty, $zoom) { - list($minx, $miny) = $this->PixelsToMeters($tx * $this->tileSize, $ty * $this->tileSize, $zoom); - list($maxx, $maxy) = $this->PixelsToMeters(($tx + 1) * $this->tileSize, ($ty + 1) * $this->tileSize, $zoom); - - return array($minx, $miny, $maxx, $maxy); - } - -//Returns bounds of the given tile in latutude/longitude using WGS84 datum - function TileLatLonBounds($tx, $ty, $zoom) { - $bounds = $this->TileBounds($tx, $ty, $zoom); - - list($minLat, $minLon) = $this->MetersToLatLon($bounds[0], $bounds[1]); - list($maxLat, $maxLon) = $this->MetersToLatLon($bounds[2], $bounds[3]); - - return array($minLat, $minLon, $maxLat, $maxLon); - } - -//Resolution (meters/pixel) for given zoom level (measured at Equator) - function Resolution($zoom) { - return $this->initialResolution / (1 < $zoom); - } - } /** @@ -1423,13 +1231,18 @@ class Router { * @param array $routes */ public static function serve($routes) { - $request_method = strtolower($_SERVER['REQUEST_METHOD']); $path_info = '/'; global $config; - $config['protocol'] = ( isset($_SERVER["HTTPS"]) or $_SERVER['SERVER_PORT'] == '443') ? "https" : "http"; + $xForwarded = false; + if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) { + if ($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') { + $xForwarded = true; + } + } + $config['protocol'] = ((isset($_SERVER['HTTPS']) or (isset($_SERVER['SERVER_PORT']) && $_SERVER['SERVER_PORT'] == 443)) or $xForwarded) ? 'https' : 'http'; if (!empty($_SERVER['PATH_INFO'])) { $path_info = $_SERVER['PATH_INFO']; - } else if (!empty($_SERVER['ORIG_PATH_INFO']) && $_SERVER['ORIG_PATH_INFO'] !== '/tileserver.php') { + } else if (!empty($_SERVER['ORIG_PATH_INFO']) && strpos($_SERVER['ORIG_PATH_INFO'], 'tileserver.php') === false) { $path_info = $_SERVER['ORIG_PATH_INFO']; } else if (!empty($_SERVER['REQUEST_URI']) && strpos($_SERVER['REQUEST_URI'], '/tileserver.php') !== false) { $path_info = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; @@ -1440,14 +1253,14 @@ public static function serve($routes) { } } $discovered_handler = null; - $regex_matches = array(); + $regex_matches = []; if ($routes) { - $tokens = array( + $tokens = [ ':string' => '([a-zA-Z]+)', ':number' => '([0-9]+)', - ':alpha' => '([a-zA-Z0-9-_@]+)' - ); + ':alpha' => '([a-zA-Z0-9-_@\.]+)' + ]; //global $config; foreach ($routes as $pattern => $handler_name) { $pattern = strtr($pattern, $tokens); @@ -1468,7 +1281,7 @@ public static function serve($routes) { $discoverered_class = explode(':', $discovered_handler); $discoverered_method = explode(':', $discovered_handler); $handler_instance = new $discoverered_class[0]($regex_matches); - call_user_func(array($handler_instance, $discoverered_method[1])); + call_user_func([$handler_instance, $discoverered_method[1]]); } else { $handler_instance = new $discovered_handler($regex_matches); } @@ -1477,9 +1290,9 @@ public static function serve($routes) { } } else { if (!isset($config['baseUrls'][0])) { - $config['baseUrls'][0] = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI'] . '?'; + $config['baseUrls'][0] = $_SERVER['HTTP_HOST'] . $_SERVER['REQUEST_URI']; } - if (strpos($_SERVER['REQUEST_URI'], '=') != FALSE) { + if (strpos($_SERVER['REQUEST_URI'], '=') != false) { $kvp = explode('=', $_SERVER['REQUEST_URI']); $_GET['callback'] = $kvp[1]; $params[0] = 'index';