Project

General

Profile

1 3032 perry
<?php
2
/*
3
4
This is a PHP file to be used as a backend for a ka-Map layer. It requires
5
PHP with Mapscript and libgd modules installed. The top of the file
6
is a configuration section: please edit the variables in this configuration
7
section to meet your needs, then rename this file to tile.php or something
8
similar and put it in a web accessible directory. More information
9
on the OpenLayers ka-Map layer is available from:
10
11
  http://trac.openlayers.org/wiki/OpenLayers.Layer.KaMap
12
13
*/
14
/**********************************************************************
15
 *
16
 * $Id$
17
 *
18
 * purpose: a simple phpmapscript-based tile renderer that implements
19
 *          rudimentary caching for reasonable efficiency.  Note the
20
 *          cache never shrinks in this version so your disk could
21
 *          easily fill up!
22
 *
23
 * author: Paul Spencer (pspencer@dmsolutions.ca)
24
 *
25
 * modifications by Daniel Morissette (dmorissette@dmsolutions.ca)
26
 *
27
 * Modified by Christopher Schmidt for OpenLayers redistribution.
28
 *
29
 **********************************************************************
30
 *
31
 * Copyright (c) 2005, DM Solutions Group Inc.
32
 *
33
 * Permission is hereby granted, free of charge, to any person obtaining a
34
 * copy of this software and associated documentation files (the "Software"),
35
 * to deal in the Software without restriction, including without limitation
36
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
37
 * and/or sell copies of the Software, and to permit persons to whom the
38
 * Software is furnished to do so, subject to the following conditions:
39
 *
40
 * The above copyright notice and this permission notice shall be included
41
 * in all copies or substantial portions of the Software.
42
 *
43
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
44
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
45
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
46
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
47
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
48
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
49
 * DEALINGS IN THE SOFTWARE.
50
 *
51
 **********************************************************************/
52
53
54
/******************************************************************************
55
 * basic system configuration
56
 *
57
 * kaMap! uses PHP/MapScript and the PHP GD extension to
58
 * render tiles, and uses PHP/MapScript to generate initialization parameters
59
 * a legend, and a keymap from the selected map file.
60
 *
61
 * Make sure to set the correct module names for your PHP extensions.
62
 *
63
 * WINDOWS USERS: you will likely need to use php_gd2.dll instead of php_gd.dll
64
 */
65
$szPHPMapScriptModule = 'php_mapscript.'.PHP_SHLIB_SUFFIX;
66
$szPHPGDModule = 'php_gd.'.PHP_SHLIB_SUFFIX;
67
68
/******************************************************************************
69
 * tile generation parameters
70
 *
71
 * kaMap! generates tiles to load in the client application by first rendering
72
 * larger areas from the map file and then slicing them up into smaller tiles.
73
 * This approach reduces the overhead of loading PHP/MapScript and PHP GD and
74
 * drawing the map file.  These larger areas are referred to as metaTiles in
75
 * the code.  You can set the size of both the small tiles and the metaTiles
76
 * here.  A reasonable size for the small tiles seems to be 200 pixels square.
77
 * Smaller tiles seem to cause problems in client browsers by causing too many
78
 * images to be created and thus slowing performance of live dragging.  Larger
79
 * tiles take longer to download to the client and are inefficient.
80
 *
81
 * The number of smaller tiles that form a metaTile can also be configured.
82
 * This parameter allows tuning of the tile generator to ensure optimal
83
 * performance and for label placement.  MapServer will produce labels only
84
 * within a rendered area.  If the area is too small then features may be
85
 * labelled multiple times.  If the area is too large, it may exceed MapServer,s
86
 * maximum map size (by default 2000x2000) or be too resource-intensive on the
87
 * server, ultimately reducing performance.
88
 */
89
$tileWidth = 256;
90
$tileHeight = 256;
91
$metaWidth = 5;
92
$metaHeight = 5;
93
/* $metaBuffer = Buffer size in pixels to add around metatiles to avoid
94
 * rendering issues along the edge of the map image
95
 */
