phpDocumentor SMap
[ class tree: SMap ] [ index: SMap ] [ all elements ]

Source for file SMap.php

Documentation is available at SMap.php

  1. <?php
  2. /**
  3.  * The root file of SMap
  4.  * 
  5.  * The layered map view is not for the faint of heart. If you are new to this,
  6.  * expect to spend quite a bit of time going back and fourth trying to
  7.  * understand what is going on. Of course I've done my best to make things
  8.  * simple, but this is simply complicated by nature.
  9.  * 
  10.  * I'll attempt an overview:
  11.  * 
  12.  * I've tried to implement common functionality, but you'll <i>need</i> to
  13.  * extend classes. At a minimum, you need to extend {@link SMap_Data},
  14.  * {@link SMap_Form}{@link SMap_Lang}, and {@link SMap_Geo} to define the
  15.  * characteristics of your map. You will most likely extend varients of
  16.  * {@link SMap_Object} and {@link SMap_Layer} to draw out your map.
  17.  * 
  18.  * The calling structure is to create instances of your custom classes, and pass
  19.  * them to the {@link SMap::__construct() constructor} of {@link SMap}. Once you
  20.  * have an instance of SMap, you call {@link SMap::addView()} to add your view
  21.  * to the map. Each view can be thought of as a drawing window. By default, they
  22.  * are linked to move and zoom together. So, you can display the same area with
  23.  * different layers showing.
  24.  * 
  25.  * The interesting  work takes place within {@link SMap_Layer the layer}. Layers
  26.  * are added to each view via {@link SMap_View::addLayer()}. When the time
  27.  * comes, {@link SMap_Layer::getImgObjs()} or {@link SMap_Layer::getMapObjs()}
  28.  * will be called. The layer will construct, organize, and return a set of
  29.  * objects appropriate for the view and bounds. The objects will draw your info.
  30.  * 
  31.  * {@link SMap_Object} handles the brunt work of translating from whatever your
  32.  * layer wants to the {@link SMap_Canvas canvas}. Because these are primitives,
  33.  * you may or may not need to extend them.
  34.  * 
  35.  * Returning to our instance of {@link SMap}, we have now set up our map
  36.  * structure. SMap takes over the hard part from here. Layers have been turned
  37.  * on or off and other changes may have been returned via {@link SMap_Form}.
  38.  * Those are collected and applied. Call {@link SMap::getViewXHTML()} to get the
  39.  * XHTML to insert into the page. Or call {@link SMap::dispTileImg()} to
  40.  * generate the raw image to return to the browser. And you're done!
  41.  * 
  42.  * Ideas for source data:
  43.  * - {@link http://wms.jpl.nasa.gov/}
  44.  * - {@link http://bluemarble.nasa.gov/}
  45.  * - {@link http://www2.jpl.nasa.gov/srtm/}
  46.  * 
  47.  * SMap requires at least PHP version 5, but there are important bug fixes in
  48.  * more recent versions of PHP. Try to stay current.
  49.  *
  50.  * <b>License</b>:
  51.  * 
  52.  * Copyright (c) 2006-2007, Seth Price <{@link mailto:seth@pricepages.org}
  53.  * seth@pricepages.org}> All rights reserved.
  54.  *
  55.  * Redistribution and use in source and binary forms, with or without
  56.  * modification, are permitted provided that the following conditions
  57.  * are met:
  58.  *
  59.  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  60.  * - 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.
  61.  * - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
  62.  *
  63.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  64.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  65.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  66.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  67.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  68.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  69.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  70.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  71.  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  72.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  73.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  74.  *
  75.  * @copyright    Copyright (c) 2006-2007, Seth Price
  76.  * @author        Seth Price <seth@pricepages.org>
  77.  * @license        http://opensource.org/licenses/bsd-license.php New BSD License
  78.  * @access        public
  79.  * @package        SMap
  80.  * @see            SMap::__construct()
  81.  * @version        0.1
  82.  */
  83.  
  84. /**
  85.  * SMap is split into parts for file size reasons
  86.  */
  87. if(!class_exists('SMap_Object'))
  88.     require 'SMap/Object.php';
  89. if(!class_exists('SMap_Layer'))
  90.     require 'SMap/Layer.php';
  91. if(!class_exists('SMap_Form'))
  92.     require 'SMap/Form.php';
  93. if(!class_exists('SMap_Tile'))
  94.     require 'SMap/Tile.php';
  95. if(!class_exists('SMap_View'))
  96.     require 'SMap/View.php';
  97.  
  98. /**
  99.  * Inetrface for the main mapping functions
  100.  * 
  101.  * The SMap is the object which binds all the mapping objects together. Kind of
  102.  * like {@link http://en.wikipedia.org/wiki/One_Ring The One Ring}.
  103.  * 
  104.  * @package        SMap
  105.  * @link            http://en.wikipedia.org/wiki/One_Ring
  106.  */
  107. class SMap {
  108.     /**
  109.      * Indexes for the bounds
  110.      * 
  111.      * These are used to define a rectangle on the map. The rectangle is
  112.      * commonly some sort of viewport, or some sort of clickable label.
  113.      * 
  114.      * @var        integer 
  115.      */
  116.     const MAXY    1;
  117.     const MAXX    2;
  118.     const MINY    4;
  119.     const MINX    8;
  120.     
  121.     /**
  122.      * The form that will be used to display the map view(s) and process input
  123.      * 
  124.      * @see        SMap_Form
  125.      */
  126.     protected $form;
  127.     
  128.     /**
  129.      * The data that this will be pulled from
  130.      * 
  131.      * @see        SMap_Data
  132.      */
  133.     protected $data;
  134.     
  135.     /**
  136.      * Geography centric calculations
  137.      * 
  138.      * Anything that isn't in the native X,Y system should be passed through
  139.      * these functions
  140.      * 
  141.      * @see        SMap_Geo
  142.      */
  143.     protected $geo;
  144.     
  145.     /**
  146.      * Language strings
  147.      * 
  148.      * @see        SMap_Lang
  149.      */
  150.     protected $lang;
  151.     
  152.     /**
  153.      * The views that this controller controls
  154.      * 
  155.      * The main view is at index 0.
  156.      * 
  157.      * @see        SMap_View
  158.      */
  159.     protected $views = array();
  160.     
  161.     /**
  162.      * The layers that are currently visible
  163.      * 
  164.      * @var        array 
  165.      */
  166.     protected $visibleLayers;
  167.     
  168.     /**
  169.      * What zoom level are we at?
  170.      * 
  171.      * @var        integer 
  172.      */
  173.     protected $zoom;
  174.  
  175.     /**
  176.      * The map's  key
  177.      * 
  178.      * Generate it now so we don't have to later.
  179.      * 
  180.      * @var        string 
  181.      */
  182.     protected $key;
  183.     
  184.     /**
  185.      * The language key
  186.      * 
  187.      * @var        string 
  188.      */
  189.     protected $langKey;
  190.     
  191.     /**
  192.      * How many tiles are we viewing?
  193.      * 
  194.      * @var        integer 
  195.      */
  196.     protected $tilesX;
  197.     protected $tilesY;
  198.     
  199.     /**
  200.      * What is the scale in each direction?
  201.      * 
  202.      * This is SMap Units per one pixel.
  203.      * 
  204.      * @var        array 
  205.      */
  206.     protected $scale;
  207.     
  208.     /**
  209.      * Save the view bounds
  210.      * 
  211.      * @var        array 
  212.      */
  213.     protected $bounds;
  214.     
  215.     /**
  216.      * Wrapping info
  217.      * 
  218.      * @var        array 
  219.      * @see        SMap_Geo::getWrap()
  220.      */
  221.     protected $wrap;
  222.     
  223.     /**
  224.      * Options that can be set
  225.      * 
  226.      * The size of the tile is the granularity of the movement of the view.
  227.      * Smaller tiles mean more requests, but the view is more precise and it is
  228.      * more likely that a tile will stay cached on the broweser.
  229.      * 
  230.      * Directory to dump the cached data into. The web process must have write
  231.      * access here.
  232.      * 
  233.      * Maximum cache time of a tile. Applies mostly to images.
  234.      * 
  235.      * It is strongly suggested that data caching be enabled. Labels may not
  236.      * work correctly if it isn't.
  237.      * 
  238.      * It is strongly suggested that caching be enabled. The only reason that it
  239.      * isn't enabled by default is so people don't get confused when
  240.      * experimenting with the package and things don't show up right away.
  241.      * 
  242.      * Dubugging prints out a mass of debugging info, instead of doing anything
  243.      * useful.
  244.      * 
  245.      * This library relies on comparing floating point numbers often. This is
  246.      * normally a fairly stupid thing to do, but there is no good way around it
  247.      * here. To get around the problem of 1.0 turning into 0.99999... we are
  248.      * going to be rounding floating point numbers often. How many significant
  249.      * figures should we round to when using {@link SMap_Util::reduceSigFigs()}?
  250.      * 
  251.      * The hack that I use to enable PNG transparency in IE requires an image
  252.      * with binary transparency set to clear.
  253.      * 
  254.      * When producing JPEG compressed images, we can control the quality of the
  255.      * compression using 'jpegQuality'. When caching images, we put some extra
  256.      * effort into making them small. Specifically, we use the jpegtran command
  257.      * line utility to compress the files. If we want to create a progressive
  258.      * jpeg in this manner, we can use 'jpegProgressive'.
  259.      * 
  260.      * @var        array 
  261.      */
  262.     private static $options array(
  263.         'tileWidthPx' => 256,
  264.         'tileHeightPx' => 256,
  265.         'backgroundColor' => array(255255255127),
  266.         'cacheDir' => '/tmp/map_cache',
  267.         'cacheTime' => 86400,
  268.         'cacheEnabledData' => true,
  269.         'cacheEnabledImg' => false,
  270.         'maxFileSize' => 4000000// ~4MB
  271.                 'debug' => false,
  272.         'sigFigs' => 5,
  273.         'transPNG' => '/imgs/spacer.png',
  274.         'jpegQuality' => 75,
  275.         'jpegProgressive' => true,
  276.         'jpegtranBin' => '/usr/local/bin/jpegtran',
  277.         'optipngBin' => '/usr/local/bin/optipng',
  278.         'optipngArg' => '-o5');
  279.     
  280.     /**
  281.      * Registered views
  282.      * 
  283.      * @var        array 
  284.      */
  285.     private static $regViews array(
  286.         'raster' => array(false'SMap_View_Raster'),
  287.         'inset' => array(false'SMap_View_Inset'),
  288.         'links' => array(false'SMap_View_Links'));
  289.     
  290.     /**
  291.      * Constructs with the form and data objects that will be used
  292.      * 
  293.      * @param    object    SMap_Form subclass
  294.      * @param    object    SMap_Data subclass
  295.      * @param    object    SMap_Geo subclass
  296.      * @param    object    SMap_Lang subclass
  297.      * @uses        SMap_Form::getMapVars()
  298.      * @uses        SMap_Util::genMapKey()
  299.      * @uses        SMap_Geo::getWrap()
  300.      * @uses        SMap_Geo::zoomFctr()
  301.      * @uses        SMap_Form::getBoundVars()
  302.      * @uses        SMap_Form::getTileVars()
  303.      * @uses        initRWDir()
  304.      */
  305.     function __construct(SMap_Form $formSMap_Data $dataSMap_Geo $geoSMap_Lang $lang){
  306.         $this->form    = $form;
  307.         $this->data    = $data;
  308.         $this->geo    = $geo;
  309.         $this->lang    = $lang;
  310.         
  311.         //Log any options that should be changed
  312.         if(SMap::getOption('debug')){
  313.             trigger_error('Debug mode on'E_USER_NOTICE);
  314.         }
  315.         
  316.         //While we are here, make sure that the cache dir works
  317.         $cacheDir self::getOption('cacheDir');
  318.         $this->initRWDir($cacheDir);
  319.         $this->initRWDir($cacheDir.'/imgs');
  320.         
  321.         //Save the current map information
  322.         $map $form->getMapVars();
  323.         $this->visibleLayers        = $map['layers'];
  324.         $this->zoom                = $map['zoom'];
  325.         $this->langKey            = $map['lang'];
  326.         
  327.         $this->key = SMap_Util::genMapKey(    $map['layers'],
  328.                                             $map['zoom'],
  329.                                             $map['lang']);
  330.         
  331.         //Save the geographical data
  332.         $this->wrap = $geo->getWrap();
  333.         
  334.         //Get the current viewport size & save
  335.         $this->scale = $geo->zoomFctr($map['zoom']);
  336.         $this->bounds = $form->getBoundVars();
  337.         
  338.         $tg $form->getTileVars();
  339.         $this->tilesX = $tg['tileGrid'][SMap_Tile::TILESX];
  340.         $this->tilesY = $tg['tileGrid'][SMap_Tile::TILESY];
  341.     }
  342.     
  343.     /**
  344.      * Init a folder
  345.      * 
  346.      * @param    string    Dir name
  347.      */
  348.     protected function initRWDir($dir){
  349.         if(!file_exists($dir)){
  350.             mkdir($dir0755);
  351.             
  352.             if(!file_exists($dir)){
  353.                 throw new SMap_Ex('Was not able to create cacheDir "'.$dir.'"'SMap_Ex::FILE_WRITE);
  354.             }
  355.         }
  356.         
  357.         if(!is_writable($dir)){
  358.             throw new SMap_Ex('cacheDir "'.$dir.'" not writable'SMap_Ex::FILE_WRITE);
  359.         }
  360.     }
  361.     
  362.     /**
  363.      * Access useful vars read-only
  364.      * 
  365.      * @param    string    Name of member
  366.      * @return    mixed    Value of member
  367.      */
  368.     public function __get($nm){
  369.         if(isset($this->$nm)){
  370.             return $this->$nm;
  371.         else {
  372.             throw new SMap_Ex('Attempted to read non-existant member '.$nm);
  373.         }
  374.     }
  375.     
  376.     /**
  377.      * Gets XHTML for a map view window
  378.      * 
  379.      * The main map view is always stored in index 0
  380.      * 
  381.      * @param    integer    Index of view
  382.      * @return    string    XHTML
  383.      * @uses        SMap_View::getXHTML()
  384.      * @uses        SMap_View::getTiles()
  385.      * @uses        $views
  386.      * @uses        SMap_Form::getMapVars()
  387.      */
  388.     public function getViewXHTML($viewId 0){
  389.         if(empty($this->views[$viewId])){
  390.             throw new SMap_Ex(    'SMap_View ID '.$viewId.' not found',
  391.                                 SMap_Ex::VIEW_NOT_FOUND);
  392.         }
  393.         
  394.         //Return if view enabled
  395.         $map $this->form->getMapVars();
  396.         if(in_array($viewId$map['views'])){
  397.             $tiles $this->views[$viewId]->getTiles($this->form);
  398.             return $this->views[$viewId]->getXHTML($tiles);
  399.         else {
  400.             return '';
  401.         }
  402.     }
  403.  
  404.     /**
  405.      * Adds a view
  406.      * 
  407.      * The main view is always stored at index 0. Throws an exception if there
  408.      * is already a view at that index.
  409.      * 
  410.      * @param    mixed    SMap_View subclass or view to create
  411.      * @param    mixed    Passed to the view constructor, normally a View ID
  412.      * @param    mixed    Additional arguments passed to view constructor
  413.      * @return    object    The added view
  414.      * @see        SMap_View
  415.      * @uses        createView()
  416.      */
  417.     public function addView(        $view,        $arg1 null$arg2 null,
  418.                                 $arg3 null$arg4 null$arg5 null,
  419.                                 $arg6 null$arg7 null$arg8 null){
  420.         
  421.         if(is_string($view)){
  422.             $view $this->createView($view$this$arg1$arg2$arg3$arg4$arg5$arg6$arg7$arg8);
  423.         elseif(!($view instanceof SMap_View)){
  424.             throw new SMap_Ex('Passed $view not an instance of SMap_View.');
  425.         }
  426.         
  427.         $vid $view->id;
  428.  
  429.         //Throw error if already exists
  430.         if(!empty($this->views[$vid])){
  431.             throw new SMap_Ex(    'SMap_View of ID '.$vid.' already exists.',
  432.                                 SMap_Ex::DUP_VIEW);
  433.         }
  434.         
  435.         //Save view
  436.         return $this->views[$vid$view;;
  437.     }
  438.     
  439.     /**
  440.      * Creates a view
  441.      * 
  442.      * Serves as a view factory. The first argument is the name of a view that
  443.      * is registered with, and the rest of the arguments are passed to the view.
  444.      * 
  445.      * @param    string    View name
  446.      * @param    mixed    Additional arguments...
  447.      * @return    object    The created view
  448.      * @uses        SMap_View::__construct()
  449.      */
  450.     public static function createView(    $view$arg1$arg2 null$arg3 null,
  451.                                         $arg4 null$arg5 null$arg6 null,
  452.                                         $arg7 null$arg8 null$arg9 null){
  453.         $view strtolower($view);
  454.         
  455.         if(empty(self::$regViews[$view])){
  456.             throw new Exception('The given view type "'.$view.'" is not built in. ' .
  457.                     'If you have created a custom view, please simply ' .
  458.                     'pass the object to addView().');
  459.         }
  460.         
  461.         $meta self::$regViews[$view];
  462.         
  463.         if($meta[0&& !class_exists($meta[1])){
  464.             include_once($meta[0]);
  465.         }
  466.         
  467.         $v new $meta[1]($arg1$arg2$arg3$arg4$arg5$arg6$arg7$arg8$arg9);
  468.         
  469.         if(!($v instanceof SMap_View)){
  470.             throw new SMap_Ex('View not a subclass of SMap_View.');
  471.         }
  472.         
  473.         return $v;
  474.     }
  475.     
  476.     /**
  477.      * Display a map tile image with appropriate headers.
  478.      * 
  479.      * This displays the image itself, and should be called from a script
  480.      * expecting a dump of the image. Caching is handled internally. The ini
  481.      * value of 'display_errors' is unset so an image is always displayed. For
  482.      * errors, please look in your log file.
  483.      * 
  484.      * @uses        SMap_Tile
  485.      * @uses        SMap_View_Raster
  486.      * @uses        SMap_View_Raster::dispTileImg()
  487.      */
  488.     public function dispTileImg(){
  489.         //Look in the log, plz
  490.         ini_set('display_errors'false);
  491.  
  492.         $tileVars $this->form->getTileVars();
  493.         
  494.         if(empty($this->views[$tileVars['viewId']])){
  495.             throw new SMap_Ex('View ID of '.$tileVars['viewId'].' requested but doesn\'t exist.');
  496.         }
  497.         
  498.         $view $this->views[$tileVars['viewId']];
  499.  
  500.         //Make sure we are dealing with what we think we are dealing with
  501.         if(!($view instanceof SMap_View_Raster)){
  502.             throw new SMap_Ex(    'Not insanceof SMap_View_Raster, but of '.get_class($view),
  503.                                 SMap_Ex::NOT_VIEW_RASTER);
  504.         }
  505.         
  506.         $view->dispTileImg(new SMap_Tile($this,$view,$this->bounds,$tileVars['tileGrid']));
  507.     }
  508.     
  509.     /**
  510.      * Handle a possible imagemap click
  511.      * 
  512.      * Handle the possibility that the user clicked on a server-side image map.
  513.      * 
  514.      * @return    mixed    Clicked URL or false
  515.      * @uses        SMap_View::getTiles()
  516.      * @uses        SMap_Form::getClickCoords()
  517.      * @uses        SMap_Tile::getImageMap()
  518.      * @uses        mapIntersect()
  519.      */
  520.     public function handleImageMap(){
  521.  
  522.         //What tile was clicked?
  523.         $tg $this->form->getTileVars();
  524.         
  525.         //See if we have positioning info and an accurate view ID
  526.         if(    !isset($tg['tileGrid'][SMap_Tile::POSX]||
  527.             !isset($tg['tileGrid'][SMap_Tile::POSY]||
  528.             !$tg['viewId'){
  529.             
  530.             return false;
  531.         }
  532.         
  533.         $viewId $tg['viewId'];
  534.         $tx $tg['tileGrid'][SMap_Tile::POSX];
  535.         $ty $tg['tileGrid'][SMap_Tile::POSY];
  536.         
  537.         if(!isset($this->views[$viewId])){
  538.             throw new SMap_Ex('Attempted to handle map on non-existant view id: '.$viewId);
  539.         }
  540.         
  541.         $tiles $this->views[$viewId]->getTiles($this->form);
  542.         
  543.         if(!isset($tiles[$ty][$tx])){
  544.             /*
  545.              * This may ocour when the parameters passed as the tile grid
  546.              * position are actually outside of the current grid. This can
  547.              * happen when changing the dimensions of the grid after using the
  548.              * imagemap coordinates.
  549.              */
  550.             return false;
  551.         }
  552.         
  553.         $tile $tiles[$ty][$tx];
  554.         
  555.         //Were click coordinates parsed out?
  556.         if(!($coords $this->form->getClickCoords($tile->bounds))){
  557.             return false;
  558.         }
  559.         
  560.         //Foreach layer in the tile
  561.         if($maps $tile->getImageMap()){
  562.             //Foreach imagemap in the layer
  563.             foreach($maps as $map){
  564.                 //If it intersects
  565.                 if($url $this->mapIntersect($map$coords)){
  566.                     //Return the url
  567.                     return $url;
  568.                 }
  569.             }
  570.         }
  571.         
  572.         //Otherwise...
  573.         return false;
  574.     }
  575.     
  576.     /**
  577.      * Find if the click matches the area map
  578.      * 
  579.      * @param    array    Coordinate/URL set
  580.      * @param    array    Click coordinates
  581.      * @return    string    URL or false
  582.      */
  583.     protected function mapIntersect($map$coords){
  584.         $m array();
  585.         
  586.         list($x$y$coords['px'];
  587.         
  588.         switch($map['shape']){
  589.             case 'rect':
  590.                 if(!preg_match('/^(\d+\.?\d*),(\d+\.?\d*) (\d+\.?\d*),(\d+\.?\d*)$/'$map['coords']$m)){
  591.                     var_dump($map);
  592.                     var_dump($m);
  593.                     exit;
  594.                     break;
  595.                 }
  596.                 
  597.                 if($m[1<= $x && $m[2<= $y && $m[3>= $x && $m[4>= $y){
  598.                     return $map['href'];
  599.                 }
  600.                 
  601.                 break;
  602.                 
  603.             case 'poly':
  604.                 $npol preg_match_all('/(\d+),(\d+)/'$map['coords']$mPREG_PATTERN_ORDER);
  605.                 if(!$npol){
  606.                     break;
  607.                 }
  608.                 
  609.                 /*
  610.                  * Taken from
  611.                  * http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html
  612.                  * Don't blame me if your head explodes.
  613.                  */
  614.                 $c false;
  615.                 $xp $m[1];
  616.                 $yp $m[2];
  617.                 for ($i 0$j $npol-1$i $npol$j $i++{
  618.                     if ((($yp[$i<= $y && $y $yp[$j]||
  619.                          ($yp[$j<= $y && $y $yp[$i])) &&
  620.                         ($x ($xp[$j$xp[$i]($y $yp[$i]($yp[$j$yp[$i]$xp[$i])){
  621.  
  622.                         $c !$c;
  623.                     }
  624.                 }
  625.                 
  626.                 if($c){
  627.                     return $map['href'];
  628.                 }
  629.                 
  630.                 break;
  631.             
  632.             case 'ismap':
  633.                 return call_user_func($map['callback']$coords);
  634.                 
  635.             default:
  636.                 throw new Exception('Unrecognized shape: '.$map['shape']);
  637.         }
  638.         
  639.         return false;
  640.     }
  641.     
  642.     /**
  643.      * Set a global option
  644.      * 
  645.      * All available options are pre-set in the {@link $options} array, so we
  646.      * will be avoiding some frusteration if we throw an error on a bad option
  647.      * selection. Otherwise, we could think that we are retrieving a null value,
  648.      * but we have just misspelled the index.
  649.      * 
  650.      * @param    string    Option name
  651.      * @param    string    Option value
  652.      * @uses        $options
  653.      */
  654.     final public static function setOption($name$value){
  655.         if(isset(self::$options[$name])){
  656.             self::$options[$name$value;
  657.         else {
  658.             throw new Exception('The requested option "'.$name.'" doesn\'t exist.');
  659.         }
  660.     }
  661.     
  662.     /**
  663.      * Get a global option
  664.      * 
  665.      * All available options are pre-set in the {@link $options} array, so we
  666.      * will be avoiding some frusteration if we throw an error on a bad option
  667.      * selection. Otherwise, we could think that we are retrieving a null value,
  668.      * but we have just misspelled the index.
  669.      * 
  670.      * @param    string    Option name
  671.      * @return    string    Option value
  672.      * @uses        $options
  673.      */
  674.     final public static function getOption($name){
  675.         if(isset(self::$options[$name])){
  676.             return self::$options[$name];
  677.         else {
  678.             throw new Exception('The requested option "'.$name.'" doesn\'t exist.');
  679.         }
  680.     }
  681. }
  682.  
  683. /**
  684.  * Generic SMap exception
  685.  * 
  686.  * @package        SMap
  687.  */
  688. class SMap_Ex extends Exception {
  689.     /**
  690.      * Not passed a SMap_View object
  691.      */
  692.     const NOT_VIEW                =  -1;
  693.     
  694.     /**
  695.      * Not passed a SMap_Form object
  696.      */
  697.     const NOT_FORM                =  -2;
  698.     
  699.     /**
  700.      * Not passed a SMap_Data object
  701.      */
  702.     const NOT_DATA                =  -3;
  703.     
  704.     /**
  705.      * Not passed a SMap_Geo object
  706.      */
  707.     const NOT_GEO                =  -4;
  708.     
  709.     /**
  710.      * Not passed a SMap_Layer object
  711.      */
  712.     const NOT_LAYER                =  -5;
  713.     
  714.     /**
  715.      * Not passed a SMap_View_Raster object
  716.      */
  717.     const NOT_VIEW_RASTER        =  -6;
  718.     
  719.     /**
  720.      * Not passed a SMap_View_Links object
  721.      */
  722.     const NOT_VIEW_LINKS            =  -7;
  723.     
  724.     /**
  725.      * Not passed a SMap_Object object
  726.      */
  727.     const NOT_OBJECT                =  -8;
  728.  
  729.     /**
  730.      * Not passed a SMap_Tile object
  731.      */
  732.     const NOT_TILE                =  -9;
  733.  
  734.     /**
  735.      * Requested view not found
  736.      */
  737.     const VIEW_NOT_FOUND            = -10;
  738.     
  739.     /**
  740.      * Duplicate map view
  741.      */
  742.     const DUP_VIEW                = -11;
  743.     
  744.     /**
  745.      * Duplicate map layer
  746.      */
  747.     const DUP_LAYER                = -12;
  748.     
  749.     /**
  750.      * Poorly formatted bounds array
  751.      */
  752.     const BAD_BOUNDS                = -13;
  753.     
  754.     /**
  755.      * Poorly formatted argument
  756.      */
  757.     const BAD_ARG                = -14;
  758.     
  759.     /**
  760.      * Filesystem error
  761.      */
  762.     const FILESYSTEM                = -15;
  763.     
  764.     /**
  765.      * File read error
  766.      */
  767.     const FILE_READ                = -16;
  768.     
  769.     /**
  770.      * File write error
  771.      */
  772.     const FILE_WRITE                = -17;
  773.     
  774.     /**
  775.      * Resource collision
  776.      */
  777.     const RES_COLL                = -18;
  778. }
  779.  
  780. /**
  781.  * Utility functions
  782.  * 
  783.  * These should all be callable in static context.
  784.  * 
  785.  * @package    SMap
  786.  */
  787. class SMap_Util {
  788.     
  789.     /**
  790.      * Rounds to reduce the number of sig figs in a float
  791.      * 
  792.      * Useful when comparing two floating point numbers and printing numbers on
  793.      * a page. It should print numbers such that similar floating point numbers
  794.      * are always printed with the same string.
  795.      * 
  796.      * Use the 'sigFigs' option to alter the precision of the produced strings.
  797.      * 
  798.      * @param    float    Just another float.
  799.      * @return    string    Float expressed as a string.
  800.      */
  801.     public static function reduceSigFigs($float){
  802.         if($float == 0.0){
  803.             return 0.0;
  804.         }
  805.         
  806.         return str_replace('+'''sprintf('%.'.SMap::getOption('sigFigs').'e'$float));
  807.     }
  808.     
  809.     /**
  810.      * Generates a map key that can be used to identify a layout that may be
  811.      * stored in the database or filesystem.
  812.      * 
  813.      * The layers list must be a numrical list in the order that the layers were
  814.      * applied.
  815.      * 
  816.      * @param    array    Active layers.
  817.      * @param    integer    Current view level.
  818.      * @param    string    ISO 639-2 three letter language string
  819.      * @return    string    SMap key, 40 chars or less
  820.      */
  821.     public static function genMapKey($layers$zoom$lang){
  822.         //Make sure we get what we expect
  823.         if(!is_array($layers)){
  824.             throw new SMap_Ex('Improperly formatted layers'SMap_Ex::BAD_ARG);
  825.  
  826.         elseif(!is_numeric($zoom)){
  827.             throw new SMap_Ex('Improperly formatted zoom'SMap_Ex::BAD_ARG);
  828.  
  829.         elseif(strlen($lang!= 3){
  830.             throw new SMap_Ex('Improperly formatted lang'SMap_Ex::BAD_ARG);
  831.  
  832.         elseif(isset($layers[0]&& !is_int($layers[0])){
  833.             throw new SMap_Ex('Improperly formatted layer elm'SMap_Ex::BAD_ARG);
  834.         }
  835.         
  836.         sort($layers);
  837.         
  838.         return crc32(    serialize($layers).'_'.
  839.                         intval($zoom).'_'.
  840.                         $lang2147483647// 2^31-1, prevent negative
  841.     }
  842.     
  843.     /**
  844.      * Generate a key for a tile
  845.      * 
  846.      * The layers list must be a numrical list in the order that the layers were
  847.      * applied. Uses {@link reduceSigFigs()} to round the floats, so you can use
  848.      * 'sigFigs' to alter the size of thte numbers produced.
  849.      * 
  850.      * @param    array    Bounds array
  851.      * @param    array    Position relative to other tiles
  852.      * @return    string    Key for this tile
  853.      * @uses        reduceSigFigs()
  854.      */
  855.     public static function genTileKey($bounds$tileGrid){
  856.         //Make sure we get what we expect
  857.         if(!is_array($bounds)){
  858.             throw new SMap_Ex('Improperly formatted bounds'SMap_Ex::BAD_ARG);
  859.             
  860.         elseif(!is_array($tileGrid)){
  861.             throw new SMap_Ex('Improperly formatted tile grid'SMap_Ex::BAD_ARG);
  862.  
  863.         elseif(isset($layers[0]&& !is_int($layers[0])){
  864.             throw new SMap_Ex('Improperly formatted layer elm'SMap_Ex::BAD_ARG);
  865.         }
  866.         
  867.         $str =     self::reduceSigFigs($bounds[SMap::MAXY]).'_'.
  868.                 self::reduceSigFigs($bounds[SMap::MAXX]).'_'.
  869.                 self::reduceSigFigs($bounds[SMap::MINY]).'_'.
  870.                 self::reduceSigFigs($bounds[SMap::MINX]);
  871.         
  872.         if($tileGrid){
  873.             $str .=    '_'.
  874.                     intval($tileGrid[SMap_Tile::POSX]).'_'.
  875.                     intval($tileGrid[SMap_Tile::VIEWX]).'_'.
  876.                     intval($tileGrid[SMap_Tile::POSY]).'_'.
  877.                     intval($tileGrid[SMap_Tile::VIEWY]);
  878.         }
  879.         
  880.         return crc32($str2147483647// 2^31-1, prevent negative
  881.     }
  882.     
  883.     /**
  884.      * Shifts a view around a wrapped map
  885.      * 
  886.      * @param    array    The bounds to wrap
  887.      * @param    array    The wrapping information
  888.      */
  889.     public static function wrapBounds(&$bounds$wrap){
  890.         while(isset($wrap[SMap::MAXY]&& $wrap[SMap::MAXY$bounds[SMap::MINY]){
  891.             $diff $wrap[SMap::MAXY$wrap[SMap::MINY];
  892.             $bounds[SMap::MAXY-= $diff;
  893.             $bounds[SMap::MINY-= $diff;
  894.         }
  895.         
  896.         while(isset($wrap[SMap::MAXX]&& $wrap[SMap::MAXX$bounds[SMap::MINX]){
  897.             $diff $wrap[SMap::MAXX$wrap[SMap::MINX];
  898.             $bounds[SMap::MAXX-= $diff;
  899.             $bounds[SMap::MINX-= $diff;
  900.         }
  901.         
  902.         while(isset($wrap[SMap::MINY]&& $wrap[SMap::MINY$bounds[SMap::MAXY]){
  903.             $diff $wrap[SMap::MINY$wrap[SMap::MAXY];
  904.             $bounds[SMap::MINY-= $diff;
  905.             $bounds[SMap::MAXY-= $diff;
  906.         }
  907.         
  908.         while(isset($wrap[SMap::MINX]&& $wrap[SMap::MINX$bounds[SMap::MAXX]){
  909.             $diff $wrap[SMap::MINX$wrap[SMap::MAXX];
  910.             $bounds[SMap::MINX-= $diff;
  911.             $bounds[SMap::MAXX-= $diff;
  912.         }
  913.     }
  914.     
  915.     /**
  916.      * Shifts the given bounds to be "normal"
  917.      * 
  918.      * Shifts the bounds to contain an integer multiple of tiles, and for there
  919.      * to be an integer multiple of tiles between the current bounds and (0,0).
  920.      * When tiles are normalized like this there can be less precision in the
  921.      * URL. Any rounding errors are normalized out here.
  922.      * 
  923.      * @param    array    Bounds to normalize
  924.      * @param    float    Map units per tile X
  925.      * @param    float    Map units per tile Y
  926.      * @param    integer    Number of tiles that should be in the X dir
  927.      * @param    integer    Number of tiles that should be in the Y dir
  928.      */
  929.     public static function normalizeBounds(&$B$muPerTileX$muPerTileY$tilesX$tilesY){
  930.         
  931.         //Round to nearest multiple of (Dimension * Scale).
  932.         $B[SMap::MINXround($B[SMap::MINX]/$muPerTileX$muPerTileX;
  933.         $B[SMap::MAXYround($B[SMap::MAXY]/$muPerTileY$muPerTileY;
  934.         
  935.         //Adjust the lower right corner to match
  936.         $B[SMap::MAXX$B[SMap::MINX$muPerTileX $tilesX;
  937.         $B[SMap::MINY$B[SMap::MAXY$muPerTileY $tilesY;
  938.     }
  939.     
  940.     /**
  941.      * Create bounds centered on this point
  942.      * 
  943.      * @param    float    Center point X
  944.      * @param    float    Center point Y
  945.      * @param    float    Map units per tile X
  946.      * @param    float    Map units per tile Y
  947.      * @param    integer    Number of tiles that should be in the X dir
  948.      * @param    integer    Number of tiles that should be in the Y dir
  949.      * @param    boolean    Normalizes bounds also (default= true)
  950.      * @return    array    The new bounds
  951.      * @uses        normalizeBounds()
  952.      */
  953.     public static function centerBounds(    $Bx$By,
  954.                                         $muPerTileX$muPerTileY,
  955.                                         $tilesX$tilesY,
  956.                                         $normalize true){
  957.         
  958.         //Move the upper-left corner while remaining centered on the requested position
  959.         $B[SMap::MINX$Bx ($muPerTileX $tilesX)/2;
  960.         $B[SMap::MAXY$By ($muPerTileY $tilesY)/2;
  961.         
  962.         if($normalize){
  963.             self::normalizeBounds($B$muPerTileX$muPerTileY$tilesX$tilesY);
  964.         else {
  965.             //Adjust the lower right corner to match
  966.             $B[SMap::MAXX$B[SMap::MINX$muPerTileX $tilesX;
  967.             $B[SMap::MINY$B[SMap::MAXY$muPerTileY $tilesY;
  968.         }
  969.         
  970.         return $B;
  971.     }
  972.     
  973.     /**
  974.      * Encode an integer as a char
  975.      * 
  976.      * This function is used so we can pack the URL smaller. The chars used are
  977.      * [0-9A-Za-z].
  978.      * 
  979.      * @param    integer 
  980.      * @return    string 
  981.      */
  982.     public static function enc($int){
  983.         if(!$int){
  984.             return '0';
  985.         elseif($int 10){
  986.             return (string) $int;
  987.         elseif($int 36){
  988.             return chr($int 55);
  989.         else {
  990.             return chr($int 61);
  991.         }
  992.     }
  993.     
  994.     /**
  995.      * Decode a char as an integer
  996.      * 
  997.      * Unpack the url.
  998.      * 
  999.      * @param    string 
  1000.      * @return    integer 
  1001.      * @see        enc()
  1002.      */
  1003.     public static function dec($chr){
  1004.         $int ord($chr);
  1005.         if($int 58){
  1006.             return $int 48;
  1007.         elseif($int 91){
  1008.             return $int 55;
  1009.         else {
  1010.             return $int 61;
  1011.         }
  1012.     }
  1013.     
  1014.     /**
  1015.      * Make an XHTML image tag compatable with IE
  1016.      * 
  1017.      * MSIE sucks at the whole "internet" thing. Maybe they'll get it right next
  1018.      * time. In the mean time, this function specifically emits an image tag
  1019.      * that hacks transparency into a given PNG.
  1020.      * 
  1021.      * @param    string    Image URL
  1022.      * @param    string    Image Alt tag
  1023.      * @param    integer    Image width
  1024.      * @param    integer    Image height
  1025.      * @param    string    Extra tags (caller escapes)
  1026.      * @param    string    Extra style (caller escapes)
  1027.      * @return    string    Image tag
  1028.      */
  1029.     public static function transPng($url$alt$w$h$tags$style){
  1030.         
  1031.         static $isEvil null;
  1032.         
  1033.         //Escape text, because it's a good idea
  1034.                 $url htmlspecialchars($url);
  1035.         $alt htmlspecialchars($alt);
  1036.         $w = (int) $w;
  1037.         $h = (int) $h;
  1038.         
  1039.         //Is the web browser evil?
  1040.                 if($isEvil === null){
  1041.             $msie '/msie\s(5\.[5-9]|[6]\.[0-9]*).*(win)/i';
  1042.             if(    !isset($_SERVER['HTTP_USER_AGENT']||
  1043.                 !preg_match($msie,$_SERVER['HTTP_USER_AGENT']||
  1044.                 preg_match('/opera/i',$_SERVER['HTTP_USER_AGENT'])){
  1045.                 
  1046.                 $isEvil false;
  1047.             else {
  1048.                 $isEvil true;
  1049.             }
  1050.         }
  1051.         
  1052.         //Ignore non-PNGs
  1053.                 if($isEvil && substr_compare($url'.png'strlen($url)-44true)){
  1054.             $isEvil false;
  1055.         }
  1056.         
  1057.         //If not evil, return a normal image
  1058.                 if(!$isEvil){
  1059.             if($style){
  1060.                 $style ' style="'.$style.'"';
  1061.             }
  1062.             
  1063.             return '<img src="'.$url.'" alt="'.$alt.'" ' .
  1064.                     'width="'.$w.'" height="'.$h.'" '.$tags.$style.' />';
  1065.         }
  1066.         
  1067.         //Damn evil browsers
  1068.         
  1069.                 
  1070.         return '<img src="'.SMap::getOption('transPNG').'" alt="'.$alt.'" ' .
  1071.                 'width="'.$w.'" height="'.$h.'" ' .
  1072.                 'style="width:'.$w.';height:'.$h.';'.$style .
  1073.                         'filter:progid:DXImageTransform.Microsoft.' .
  1074.                         'AlphaImageLoader(src=\''.$url.'\',sizingMethod=scale);" '.
  1075.                 $tags.' />';
  1076.     }
  1077. }
  1078.  
  1079. /**
  1080.  * We need to connect to the DB somehow, all relevant methods are defined here.
  1081.  * 
  1082.  * I've defined some basic SQL queries as constants, feel free to use them or
  1083.  * abuse them as needed. The selects are all prepared statements and should be
  1084.  * usable with PEAR's DB.
  1085.  * 
  1086.  * It is my intent to make these statements simple enough to be usable with
  1087.  * both SQLite and MySQL while still maintaining functionality. I am aware that
  1088.  * people may want more powerful syntax, and they are encouraged to extend this
  1089.  * class.
  1090.  * 
  1091.  * @package        SMap
  1092.  */
  1093. abstract class SMap_Data {
  1094.     /**
  1095.      * CREATE TABLE cached_rec_bounds SQL
  1096.      * 
  1097.      * Rectangle bounds data is stored here across sessions. We need a map_id
  1098.      * and bound_id to identify the bound that we are interested in, and most of
  1099.      * our lookups will be selecting for either map_key and rec_key, or all
  1100.      * bounds of a given map_key that fall in a given bounds. The former is
  1101.      * preferred for performance reasons, though.
  1102.      * 
  1103.      * For a given set of bounds, there should be a unique map_key, rec_key
  1104.      * pair, and for the pair, there should be unique bounds.
  1105.      */
  1106.     const CREATE_CACHED_RECT_BOUNDS =
  1107.         'CREATE TABLE cached_rec_bounds (
  1108.             map_key INTEGER NOT NULL,
  1109.             rec_key INTEGER NOT NULL,
  1110.             
  1111.             maxx REAL NOT NULL,
  1112.             maxy REAL NOT NULL,
  1113.             minx REAL NOT NULL,
  1114.             miny REAL NOT NULL,
  1115.             
  1116.             time INTEGER NOT NULL, 
  1117.             data TEXT NOT NULL, 
  1118.             
  1119.             PRIMARY KEY (map_key, rec_key))';
  1120.     
  1121.     /**
  1122.      * CREATE INDEX ON the rect bounds
  1123.      * 
  1124.      * I think that only one of top-bottom / left-right indices will be useful
  1125.      * and all four indices is overkill (and will delay inserts). So I'm going
  1126.      * to choose to keep the left-right index under the assumption that most
  1127.      * maps will be wider than they are tall (same with monitors).
  1128.      */
  1129.     const CREATE_CACHED_RECT_INDEX =
  1130.         'CREATE INDEX bounds ON cached_rec_bounds (map_key, maxx, minx)';
  1131.     
  1132.     /**
  1133.      * INSERT a new bound in the cache
  1134.      * 
  1135.      * Parameters: map_key, rec_key, top, bottom, left, right, time
  1136.      */
  1137.     const INSERT_CACHED_RECT_DATA =
  1138.         'INSERT IGNORE
  1139.             INTO cached_rec_bounds (map_key, rec_key, maxx, maxy, minx, miny, data, time)
  1140.             VALUES (:map_key, :rec_key, :maxx, :maxy, :minx, :miny, :data, :time)';
  1141.     
  1142.     /**
  1143.      * SELECT a cached rect by key
  1144.      * 
  1145.      * Parameters: map_key, rec_key
  1146.      * 
  1147.      * Results: maxx, maxy, minx, miny, time
  1148.      */
  1149.     const SELECT_CACHED_RECT_BY_KEY =
  1150.         'SELECT maxx, maxy, minx, miny, data, time
  1151.             FROM cached_rec_bounds 
  1152.             WHERE map_key=:map_key AND rec_key=:rec_key';
  1153.     
  1154.     /**
  1155.      * SELECT a session rect by the a boundry that intersects it
  1156.      * 
  1157.      * This SQL assumes that up is positive Y and right is positive X (like the
  1158.      * latitude/longitude coordinate system).
  1159.      * 
  1160.      * Parameters: map_key, maxx, maxy, minx, miny
  1161.      * 
  1162.      * Results: rec_key, maxx, maxy, minx, miny, data, time
  1163.      */
  1164.     const SELECT_CACHED_RECT_IN_BOUND =
  1165.         'SELECT rec_key, maxx, maxy, minx, miny, data, time
  1166.             FROM cached_rec_bounds
  1167.             WHERE
  1168.                 map_key = :map_key AND
  1169.                 maxx >= :minx AND minx <= :maxx AND
  1170.                 maxy >= :miny AND miny <= :maxy';
  1171.     
  1172.     /**
  1173.      * DELETE session data before a given timestamp
  1174.      * 
  1175.      * The session results are calculated from the position of objects on the
  1176.      * map, and should be recalculated depending on how often the objects
  1177.      * change.
  1178.      * 
  1179.      * Parameter: time
  1180.      */
  1181.     const DELETE_CACHED_RECT_BEFORE =
  1182.         'DELETE 
  1183.             FROM cached_rec_bounds
  1184.             WHERE time < :time';
  1185.     
  1186.     /**
  1187.      * Some enumerations for types of rects
  1188.      */
  1189.     const TYP_LABEL = 1;
  1190.     
  1191.     /**
  1192.      * Database connection
  1193.      * 
  1194.      * The currently defined functions expect this to be an instance of PHP's
  1195.      * PDO, but you can override them and use whatever database/schema that you
  1196.      * wish.
  1197.      * 
  1198.      * @var        object 
  1199.      */
  1200.     protected $conn = null;
  1201.     
  1202.     /**
  1203.      * Create the database connection
  1204.      * 
  1205.      * Currently, the other functions expect this to create a new instance of
  1206.      * PDO and save it at $this->conn. We'll attempt to worry about a lack of
  1207.      * table as they are needed (queries fail, exceptions ocour).
  1208.      * 
  1209.      * The connection is automatically generated when you call
  1210.      * {@link prepare()}, but if the need arises in your code, feel free to call
  1211.      * whenever.
  1212.      * 
  1213.      * @uses        $conn
  1214.      */
  1215.     abstract protected function connect();
  1216.     
  1217.     /**
  1218.      * CREATEs the cached_rec_bounds table
  1219.      * 
  1220.      * @uses        SMap_Data_Ex
  1221.      */
  1222.     protected function createCachedRectBounds(){
  1223.         try {
  1224.             $this->conn->exec(self::CREATE_CACHED_RECT_BOUNDS);
  1225.             $this->conn->exec(self::CREATE_CACHED_RECT_INDEX);
  1226.         catch(PDOException $e){
  1227.             throw new SMap_Data_Ex('Trouble creating tables: '.$rs->getMessage());
  1228.         }
  1229.     }
  1230.     
  1231.     /**
  1232.      * INSERT new rectangle(s) into the cache
  1233.      * 
  1234.      * The parameter is an array of new rectangle(s) to insert into the
  1235.      * cache. The new array should have keys for: ['map_key'], ['rec_key'],
  1236.      * ['maxx'], ['maxy'], ['minx'], ['miny'].
  1237.      * 
  1238.      * @param    array    Input data
  1239.      * @throws    SMap_Exception
  1240.      */
  1241.     public function insertCachedRectData($newRects){
  1242.         static $stmt;
  1243.         if(!$stmt){
  1244.             $stmt $this->prepare(self::INSERT_CACHED_RECT_DATA);
  1245.         }
  1246.         
  1247.         $time time();
  1248.         
  1249.         foreach($newRects as $rect){
  1250.             $stmt->execute(array(    ':map_key' => $rect['map_key'],
  1251.                                     ':rec_key' => $rect['rec_key'],
  1252.                                     ':maxx' => $rect[SMap::MAXX],
  1253.                                     ':maxy' => $rect[SMap::MAXY],
  1254.                                     ':minx' => $rect[SMap::MINX],
  1255.                                     ':miny' => $rect[SMap::MINY],
  1256.                                     ':data' => $rect['data'],
  1257.                                     ':time' => $time));
  1258.         }
  1259.     }
  1260.     
  1261.     /**
  1262.      * SELECT cached rect by its key
  1263.      * 
  1264.      * @param    string    A key that identifies the map the rect is on.
  1265.      * @param    string    A key that identifies the specific rectangle.
  1266.      * @return    mixed    Array on result or null on no result
  1267.      */
  1268.     public function selectCachedRectByKey($mapKey$recKey){
  1269.         static $stmt;
  1270.         
  1271.         if(!SMap::getOption('cacheEnabledData')){
  1272.             return array();
  1273.         }
  1274.         
  1275.         if(!$stmt){
  1276.             $stmt $this->prepare(self::SELECT_CACHED_RECT_BY_KEY);
  1277.         }
  1278.         
  1279.         $this->execute(array(    ':map_key' => $mapKey,
  1280.                                 ':rec_key' => $recKey));
  1281.         
  1282.         return $stmt->fetchAll();
  1283.     }
  1284.  
  1285.     /**
  1286.      * SELECT a cached rect by the its intersection with another rect
  1287.      * 
  1288.      * @param    string    A string to identify the map we are looking in.
  1289.      * @param    array    Selection bounds
  1290.      * @return    array    Any rectangles in the selection.
  1291.      */
  1292.     public function selectCachedRectInBounds($mapKey$B){
  1293.         static $stmt;
  1294.         if(!SMap::getOption('cacheEnabledData')){
  1295.             return array();
  1296.         }
  1297.         
  1298.         if(!$stmt){
  1299.             $stmt $this->prepare(self::SELECT_CACHED_RECT_IN_BOUND);
  1300.         }
  1301.         
  1302.         $stmt->execute(array(    ':map_key' => $mapKey,
  1303.                                 ':maxx' => $B[SMap::MAXX],
  1304.                                 ':maxy' => $B[SMap::MAXY],
  1305.                                 ':minx' => $B[SMap::MINX],
  1306.                                 ':miny' => $B[SMap::MINY));
  1307.         return $stmt->fetchAll();
  1308.     }
  1309.     
  1310.     /**
  1311.      * DELETE all old cached data
  1312.      * 
  1313.      * @param    integer    Old timestamp
  1314.      */
  1315.     public function deleteCachedRectBefore($oldTime 0){
  1316.         $stmt $this->prepare(self::CREATE_CACHED_RECT_BOUNDS);
  1317.         
  1318.         if (!$oldTime){
  1319.             $oldTime time(SMap::getOption('cacheTime');
  1320.         }
  1321.         
  1322.         $stmt->execute(array(':time' => $oldTime));
  1323.     }
  1324.     
  1325.     /**
  1326.      * Prepare a statement
  1327.      * 
  1328.      * All statements should be prepared before execution. Besides being
  1329.      * prepared, we check here to ensure that a DB connection exists, and that
  1330.      * the tables exist in the DB.
  1331.      * 
  1332.      * @param    string    SQL statement to prepare
  1333.      * @return    resource    Prepared statement
  1334.      * @uses        $conn
  1335.      * @uses        connect()
  1336.      * @uses        createCachedRectBounds()
  1337.      * @uses        SMap_Data_Ex
  1338.      */
  1339.     protected function prepare($sql){
  1340.         if($this->conn === null){
  1341.             $this->connect();
  1342.         }
  1343.         
  1344.         try {
  1345.             return $this->conn->prepare($sql);
  1346.         catch(PDOException $e){
  1347.             if($e->getCode(== '42S02')// This must be the 'no table' code for MySQL?
  1348.                 $this->createCachedRectBounds();
  1349.                 $this->prepare($sql)// Careful! This may overflow!!! (FIXME)
  1350.             else {
  1351.                 throw new SMap_Data_Ex('Statement failed: '.$e->getCode().' '.$e->getMessage());
  1352.             }
  1353.         }
  1354.     }
  1355.     
  1356. }
  1357.  
  1358. /**
  1359.  * SMap DB exception class
  1360.  * 
  1361.  * @package    SMap
  1362.  */
  1363. class SMap_Data_Ex extends SMap_Ex {
  1364. }
  1365.  
  1366. /**
  1367.  * Controls geographic charicteristics of the view
  1368.  * 
  1369.  * The user of the library will implement these to return appropriate numbers
  1370.  * for whatever coordinate system this is being used in. Also sets geographical
  1371.  * defaults.
  1372.  * 
  1373.  * @package    SMap
  1374.  */
  1375. abstract class SMap_Geo {
  1376.     /**
  1377.      * Get the maximum zoom level
  1378.      * 
  1379.      * @return    integer 
  1380.      */
  1381.     abstract public function getMaxZoom();
  1382.     
  1383.     /**
  1384.      * Give the distance between two points in meters
  1385.      * 
  1386.      * @param    object    First point
  1387.      * @param    object    Second point
  1388.      */
  1389.     abstract public function distBetween(SMap_Object_Point $p1SMap_Object_Point $p2);
  1390.     
  1391.     /**
  1392.      * Return a point that has followed a path for a distance
  1393.      * 
  1394.      * @param    object    SMap_Object_Path 
  1395.      * @param    float    Distance in meters
  1396.      */
  1397.     abstract public function follow(SMap_Object_Path $path$dist);
  1398.     
  1399.     /**
  1400.      * Return a line that has followed a bearing from a point
  1401.      * 
  1402.      * @param    object    SMap_Object_Point 
  1403.      * @param    float    Distance in meters
  1404.      * @param    float    Angle in radians
  1405.      * @return    object    SMap_Object_Line 
  1406.      */
  1407.     abstract public function bearing(SMap_Object_Point $start$dist$bearing);
  1408.     
  1409.     /**
  1410.      * Get the zoom factor
  1411.      * 
  1412.      * At the chosen zoom number, one pixel equals how many SMap Units?
  1413.      * 
  1414.      * (SMap Units per pixel)
  1415.      * 
  1416.      * This number should be zero or a positive integer. Zero is zoomed in all
  1417.      * the way.
  1418.      * 
  1419.      * @param    integer    Zoom number
  1420.      * @return    array    Scaling factor X,Y
  1421.      * @see        SMap_Form::setDefaults()
  1422.      */
  1423.     abstract public function zoomFctr($zoomNum);
  1424.     
  1425.     /**
  1426.      * Get globe wrapping information
  1427.      * 
  1428.      * Does this map wrap? On what bounds does it wrap? This should return an
  1429.      * array of bounds which are wrapped.
  1430.      * 
  1431.      * @return    array    Wrapping information
  1432.      */
  1433.     abstract public function getWrap();
  1434.     
  1435.     /**
  1436.      * Return a nicely printed coordinate
  1437.      * 
  1438.      * @return    string    Coordinate text
  1439.      */
  1440.     abstract public function pretty($val$side);
  1441. }
  1442.  
  1443. /**
  1444.  * Controls the language used in the view
  1445.  * 
  1446.  * The language differs on the units used and the humans used. All language
  1447.  * codes used in the map package are three letter ISO 639-2 codes.
  1448.  * 
  1449.  * @package        SMap
  1450.  */
  1451. abstract class SMap_Lang {
  1452.     
  1453.     /**
  1454.      * The language that we will be using
  1455.      * 
  1456.      * @var        string 
  1457.      */
  1458.     protected $langCode = '';
  1459.     
  1460.     /**
  1461.      * Sets the language that we will be using
  1462.      * 
  1463.      * You can only set this once though
  1464.      * 
  1465.      * @param    string    The language code
  1466.      */
  1467.     public function setLanguage($code){
  1468.         if(empty($this->langCode)){
  1469.             $this->langCode = $code;
  1470.         else {
  1471.             throw new SMap_Ex('Refusing to re-set the language.');
  1472.         }
  1473.     }
  1474.     
  1475.     /**
  1476.      * Gets the current language
  1477.      * 
  1478.      * @return    string    The current language code
  1479.      */
  1480.     public function getLanguage(){
  1481.         return $this->langCode;
  1482.     }
  1483.     
  1484.     /**
  1485.      * Get a layer's name
  1486.      * 
  1487.      * @param    integer    The ID of the requested layer
  1488.      * @return    string    The name of the layer
  1489.      */
  1490.     abstract public function genLayerName($layerId);
  1491. }
  1492.  
  1493. /**
  1494.  * A canvas for drawing the image
  1495.  * 
  1496.  * @package        SMap
  1497.  */
  1498. class SMap_Canvas {
  1499.     /**
  1500.      * Image bitmap resource
  1501.      * 
  1502.      * @var        resource 
  1503.      */
  1504.     protected $img = null;
  1505.     
  1506.     /**
  1507.      * Keep track of references to the image resource
  1508.      * 
  1509.      * @var        integer 
  1510.      */
  1511.     protected $imgRefCount = 0;
  1512.     
  1513.     /**
  1514.      * The tile which this canvas is drawing
  1515.      * 
  1516.      * @var        object 
  1517.      */
  1518.     protected $tile;
  1519.     
  1520.     /**
  1521.      * Create a canvas which fits the current tile
  1522.      * 
  1523.      */
  1524.     function __construct(SMap_Tile $tile){
  1525.         $this->tile = $tile;
  1526.     }
  1527.  
  1528.     /**
  1529.      * Access useful vars read-only
  1530.      * 
  1531.      * Creates  the image resource on demand
  1532.      * 
  1533.      * @param    string    Name of member
  1534.      * @return    mixed    Value of member
  1535.      */
  1536.     function __get($nm){
  1537.         if(isset($this->$nm)){
  1538.             return $this->$nm;
  1539.         elseif($nm == 'img'){
  1540.             $this->allocImage();
  1541.             return $this->img;
  1542.         else {
  1543.             throw new SMap_Ex('Attempted to read non-existant member '.$nm);
  1544.         }
  1545.     }
  1546.     
  1547.     /**
  1548.      * Destroy image also
  1549.      * 
  1550.      * "If I go down, I'm taking this image with me!"
  1551.      * 
  1552.      * "Don't do it man! Noooooooo!!!!"
  1553.      */
  1554.     function __destruct(){
  1555.         if(empty($this->img|| !is_resource($this->img)){
  1556.             return;
  1557.         }
  1558.         
  1559.         if($this->imgRefCount <= 1){
  1560.             imagedestroy($this->img);
  1561.         }
  1562.         --$this->imgRefCount;
  1563.     }
  1564.     
  1565.     /**
  1566.      * Allocate an image
  1567.      * 
  1568.      * Allocates a true color image and initialazes it to be clear.
  1569.      * 
  1570.      * @return    resource    A GD image resource
  1571.      */
  1572.     protected function allocImage(){
  1573.         if($this->img)
  1574.             throw new SMap_Ex('Image already alloced!');
  1575.         
  1576.         $w $this->tile->widthPx;
  1577.         $h $this->tile->heightPx;
  1578.         
  1579.         $this->img = $img imagecreatetruecolor(    $w$h);
  1580.         $this->imgRefCount = 1;
  1581.         
  1582.         //Ensure image creation
  1583.         if(!$img){
  1584.             throw new SMap_Ex('Unable to allocate image!');
  1585.         }
  1586.         
  1587.         //Start with a transparent canvas
  1588.         imagealphablending($imgfalse);
  1589.         imagefilledrectangle(    $img0,0$w-1,$h-1,
  1590.                                 $this->allocColor($this->tile->view->bkCol));
  1591.         imagealphablending($imgtrue);
  1592.     }
  1593.     
  1594.     /**
  1595.      * Allocate a color for an image
  1596.      * 
  1597.      * @param    array    Color in RGB or RGBA
  1598.      * @return    integer    A color handle
  1599.      */
  1600.     public function allocColor($color){
  1601.         if(empty($this->img))
  1602.             $this->allocImage();
  1603.         
  1604.         if(!isset($color[3]|| $color[3=== null){
  1605.             return imagecolorexact(    $this->img,
  1606.                                     $color[0],
  1607.                                     $color[1],
  1608.                                     $color[2]);
  1609.         else {
  1610.             return imagecolorexactalpha(    $this->img,
  1611.                                         $color[0],
  1612.                                         $color[1],
  1613.                                         $color[2],
  1614.                                         $color[3]);
  1615.         }
  1616.     }
  1617.     
  1618.     /**
  1619.      * Composite another canvas atop this one
  1620.      * 
  1621.      * @param    object    Top canvas
  1622.      * @uses        compositeRes()
  1623.      */
  1624.     public function composite(SMap_Canvas $atop){
  1625.         if(empty($atop->img)) {
  1626.             return;
  1627.         }
  1628.         
  1629.         if(empty($this->img)){
  1630.             ++$atop->imgRefCount;
  1631.             $this->img = $atop->img;
  1632.             $this->imgRefCount &= $atop->imgRefCount;
  1633.         else {
  1634.             $this->compositeRes($atop->img0,0$this->tile->widthPx,$this->tile->heightPx);
  1635.         }
  1636.     }
  1637.     
  1638.     /**
  1639.      * Composite an image resource
  1640.      * 
  1641.      * @param    resource 
  1642.      */
  1643.     protected function compositeRes($res$dstX,$dstY$dstW,$dstH){
  1644.         if(empty($this->img))
  1645.             $this->allocImage();
  1646.             
  1647.         if($dstW == imagesx($res&& $dstH == imagesy($res)){
  1648.             imagecopy($this->img$res$dstX,$dstY0,0$dstW$dstH);
  1649.         else {
  1650.             imagecopyresampled(    $this->img$res,
  1651.                                 $dstX,$dstY0,0,
  1652.                                 $dstW$dstH,
  1653.                                 imagesx($res)imagesy($res));
  1654.         }
  1655.         
  1656.         imagedestroy($res);
  1657.     }
  1658.     
  1659.     /**
  1660.      * Load an image from file
  1661.      * 
  1662.      * @param    integer    File type
  1663.      * @param    string    File name
  1664.      * @param    integer    Place at X
  1665.      * @param    integer    Place at Y
  1666.      * @param    integer    Load from X
  1667.      * @param    integer    Load from Y
  1668.      * @param    integer    Load width
  1669.      * @param    integer    Load height
  1670.      * @return    boolean    Image loaded
  1671.      * @uses        loadRaw()
  1672.      * @uses        compositeRes()
  1673.      */
  1674.     public function load($ftype$fname$dstX,$dstY$srcX,$srcY$width,$height){
  1675.         $rawImg $this->loadRaw($ftype$fname$srcX,$srcY$width,$height);
  1676.         if(!is_resource($rawImg)){
  1677.             return false;
  1678.         }
  1679.         
  1680.         //Composite if needed
  1681.         if(    $dstX || $dstY ||
  1682.             $this->tile->widthPx != $width || $this->tile->heightPx != $height ||
  1683.             !empty($this->img|| !imageistruecolor($rawImg)){
  1684.             
  1685.             $this->compositeRes($rawImg$dstX,$dstY$width,$height);
  1686.         else {
  1687.             $this->img = $rawImg;
  1688.             $this->imgRefCount = 1;
  1689.         }
  1690.         
  1691.         return (bool) $this->img;
  1692.     }
  1693.     
  1694.     /**
  1695.      * Load an image from file
  1696.      * 
  1697.      * @param    integer    File type
  1698.      * @param    string    File name
  1699.      * @param    integer    Load from X
  1700.      * @param    integer    Load from Y
  1701.      * @param    integer    Load width
  1702.      * @param    integer    Load height
  1703.      * @return    resource    GD Image
  1704.      */
  1705.     protected function loadRaw($ftype$fname$srcX,$srcY$width,$height){
  1706.         //Open the image
  1707.         switch($ftype){
  1708.         case IMAGETYPE_JPEG:
  1709.             $rawImg imagecreatefromjpeg($fname);
  1710.             break;
  1711.         case IMAGETYPE_PNG:
  1712.             $rawImg imagecreatefrompng($fname);
  1713.             break;
  1714.         case IMAGETYPE_GD2:
  1715.             if($width || $height 0){
  1716.                 return imagecreatefromgd2($fname);
  1717.             else {
  1718.                 return imagecreatefromgd2part($fname$srcX,$srcY$width,$height);
  1719.             }
  1720.         default:
  1721.             throw new SMap_Ex(    'Unable to recognize image type: '.$ftype,
  1722.                                 SMap_Ex::FILE_READ);
  1723.         }
  1724.         
  1725.         //Trim image
  1726.         if($width >= && $height >= 0){
  1727.             $img imagecreatetruecolor($width$height);
  1728.             imagecopy($img$rawImg0,0$srcX,$srcY$width$height);
  1729.             imagedestroy($rawImg);
  1730.             $rawImg $img;
  1731.         }
  1732.         
  1733.         return $rawImg;
  1734.     }
  1735.     
  1736.     /**
  1737.      * Display an image
  1738.      * 
  1739.      * @param    integer    Requested format
  1740.      */
  1741.     public function display($fmt){
  1742.         if(empty($this->img))
  1743.             $this->allocImage();
  1744.         
  1745.         switch($fmt){
  1746.             case IMAGETYPE_JPEG:
  1747.                 imagejpeg($this->img);
  1748.                 return;
  1749.             default:
  1750.             case IMAGETYPE_PNG:
  1751.                 imagealphablending($this->imgfalse);
  1752.                 imagesavealpha($this->imgtrue);
  1753.                 imagepng($this->img);
  1754.                 return;
  1755.         }
  1756.     }
  1757.     
  1758.     /**
  1759.      * Set a single pixel
  1760.      * 
  1761.      * @param    integer    X dim
  1762.      * @param    integer    Y dim
  1763.      * @param    integer    Color handle
  1764.      */
  1765.     public function setPixel($x$y$col){
  1766.         if(empty($this->img))
  1767.             $this->allocImage();
  1768.         
  1769.         imagesetpixel($this->img$x$y$col);
  1770.     }
  1771.     
  1772.     /**
  1773.      * Draw a line
  1774.      * 
  1775.      * @param    integer    Beginning X
  1776.      * @param    integer    Beginning Y
  1777.      * @param    integer    Ending X
  1778.      * @param    integer    Ending Y
  1779.      * @param    integer    Color handle
  1780.      */
  1781.     public function line($begX,$begY,$endX,$endY$col){
  1782.         if(empty($this->img))
  1783.             $this->allocImage();
  1784.         
  1785.         imageline($this->img$begX,$begY,$endX,$endY$col);
  1786.     }
  1787.     
  1788.     /**
  1789.      * Draw a rectangle
  1790.      * 
  1791.      * @param    integer    Upper left corner X
  1792.      * @param    integer    Upper left corner Y
  1793.      * @param    integer    Lower right corner X
  1794.      * @param    integer    Lower right corner Y
  1795.      * @param    integer    Color handle
  1796.      */
  1797.     public function rectangle($x1$y1$x2$y2$col){
  1798.         if(empty($this->img))
  1799.             $this->allocImage();
  1800.         
  1801.         imagerectangle($this->img$x1$y1$x2$y2$col);
  1802.     }
  1803.     
  1804.     /**
  1805.      * Draw a filled rectangle
  1806.      * 
  1807.      * @param    integer    Upper left corner X
  1808.      * @param    integer    Upper left corner Y
  1809.      * @param    integer    Lower right corner X
  1810.      * @param    integer    Lower right corner Y
  1811.      * @param    integer    Color handle
  1812.      */
  1813.     public function filledRectangle($x1$y1$x2$y2$col){
  1814.         if(empty($this->img))
  1815.             $this->allocImage();
  1816.         
  1817.         imagefilledrectangle($this->img$x1$y1$x2$y2$col);
  1818.     }
  1819.  
  1820.     /**
  1821.      * Draw a polygon
  1822.      * 
  1823.      * @param    array    Pairs of points
  1824.      * @param    integer    Number of points
  1825.      * @param    integer    Color to draw
  1826.      */
  1827.     public function polygon(&$pts$numPts$col){
  1828.         if(empty($this->img))
  1829.             $this->allocImage();
  1830.         
  1831.         imagepolygon($this->img$pts$numPts$col);
  1832.     }
  1833.     
  1834.     /**
  1835.      * Draw a filled polygon
  1836.      * 
  1837.      * @param    array    Pairs of points
  1838.      * @param    integer    Number of points
  1839.      * @param    integer    Color to draw
  1840.      */
  1841.     public function filledPolygon(&$pts$numPts$col){
  1842.         if(empty($this->img))
  1843.             $this->allocImage();
  1844.         
  1845.         imagefilledpolygon($this->img$pts$numPts$col);
  1846.     }
  1847.     
  1848.     /**
  1849.      * Draw a string
  1850.      * 
  1851.      * @param    float    Angle to draw the text at
  1852.      * @param    resource    Font to use
  1853.      * @param    integer    X Position to draw text
  1854.      * @param    integer    Y Position to draw text
  1855.      * @param    integer    Color to draw
  1856.      */
  1857.     public function str($ang$font$x,$y$text$col){
  1858.         if(empty($this->img))
  1859.             $this->allocImage();
  1860.         
  1861.         if($ang == 0){
  1862.             imagestring($this->img$font$x$y$text$col);
  1863.         elseif($ang == 90){
  1864.             imagestringup($this->img$font$x$y$text$col);
  1865.         else {
  1866.             throw new SMap_Ex('Text angle not supported: '.$ang);
  1867.         }
  1868.     }
  1869.     
  1870.     /**
  1871.      * Find a font width in pixels
  1872.      * 
  1873.      * @param    integer    Font resource
  1874.      * @param    string    Text to measure
  1875.      * @return    integer    Font width in pixels
  1876.      */
  1877.     public static function fontWidth($font$str){
  1878.         return imagefontwidth($font)*strlen($str);
  1879.     }
  1880.     
  1881.     /**
  1882.      * Find a font height in pixels
  1883.      * 
  1884.      * @param    integer    Font resource
  1885.      * @param    string    Text to measure
  1886.      * @return    integer    Font height in pixels
  1887.      */
  1888.     public static function fontHeight($font$str){
  1889.         return imagefontheight($font);
  1890.     }
  1891. }
  1892.  
  1893. /**
  1894.  * A class for caching tiles
  1895.  * 
  1896.  * This class is used to cache tile images to disk. If you are caching the
  1897.  * layered tile, pass the image type to {@link load()} and an optimized version
  1898.  * of the tile will be saved when {@link display()} is called. For optimization,
  1899.  * ensure that you have the command line utilites jpegtran and optipng
  1900.  * installed.
  1901.  * 
  1902.  * If you have image caching disabled, I expect that you will enable it
  1903.  * eventually, so I enable a "debug mode" in {@link cache()} which reads the
  1904.  * images back after saving them to help ensure that there is no image
  1905.  * corruption.
  1906.  * 
  1907.  * If you are caching an individual layer, you should use IMAGETYPE_GD2 for the
  1908.  * format. Then embed this cache in {@link SMap_Object_CachedImage}.
  1909.  * 
  1910.  * @package        SMap
  1911.  * @internal
  1912.  */
  1913. class SMap_Canvas_Cache extends SMap_Canvas {
  1914.     
  1915.     /**
  1916.      * The retrieved image data
  1917.      * 
  1918.      * @var        string 
  1919.      */
  1920.     protected $imgRaw;
  1921.     
  1922.     /**
  1923.      * The size of the retrieved image
  1924.      * 
  1925.      * @var        integer 
  1926.      */
  1927.     protected $imgBytes;
  1928.     
  1929.     /**
  1930.      * The type of image
  1931.      * 
  1932.      * @var        integer 
  1933.      */
  1934.     protected $imgType;
  1935.     
  1936.     /**
  1937.      * The cached layers
  1938.      * 
  1939.      * @var        array 
  1940.      */
  1941.     protected $layers;
  1942.     
  1943.     /**
  1944.      * The parent map
  1945.      * 
  1946.      * @var        object 
  1947.      */
  1948. //    protected $map;
  1949.     
  1950.         
  1951.     /**
  1952.      * Create a cache object
  1953.      * 
  1954.      * The layers parameter
  1955.      * 
  1956.      * @param    object    The tile
  1957.      * @param    array    Layers contained in this cache
  1958.      * @param    integer    Image format
  1959.      */
  1960.     function __construct(SMap_Tile $tile$layers$fType){
  1961.         parent::__construct($tile);
  1962.         $this->layers = $layers;
  1963.         $this->imgType = $fType;
  1964.     }
  1965.     
  1966.     /**
  1967.      * Creates the filename for a cached tile
  1968.      * 
  1969.      * We will need the identity of the map, which is passed in as a string. We
  1970.      * will also need the identity of this tile, which we will be generating.
  1971.      */
  1972.     protected function getCachedFName(){
  1973.         //Get the key for the tile
  1974.         $tileKey SMap_Util::genTileKey($this->tile->boundsarray());
  1975.         
  1976.         if(isset($this->layers[1])){
  1977.             $layerKey '';
  1978.         else {
  1979.             $layerKey '_'.$this->layers[0];
  1980.         }
  1981.         
  1982.         $fname SMap::getOption('cacheDir').'/imgs/'.$this->tile->map->key.'_'.$tileKey.$layerKey;
  1983.         
  1984.         //Send header
  1985.         switch($this->imgType){
  1986.             case IMAGETYPE_JPEG:
  1987.                 $ext '.jpg';
  1988.                 break;
  1989.             case IMAGETYPE_PNG:
  1990.                 $ext '.png';
  1991.                 break;
  1992.             case IMAGETYPE_GD2:
  1993.                 $ext '.gd2';
  1994.                 break;
  1995.             default:
  1996.                 throw new SMap_Ex('Unrecognized image format: '.$this->imgType);
  1997.         }
  1998.  
  1999.         return $fname.$ext;
  2000.     }
  2001.     
  2002.     /**
  2003.      * Was a cached image loaded?
  2004.      * 
  2005.      * @return    boolean 
  2006.      */
  2007.     public function isLoaded(){
  2008.         return !empty($this->img|| !empty($this->imgRaw);
  2009.     }
  2010.     
  2011.     /**
  2012.      * Loads a cached tile image
  2013.      * 
  2014.      * If a cached version of this tile exists, load it, but respect the cache
  2015.      * time. If the file exists, but the age of the file is greater than the
  2016.      * modification time, no image is loaded.
  2017.      * 
  2018.      * The expected filetype is one of the IMAGETYPE_* constants.
  2019.      * 
  2020.      * The last modified time is set as the modified time of the file using
  2021.      * {@link setLastModified()}. The size of file read is limited to 4 MB. I
  2022.      * can't think of a good reason for your tile to be larger than that
  2023.      * anyway, so this may signal an error.
  2024.      * 
  2025.      * @param    integer    Minimum file modification time.
  2026.      * @return    mixed    False if not found, GD resource, or file contents
  2027.      * @uses        getCachedFName()
  2028.      * @uses        setLastModified()
  2029.      * @uses        $loaded
  2030.      */
  2031.     public function loadFromCache($mtime){
  2032.         
  2033.         //Save us from being stupid
  2034.         if($this->isLoaded()){
  2035.             throw new SMap_Ex('Attempted to load from cache when we already had ' .
  2036.                     'an image loaded.'SMap_Ex::RES_COLL);
  2037.         }
  2038.         
  2039.         //Disable if needed
  2040.         if(!SMap::getOption('cacheEnabledImg')){
  2041.             return false;
  2042.         }
  2043.         
  2044.         //Get file name
  2045.         $fname $this->getCachedFName();
  2046.         
  2047.         //Files exist?
  2048.         if(!file_exists($fname)){
  2049.             return false;
  2050.         }
  2051.         
  2052.         //Get file lock
  2053.         if(!($fp fopen($fname.'.lock''r')) || !flock($fpLOCK_SH)){
  2054.             
  2055.             //Close if we have a file pointer
  2056.             if($fp){
  2057.                 flock($fpLOCK_UN);
  2058.                 fclose($fp);
  2059.             }
  2060.             
  2061.             return false;
  2062.         }
  2063.         
  2064.         //Read it if valid, delete it if not
  2065.         $mod filemtime($fname);
  2066.         if($mod >= $mtime){
  2067.             if($this->imgType == IMAGETYPE_GD2){
  2068.                 $this->img = imagecreatefromgd2($fname);
  2069.                 $this->imgRefCount = 1;
  2070.             else {
  2071.                 $this->imgRaw = file_get_contents($fnamefalsenull0SMap::getOption('maxFileSize'));
  2072.                 $this->imgSize filesize($fname);
  2073.             }
  2074.             $this->tile->setLastModified($mod);
  2075.         else {
  2076.             unlink($fname);
  2077.         }
  2078.         
  2079.         //Release file lock
  2080.         flock($fpLOCK_UN);
  2081.         fclose($fp);
  2082.         
  2083.         //Did we read the file?
  2084.         return $this->isLoaded();
  2085.     }
  2086.  
  2087.     /**
  2088.      * Caches the image
  2089.      * 
  2090.      * @uses        getCachedFName()
  2091.      */
  2092.     public function cache($fmt){
  2093.         
  2094.         //Get file names
  2095.         $fName $this->getCachedFName();
  2096.         
  2097.         //Get lock
  2098.         if(!($fp fopen($fName.'.lock''w')) || !flock($fpLOCK_EX LOCK_NB)){
  2099.             
  2100.             //Close if we have a file pointer
  2101.             if($fp){
  2102.                 flock($fpLOCK_UN);
  2103.                 fclose($fp);
  2104.             }
  2105.  
  2106.             //Die if we can't
  2107.             return;
  2108.         }
  2109.         
  2110.         if(empty($this->img))
  2111.             $this->allocImage();
  2112.         $testImg true;
  2113.         
  2114.         /*
  2115.          * Save the image
  2116.          * 
  2117.          * If the image is worth caching, it's worth compressing. So compress it
  2118.          * here so we can save bandwidth in the future.
  2119.          * 
  2120.          * Otherwise, we're enabling a "debug" mode which checks the validity of
  2121.          * the files. If the files may get corrupted, we best know about it
  2122.          * before we enable caching.
  2123.          */
  2124.         switch($fmt){
  2125.         case IMAGETYPE_GD2:
  2126.             $widthPx  SMap::getOption('tileWidthPx');
  2127.             $heightPx SMap::getOption('tileHeightPx');
  2128.             
  2129.             imagegd2($this->img$fNamemax($widthPx$heightPx)IMG_GD2_COMPRESSED);
  2130.             
  2131.             if(!SMap::getOption('cacheEnabledImg')){
  2132.                 $testImg imagecreatefromgd2($fName);
  2133.             }
  2134.             break;
  2135.             
  2136.         case IMAGETYPE_PNG:
  2137.             //Save the img
  2138.             imagepng($this->img$fName);
  2139.             
  2140.             if(SMap::getOption('cacheEnabledImg')){
  2141.  
  2142.                 //Compress the png a bit more
  2143.                 $cmd escapeshellcmd(SMap::getOption('optipngBin')).' -q '.
  2144.                             escapeshellarg(SMap::getOption('optipngArg')).' '.
  2145.                             escapeshellarg($fName);
  2146.                 
  2147.                 if($err exec($cmd)){
  2148.                     trigger_error($errE_USER_WARNING);
  2149.                 }
  2150.             else {
  2151.                 $testImg imagecreatefrompng($fName);
  2152.             }
  2153.             break;</