Location: PHPKode > projects > Gallery 2 Google Map Integration > map/GoogleEarth.inc
<?php
/*
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2007 Bharat Mediratta
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */
/* Written by Nic Roets. Placed in the public domain. */
/* Changelog:
 Changed from map module 0.5.1d (Karl Newman, 2006-12-22):
 * Fixed reversed longitude/latitude bug
 * Fixed several bugs where conditional test variables were not initialized every pass
 * Removed lots of dead code related to importing GPS track
 * Validates field values to ensure they are in legal range
 * Changed to use mapHelper function to retrieve field values
 * Changed to recursive function to export a single item or create a hierarchy of
    all subitems of current item
 * Only include subitems (in particular ChildDataItems) that have GPS coordinates set
 * Added support for optional 'GELookAt' field to customize how GE portrays the location
 * Got rid of the View and associated form, which wasn't doing anything but confusing people.
    Note that this requires a change to the "Export to GE" link in map/module.inc
    to call this as 'controller' instead of 'view'.
    It makes the "Export to GE" a direct link to download to Google Earth.
 Further updates (Karl Newman, 2006-12-26)
 * Added "Document" enclosing structure; fixed styles to avoid using deprecated icons.
 Still more updates (Karl Newman, 2006-12-27)
 * Use "LookAt" block for albums if they have coordinates set (still won't show a point, though)
 * Include albums if they have coordinates set, even if no children do.
 * Show the album thumbnail and link to the album in the folder description.
 More refinements (Karl Newman, 2006-12-28)
 * Changed the prefix to GoogleEarth instead of GoogleEarthAlbum, since it can be used to view a
    single item as well as an album. Also, this really should be a View, not a Controller, since
    it doesn't modify any data. So, I changed it to an Immediate view, patterned after
    DownloadItem in the Core module. These changes will affect module.inc as well.
 * Added function documentation, prefix private functions with _
 */

/**
 * Module to export items to a KML file for display in Google Earth
 *
 * @package map
 * @author Nic Roets
 * @author Karl Newman
 * @version $Revision: 1253 $
 */
class GoogleEarthView extends GalleryView {
    /**
     * @see GalleryView::isImmediate
     */
    function isImmediate() {
	return true;
    }

    /**
     * @see GalleryView::isAllowedInEmbedOnly
     */
    function isAllowedInEmbedOnly() {
	return true;
    }

    /**
     * @see GalleryView::shouldSaveSession
     */
    function shouldSaveSession() {
	return false;
    }

    /**
     * @see GalleryView::renderImmediate
     */
    function renderImmediate($status, $error) {
	global $gallery;
	$phpVm = $gallery->getPhpVm();

	$itemId = GalleryUtilities::getRequestVariables('itemId');
	list ($ret, $item) = GalleryCoreApi::loadEntitiesById($itemId);
	if ($ret) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}

