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