├── .gitignore ├── LICENSE ├── README.md ├── composer.json ├── src └── HTML24 │ └── MBTilesGenerator │ ├── Core │ └── MBTileFile.php │ ├── Exception │ └── TileNotAvailableException.php │ ├── MBTilesGenerator.php │ ├── Model │ ├── BoundingBox.php │ └── Tile.php │ ├── TileSources │ ├── MBTilesTileSource.php │ ├── RemoteCachingTileSource.php │ └── TileSourceInterface.php │ └── Util │ └── Calculator.php └── tests ├── mbtiles-source.php └── test.php /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, HTML24 ApS 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of HTML24 nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MBTilesGenerator 2 | 3 | A PHP library for generating MBTiles files. 4 | 5 | Supports fetching tiles from a web resource, implementing the [Slippy map tilenames specification](http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames). 6 | 7 | Also supports fetching tiles from another MBTiles file, for doing a subset of tiles. 8 | 9 | Using a custom source, is as easy as implementing the `TileSourceInterface`. 10 | 11 | ## What is a MBTiles file ? 12 | 13 | An MBTiles file is a file format for storing map tiles, for easy transfer and storing. 14 | It is optimal for doing offline maps in mobile apps. 15 | 16 | This library's output has been tested for usage with [Nutiteq 3D Android mapping SDK](https://github.com/nutiteq/hellomap3d) and [Mapbox iOS SDK](https://github.com/mapbox/mapbox-ios-sdk). 17 | 18 | [MBTiles specification](https://github.com/mapbox/mbtiles-spec) 19 | 20 | ## Installation 21 | 22 | composer require html24/mbtiles-generator dev-master 23 | 24 | ## Usage 25 | 26 | This example code will download the tiles necessary for a small area in Copenhagen, Denmark and output a 27 | mbtiles file as output.mbtiles. 28 | 29 | setAttribution('Data, imagery and map information provided by MapQuest, OpenStreetMap and contributors, ODbL .'); 36 | 37 | 38 | $tile_generator = new MBTilesGenerator($tile_source); 39 | 40 | $bounding_box = new BoundingBox('12.6061,55.6615,12.6264,55.6705'); 41 | 42 | $tile_generator->setMaxZoom(18); 43 | $tile_generator->generate($bounding_box, 'output.mbtiles'); 44 | 45 | 46 | ## License 47 | 48 | This library is released under the BSD (3-clause) License, please see LICENSE for details. 49 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "html24/mbtiles-generator", 3 | "description": "Library for generating MBTiles files from different sources.", 4 | "license": "BSD-3-Clause", 5 | "authors": [ 6 | { 7 | "name": "Dennis Væversted", 8 | "email": "dv@html24.net" 9 | } 10 | ], 11 | "minimum-stability": "dev", 12 | "require": { 13 | "php": ">=5.3.10", 14 | "ext-pdo": "*", 15 | "ext-pdo_sqlite": "*" 16 | }, 17 | "autoload": { 18 | "psr-0": { 19 | "HTML24\\MBTilesGenerator": "src/" 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/HTML24/MBTilesGenerator/Core/MBTileFile.php: -------------------------------------------------------------------------------- 1 | db = new \PDO('sqlite:' . $file); 32 | 33 | $this->db->exec('CREATE TABLE metadata (name TEXT, value TEXT);'); 34 | $this->db->exec( 35 | 'CREATE TABLE tiles (zoom_level INTEGER, tile_column INTEGER, tile_row INTEGER, tile_data BLOB);' 36 | ); 37 | $this->db->exec('CREATE UNIQUE INDEX tile_index ON tiles (zoom_level, tile_column, tile_row);'); 38 | 39 | $this->db->beginTransaction(); 40 | 41 | $this->addMetaStmt = $this->db->prepare('INSERT INTO metadata (name, value) VALUES (:name, :value)'); 42 | $this->addTileStmt = $this->db->prepare( 43 | 'INSERT INTO tiles (zoom_level, tile_column, tile_row, tile_data) VALUES (:z, :x, :y, :data)' 44 | ); 45 | } 46 | 47 | public function __destruct() 48 | { 49 | $this->db->commit(); 50 | } 51 | 52 | /** 53 | * @param string $name 54 | * @param string $value 55 | */ 56 | public function addMeta($name, $value) 57 | { 58 | $this->addMetaStmt->bindValue(':name', $name, \PDO::PARAM_STR); 59 | $this->addMetaStmt->bindValue(':value', $value, \PDO::PARAM_STR); 60 | $this->addMetaStmt->execute(); 61 | } 62 | 63 | /** 64 | * @param int $zoom_level 65 | * @param int $x 66 | * @param int $y 67 | * @param string $data 68 | */ 69 | public function addTile($zoom_level, $x, $y, $data) 70 | { 71 | $this->addTileStmt->bindValue(':z', $zoom_level, \PDO::PARAM_INT); 72 | $this->addTileStmt->bindValue(':x', $x, \PDO::PARAM_INT); 73 | $this->addTileStmt->bindValue(':y', $y, \PDO::PARAM_INT); 74 | $this->addTileStmt->bindValue(':data', $data, \PDO::PARAM_LOB); 75 | $this->addTileStmt->execute(); 76 | } 77 | } -------------------------------------------------------------------------------- /src/HTML24/MBTilesGenerator/Exception/TileNotAvailableException.php: -------------------------------------------------------------------------------- 1 | tileSource = $tileSource; 56 | } 57 | 58 | /** 59 | * Prepares the tiles by getting the tileSource to generate or download them. 60 | * 61 | * @param BoundingBox $boundingBox 62 | * @param string $destination File destination to write the mbtiles file to 63 | * @param string $name 64 | * 65 | * @throws 66 | * @return bool 67 | */ 68 | public function generate(BoundingBox $boundingBox, $destination, $name = 'mbtiles-generator') 69 | { 70 | if (file_exists($destination)) { 71 | throw new \Exception('Destination file already exists'); 72 | } 73 | $tiles = $this->generateTileList($boundingBox); 74 | 75 | $this->tileSource->cache($tiles); 76 | 77 | // Start constructing our file 78 | $mbtiles = new MBTileFile($destination); 79 | 80 | // Set the required meta data 81 | $this->addMetaDataToDB($mbtiles, $boundingBox, $name); 82 | 83 | // Add tiles to the database 84 | $this->addTilesToDB($mbtiles, $tiles); 85 | 86 | } 87 | 88 | /** 89 | * Set maximum zoom on this instance, defaults to 18. 90 | * @param int $zoom 91 | */ 92 | public function setMaxZoom($zoom) { 93 | $this->maxZoom = $zoom; 94 | } 95 | 96 | /** 97 | * Sets the allowed failures 98 | * @param $allowedFail 99 | */ 100 | public function setAllowedFail($allowedFail) { 101 | $this->allowedFail = $allowedFail; 102 | } 103 | 104 | /** 105 | * Returns the effective zoom we accomplished on last run. 106 | * @return int 107 | */ 108 | public function getEffectiveZoom() { 109 | return $this->effectiveZoom; 110 | } 111 | 112 | /** 113 | * @param MBTileFile $mbtiles 114 | * @param Tile[] $tiles 115 | * @throws 116 | */ 117 | protected function addTilesToDB(MBTileFile $mbtiles, $tiles) 118 | { 119 | $failed_tiles = 0; 120 | 121 | foreach ($tiles as $tile) { 122 | try { 123 | $mbtiles->addTile($tile->z, $tile->x, $tile->y, $this->tileSource->getTile($tile)); 124 | } catch (TileNotAvailableException $e) { 125 | $failed_tiles++; 126 | continue; 127 | } 128 | } 129 | 130 | $failed_percentage = ceil(($failed_tiles / count($tiles)) * 100); 131 | 132 | if ($failed_percentage > $this->allowedFail) { 133 | // Too many tiles failed 134 | throw new \Exception($failed_percentage . '% of the tiles failed, which is more than the allowed ' . $this->allowedFail . '%'); 135 | } 136 | } 137 | 138 | /** 139 | * @param MBTileFile $mbtiles 140 | * @param BoundingBox $boundingBox 141 | * @param string $name 142 | */ 143 | protected function addMetaDataToDB(MBTileFile $mbtiles, BoundingBox $boundingBox, $name) 144 | { 145 | $mbtiles->addMeta('name', $name); 146 | $mbtiles->addMeta('type', 'baselayer'); 147 | $mbtiles->addMeta('version', '1'); 148 | $mbtiles->addMeta('format', $this->tileSource->getFormat()); 149 | $mbtiles->addMeta('attribution', $this->tileSource->getAttribution()); 150 | 151 | $mbtiles->addMeta('bounds', (string)$boundingBox); 152 | $mbtiles->addMeta('minzoom', 0); 153 | $mbtiles->addMeta('maxzoom', $this->effectiveZoom); 154 | } 155 | 156 | /** 157 | * @param BoundingBox $boundingBox 158 | * @return Tile[] 159 | */ 160 | protected function generateTileList(BoundingBox $boundingBox) 161 | { 162 | $tiles = array(); 163 | for ($i = 0; $i <= $this->maxZoom; $i++) { 164 | $zoom_tiles = $this->generateTileListForZoom($boundingBox, $i); 165 | if (count($tiles) + count($zoom_tiles) < $this->tileLimit) { 166 | $tiles = array_merge($tiles, $zoom_tiles); 167 | $this->effectiveZoom = $i; 168 | } else { 169 | // We got to many tiles, so no more zoom levels. 170 | break; 171 | } 172 | } 173 | 174 | return $tiles; 175 | } 176 | 177 | /** 178 | * @param BoundingBox $boundingBox 179 | * @param int $zoom 180 | * @return Tile[] 181 | */ 182 | protected function generateTileListForZoom(BoundingBox $boundingBox, $zoom) 183 | { 184 | $tiles = array(); 185 | $start_tile = $this->coordinatesToTile( 186 | $boundingBox->getLeft(), 187 | $boundingBox->getBottom(), 188 | $zoom 189 | ); 190 | 191 | $end_tile = $this->coordinatesToTile( 192 | $boundingBox->getRight(), 193 | $boundingBox->getTop(), 194 | $zoom 195 | ); 196 | 197 | for ($x = $start_tile->x; $x <= $end_tile->x; $x++) { 198 | for ($y = $start_tile->y; $y <= $end_tile->y; $y++) { 199 | $tiles[] = new Tile($x, $y, $zoom); 200 | } 201 | } 202 | 203 | return $tiles; 204 | } 205 | 206 | /** 207 | * @param float $longitude 208 | * @param float $latitude 209 | * @param int $zoom 210 | * @return Tile 211 | */ 212 | protected function coordinatesToTile($longitude, $latitude, $zoom) 213 | { 214 | $tile = new Tile(); 215 | $tile->z = $zoom; 216 | $tile->x = Calculator::longitudeToX($longitude, $zoom); 217 | $tile->y = Calculator::latitudeToY($latitude, $zoom); 218 | 219 | return $tile; 220 | } 221 | } -------------------------------------------------------------------------------- /src/HTML24/MBTilesGenerator/Model/BoundingBox.php: -------------------------------------------------------------------------------- 1 | left = floatval($coordinates[0]); 45 | $this->bottom = floatval($coordinates[1]); 46 | $this->right = floatval($coordinates[2]); 47 | $this->top = floatval($coordinates[3]); 48 | } 49 | } 50 | 51 | /** 52 | * @return string 53 | */ 54 | public function __toString() 55 | { 56 | return $this->left . ',' . $this->bottom . ',' . $this->right . ',' . $this->top; 57 | } 58 | 59 | /** 60 | * @param float $bottom 61 | */ 62 | public function setBottom($bottom) 63 | { 64 | $this->bottom = $bottom; 65 | } 66 | 67 | /** 68 | * @return float 69 | */ 70 | public function getBottom() 71 | { 72 | return $this->bottom; 73 | } 74 | 75 | /** 76 | * @param float $left 77 | */ 78 | public function setLeft($left) 79 | { 80 | $this->left = $left; 81 | } 82 | 83 | /** 84 | * @return float 85 | */ 86 | public function getLeft() 87 | { 88 | return $this->left; 89 | } 90 | 91 | /** 92 | * @param float $right 93 | */ 94 | public function setRight($right) 95 | { 96 | $this->right = $right; 97 | } 98 | 99 | /** 100 | * @return float 101 | */ 102 | public function getRight() 103 | { 104 | return $this->right; 105 | } 106 | 107 | /** 108 | * @param float $top 109 | */ 110 | public function setTop($top) 111 | { 112 | $this->top = $top; 113 | } 114 | 115 | /** 116 | * @return float 117 | */ 118 | public function getTop() 119 | { 120 | return $this->top; 121 | } 122 | 123 | 124 | 125 | } -------------------------------------------------------------------------------- /src/HTML24/MBTilesGenerator/Model/Tile.php: -------------------------------------------------------------------------------- 1 | x = $x; 29 | $this->y = $y; 30 | $this->z = $z; 31 | } 32 | 33 | 34 | } -------------------------------------------------------------------------------- /src/HTML24/MBTilesGenerator/TileSources/MBTilesTileSource.php: -------------------------------------------------------------------------------- 1 | db = new \PDO('sqlite:' . $source_file); 36 | 37 | $this->validateTable(); 38 | 39 | $this->tile_fetcher = $this->db->prepare( 40 | 'SELECT tile_data FROM tiles WHERE tile_column = :x AND tile_row = :y LIMIT 1' 41 | ); 42 | } 43 | 44 | /** 45 | * @throws \Exception 46 | */ 47 | protected function validateTable() 48 | { 49 | // Check if this looks like a mbtiles database 50 | 51 | $tables = $this->db->query( 52 | 'SELECT tbl_name FROM sqlite_master WHERE type = \'table\' OR type = \'view\'' 53 | )->fetchAll(\PDO::FETCH_COLUMN, 0); 54 | 55 | if (!in_array('metadata', $tables) || !in_array('tiles', $tables)) { 56 | throw new \Exception('This does not look like a valid mbtiles file'); 57 | } 58 | 59 | // Check for the necessary columns 60 | $metadata = $this->db->query('PRAGMA table_info(metadata)')->fetchAll(\PDO::FETCH_COLUMN, 1); 61 | $tiles = $this->db->query('PRAGMA table_info(tiles)')->fetchAll(\PDO::FETCH_COLUMN, 1); 62 | 63 | if ( 64 | !in_array('name', $metadata) || 65 | !in_array('value', $metadata) 66 | ) { 67 | throw new \Exception('Metadata table/view is of incorrect format'); 68 | } 69 | 70 | if ( 71 | !in_array('zoom_level', $tiles) || 72 | !in_array('tile_column', $tiles) || 73 | !in_array('tile_row', $tiles) || 74 | !in_array('tile_data', $tiles) 75 | ) { 76 | throw new \Exception('Tiles table/view is of incorrect format'); 77 | } 78 | } 79 | 80 | /** 81 | * This method will be called before actually requesting single tiles. 82 | * 83 | * Use this to batch generate/download tiles. 84 | * @param Tile[] $tiles 85 | * @return void 86 | */ 87 | public function cache($tiles) 88 | { 89 | // This source does not need to cache anything 90 | } 91 | 92 | /** 93 | * For every tile needed, this function will be called 94 | * 95 | * Return the blob of this image 96 | * @param Tile $tile 97 | * @throws TileNotAvailableException 98 | * @return string Blob of this image 99 | */ 100 | public function getTile(Tile $tile) 101 | { 102 | $this->tile_fetcher->bindValue(':x', $tile->x); 103 | $this->tile_fetcher->bindValue(':y', $tile->y); 104 | 105 | $this->tile_fetcher->execute(); 106 | 107 | $tile_data = $this->tile_fetcher->fetchColumn(); 108 | if ($tile_data === false) { 109 | throw new TileNotAvailableException(); 110 | } 111 | 112 | return $tile_data; 113 | } 114 | 115 | /** 116 | * Return the attribution text as HTML/text 117 | * 118 | * @return string 119 | */ 120 | public function getAttribution() 121 | { 122 | $attribution = $this->db->query('SELECT value FROM metadata WHERE name = \'attribution\''); 123 | if ($attribution->columnCount() > 0) { 124 | return $attribution->fetchColumn(); 125 | } else { 126 | return ''; 127 | } 128 | } 129 | 130 | /** 131 | * Should return the format of the tiles, either 'jpg' or 'png' 132 | * 133 | * @return string 134 | */ 135 | public function getFormat() 136 | { 137 | $format = $this->db->query('SELECT value FROM metadata WHERE name = \'format\''); 138 | if ($format->columnCount() > 0) { 139 | return $format->fetchColumn(); 140 | } else { 141 | return 'jpg'; 142 | } 143 | } 144 | 145 | } 146 | -------------------------------------------------------------------------------- /src/HTML24/MBTilesGenerator/TileSources/RemoteCachingTileSource.php: -------------------------------------------------------------------------------- 1 | cacheDir = $temporary_folder . '/mbtiles-generator/' . preg_replace( 67 | array('/\s/', '/\.[\.]+/', '/[^\w_\.\-]/'), 68 | array('_', '.', '_'), 69 | $url 70 | ); 71 | 72 | if ($subDomains) { 73 | $this->domains = array(); 74 | foreach ($subDomains as $subDomain) { 75 | $this->domains[] = str_replace('{s}', $subDomain, $url); 76 | } 77 | } else { 78 | $this->domains = array($url); 79 | } 80 | 81 | // Attempt to figure out the format, automatically. Fallback to jpg. 82 | if (strtolower(substr($url, -3)) == 'png') { 83 | $this->format = 'png'; 84 | } else { 85 | $this->format = 'jpg'; 86 | } 87 | } 88 | 89 | /** 90 | * Set the attribution for this tile source 91 | * @param string $attribution 92 | */ 93 | public function setAttribution($attribution) 94 | { 95 | $this->attribution = (string)$attribution; 96 | } 97 | 98 | /** 99 | * @return string 100 | */ 101 | public function getAttribution() 102 | { 103 | return (string)$this->attribution; 104 | } 105 | 106 | /** 107 | * This method will be called before actually requesting single tiles. 108 | * 109 | * Use this to batch generate/download tiles. 110 | * @param Tile[] $tiles 111 | * @return void 112 | */ 113 | public function cache($tiles) 114 | { 115 | // Start CURL Multi handle 116 | $this->curl_multi = curl_multi_init(); 117 | 118 | // Queue up downloads 119 | foreach ($tiles as $tile) { 120 | $this->queueTile($tile); 121 | } 122 | 123 | // Execute the download 124 | $this->downloadTiles(); 125 | } 126 | 127 | /** 128 | * Download all tiles in the queue 129 | */ 130 | protected function downloadTiles() 131 | { 132 | 133 | while (count($this->queue) > 0) { 134 | $this->waitForRequestsToDropBelow($this->maxRequests); 135 | 136 | $item = array_shift($this->queue); 137 | 138 | $ch = $this->newCurlHandle($item['url']); 139 | curl_multi_add_handle($this->curl_multi, $ch); 140 | 141 | $key = (int)$ch; 142 | $this->active_requests[$key] = $item; 143 | 144 | $this->checkForCompletedRequests(); 145 | } 146 | // Wait for transfers to finnish 147 | $this->waitForRequestsToDropBelow(1); 148 | } 149 | 150 | /** 151 | * @param int $max 152 | */ 153 | protected function waitForRequestsToDropBelow($max) 154 | { 155 | while (1) { 156 | $this->checkForCompletedRequests(); 157 | if (count($this->active_requests) < $max) { 158 | break; 159 | } 160 | usleep(10000); 161 | } 162 | } 163 | 164 | /** 165 | * Check and process any completed requests 166 | */ 167 | protected function checkForCompletedRequests() 168 | { 169 | do { 170 | $mrc = curl_multi_exec($this->curl_multi, $active); 171 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 172 | 173 | while ($active && $mrc == CURLM_OK) { 174 | if (curl_multi_select($this->curl_multi) != -1) { 175 | do { 176 | $mrc = curl_multi_exec($this->curl_multi, $active); 177 | } while ($mrc == CURLM_CALL_MULTI_PERFORM); 178 | } else { 179 | return; 180 | } 181 | } 182 | 183 | // Grab information about completed requests 184 | while ($info = curl_multi_info_read($this->curl_multi)) { 185 | 186 | $ch = $info['handle']; 187 | 188 | $ch_array_key = (int)$ch; 189 | 190 | if (!isset($this->active_requests[$ch_array_key])) { 191 | die("Error - handle wasn't found in requests: '$ch' in " . 192 | print_r($this->active_requests, true)); 193 | } 194 | 195 | $request = $this->active_requests[$ch_array_key]; 196 | $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); 197 | 198 | if ($info['result'] !== CURLE_OK) { 199 | //echo 'Error on tile: ' . $request['url'] . ' - ' . curl_error($ch) . "\n"; 200 | } else if ($status != 200) { 201 | //echo 'Error on tile: ' . $request['url'] . ' - HTTP Status: ' . $status . "\n"; 202 | } else { 203 | // Create destination 204 | static::createPath($request['destination'], true); 205 | 206 | // Write content to destination 207 | file_put_contents($request['destination'], curl_multi_getcontent($ch)); 208 | } 209 | 210 | 211 | unset($this->active_requests[$ch_array_key]); 212 | 213 | curl_multi_remove_handle($this->curl_multi, $ch); 214 | } 215 | } 216 | 217 | /** 218 | * Adds a tile to the download queue 219 | * @param Tile $tile 220 | */ 221 | protected function queueTile(Tile $tile) 222 | { 223 | $tile_destination = $this->tileDestination($tile); 224 | 225 | // Check if we already have the tile in cache 226 | if (file_exists($tile_destination)) { 227 | //echo 'Cache hit for tile: ' . $tile->z . '/' . $tile->x . '/' . $tile->y . "\n"; 228 | 229 | return; 230 | } 231 | 232 | // The tile was not in the cache, add a queue item 233 | $this->queue[] = array( 234 | 'url' => $this->tileUrl($tile), 235 | 'destination' => $this->tileDestination($tile), 236 | ); 237 | 238 | //echo 'Cache miss for tile: ' . $tile->z . '/' . $tile->x . '/' . $tile->y . "\n"; 239 | } 240 | 241 | /** 242 | * @param Tile $tile 243 | * @return string 244 | */ 245 | protected function tileDestination(Tile $tile) 246 | { 247 | return $this->cacheDir . '/' . $tile->z . '/' . $tile->x . '/' . $tile->y . '.' . $this->getFormat(); 248 | } 249 | 250 | /** 251 | * @param Tile $tile 252 | * @return string 253 | */ 254 | protected function tileUrl(Tile $tile) 255 | { 256 | $url = $this->domains[array_rand($this->domains)]; 257 | $url = str_replace('{z}', $tile->z, $url); 258 | $url = str_replace('{x}', $tile->x, $url); 259 | $url = str_replace('{y}', Calculator::flipYTmsToOsm($tile->y, $tile->z), $url); 260 | 261 | return $url; 262 | } 263 | 264 | /** 265 | * Returns a new curl handle 266 | * @param string $url 267 | * @return resource 268 | */ 269 | protected function newCurlHandle($url) 270 | { 271 | $ch = curl_init(); 272 | 273 | curl_setopt_array( 274 | $ch, 275 | array( 276 | CURLOPT_URL => $url, 277 | CURLOPT_RETURNTRANSFER => 1, 278 | CURLOPT_TIMEOUT => 10, 279 | ) 280 | ); 281 | 282 | return $ch; 283 | } 284 | 285 | /** 286 | * Function to recursively create a file path 287 | * @param string $path 288 | * @param bool $is_filename 289 | * @return bool 290 | */ 291 | protected static function createPath($path, $is_filename = false) 292 | { 293 | if ($is_filename) { 294 | $path = substr($path, 0, strrpos($path, '/')); 295 | } 296 | 297 | if (is_dir($path) || empty($path)) { 298 | return true; 299 | } 300 | 301 | if (static::createPath(substr($path, 0, strrpos($path, '/')))) { 302 | if (!file_exists($path)) { 303 | return mkdir($path); 304 | } 305 | } 306 | 307 | return false; 308 | } 309 | 310 | /** 311 | * For every tile needed, this function will be called 312 | * 313 | * Return the blob of this image 314 | * @param Tile $tile 315 | * @throws TileNotAvailableException 316 | * @return string Blob of this image 317 | */ 318 | public function getTile(Tile $tile) 319 | { 320 | $tile_path = $this->tileDestination($tile); 321 | if (!file_exists($tile_path)) { 322 | throw new TileNotAvailableException(); 323 | } 324 | 325 | return file_get_contents($tile_path); 326 | } 327 | 328 | /** 329 | * Should return the format of the tiles, either 'jpg' or 'png' 330 | * 331 | * @return string 332 | */ 333 | public function getFormat() 334 | { 335 | return $this->format; 336 | } 337 | 338 | /** 339 | * Manually override the format for this source. 340 | * Either 'jpg' or 'png' 341 | * 342 | * @param string $format 343 | * @throws 344 | */ 345 | public function setFormat($format) { 346 | if ($format === 'jpg' || $format === 'png') { 347 | $this->format = $format; 348 | } else { 349 | throw new \Exception('Unknown format ' . $format . ' supplied'); 350 | } 351 | } 352 | 353 | 354 | } -------------------------------------------------------------------------------- /src/HTML24/MBTilesGenerator/TileSources/TileSourceInterface.php: -------------------------------------------------------------------------------- 1 | setMaxZoom(18); 19 | $tile_generator->generate($bounding_box, 'sub-output.mbtiles'); 20 | 21 | 22 | echo "\nMax memory usage: " . number_format(memory_get_peak_usage() / 1024 / 1024, 2) . " MiB\n"; -------------------------------------------------------------------------------- /tests/test.php: -------------------------------------------------------------------------------- 1 | setAttribution('Data, imagery and map information provided by MapQuest, OpenStreetMap and contributors, ODbL .'); 14 | 15 | 16 | $tile_generator = new MBTilesGenerator($tile_source); 17 | 18 | $bounding_box = new BoundingBox('12.6061,55.6615,12.6264,55.6705'); 19 | 20 | $tile_generator->setMaxZoom(18); 21 | $tile_generator->generate($bounding_box, 'output.mbtiles'); 22 | 23 | 24 | echo "\nMax memory usage: " . number_format(memory_get_peak_usage() / 1024 / 1024, 2) . " MiB\n"; 25 | --------------------------------------------------------------------------------