	list ($ret, $placemarks) = GoogleEarthView::_createGETree($item, array(), array());
	if ($ret) {
	    return array($ret->wrap(__FILE__, __LINE__), null);
	}
	if (!empty($placemarks)) {
	    $outstring = '<?xml version="1.0" encoding="UTF-8"?>'
	    . '<kml xmlns="http://earth.google.com/kml/2.0">'
	    . "\n<Document><name>Gallery2</name><open>1</open>\n"
	    . '<Style id="PhotoIconNormal"><IconStyle>' . "\n"
	    . '<Icon><href>http://maps.google.com/mapfiles/kml/pal4/icon46.png</href></Icon>' . "\n"
	    . '</IconStyle></Style>' . "\n"
	    . '<Style id="PhotoIconHighlight"><IconStyle><scale>1.1</scale>' . "\n"
	    . '<Icon><href>http://maps.google.com/mapfiles/kml/pal4/icon38.png</href></Icon>' . "\n"
	    . '</IconStyle><LabelStyle><scale>1.1</scale></LabelStyle></Style>' . "\n"
	    . '<StyleMap id="PhotoIconPair">' . "\n"
	    . '<Pair><key>normal</key><styleUrl>#PhotoIconNormal</styleUrl></Pair>' . "\n"
	    . '<Pair><key>highlight</key><styleUrl>#PhotoIconHighlight</styleUrl></Pair></StyleMap>'
	    . "\n" . $placemarks . "</Document>\n</kml>\n";
	    $phpVm->header('Content-Type: application/vnd.google-earth.kml+xml');
	    $phpVm->header('Content-Description: View Gallery items in Google Earth');
	    $phpVm->header("Content-Disposition: inline; filename=GalleryGE-{$itemId}.kml");
	    $phpVm->header('Content-Length: ' . strlen($outstring));
	    echo $outstring;
	} else {
	    /* Should probably return some error that no child elements had valid GPS coordinates */
        }
        return null;
    }

    /*
     * Recursive function to generate the Folder and Placemark elements to represent the albums
     * and items that have GPS coordinates set.
     *
     * param GalleryItem $item the current item/album to process
     * param array $data array of arrays (keyed by itemID) of field data (keyed by field name)
     *     associated with the $item (e.g., GPS coordinates)
     * param array $ancestors list of parent item IDs already processed (to prevent infinite loops)
     *
     * returns string KML subelements: folder(s) and/or placemark(s)
     */
    function _createGETree($item, $data, $ancestors) {
	GalleryCoreApi::requireOnce('modules/map/classes/mapHelper.class');

	$returnstring = ''; /* start with a blank string */
	if (empty($data)) { /* workaround for first call if no field data passed */
	    list ($ret, $data) = mapHelper::fetchFieldValues(array($item));
	}

	/* If it's an album or similar */
	if ($item->getCanContainChildren()) {
	    /* Get all the direct children of this item, in album sort order */
	    list ($ret, $childIds) = GalleryCoreApi::fetchChildItemIds($item);
	    if ($ret) {
		return array($ret->wrap(__FILE__, __LINE__), null);
	    }
	    /* If the list of direct children wasn't empty */
	    if (!empty($childIds)) {
		/* If we haven't already included this item in a parent folder */
		if (!array_search($item->getId(), $ancestors)) {
		    /* Add ourselves to the ancestors array */
		    $ancestors[] = $item->getId();
		    /* Load the children */
		    list ($ret, $childItems) = GalleryCoreApi::loadEntitiesById($childIds);
		    if ($ret) {
			return array($ret->wrap(__FILE__, __LINE__), null);
		    }

		    /* Grab the field data for the child items */
		    list ($ret, $fielddata) = mapHelper::fetchFieldValues($childItems);
		    if ($ret) {
			return array($ret->wrap(__FILE__, __LINE__), null);
		    }

		    foreach ($childItems as $childItem) {
			/* Recurse into this function with the next level down */
			list ($ret, $childstring) =
				GoogleEarthView::_createGETree($childItem, $fielddata, $ancestors);
			if ($ret) {
			    return array($ret->wrap(__FILE__, __LINE__), null);
			}
			$returnstring .= $childstring;
		    }

		    /*
		     * Only add containing folder info if there are child elements or if the folder
		     * has coordinates of its own.
		     */
		    if (!empty($returnstring) or !empty($data[$item->getId()]['GPS'])) {
			$coordstring = (!empty($data[$item->getId()]['GPS'])) 
					    ? $data[$item->getId()]['GPS']
					    : '';
			$LookAtstring = (!empty($data[$item->getId()]['GELookAt']))
					    ? $data[$item->getId()]['GELookAt']
					    : '';
			$returnstring = '<Folder id="g2id_'. $item->getId() . '">'
				. GoogleEarthView::_createTextElements($item)
				. GoogleEarthView::_createLookAtBlock($coordstring, $LookAtstring)
				. $returnstring . "</Folder>\n";
		    }
		}
	    }
	}
	else { /* It's not a container item */
	    if (GalleryUtilities::isA($item, 'GalleryDataItem')) {
		list ($ret, $childitemstring) =
			GoogleEarthView::_createGEChildItem($item, $data[$item->getId()]);
		if ($ret) {
		    return array($ret->wrap(__FILE__, __LINE__), null);
		}
		$returnstring .= $childitemstring;
	    }
	}
	return array(null, $returnstring);
    }

    /*
     * Create name, Snippet and description elements single Placemark element
     *
     * param GalleryItem $item the current GalleryDataItem for which to generate text elements
     *
     * return string name, Snippet and description elements for the item, properly CDATA-escaped
     *
     */
    function _createTextElements($item) {
	$returnstring = '';

	GalleryCoreApi::requireOnce('lib/smarty_plugins/modifier.entitytruncate.php');

	/* Get the item title if not blank, else use the file/directory name (path component) */
	$title = $item->getTitle() ? $item->getTitle() : $item->getPathComponent();
	/* Use the item summary for the Snippet, otherwise use the item description */
	$summstring = $item->getSummary()
			? GalleryUtilities::markup($item->getSummary(), 'strip')
			: smarty_modifier_entitytruncate(
				GalleryUtilities::markup($item->getDescription(), 'strip'),
				80);
	/** @todo If/when KML officially supports HTML in Snippet blocks, remove the 'strip' */

	$returnstring = '<name><![CDATA[' . GalleryUtilities::markup($title)
			. "]]></name>\n<description><![CDATA["
			. ($item->getSummary()
				? GalleryUtilities::markup($item->getSummary()) . "<br>\n"
				: '')
			. GoogleEarthView::_createThumbnailLink($item)
			. GalleryUtilities::markup($item->getDescription())
			. ']]></description><Snippet>' . $summstring . "</Snippet>\n";
	return $returnstring;
    }

    /*
     * Create a single Placemark element
     *
     * param GalleryItem $item the current GalleryDataItem to represent in a Placemark
     * param array $fielddata field data associated with the $item
     *
     * return string KML placemark element
     *
     */
    function _createGEChildItem($item, $fielddata) {
	$goodcoords = false;
	$returnstring = '';

	/* Select items that have GPS coordinates filled out */
	if (!empty($fielddata['GPS'])) {
	    $posxy = explode(',', $fielddata['GPS']);
	    if (count($posxy) >= 2) { /* changed to >= 2 to accommodate possible elevation field */
		$lat = (float)$posxy[0];
		$lon = (float)$posxy[1];
		$goodcoords = ((abs($lat) < 90) and (abs($lat) < 180));
	    }
	}

	if ($goodcoords) {
	    $coord = sprintf('%s,%s', $posxy[1], $posxy[0]);
	    /* This spot is one of the weird ones that wants longitude listed first */
	    $point = "<Point><altitudeMode>clampToGround</altitudeMode>\n<coordinates>" 
		     . $coord . ",0</coordinates>\n</Point>\n";

	    /* Output Placemark to Google Earth */
	    $coordstring = (!empty($fielddata['GPS'])) ? $fielddata['GPS'] : '';
	    $LookAtstring = (!empty($fielddata['GELookAt'])) ? $fielddata['GELookAt'] : '';
	    $returnstring .= '<Placemark id="g2id_' . $item->getId() . '">'
			     . GoogleEarthView::_createTextElements($item) 
			     . "<styleUrl>#PhotoIconPair</styleUrl>\n" . $point
			     . GoogleEarthView::_createLookAtBlock($coordstring, $LookAtstring)
			     . "</Placemark>\n";
	}
	return array(null, $returnstring);
    }

    /*
     * Create a LookAt block to apply to a folder or placemark
     *
     * param string $GPSstring the GPS coordinates in latitude,longitude format
     * param string $LookAtstring the Google Earth LookAt parameters in heading,tilt,range format
     *
     * returns string KML LookAt block
     *
     */
    function _createLookAtBlock($GPSstring, $LookAtstring) {
	$headingstr = "0";
	$tiltstr = "45";
	$rangestr = "3000";
	$returnstring = '';
	$goodcoords = false;
	$posxy = array();

	if (!empty($GPSstring)) {
	    $posxy = explode(',', $GPSstring);
	    if (count($posxy) >= 2) { /* changed to >= 2 to accommodate possible elevation field */
		$lat = (float)$posxy[0];
		$lon = (float)$posxy[1];
		$goodcoords = ((abs($lat) < 90) and (abs($lon) < 180));
	    }
	}
	/*
	 * Use sprintf and %F to force non-locale-aware float printing to avoid inadvertant
	 * commas in the float values. Limit decimal places for range, tilt, heading. If %F is
	 * not supported (PHP 4.3.9 or 5.0.2 or earlier), fall back to the raw string.
	 */
	
	if ($goodcoords) {
	    /* Parse the 'GELookAt' field */
	    if (!empty($LookAtstring)) {
		$lookat = explode(',', $LookAtstring);
		/*
		 * If at least one parameter, then #1 is the heading in degrees.
		 * Must be between 0 and 360 degrees
		 */
		if (count($lookat) >= 1 and !empty($lookat[0])
			and $lookat[0] >= -360 and $lookat[0] <= 360) {
		    $heading = (float)$lookat[0];
		    $headingstr = sprintf('%.2F', $heading) == '' ? $lookat[0]
		    						  : sprintf('%.2F', $heading); 
		}
		/*
		 * If at least two parameters, then #2 is the tilt in degrees.
		 * Must be between 0 and 90 degrees
		 */
		if (count($lookat) >= 2 and !empty($lookat[1])
			and $lookat[1] >= 0 and $lookat[1] <= 90) {
		    $tilt = (float)$lookat[1];
		    $tiltstr = sprintf('%.2F', $tilt) == '' ? $lookat[1]
		    					    : sprintf('%.2F', $tilt); 
		}
		/* If at least three parameters, then #3 is the range in meters. Must be > 0 */
		if (count($lookat) >= 3 and !empty($lookat[2]) and $lookat[2] > 0) {
		    $range = (float)$lookat[2];
		    $rangestr = sprintf('%.2F', $range) == '' ? $lookat[2]
		    					    : sprintf('%.2F', $range); 
		}
	    }
	    $lonstr = $posxy[1]; 
	    $latstr = $posxy[0];
	    $returnstring = sprintf('<LookAt><longitude>%s</longitude><latitude>%s</latitude>'
				    . '<range>%s</range><tilt>%s</tilt><heading>%s</heading>',
	    			    $lonstr, $latstr, $rangestr, $tiltstr, $headingstr) 
	    			    . "</LookAt>\n";
	}
	return $returnstring;
    }

    /*
     * Create an HTML snippet to display the thumbnail for an item and create a link to the gallery
     * item
     *
     * param GalleryItem $item the current GalleryDataItem to represent in a Placemark
     *
     * returns string HTML snippet suitable for inclusion in a CDATA-escaped description element
     *
     */
    function _createThumbnailLink($item) {    				
	$hsitemLink = '';
	$dthumbLink = '';
	global $gallery;

	$urlGenerator =& $gallery->getUrlGenerator();
	$returnstring = '';
	$theid = $item->getId();
	/* Get the thumbnail item ID for the item */
	list ($ret, $thumbs) = GalleryCoreApi::fetchThumbnailsByItemIds(array($theid));

	$thumbId = $thumbs[$theid]->getId();

	$dthumbLink = $urlGenerator->generateUrl(
		array('view' => 'core.DownloadItem', 'itemId' => $thumbId),
		array('forceFullUrl' => true, 'htmlEntities' => false));
	$hsitemLink = $urlGenerator->generateUrl(
		array('view' => 'core.ShowItem', 'itemId' => $theid),
		array('forceFullUrl' => true, 'htmlEntities' => false));
	if ($hsitemLink != '' and $dthumbLink != '') {
	    $returnstring = '<a href="' . $hsitemLink . "\">\n<img src=\"" . $dthumbLink 
			    . '"></a><br>';
	}
	return $returnstring;
    }
}
?>
Return current item: Gallery 2 Google Map Integration