PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for the ‘ajax’ tag

GeoLocation mit modernen Browsern

with 8 comments

Der ein oder andere von euch hat vielleicht schonmal mit GeoLocations experimentiert. Es gibt freie GeoDatenbanken und GeoIP-Datenbanken, mit denen man eine IP-Adresse umwandeln kann in einen Ort (bzw. Region/Land).

Als ich nun vor einigen Monaten hörte, dass der Firefox bald eine neue GeoLocation-Funktionalität erhält, womit dann viele neue standortabhängige Dienste möglich sind, war ich gespannt was tolles neues dabei rauskommt. Zuerst dachte ich, dass man im Browser seine Daten eingibt, und dann sendet das der Browser an die Webseite, falls die das wissen möchte.

Doch ich habe die Rechnung ohne die ganzen mobilen Geräte gemacht, für die das Feature primär interessant ist (ich selbst habe zwar einen PDA, aber keinen Internet-Daten-Tarif, deshalb hab ich daran nicht wirklich gedacht).

Was kann das Feature nun? Es passiert folgendes: Falls die Webseite die GeoLocation ermitteln möchte, bekommt der Benutzer eine Frage präsentiert:

geolocation1

Man kann dann entscheiden, ob man diese Informationen senden möchte oder nicht. Falls man „Ja“ klickt, wird die IP-Adresse an einen Google-Service geschickt. Außerdem werden noch GPS-Informationen und erreichbare WLAN-Netze (incl. ermittelter Empfangsstärke) versendet an Google Location Services. Daraus kann Google dann eine mehr oder weniger genaue Position ermitteln (bei GPS natürlich sehr genau bis auf wenige Meter, bei WLAN-Informationen kann es auch schon sehr ungenau werden, je nachdem wieviele WLANs da sind und ob diese bereits kartographiert wurden).

Diese Standort-Information erhält dann der Browser, und dann kann diese Information mittels Javascript API abgefragt werden. Das hier sind die Informationen, die man erhält:

geolocation3

Man erhält also die Angaben in Form von Latitude und Longitude, sowie evtl. weitere GPS-Informationen wie Höhe, Geschwindigkeit usw. Falls man diese Informationen in einen Ortsnamen umwandeln möchte, benötigt man entweder wieder eine Datenbank mit Informationen oder man nutzt einen Webservice, der das selbe tut.
In meiner Demo nutze ich letzteres, und lasse anhand der Latitude/Longitude den Namen der Stadt ermitteln, dazu nutze ich www.geonames.org.

Inklusive Stadt sieht das Endergebnis dann so aus:

geolocation5

Wie sieht das nun im Quelltext aus? Eigentlich sehr übersichtlich:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>

    <title>PHP Gangsta Geolocation</title>
    <SCRIPT TYPE="text/javascript" SRC="http://ajax.googleapis.com/ajax/libs/dojo/1.3/dojo/dojo.xd.js"></SCRIPT>

    <script type="text/javascript">
        function startgeolocation() {
            // Check for geoLocation Support
            if (navigator.geolocation) {
                // the location is looked up only once
                navigator.geolocation.getCurrentPosition(renderPosition, renderError);
                // every time the location changes
                // navigator.geolocation.watchPosition(renderPosition, renderError);
            } else {
            	dojo.byId('geoResults').innerHTML = '<p>Your browser does not support geolocation.</p>';
            }
        }

        // I use Dojo to render the output and do an AJAX call to get city name
        function renderPosition(position) {
            if (!window.count) window.count = 0;

            count ++;
            // var urlJSON = 'http://ws.geonames.org/findNearbyPlaceNameJSON?lat=-36.9104718&lng=174.9133483';
            var urlJSON = 'geonames.php?lat='+position.coords.latitude+'&lon='+position.coords.longitude;

            dojo.byId('d').innerHTML = '<div id="results" style="width: 200px; height: 200px; text-align: left"><p>'
                            + 'Latitude: ' + position.coords.latitude + '<br />'
                            + 'Longitude: ' + position.coords.longitude + '<br />'
                            + 'Accuracy: ' + position.coords.accuracy + '<br />'
                            + 'Update number: ' + count + '<br />'
                            + 'Altitude: ' + position.coords.altitude + '<br />'
                            + 'Altitude accuracy: ' + position.coords.altitudeAccuracy + '<br />'
                            + 'Heading: ' + position.coords.heading + '<br />'
                            + 'Speed: ' + position.coords.speed + '<br />'
                            + 'Google Maps: <a href="http://maps.google.com/maps?geocode=&q=' +
                            			position.coords.latitude + '+' + position.coords.longitude +
                            			'">Click</a><br />'
                            + '</p></div>';

            // now get the XML reverse geo data
           dojo.xhrGet( { //
		        // The following URL must match that used to test the server.
		        url: urlJSON,
		        handleAs: "json",
		        timeout: 5000, // Time in milliseconds
		        // The LOAD function will be called on a successful response.
		        load: function(responseObject, ioArgs) {
		        	for each (var item in responseObject.geonames) {
		        		dojo.byId('jsonResults').innerHTML = '<p>You live in: ' + item.name + '</p>';
		        	}
		          	return responseObject;
		        },
		        // The ERROR function will be called in an error case.
		        error: function(responseObject, ioArgs) { //
		          	console.error("HTTP status code: ", ioArgs.xhr.status); //
		          	return responseObject;
	          	}
		    });
        }

        function renderError() {
        	dojo.byId('geoResults').innerHTML = '<p>The page could not get your location.</p>';
        }
    </script>
</head>

<body onload="startgeolocation();">

	<div align="center">
	<h1>GeoLocation Demo</h1>
	<div id="geoResults">
	    <div id="d"><img src="loadingAnimation.gif" /></div>
	    <div id="jsonResults">city information are fetched...</div>
	</div>

