User:Mstriewe/OSMTileProxy

From OpenStreetMap Wiki
Jump to navigation Jump to search

This is an extension of ProxySimplePHP5 written by User:Kgolding which in turn is an extension of ProxySimplePHP written by User:Lizard.

I made some changes to get rid of the retry loop and to add basic logging, filtering of requests based on HTTP referers and a status page.

tiles.php:

<?php
	/* Original source https://wiki.openstreetmap.org/wiki/ProxySimplePHP
	 * and https://wiki.openstreetmap.org/wiki/ProxySimplePHP5
	 */

	// Config section
	$cacheDays = 15;
	$minZoom = 5;
	$maxZoom = 15;
	$operatorMail = "your@email.com";
	$allowedReferers = array(
		"https://www.your-domain.de",
		"https://www.another-domain.de"
	);
	$server = array(
		"a.tile.openstreetmap.org",
		"b.tile.openstreetmap.org",
		"c.tile.openstreetmap.org"
	);

	// Add a log handler
	set_error_handler(function($errno, $errstr, $errfile, $errline){
		if($errno === E_ERROR || $errno === E_WARNING){
			$fp = fopen("log.txt", "a");
			fwrite($fp, $errstr . "\r\n" . $errfile . " in line " . $errline . "\r\n");
			fclose($fp);
		}
	});
			
	// Read parameters
	$referer = $_SERVER['HTTP_REFERER'];
    $x = intval($_GET['x']);
    $y = intval($_GET['y']);
    $z = intval($_GET['z']);

	// Start processing
	if (!empty($referer) && !empty($x) && !empty($y) && !empty($z)) { // Request has all required parameters
		// Check request
		$allowedRequest = false;

		foreach ($allowedReferers as $a) {
			if (strpos($referer, $a) === 0) {
				$allowedRequest = true;
				break;
			}
		}
		
		if (!$allowedRequest) {
			echo "Access denied";
			die;
		}
		
		if ($z < $minZoom || $z > $maxZoom) {
			echo "Sorry, this tile proxy does not serve tiles for the requested zoom level";
			die;
		}

		// Prepare for chache lookup
		$file = "tiles/$z/$x/$y.png";
		$ttl = 60 * 60 * 24 * $cacheDays; //cache timeout in seconds
		$img = null;
		
		if (is_file($file) && @filemtime($file) >= time()-$ttl) { // We can serve a sufficiently fresh tile from cache
			$img = file_get_contents($file);
		} else { // We don't have the tile or it is too old
			$url = 'https://'.$server[array_rand($server)];
			$url .= "/".$z."/".$x."/".$y.".png";

			$options = array(
				'http'=>array(
					'method'=>"GET",
					'header'=>"User-Agent: OSMTileProxy/1.0 (operated by " . $operatorMail . ") based on https://wiki.openstreetmap.org/wiki/ProxySimplePHP\r\n
					Referer: " . $referer . "\r\n"
				)
			);
			$context = stream_context_create($options);
			$img = @file_get_contents($url, false, $context);
			
			if ($img) { // We got a fresh tile, so we store it
				if (!file_exists(dirname($file))) {
					@mkdir(dirname($file), 0755, true);
				}

				$fp = fopen($file, "w");
				fwrite($fp, $img);
				fclose($fp);
			} else if (is_file($file)) { // If we got no fresh tile but have an old one, we serve that
				$img = file_get_contents($file);
			}
		}
	
		if ($img) { // There is something to serve
			$exp_gmt = gmdate("D, d M Y H:i:s", time() + $ttl * 2) ." GMT";
			$mod_gmt = gmdate("D, d M Y H:i:s", filemtime($file)) ." GMT";

			header("Expires: " . $exp_gmt);
			header("Cache-Control: public, max-age=" . $ttl * 2);
			// for MSIE 5
			header("Cache-Control: pre-check=" . $ttl * 2, FALSE);
			header("Last-Modified: " . $mod_gmt);
			header('Content-Type: image/png');

			echo $img;

			$fp = fopen("lastServed.txt", "w");
			fwrite($fp, $file);
			fclose($fp);
		} else { // We cannot answer the request, so we log an error
			$fp = fopen("lastMissed.txt", "w");
			fwrite($fp, $file);
			fclose($fp);
		}
	} else { // Request misses something, so we show the status page instead
		$ttl = 60 * 60 * 24 * $cacheDays; //cache timeout in seconds
		$fileCount = array();
		$fileSize = array();
		$fileFreshCount = array();
		$lastRefreshed['file'] = "";
		$lastRefreshed['time'] = 0;
		$freshLimit = time()-$ttl;
	
		// Create file statistics
		for ($z = $minZoom; $z <= $maxZoom; $z++) {
			if (is_dir('tiles/' . $z)) {
				$xFolders = array_diff(scandir('tiles/' . $z), array('..', '.'));
				foreach ($xFolders as $x) {
					$yFiles = array_diff(scandir('tiles/' . $z . '/' . $x), array('..', '.'));
					$fileCount[$z] += sizeof($yFiles);
					foreach ($yFiles as $y) {
						$fileSize[$z] += filesize('tiles/' . $z . '/' . $x . '/' . $y);
						$time = filemtime('tiles/' . $z . '/' . $x . '/' . $y);
						if ($time >= $freshLimit) {
							$fileFreshCount[$z]++;
						}
						if ($time > $lastRefreshed['time']) {
							$lastRefreshed['time'] = $time;
							$lastRefreshed['file'] = 'tiles/' . $z . '/' . $x . '/' . $y;
						}
					}
				}
			}
		}
	
		// Write page content
		echo '<html><head><title>OSM Tile Proxy (v1.0) status page</title></head><body>';
		echo '<h1>OSM Tile Proxy (v1.0) status page</h1>';
		echo '<p>This tile proxy is operated by ' . $operatorMail . '.</p>';
		
		if (is_file("log.txt") && filesize("log.txt") > 0) {
			echo '<p>There are errors or warnings in the log file!</p>';
		}
		if (is_file("lastMissed.txt") && filesize("lastMissed.txt") > 0) {
			echo '<p>Last tile that could not be refreshed: ' . file_get_contents("lastMissed.txt") . '. Check log file for details.</p>';
		}
		
		echo '<h2>Latest activity</h2>';
		$lastServed = @file_get_contents("lastServed.txt");
		echo '<table border="1"><tr>';
		echo '<td style="text-align:center">Last tile delivered:<br /><img src="' . $lastServed . '"><br />' . $lastServed . '<br />at ' . date(DATE_ATOM, filemtime("lastServed.txt")) . '</td>';
		echo '<td style="text-align:center">Last tile refreshed:<br /><img src="' . $lastRefreshed['file'] . '"><br />' . $lastRefreshed['file'] . '<br />at ' . date(DATE_ATOM, $lastRefreshed['time']) . '</td>';
		echo '</tr></table>';

		$totalFiles = 0;
		$totalSize = 0;
		$totalFresh = 0;
		echo '<h2>File statistics</h2>';
		echo '<table border="1"><tr><th>&nbsp;</th><th>#Files in cache</th><th>#Files not older<br />than ' . $cacheDays . ' days</th></tr>';
		for ($i = $minZoom; $i <= $maxZoom; $i++) {
			echo '<tr><td>Zoom level ' . $i . '</td><td>' . $fileCount[$i] . ' (' . round($fileSize[$i]*100/1024/1024)/100 . ' MB)</td><td>' . $fileFreshCount[$i] . ' (' . round($fileFreshCount[$i]*100/$fileCount[$i]) . ' %)</td></tr>';
			$totalFiles += $fileCount[$i];
			$totalSize += $fileSize[$i];
			$totalFresh += $fileFreshCount[$i];
		}
		echo '<tr><td>Total</td><td>' . $totalFiles . ' (' . round($totalSize*100/1024/1024)/100 . ' MB)</td><td>' . $totalFresh . ' (' . round($totalFresh*100/$totalFiles) . ' %)</td></tr></table>';
		
		echo '</body></html>';
	}
?>

Usage instructions are as per ProxySimplePHP except you do not need to make the tiles directory. The directory where you place "tiles.php" must be writable!