96
$metaBuffer = 10;
97
98
/******************************************************************************
99
 * in-image debugging information - tile location, outlines etc.
100
 * to use this, you need to remove images from your cache first.  This also
101
 * affects the meta tiles - if debug is on, they are not deleted.
102
 */
103
$bDebug = false;
104
105
/******************************************************************************
106
 * aszMapFiles - an array of map files available to the application.  How this
107
 * is used is determined by the application.  Each map file is entered into
108
 * this array as a key->value pair.
109
 *
110
 * The key is the name to be used by the tile caching system to store cached
111
 * tiles within the base cache directory.  This key should be a single word
112
 * that uniquely identifies the map.
113
 *
114
 * The value associated with each key is an array of three values.  The first
115
 * value is a human-readable name to be presented to the user (should the
116
 * application choose to do so) and the second value is the path to the map
117
 * file.  It is assumed that the map file is fully configured for use with
118
 * MapServer/MapScript as no error checking or setting of values is done.  The
119
 * third value is an array of scale values for zooming.
120
 */
121
122
$aszMapFiles = array(
123
  "world"   => array( "World", "/path/to/your/mapfile",
124
               array( 10000  ), # in openlayers, the scale array doesn't matter.
125
               "PNG24")
126
127
/* Add more elements to this array to offer multiple mapfiles */
128
129
);
130
131
/******************************************************************************
132
 * figure out which map file to use and set up the necessary variables for
133
 * the rest of the code to use.  This does need to be done on every page load
134
 * unfortunately.
135
 *
136
 * szMap should be set to the default map file to use but can change if
137
 * this script is called with map=<mapname>.
138
 */
139
$szMap = 'world';
140
141
/******************************************************************************
142
 * kaMap! caching
143
 *
144
 * this is the directory within which kaMap! will create its tile cache.  The
145
 * directory does NOT have to be web-accessible, but it must be writable by the
146
 * web-server-user and allow creation of both directories AND files.
147
 *
148
 * the tile caching system will create a separate subdirectory within the base
149
 * cache directory for each map file.  Within the cache directory for each map
150
 * file, directories will be created for each group of layers.  Within the group
151
 * directories, directories will be created at each of the configured scales
152
 * for the application (see mapfile configuration above.)
153
 */
154
$szBaseCacheDir =  "/var/cache/kamap/";
155
156
/*****  END OF CONFIGURABLE STUFF - unless you know what you are doing   *****/
157
/*****                                                                   *****/
158
/*****                                                                   *****/
159
/*****                                                                   *****/
160
/*****  END OF CONFIGURABLE STUFF - unless you know what you are doing   *****/
161
162
if (isset($_REQUEST['map']) && isset($aszMapFiles[$_REQUEST['map']]))
163
{
164
    $szMap = $_REQUEST['map'];
165
}
166
167
$szMapCacheDir = $szBaseCacheDir.$szMap."/";
168
$szMapName = $aszMapFiles[$szMap][0];
169
$szMapFile = $aszMapFiles[$szMap][1];
170
$anScales = $aszMapFiles[$szMap][2];
171
setOutputFormat($aszMapFiles[$szMap][3]);
172
/******************************************************************************
173
 * output format of the map and resulting tiles
174
 *
175
 * The output format used with MapServer can greatly affect appearance and
176
 * performance.  It is recommended to use an 8 bit format such as PNG
177
 *
178
 * NOTE: the tile caching code in tile.php is not configurable here.  It
179
 * currently assumes that it is outputting 8bit PNG files.  If you change to
180
 * PNG24 here then you will need to update tile.php to use the gd function
181
 * imagecreatetruecolor.  If you change the output format to jpeg then
182
 * you would need to change imagepng() to imagejpeg().  A nice enhancement
183
 * would be to make that fully configurable from here.
184
 */
