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

Source for file Tile.php

Documentation is available at Tile.php

  1. <?php
  2. /**
  3.  * The subdivision  of a view
  4.  * 
  5.  * SMap requires at least PHP version 5, but there are important bug fixes in
  6.  * more recent versions of PHP. Try to stay current.
  7.  *
  8.  * <b>License</b>:
  9.  * 
  10.  * Copyright (c) 2006-2007, Seth Price <{@link mailto:seth@pricepages.org}
  11.  * seth@pricepages.org}> All rights reserved.
  12.  *
  13.  * Redistribution and use in source and binary forms, with or without
  14.  * modification, are permitted provided that the following conditions
  15.  * are met:
  16.  *
  17.  * - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
  18.  * - 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.
  19.  * - The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission.
  20.  *
  21.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
  22.  * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
  23.  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
  24.  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  25.  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
  26.  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  27.  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
  28.  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  29.  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  30.  * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  31.  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  32.  *
  33.  * @copyright    Copyright (c) 2006-2007, Seth Price
  34.  * @author        Seth Price <seth@pricepages.org>
  35.  * @license        http://opensource.org/licenses/bsd-license.php New BSD License
  36.  * @access        public
  37.  * @package        SMap
  38.  * @version        0.1
  39.  */
  40.  
  41. /**
  42.  * Represents a piece of a view
  43.  * 
  44.  * Each tile is a section of the {@link SMap_View view}. A tile is as large or
  45.  * small as needed. Larger tiles mean fewer requests to the webserver. Smaller
  46.  * tiles mean a greater opportunity for caching.
  47.  * 
  48.  * @package    SMap
  49.  */
  50. class SMap_Tile {
  51.  
  52.     /**
  53.      * Array indicies for tile position
  54.      * 
  55.      * POSX,POSY are the position of the current tile in the grid. VIEWX,VIEWY
  56.      * are the dimensions of the view. TILESX,TILESY are the dimensions of the
  57.      * current map. VIEWX,VIEWY normaly equals TILESX,TILESY, except when a tile
  58.      * is being rendered. TILESX,TILESY are the only indicies that always are
  59.      * set.
  60.      * 
  61.      * @var        integer 
  62.      */
  63.     const POSX        1;
  64.     const VIEWX        2;
  65.     const POSY        4;
  66.     const VIEWY        8;
  67.     const TILESX        16;
  68.     const TILESY        32;
  69.     
  70.     /**
  71.      * The map that contains this tile
  72.      * 
  73.      * @var        object 
  74.      */
  75.     protected $map;
  76.     
  77.     /**
  78.      * View that this tile is in
  79.      * 
  80.      * @var        object 
  81.      */
  82.     protected $view;
  83.     
  84.     /**
  85.      * List of layers that have been applied to this tile
  86.      * 
  87.      * @var        array 
  88.      */
  89.     protected $layers = array();
  90.     
  91.     /**
  92.      * The bounds of this tile
  93.      * 
  94.      * @var        array 
  95.      */
  96.     protected $bounds = array();
  97.     
  98.     /**
  99.      * This tile's position relative to other tiles
  100.      * 
  101.      * The tile grid marks the position of this tile in the rest of the view.
  102.      * This should be used sparingly in order to allow caching. The grid also
  103.      * won't be available if we have some sort of dynamic {@link }
  104.      * http://maps.google.com Google Maps} thing going on.
  105.      * 
  106.      * @var        array 
  107.      */
  108.     protected $tileGrid = array();
  109.     
  110.     /**
  111.      * Is the tile grid needed to draw this tile?
  112.      * 
  113.      * If not, then we shouldn't pass those vars to the form. Not placing the
  114.      * grid for every tile will allow more effective caching.
  115.      * 
  116.      * @var        boolean 
  117.      */
  118.     protected $tileGridNeeded = false;
  119.     
  120.     /**
  121.      * Tile width in pixels
  122.      * 
  123.      * @var        integer 
  124.      */
  125.     protected $widthPx;
  126.     
  127.     /**
  128.      * Tile height in pixels
  129.      * 
  130.      * @var        integer 
  131.      */
  132.     protected $heightPx;
  133.     
  134.     /**
  135.      * A canvas
  136.      * 
  137.      * @var        object 
  138.      */
  139.     protected $canvas;
  140.     
  141.     /**
  142.      * The raw image as retrieved from cache
  143.      * 
  144.      * @var        string 
  145.      */
  146.     protected $imgCache;
  147.     
  148.     /**
  149.      * Image map for this image
  150.      * 
  151.      * @var        array 
  152.      */
  153.     protected $imgMap;
  154.     
  155.     /**
  156.      * Cache time of this tile's data in seconds
  157.      * 
  158.      * The option 'cacheTime' by default. There is no 'setter' function for
  159.      * cache time because {@link flatten()} calls
  160.      * {@link SMap_Layer::cacheStatus()} in order to set this value.
  161.      * 
  162.      * @var        integer 
  163.      */
  164.     protected $cacheTime = false;
  165.     
  166.     /**
  167.      * Last modified time for this tile
  168.      * 
  169.      * @var        integer 
  170.      */
  171.     protected $lastModified = 0;
  172.     
  173.     /**
  174.      * View format
  175.      * 
  176.      * @var        integer 
  177.      */
  178.     protected $imgFmt = IMAGETYPE_PNG;
  179.     
  180.     /**
  181.      * Might we be dealing with cached rects?
  182.      * 
  183.      * @var        boolean 
  184.      */
  185.     protected $cachedRects = false;
  186.     
  187.     /**
  188.      * Have we flattened this tile yet?
  189.      * 
  190.      * @var        boolean 
  191.      */
  192.     protected $flattened = false;
  193.     
  194.     /**
  195.      * Have we constrained this tile yet?
  196.      * 
  197.      * @var        boolean 
  198.      */
  199.     protected $constrained = false;
  200.     
  201.     /**
  202.      * Have we rendered this tile yet?
  203.      * 
  204.      * @var        boolean 
  205.      */
  206.     protected $rendered = false;
  207.     
  208.     /**
  209.      * Each layer's objects
  210.      * 
  211.      * Seperate each layer into groups of objects. Once {@link constrain()} is
  212.      * called, the objects are placed in {@link $objs}.
  213.      * 
  214.      * @var        array 
  215.      */
  216.     protected $layerObjs = array();
  217.     
  218.     /**
  219.      * Objects that need to be rendered
  220.      * 
  221.      * Objects are {@link constrain() constrained} and dumped in here (in
  222.      * order) via {@link addObject()}.
  223.      * 
  224.      * @var        array 
  225.      */
  226.     protected $objs = array();
  227.     
  228.     /**
  229.      * Set the properties of this tile
  230.      * 
  231.      * @param    object    The map that contains this tile
  232.      * @param    object    The view that contains this tile
  233.      * @param    array    The real bounds of the tile
  234.      * @param    array    Where this tile is in relation to the others
  235.      */
  236.     function __construct(SMap $mapSMap_View $view$bounds$grid){
  237.         if(    $bounds[SMap::MAXX<= $bounds[SMap::MINX||
  238.             $bounds[SMap::MAXY<= $bounds[SMap::MINY){
  239.             
  240.             throw new SMap_Ex(    'Poorly formatted bounds',
  241.                                 SMap_Ex::BAD_BOUNDS);
  242.         }
  243.         
  244.         $this->map = $map;
  245.         $this->view = $view;
  246.         $this->bounds = $bounds;
  247.         $this->tileGrid = $grid;
  248.         
  249.         $this->widthPx  = SMap::getOption('tileWidthPx');
  250.         $this->heightPx = SMap::getOption('tileHeightPx');
  251.     }
  252.  
  253.     /**
  254.      * Access useful vars read-only
  255.      * 
  256.      * @param    string    Name of member
  257.      * @return    mixed    Value of member
  258.      */
  259.     public function __get($nm){
  260.         if(isset($this->$nm)){
  261.             return $this->$nm;
  262.         else {
  263.             throw new SMap_Ex('Attempted to read non-existant member '.$nm);
  264.         }
  265.     }
  266.     
  267.     /**
  268.      * Display the current image
  269.      * 
  270.      * Also produces the appropriate headers.
  271.      * 
  272.      * @uses        createImage()
  273.      * @uses        $imgFmt
  274.      */
  275.     public function display(){
  276.         
  277.         //Get the image
  278.         $this->createImage();
  279.         
  280.         //Check if debugging is enabled
  281.         if(SMap::getOption('debug')){
  282.             echo 'Image is not being displayed because debugging is enabled.'."<br />\n";
  283.             return;
  284.         }
  285.         
  286.         //Send last modified & cache headers
  287.         if($this->cacheTime === false){
  288.             $cacheTime SMap::getOption('cacheTime');
  289.         else {
  290.             $cacheTime = (int) $this->cacheTime;
  291.         }
  292.         header('Cache-Control: public, max-age='.$cacheTime);
  293.         header('Expires: '.gmdate('D, d M Y H:i:s'(time($cacheTime)).' GMT');
  294.         
  295.         //Default to last modified now
  296.         if(empty($this->lastModified)){
  297.             $lastMod time();
  298.         else {
  299.             $lastMod $this->lastModified;
  300.         }
  301.         
  302.         header('Last-Modified: '.gmdate('D, d M Y H:i:s'$lastMod).' GMT');
  303.  
  304.         //Send header
  305.         switch($this->imgFmt){
  306.             case IMAGETYPE_JPEG:
  307.                 header('Content-Type: image/jpeg');
  308.                 break;
  309.             case IMAGETYPE_PNG:
  310.                 header('Content-Type: image/png');
  311.                 break;
  312.             default:
  313.                 throw new SMap_Ex('Unrecognized image format: '.$this->imgFmt);
  314.         }
  315.         
  316.         //Display it
  317.         $this->canvas->display($this->imgFmt);
  318.     }
  319.     
  320.     /**
  321.      * Add a layer to this tile.
  322.      * 
  323.      * Adding the {@link SMap_Layer layer} is the first step in getting it
  324.      * rendered. Each layer can only be added once. After all layers are added,
  325.      * {@link flatten()} must be called.
  326.      * 
  327.      * The creation of layers should use few resources. Use {@link }
  328.      * SMap_Layer::applyTile()} to do any major setup. If there is a cache hit,
  329.      * it will not be called.
  330.      * 
  331.      * @param    object    SMap_Layer 
  332.      */
  333.     public function addLayer(SMap_Layer $layer){
  334.         
  335.         $id $layer->getID();
  336.         
  337.         //Already flattened
  338.         if($this->flattened){
  339.             throw new SMap_Ex('Tile already flattened, you can\'t add layer id '.$id);
  340.         }
  341.         //Die if layer already added
  342.         elseif(isset($this->layers[$id])){
  343.             throw new SMap_Ex(    'SMap_Layer of ID '.$id.' already exists',
  344.                                 SMap_Ex::DUP_LAYER);
  345.         }
  346.         
  347.         $this->layers[$id$layer;
  348.     }
  349.     
  350.     /**
  351.      * Sets the background color
  352.      * 
  353.      * Only useful if called before the image is internally allocated. Exception
  354.      * thrown if called after image allocation.
  355.      * 
  356.      * @param    array    RGBA
  357.      */
  358.      /*
  359.     public function setBackground($color){
  360.         if($this->canvas){
  361.             throw new SMap_Ex('Background set after canvas allocated!');
  362.         } elseif($this->imgCache){
  363.             
  364.         }
  365.         
  366.         $this->initColor = $color;
  367.     }
  368.     */
  369.     
  370.     /**
  371.      * Ensures that we have a rendered image
  372.      * 
  373.      * If the image doesn't already exist, create it. If layers have been
  374.      * added but not rendered, render them.
  375.      * 
  376.      * @uses        SMap_Object_Area_Label::drawLabels()
  377.      * @uses        SMap_Object::draw()
  378.      */
  379.     protected function createImage(){
  380.  
  381.         //Image is already created
  382.         if($this->canvas){
  383.             return;
  384.         }
  385.         
  386.         //Error?
  387.         if(!$this->constrained){
  388.             throw new SMap_Ex('Tile must be constrained before an image can be created.');
  389.         }
  390.         
  391.         //The cache is the canvas if that's what we're doing
  392.         if($this->imgCache){
  393.             $this->canvas = $this->imgCache;
  394.         else {
  395.             $this->canvas = new SMap_Canvas($this);
  396.         }
  397.         
  398.         //Render returned objects
  399.         foreach($this->objs as $obj){
  400.             $obj->draw($this->canvas);
  401.         }
  402.         
  403.         //Finish any labels that need drawing
  404.         SMap_Object_Area_Label::drawLabels($this$this->cachedRects);
  405.     }
  406.         
  407.     /**
  408.      * Get GET request vars to create an image URL
  409.      * 
  410.      * Returns the variables that need to be passed to this tile in order to
  411.      * generate the image.
  412.      * 
  413.      * @return    array    GET variables
  414.      * @uses        getImageMap()
  415.      * @uses        $tileGridNeeded
  416.      */
  417.     public function getImageGET(){
  418.         
  419.         //If there is no image map, we need to calculate that first
  420.         if($this->imgMap === null){
  421.             $this->getImageMap();
  422.         }
  423.         
  424.         //Create the array
  425.         return array(    'layers' => array_keys($this->layers),
  426.                         'viewId' => $this->view->id,
  427.                         'bounds' => $this->bounds,
  428.                         'tileGrid' => $this->tileGrid,
  429.                         'tileGridNeeded' => $this->tileGridNeeded,
  430.                         'zoom' => intval($this->map->zoom),
  431.                         'lang' => substr($this->map->lang->getLanguage()03),
  432.                         'fmt' => $this->imgFmt );
  433.     }
  434.     
  435.     /**
  436.      * Return the parameters for an image map (links)
  437.      * 
  438.      * If we are returning a server side image map, one of the returned objects
  439.      * will be of shape 'ismap'. The form should be able to handle this case.
  440.      * If a user clicks on a server side image map, {@SMap::handleImageMap()} 
  441.      * should be called, which will return the appropriate URL the user should
  442.      * be redirected to.
  443.      * 
  444.      * If we are returning a client side image map, returned set of arrays
  445.      * represent the "area" of the image map. They have indexes at ['alt'],
  446.      * ['coords'], ['shape'], and ['href'].
  447.      * 
  448.      * If there is no link here, we will return an empty array.
  449.      * 
  450.      * @return    array    All image map parameters.
  451.      * @uses        SMap_Object::map()
  452.      * @uses        $imgMap
  453.      */
  454.     public function getImageMap(){
  455.         //Check for cached
  456.         if($this->imgMap !== null){
  457.             return $this->imgMap;
  458.         }
  459.         
  460.         //Error?
  461.         if(!$this->constrained){
  462.             throw new SMap_Ex('Tile must be constrained before it is rendered.');
  463.         }
  464.         
  465.         $map array();
  466.         
  467.         //Render returned objects
  468.         foreach($this->objs as $obj){
  469.             $map array_merge($map$obj->map());
  470.         }
  471.         
  472.         return $this->imgMap = $map;
  473.     }
  474.     
  475.     /**
  476.      * Flattens the layers by reducing them to objects
  477.      * 
  478.      * This is the second step in the process of rendering. The first is {@link }
  479.      * addLayer() adding the layers}. Here we take care of reducing each layer
  480.      * to objects, or retrieving the cached image.
  481.      * 
  482.      * Flattening is done after all layers are added so background labels can
  483.      * avoid foreground objects (such as the panning graphic). This is done
  484.      * before the final render is done so that labels have a chance to organize
  485.      * and {@link SMap_View::sendObj() send themselves} to corrisponding tiles.
  486.      * 
  487.      * Image format selection is also decided here. We hope that photo realistic
  488.      * images (such as satellite) can be compressed as JPEG, just because it's
  489.      * more efficient compression. If a single layer requests PNG compression,
  490.      * though, we'll have to use PNG. This will ensure that we aren't forcing
  491.      * lossy compression where there shouldn't be any.
  492.      * 
  493.      * If all layers can agree on a different format (not PNG or JPEG), that
  494.      * format will be used. Ensure that the chosen format is enabled in
  495.      * {@link SMap_Object_CachedImage::cache()}{@link }
  496.      * SMap_Object_CachedImage::getCachedFName()}, and {@link display()}.
  497.      * 
  498.      * If applicable, we are also handling caching here. {@link }
  499.      * SMap_Layer::cacheStatus()} is called, and we figure out which layers
  500.      * should be cached, and which we can't cache at all. If the tile is
  501.      * cacheable and we have a cached tile, we retrieve it and use it. Otherwise
  502.      * we need to break it down to layers. Is each layer cacheable? Use a
  503.      * {@link SMap_Object_CachedImage cached layer's image} in place of
  504.      * rendering it.
  505.      * 
  506.      * @param    boolean    Are we rendering an image or image map?
  507.      * @uses        $flattened
  508.      * @uses        $layers
  509.      * @uses        $imgFmt
  510.      * @uses        SMap_Layer::getImageFmt()
  511.      * @uses        SMap_Layer::getMapObjs()
  512.      */
  513.     public function flatten($isImg){
  514.         
  515.         //Handle error conditions
  516.         if($this->flattened){
  517.             throw new SMap_Ex('Attempting to flatten a flat tile.');
  518.         elseif(empty($this->layers)){
  519.             trigger_error('Tile mapped with no layers. Maybe you should add ' .
  520.                     'some.'E_USER_NOTICE);
  521.         }
  522.         
  523.         $this->flattened = true;
  524.         
  525.         foreach($this->layers as $layer){
  526.             /*
  527.              * Determine the filetype for this tile (ex: PNG or JPEG). Note that
  528.              * it would be dumb to layer a JPEG over a PNG because a JPEG has no
  529.              * alpha channel.
  530.              */
  531.             $fmt $layer->getImageFmt();
  532.             if($fmt != $this->imgFmt){
  533.                 $this->imgFmt = $fmt;
  534.             }
  535.         }
  536.         
  537.         if($isImg){
  538.             $this->flattenImg();
  539.         else {
  540.             //We are only caching images currently.
  541.             foreach($this->layers as $lid => $layer){
  542.                 //Determine if we should look for cached rects
  543.                 if(!$this->cachedRects && $layer->hasCachedRects()){
  544.                     $this->cachedRects = true;
  545.                 }
  546.                 
  547.                 $this->layerObjs[$lid$layer->getMapObjs($this->view$this);
  548.             }
  549.         }
  550.     }
  551.     
  552.     /**
  553.      * The image specific part of flattening
  554.      * 
  555.      * @uses        $layers
  556.      * @uses        SMap_Layer::cacheStatus()
  557.      * @uses        SMap_Canvas_Cache
  558.      * @uses        SMap_Object_CachedImage
  559.      */
  560.     protected function flattenImg(){
  561.         /*
  562.          * Check each layer for its preference. Layers with a specific timestamp
  563.          * return such, cacheable layers with no preference return true. This
  564.          * way, we can cache the entire (flattened, rasterized) tile easily, but
  565.          * we can also cache individual layers otherwise.
  566.          */
  567.         $getCachedTile true;
  568.         
  569.         //Get the cache status of each layer
  570.         foreach($this->layers as $lid => $layer){
  571.             if($time $layer->cacheStatus(true)){
  572.                 //Save cache time
  573.                 $cacheLayer[$lid$time;
  574.                 
  575.                 if(    $time !== true &&
  576.                     ($time $this->cacheTime || $this->cacheTime === false)){
  577.                     
  578.                     $this->cacheTime = $time;
  579.                 }
  580.             else {
  581.                 $getCachedTile false;
  582.                 $cacheLayer[$lidfalse;
  583.             }
  584.         }
  585.         
  586.         /*
  587.          * If all layers can be cached, then check for a cached tile. If the
  588.          * cached tile exists, then we have everything done. Otherwise continue
  589.          * with processing.
  590.          */
  591.         if($getCachedTile && is_int($this->cacheTime)){
  592.