Cached image MediaWiki Extension

From OpenStreetMap Wiki
Jump to navigation Jump to search

This page describes a Cached image MediaWiki extension. See MediaWiki extension for details of other ideas / enhancements we could develop.

This is the same as Simple image MediaWiki Extension but caching functionality added. Unfortunately it doesn't work, or at least it has never been successfully tested, since the remote file access "fopen" operation is disallowed on the web hosts that Harry Wood tried testing it on.

Same as the simple version, it supports wiki markup of the form <map lat="51.485" lon="-0.15" z="11" w="300" h="200|format=jpeg" />. This time the extension will fetch the file from the remote server (User:Ojw's image generating service) and store it in the local filesystem (cache it) It spits out an IMG tag which this time will always reference a local file.


There's currently no live example MediaWiki deployment with this extension running. Feel free to link yourself here, if you have an example live on the web.

MediaWiki Versions

If this actually worked, then it would most likely work on all mediawiki versions even older version 1.4 MediaWiki installations...

...but it does require webserver support for remote "fopen" operations. Perhaps this is configuration in php.ini


  • Copy and paste the code below into a new file 'openstreetmap-slippymap.php'
  • Upload this into your mediawiki installation in the 'extensions' subdirectory
  • Edit your 'LocalSettings.php' file. Add an 'include' line:
include("extensions/openstreetmap-slippymap.php"); //openstreetmap extension
  • Try it out! e.g. Stick the slippymap syntax example above on your 'SandBox' page.

Dependency note

This introduces a dependency between your wiki and the various openstreetmap tile servers. If openstreetmap ceases to provide images under these URLs, for whatever reason, then your wiki will still function, but it will show image errors.

We are offering no guarantees about the long-term continuation of this (or indeed any) openstreetmap service. Equally the extension code may be re-written, and you should aim to use the latest code available here. e.g. for security fixes.

There are a number of limitations and things which could be enhanced. The MediaWiki extension page has lots of ideas for this.

Who's working on it

The extension code

# OpenStreetMap Cached Map - MediaWiki extension
# This defines what happens when <map> tag is placed in the wikitext
# We show a map image based on the lat/long/zoom data passed in. Usage example:
# <map>lat=51.485|long=-0.15|z=11|w=300|h=200|format=jpeg</map> 
# The behaviour is largely identical to the 'OpenStreetMap Simple Map' extension.
# With this caching extension, we fetch the image and cache it on the local
# filesystem (and susbsequently use the cached version until it gets too old)
# A caching squid proxy might be a better way of acheiving this.
# This file should be placed in the mediawiki 'extensions' directory
# and then it needs to be 'included' within LocalSettings.php

$wgExtensionFunctions[] = "wfmap";

function wfmap() {
    global $wgParser;
    # register the extension with the WikiText parser
    # the first parameter is the name of the new tag.
    # In this case it defines the tag <map> ... </map>
    # the second parameter is the callback function for
    # processing the text between the tags
    $wgParser->setHook( "map", "map" );

# The callback function for converting the input text to HTML output
function map( $input ) {
    //Parse pipe separated name value pairs (e.g. 'aaa=bbb|ccc=ddd')
    foreach ($paramStrings as $paramString) {
       $eqPos = strpos($paramString,"=");
       if ($eqPos===false) {
          $params[$paramString] = "true";
       } else {
          $params[substr($paramString,0,$eqPos)] = htmlspecialchars(substr($paramString,$eqPos+1));

    $lat    = $params["lat"];
    $long   = $params["long"];
    $zoom   = $params["z"];
    $width  = $params["w"];
    $height = $params["h"];
    $format = $params["format"];

    //default values (meaning these parameters can be missed out)
    if ($width=="")  $width ="300"; 
    if ($height=="") $height="200"; 
    if ($format=="") $format="jpeg"; 
    //trim of the 'px' on the end of pixel measurement numbers (ignore if present)
    if (substr($width,-2)=="px")  $width = substr($width,0,-2);
    if (substr($height,-2)=="px") $height = substr($height,0,-2);

    //Validation of parameters values
    if (!is_numeric($width)) {
        $error = "width (w) value '$width' is not a valid integer";
    } else if (!is_numeric($height)) {
        $error = "height (h) value '$height' is not a valid integer";
    } else if (!is_numeric($zoom)) {
        $error = "zoom (z) value '$zoom' is not a valid integer";
    } else if (!is_numeric($lat)) {
        $error = "lattitude (lat) value '$lat' is not a valid number";
    } else if (!is_numeric($long)) {
        $error = "longitude (long) value '$long' is not a valid number";
    } else if ($width>1000) {
        $error = "width (w) value cannot be greater than 1000";
    } else if ($width<50) {
        $error = "width (w) value cannot be less than 50";
    } else if ($height>1000) {
        $error = "height (h) value cannot be greater than 1000";
    } else if ($height<50) {
        $error = "height (h) value cannot be less than 50";
    } else if ($lat>90) {
        $error = "lattitude (lat) value cannot be greater than 90";
    } else if ($long<-90) {
        $error = "lattitude (lat) value cannot be less than -90";
    } else if ($long>180) {
        $error = "longitude (long) value cannot be greater than 180";
    } else if ($long<-180) {
        $error = "longitude (long) value cannot be less than -180";
    } else if ($zoom<0) {
        $error = "zoom (z) value cannot be less than zero";
    } else if ($zoom==18) {
        $error = "zoom (z) value cannot be greater than 17. ".
                 "Note that this mediawiki extension hooks into the OpenStreetMap 'osmarender' layer ".
                 "which does not go beyond zoom level 17. The Mapnik layer available on ".
                 ", goes up to zoom level 18";
    } else if ($zoom>17) {
        $error = "zoom (z) value cannot be greater than 17.";
    } else if ($format!="jpeg" && $format!="png") {
        $error = "'format' value must be either 'jpeg' or 'png', or you can omit that parameter (defaults to 'jpeg')";
    if ($error=="") {
        //Go ahead with image fetch
        $remoteURL = "".$lat."&long=".$long."&z=".$zoom."&w=".$width."&h=".$height."&format=".$format;
        $localFileName = "map".$lat."-".$long."-".$zoom."-".$width."-".$height.".".$format;
        $localFilePath = "map-images/".$localFileName;
        $status = fetchFileCacheOrRemote( $remoteURL , $localFilePath);
        if (substr_count($status,"ERROR") > 1) $error = "Image fetch failed with status:<BR>$status";
    if ($error=="") {
        //HTML for the openstreetmap image and link:
        $output  = "";
        $output .= "<span class=\"map\">";
        $output .= "<a href=\"".$lat."&lon=".$long."&zoom=".$zoom."\" title=\"See this map on\">";
        $output .= "<img src=\"".$localFilePath."\" width=\"". $width."\" height=\"".$height."\" border=\"0\"><br/>";
        $output .= "<span style=\"font-size:60%; background-color:white; position:relative; top:-15px;\">OpenStreetMap - CC-BY-SA-2.0</div>";
        $output .= "</a>\n";
        $output .= "</span>";
    } else {
        //Something was wrong. Spew the error message and input text.
        $output  = "";
        $output .= "<FONT COLOR=\"RED\"><B>map error:</B> " . $error . "</FONT><BR>\n";
        $output .= htmlspecialchars($input);
    return $output;

 * This function will either do nothing, or fetch a file from a remote URL,
 * depending on how old the existing cached copy of the file is.
function fetchFileCacheOrRemote( $remoteUrl, $localFileName ) {
    $cacheTimeout = 5; //How old can a file be (in seconds) before we fetch it again from the remote server?
    if (file_exists($localFileName)) {
        //file is present
        $modified = filemtime($localFileName);
        if ($modified < (time() - $cacheTimeout)) {
            $status  = "Cached file (fetched " . date ("F d Y H:i:s.", $modified) . ") Too old<BR>";
            $status .= fetchRemoteFile( $remoteUrl, $localFileName );
        } else {
            $status = "Cached file (last fetched " . date ("F d Y H:i:s.", $modified) . ") OK<BR>"; 
    } else {
        $status = "File not cached<BR>";
        $status .= fetchRemoteFile( $remoteUrl, $localFileName );
    return $status;

 * This function will fetch a file from a specified remoteURL, and write it
 * to the local filesystem with the specified localFileName
 * It returns a success message or an error if the file was too big. 
function fetchRemoteFile( $remoteUrl, $localFileName ) {
    $destination=fopen($localFileName, "w");
    $source=fopen($remoteUrl,"r");  // <---   This line will throw a security error if such an fopen operation is disallowed
    $maxsizeKB = 500; //Max file size before the download gets curtailed
    $maxsize   = 1024 * $maxsizeKB;  
    $chunkSize = 1024; //fetch 1 KB in each iteration
    while (($a=fread($source,$chunkSize))&&($length<=$maxsize))
        $length = $length + $chunkSize;
    if ($length>$maxsize) {
        return "ERROR: Map image file exceeded the maximum (".$maxsizeKB."KB)";
    } else {    
        return "Fetched from remote server";