185
function setOutputFormat($szFormat)
186
{
187
    switch($szFormat) {
188
        case "PNG24":
189
            $GLOBALS['szMapImageFormat'] = 'PNG24'; //mapscript format name
190
            $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng"; // appropriate GD function
191
            $GLOBALS['szImageExtension'] = '.png'; //file extension
192
            $GLOBALS['szImageCreateFunction'] = "imagecreatetruecolor"; //or imagecreatetruecolor if PNG24 ...
193
            $GLOBALS['szImageOutputFunction'] = "imagepng"; //or imagegif, imagejpeg ...
194
            $GLOBALS['szImageHeader'] = 'image/png'; //the content-type of the image
195
            break;
196
        case "GIF":
197
            $GLOBALS['szMapImageFormat'] = 'GIF'; //mapscript format name
198
            $GLOBALS['szMapImageCreateFunction'] = "imagecreatefromgif"; // appropriate GD function
199
            $GLOBALS['szImageExtension'] = '.gif'; //file extension
200
            $GLOBALS['szImageCreateFunction'] = "imagecreate"; //or imagecreatetruecolor if PNG24 ...
201
            $GLOBALS['szImageOutputFunction'] = "imagegif"; //or imagegif, imagejpeg ...
202
            $GLOBALS['szImageHeader'] = 'image/gif'; //the content-type of the image
203
            break;
204
        case "JPEG":
205
            $GLOBALS['szMapImageFormat'] = 'JPEG'; //mapscript format name
206
            $GLOBALS['szMapImageCreateFunction'] = "imagecreatefromjpeg"; // appropriate GD function
207
            $GLOBALS['szImageExtension'] = '.jpg'; //file extension
208
            $GLOBALS['szImageCreateFunction'] = "imagecreatetruecolor"; //or imagecreatetruecolor if PNG24 ...
209
            $GLOBALS['szImageOutputFunction'] = "imagejpeg"; //or imagegif, imagejpeg ...
210
            $GLOBALS['szImageHeader'] = 'image/jpeg'; //the content-type of the image
211
            break;
212
        case "PNG":
213
            $GLOBALS['szMapImageFormat'] = 'PNG'; //mapscript format name
214
            $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng"; // appropriate GD function
215
            $GLOBALS['szImageExtension'] = '.png'; //file extension
216
            $GLOBALS['szImageCreateFunction'] = "imagecreate"; //or imagecreatetruecolor if PNG24 ...
217
            $GLOBALS['szImageOutputFunction'] = "imagepng"; //or imagegif, imagejpeg ...
218
            $GLOBALS['szImageHeader'] = 'image/png'; //the content-type of the image
219
            break;
220
        case "DITHERED":
221
	case "PNG8":
222
            $GLOBALS['szMapImageFormat'] = 'dithered';
223
            $GLOBALS['szMapImageCreateFunction'] = "imagecreatefrompng";
224
            $GLOBALS['szImageExtension'] = '.png';
225
            $GLOBALS['szImageCreateFunction'] = "imagecreate";
226
            $GLOBALS['szImageOutputFunction'] = "imagepng";
227
            $GLOBALS['szImageHeader'] = 'image/png';
228
            break;
229
    }
230
}
231
232
/**
233
 * create all directories in a directory tree - found on the php web site
234
 * under the mkdir function ...
235
 */
236
function makeDirs($strPath, $mode = 0775)
237
{
238
   return is_dir($strPath) or ( makeDirs(dirname($strPath), $mode) and mkdir($strPath, $mode) );
239
}
240
241
/**
242
 * This function replaces all special characters in the given string.
243
 *
244
 * @param szString string - The string to convert.
245
 *
246
 * @return string converted
247
 */
248
function normalizeString($szString)
249
{
250
    // Normalize string by replacing all special characters
251
    // e.g.    "http://my.host.com/cgi-bin/mywms?"
252
    // becomes "http___my_host_com_cgi_bin_mywms_"
253
    return preg_replace("/(\W)/", "_", $szString);
254
}
255
256
/* bug 1253 - root permissions required to delete cached files */
257
$orig_umask = umask(0);
258
259
/* create the main cache directory if necessary */
260
if (!@is_dir($szMapCacheDir))
261
    makeDirs($szMapCacheDir);