</body>
</html>

Das einzig verwunderliche ist vielleicht der AJAX-Aufruf. Ich hatte ja geschrieben, dass ich den Namen der Stadt von geonames.org holen möchte. Da die Cross-Domain-Policy nur einen Ajax-Aufruf zum selben Server erlaubt, brauchen wir ein kleines „Proxy-Script“, welches so aussieht:

<?php
echo file_get_contents('http://ws.geonames.org/findNearbyPlaceNameJSON?lat='.$_GET['lat'].'&lng='.$_GET['lon']);
?>

Damit umgehen wir die Cross-Domain-Policy und erhalten unser Ergebnis in JSON-Form. Das war auch schon der ganze Zauber.

Aktuell beherrschen meines Wissens nach der Firefox 3.5, Chrome und die aktuellen Beta-Versionen von Safari und Opera diese Funktionalität. Google Gears bietet eine ähnliche Funktionalität (browserübergreifend), aber ich kennen niemanden, der Gears installiert hat.
Man kann übrigens im Firefox den Location Service ändern, falls man Google nicht mag:
geolocation4

Falls ihr ein Handy mit Internet habt, könnt ihr auch mal Google Maps Mobile ausprobieren. Außerdem könnt ihr hier mein Demo-Script ausprobieren.

Ich bin gespannt, was für interessante Dienste da bald aus dem Boden sprießen. Ich meine schonmal etwas gehört zu haben von „gucken wo Freunde gerade sind“ und sowas. Ich bin auch gespannt, wann ich endlich ein neues Handy mit Datentarif bekomme…

Written by Michael Kliewe

September 10th, 2009 at 10:33 am

Posted in PHP

Tagged with , ,

Nicht-HTML-Responses mit dem Zend Framework

with 5 comments

Wenn man dynamische Bilder oder RSS-Feeds oder einen AJAX/JSON-Service oder ein Excel-Export mithilfe des Zend Frameworks erstellen will, mußt man 2-3 wichtige Dinge beachten. Der Code soll dann in einem Rss-/Graph-/Ajax-/Export-Controller stehen.

Ein Problem bekommt man, wenn man ein Layout benutzt (Zend_Layout). Denn dann wird dieses Layout immer ausgegeben. Im hier betrachteten Fall wäre das aber sehr schädlich, denn dadurch würden wir unser Bild/RSS-Feed/AJAX/Excel-Response zerstören.

Unschön kann man das wie folgt lösen:

public function rssAction()
{
	// calculate rss data and echo it (with correct headers)

	exit;
}

Richtig und deutlich schöner ist das Abschalten des Layout in der Action, wie folgt:

public function rssAction()
{
	// disable layout
	$this->_helper->layout()->disableLayout();
	
	// disable view rendering
	$this->_helper->viewRenderer->setNoRender();
	
	// calculate rss data and echo it (with correct headers)
}

Wir schalten auch gleich noch den ViewRenderer mit aus, damit auch nicht versucht wird, ein Viewscript zu rendern (das es wahrscheinlich garnicht gibt).

Dieses RSS-Beispiel funktioniert natürlich genauso für die anderen Beispiele, wo kein klassischer HTML-Quelltext zurückgegeben werden soll, sondern eine Antwort in einem anderen Format gefordert ist.

Damit kann man dann seine dynmisch erstellten Bilder (z.B. mittels pChart, jpgraph oder direkt die GD-Funktionen/image* in php), RSS-Feeds (Zend_Feed), Ajax-Services (Zend_Json) usw. realisieren.

Hier noch schnell ein Beispiel eines Excel-Exports aus einer Datenbank, mit Hilfe der Spreadsheet-Klasse aus dem PEAR-Framework (vereinfacht auf das Wesentliche):

class ExportController extends Zend_Controller_Action
{	
	public function excel() {
		// disable layout
		$this->_helper->layout()->disableLayout();
		
		// disable view rendering
		$this->_helper->viewRenderer->setNoRender();

		
		// get some data from database here
		
		       
		// create empty file
		//include 'Spreadsheet/Excel/Writer.php';
		$excel = new Spreadsheet_Excel_Writer();
		// add worksheet
		$sheet =& $excel->addWorksheet('Daily Export');

		$sheet->setColumn(0,0,20);
		$sheet->setColumn(1,1,15);
		$sheet->setColumn(2,2,18);
		$sheet->setColumn(3,3,23);
		$sheet->setColumn(4,4,35);
		$sheet->setColumn(5,5,15);

		$format_bold =& $excel->addFormat();
		$format_bold->setBold();

		$format_headline =& $excel->addFormat();
		$format_headline->setBold();
		$format_headline->setSize(20);
		$format_headline->setAlign('center');

		// headline
		$sheet->write(0, 0, 'Results: '.date('d.m.Y H:i'), $format_headline);
		$sheet->mergeCells(0,0,0,5);

		// add data to worksheet
		$rowCount=2;

		foreach ($data as $groupName=>$serverData) {
			$sheet->write($rowCount, 0, $groupName, $format_bold);
			$rowCount++;

			foreach ($serverData as $row) {
				$colcount = 0;
				foreach ($row as $key => $value) {
					$sheet->write($rowCount, $colcount, $value);
					$colcount++;
				}
				$rowCount++;
			}
			$rowCount++;
		}
		// send client headers
		$excel->send('daily_export_'.date("Ymd-His").'.xls');
	}		
}

Dies hier ist alter Code, mittlerweile nutzen wir PHPExcel.

Written by Michael Kliewe

Juli 2nd, 2009 at 2:52 pm

Posted in PHP

Tagged with , , , , , ,