262
263
/* get the various request parameters
264
 * also need to make sure inputs are clean, especially those used to
265
 * build paths and filenames
266
 */
267
 /*
268
 * the tile renderer accepts several parameters and returns a tile image from
269
 * the cache, creating the tile only if necessary.
270
 *
271
 * all requests include the pixel location of the request at a certain scale
272
 * and this script figures out the geographic location of the tile from the
273
 * scale assuming that 0,0 in pixels is 0,0 in geographic units
274
 *
275
 * Request parameters are:
276
 *
277
 * map: the name of the map to use.  This is handled by config.php.
278
 *
279
 * t: top pixel position
280
 * l: left pixel position
281
 * s: scale
282
 * g: (optional) comma-delimited list of group names to draw
283
 * layers: (optional) comma-delimited list of layers to draw
284
 * force: optional.  If set, force redraw of the meta tile.  This was added to
285
 *        help with invalid images sometimes being generated.
286
 * tileid: (optional) can be used instead of t+l to specify the tile coord.,
287
 *         useful in regenerating the cache
288
 */
289
290
$top = isset( $_REQUEST['t'] ) ? intval($_REQUEST['t']) : 0;
291
$left = isset( $_REQUEST['l'] ) ? intval($_REQUEST['l']) : 0;
292
$scale = isset( $_REQUEST['s'] ) ? $_REQUEST['s'] : $anScales[0];
293
$bForce = isset($_REQUEST['force'])? true : false;
294
$groups = isset( $_REQUEST['g'] ) ? $_REQUEST['g'] : "";
295
$layers = isset( $_REQUEST['layers'] ) ? $_REQUEST['layers'] : "";
296
297
// dynamic imageformat ----------------------------------------------
298
//use the function in config.php to set the output format
299
if (isset($_REQUEST['i']))
300
   setOutputFormat( $_REQUEST['i'] );
301
//----------------------------------------------------------------
302
303
/* tileid=t#####l#### can be used instead of t+l parameters. Useful in
304
 * regenerating the cache for instance.
305
 */
306
if (isset( $_REQUEST['tileid']) &&
307
    preg_match("/t(-?\d+)l(-?\d+)/", $_REQUEST['tileid'], $aMatch) )
308
{
309
    $top = intval($aMatch[1]);
310
    $left = intval($aMatch[2]);
311
}
312
313
/* Calculate the metatile's top-left corner coordinates.
314
 * Include the $metaBuffer around the metatile to account for various
315
 * rendering issues happening around the edge of a map
316
 */
317
$metaLeft = floor( ($left)/($tileWidth*$metaWidth) ) * $tileWidth * $metaWidth;
318
$metaTop = floor( ($top)/($tileHeight*$metaHeight) ) * $tileHeight *$metaHeight;
319
$szMetaTileId = "t".$metaTop."l".$metaLeft;
320
$metaLeft -= $metaBuffer;
321
$metaTop -= $metaBuffer;
322
323
/* caching is done by scale value, then groups and layers and finally metatile
324
 * and tile id. Create a new directory if necessary
325
 */
326
$szGroupDir = $groups != "" ? normalizeString($groups) : "def";
327
$szLayerDir = $layers != "" ? normalizeString($layers) : "def";
328
329
$szCacheDir = $szMapCacheDir."/".$scale."/".$szGroupDir."/".$szLayerDir."/".$szMetaTileId;
330
if (!@is_dir($szCacheDir))
331
    makeDirs($szCacheDir);
332
333
/* resolve cache hit - clear the os stat cache if necessary */
334
$szTileId = "t".$top."l".$left;
335
$szCacheFile = $szCacheDir."/".$szTileId.$szImageExtension;
336
clearstatcache();
337
338
$szMetaDir = $szCacheDir."/meta";
339
if (!@is_Dir($szMetaDir))
340
    makeDirs($szMetaDir);
341
342
/* simple locking in case there are several requests for the same meta
343
   tile at the same time - only draw it once to help with performance */
344
$szLockFile = $szMetaDir."/lock_".$metaTop."_".$metaLeft;
345
$fpLockFile = fopen($szLockFile, "a+");
346
clearstatcache();
347
if (!file_exists($szCacheFile) || $bForce)
348
{
349
    flock($fpLockFile, LOCK_EX);
350
    fwrite($fpLockFile, ".");
351
352
    //check once more to see if the cache file was created while waiting for
353
    //the lock
354
    clearstatcache();
355
    if (!file_exists($szCacheFile) || $bForce)
356
    {
357
        if (!extension_loaded('MapScript'))
358
        {
359
            dl( $szPHPMapScriptModule );
360
        }
361
        if (!extension_loaded('gd'))
362
        {
363
            dl( $szPHPGDModule);
364
        }
365
366
        if (!@is_Dir($szMetaDir))
367
            makeDirs($szMetaDir);
368
369
        $oMap = ms_newMapObj($szMapFile);
370
371
        /* Metatile width/height include 2x the metaBuffer value */
372
        $oMap->set('width', $tileWidth * $metaWidth + 2*$metaBuffer);
373
        $oMap->set('height', $tileHeight * $metaHeight + 2*$metaBuffer);
374
375
        /* Tell MapServer to not render labels inside the metaBuffer area
376
         * (new in 4.6)
377
         * TODO: Until MapServer bugs 1353/1355 are resolved, we need to
378
         * pass a negative value for "labelcache_map_edge_buffer"
379
         */
380
        $oMap->setMetadata("labelcache_map_edge_buffer", -$metaBuffer);
381
382
        $inchesPerUnit = array(1, 12, 63360.0, 39.3701, 39370.1, 4374754);
383
        $geoWidth = $scale/($oMap->resolution*$inchesPerUnit[$oMap->units]);
384
        $geoHeight = $scale/($oMap->resolution*$inchesPerUnit[$oMap->units]);
385
386
        /* draw the metatile */
387
        $minx = $metaLeft * $geoWidth;
388
        $maxx = $minx + $geoWidth * $oMap->width;
389
        $maxy = -1 * $metaTop * $geoHeight;
390
        $miny = $maxy - $geoHeight * $oMap->height;
391
392
        $nLayers = $oMap->numlayers;
393
        $oMap->setExtent($minx,$miny,$maxx,$maxy);
394
        $oMap->selectOutputFormat( $szMapImageFormat );
395
        $aszLayers = array();
396
        if ($groups || $layers)
397
        {
398
            /* Draw only specified layers instead of default from mapfile*/
399
            if ($layers)
400
            {
401
                $aszLayers = explode(",", $layers);
402
            }
403
404
            if ($groups)
405
            {
406
                $aszGroups = explode(",", $groups);
407
            }
408
409
            for($i=0;$i<$nLayers;$i++)
410
            {
411
                $oLayer = $oMap->getLayer($i);
412
                if (($aszGroups && in_array($oLayer->group,$aszGroups)) ||
413
                    ($aszLayers && in_array($oLayer->name,$aszLayers)) ||
414
                    ($aszGroups && $oLayer->group == '' &&
415
                     in_array( "__base__", $aszGroups)))
416
                {
417
                    $oLayer->set("status", MS_ON );
418
                }
419
                else
420
                {
421
                    $oLayer->set("status", MS_OFF );
422
                }
423
            }
424
            //need transparency if groups or layers are used
425
            $oMap->outputformat->set("transparent", MS_ON );
426
        }
427
        else
428
        {
429
            $oMap->outputformat->set("transparent", MS_OFF );
430
        }
431
432
433
        $szMetaImg = $szMetaDir."/t".$metaTop."l".$metaLeft.$szImageExtension;
434
        $oImg = $oMap->draw();
435
        $oImg->saveImage($szMetaImg);
436
        $oImg->free();
437
        eval("\$oGDImg = ".$szMapImageCreateFunction."('".$szMetaImg."');");
438
        if ($bDebug)
439
        {
440
            $blue = imagecolorallocate($oGDImg, 0, 0, 255);
441
            imagerectangle($oGDImg, 0, 0, $tileWidth * $metaWidth - 1, $tileHeight * $metaHeight - 1, $blue );
442
        }
443
        for($i=0;$i<$metaWidth;$i++)
444
        {
445
            for ($j=0;$j<$metaHeight;$j++)
446
            {
447
                eval("\$oTile = ".$szImageCreateFunction."( ".$tileWidth.",".$tileHeight." );");
448
                // Allocate BG color for the tile (in case the metatile has transparent BG)
449
                $nTransparent = imagecolorallocate($oTile, $oMap->imagecolor->red, $oMap->imagecolor->green, $oMap->imagecolor->blue);
450
                //if ($oMap->outputformat->transparent == MS_ON)
451
                //{
452
                    imagecolortransparent( $oTile,$nTransparent);
453
                //}
454
                $tileTop = $j*$tileHeight + $metaBuffer;
455
                $tileLeft = $i*$tileWidth + $metaBuffer;
456
                imagecopy( $oTile, $oGDImg, 0, 0, $tileLeft, $tileTop, $tileWidth, $tileHeight );
457
                /* debugging stuff */
458
                if ($bDebug)
459
                {
460
                    $black = imagecolorallocate($oTile, 1, 1, 1);
461
                    $green = imagecolorallocate($oTile, 0, 128, 0 );
462
                    $red = imagecolorallocate($oTile, 255, 0, 0);
463
                    imagerectangle( $oTile, 1, 1, $tileWidth-2, $tileHeight-2, $green );
464
                    imageline( $oTile, 0, $tileHeight/2, $tileWidth-1, $tileHeight/2, $red);
465
                    imageline( $oTile, $tileWidth/2, 0, $tileWidth/2, $tileHeight-1, $red);
466
                    imagestring ( $oTile, 3, 10, 10, ($metaLeft+$tileLeft)." x ".($metaTop+$tileTop), $black );
467
                    imagestring ( $oTile, 3, 10, 30, ($minx+$i*$geoWidth)." x ".($maxy - $j*$geoHeight), $black );
468
                }
469
                $szTileImg = $szCacheDir."/t".($metaTop+$tileTop)."l".($metaLeft+$tileLeft).$szImageExtension;
470
                eval("$szImageOutputFunction( \$oTile, '".$szTileImg."' );");
471
                imagedestroy($oTile);
472
                $oTile = null;
473
            }
474
        }
475
        if ($oGDImg != null)
476
        {
477
            imagedestroy($oGDImg);
478
            $oGDImg = null;
479
        }
480
        if (!$bDebug)
481
        {
482
            unlink( $szMetaImg );
483
        }
484
    }
485
    //release the exclusive lock
486
    flock($fpLockFile, LOCK_UN );
487
}
488
489
//acquire shared lock for reading to prevent a problem that could occur
490
//if a tile exists but is only partially generated.
491
flock($fpLockFile, LOCK_SH);
492
493
$h = fopen($szCacheFile, "r");
494
header("Content-Type: ".$szImageHeader);
495
header("Content-Length: " . filesize($szCacheFile));
496
header("Expires: " . date( "D, d M Y H:i:s GMT", time() + 31536000 ));
497
header("Cache-Control: max-age=31536000, must-revalidate" );
498
fpassthru($h);
499
fclose($h);
500
501
//release lock
502
fclose($fpLockFile);
503
504
/* bug 1253 - root permissions required to delete cached files */
505
umask($orig_umask);
506
507
exit;
508
?>