// Do something else with the lonlat?
},
- onError: function(r) { },
+ onError: function(r)
+ {
+ KronolithCore.showNotifications([ { type: 'horde.error', message: Kronolith.text.geocode_error } ]);
+ },
- onGeocode: function(r) { },
+ onGeocode: function(r)
+ {
+ r = r.shift();
+ ll = new OpenLayers.LonLat(r.lon, r.lat);
+ this._map.moveMarker(this._marker, { lat: r.lat, lon: r.lon });
+ this._map.setCenter(ll);
+ $('kronolithEventLocationLon').value = r.lon;
+ $('kronolithEventLocationLat').value = r.lat;
+ },
geocode: function(a) {
if (!a) {
return;
}
var gc = new HordeMap.Geocoder[Kronolith.conf.maps.geocoder]();
- gc.geocode(a, function(r) {
- r = r.shift();
- ll = new OpenLayers.LonLat(r.lon, r.lat);
- this._map.moveMarker(this._marker, { lat: r.lat, lon: r.lon });
- this._map.setCenter(ll);
- }.bind(this),
- this.onError);
+ gc.geocode(a, this.onGeocode.bind(this), this.onError);
}
};
$tagger = Kronolith::getTagger();
$tagger->replaceTags($event->getUID(), $event->tags, 'event');
+ /* Update Geolocation */
+ if ($gDriver = Kronolith::getGeoDriver()) {
+ try {
+ $gDriver->setLocation($event->getId(), $event->geoLocation);
+ } catch (Horde_Exception $e) {
+ Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+ return new PEAR_Error($e->getMessage());
+ }
+ }
+
/* Notify users about the changed event. */
$result = Kronolith::sendNotification($event, 'edit');
if (is_a($result, 'PEAR_Error')) {
public $tags = array();
/**
+ * Geolocation
+ *
+ */
+ public $geoLocation = null;
+
+ /**
* Whether this is the event on the first day of a multi-day event.
*
* @var boolen
}
if ($eventObject !== null) {
+
+ /* Get tags */
$this->fromDriver($eventObject);
$tagger = Kronolith::getTagger();
$this->tags = $tagger->getTags($this->getUID(), 'event');
+
+ /* Get geolocation data */
+ if ($gDriver = Kronolith::getGeoDriver()) {
+ $this->geoLocation = $gDriver->getLocation($this->getId());
+ }
}
}
$json->et = $this->end->format($time_format);
$json->a = $this->alarm;
$json->tg = array_values($this->tags);
+ $json->gl = array_values($this->geoLocation);
if ($this->recurs()) {
$json->r = $this->recurrence->toJson();
}
// Tags.
$this->tags = Horde_Util::getFormData('tags', $this->tags);
+ // Geolocation
+ $this->geoLocation = array('lat' => Horde_Util::getFormData('lat'),
+ 'lon' => Horde_Util::getFormData('lon'));
$this->initialized = true;
}
--- /dev/null
+<?php
+/**
+ * Storage driver for Kronolith's Geo location data.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ *
+ * @package Kronolith
+ */
+abstract class Kronolith_Geo
+{
+ protected $_params;
+
+ public function __construct($params = array())
+ {
+ $this->_params = $params;
+ }
+
+ /**
+ * Obtain a Kronolith_Geo object. Currently all drivers are SQL based,
+ * so use the sql config by default.
+ *
+ * @param string $driver The type of object to return
+ * @param unknown_type $params Any driver specific parameters
+ *
+ * @return Kronolith_Geo
+ */
+ static public function factory($driver = null, $params = array())
+ {
+ $driver = basename($driver);
+ $class = 'Kronolith_Geo_' . $driver;
+
+ if (class_exists($class)) {
+ $driver = new $class(Horde::getDriverConfig('calendar', 'sql'));
+ }
+
+ $driver->initialize();
+ return $driver;
+ }
+
+ abstract public function setLocation($event_id, $point);
+ abstract public function getLocation($event_id);
+ abstract public function removeLocation($event_id);
+ abstract public function search($criteria);
+}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * Mysql implementation for storing/searching geo location data for events.
+ * Makes use of the GIS extensions available in mySQL 4.1 and later.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ *
+ * @package Kronolith
+ */
+class Kronolith_Geo_Mysql extends Kronolith_Geo_Sql
+{
+ // Rouughly 69 miles per distance unit
+ private $_conversionFactor = 69;
+
+ /**
+ * Set the location of the specified event _id
+ *
+ * @see kronolith/lib/Driver/Kronolith_Driver_Geo#setLocation($event_id, $point)
+ */
+ public function setLocation($event_id, $point)
+ {
+ var_dump($event_id);
+ var_dump($point);
+ /* First make sure it doesn't already exist */
+ $sql = "SELECT COUNT(*) FROM kronolith_events_geo WHERE event_id = ('" . $event_id . "')";
+ $count = $this->_db->getOne($sql);
+ if ($count instanceof PEAR_Error) {
+ throw new Horde_Exception($count->getMessage());
+ }
+ /* INSERT or UPDATE */
+ if ($count) {
+ $sql = "UPDATE kronolith_events_geo SET coordinates = GeomFromText('POINT(" . $point['lat'] . " " . $point['lon'] . ")') WHERE event_id = '" . $event_id . "'";
+ } else {
+ $sql = "INSERT into kronolith_events_geo (event_id, coordinates) VALUES('" . $event_id . "', GeomFromText('POINT(" . $point['lat'] . " " . $point['lon'] . ")'))";
+ }
+ $result = $this->_write_db->query($sql);
+ if ($result instanceof PEAR_Error) {
+ throw new Horde_Exception($result->getMessage());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the location of the provided event_id.
+ *
+ * @see kronolith/lib/Driver/Kronolith_Driver_Geo#getLocation($event_id)
+ */
+ public function getLocation($event_id)
+ {
+ $sql = "SELECT x(coordinates) as lat, y(coordinates) as lon FROM kronolith_events_geo WHERE event_id = '" . $event_id . "'";
+ $result = $this->_db->getRow($sql, null, DB_FETCHMODE_ASSOC);
+ if ($result instanceof PEAR_Error) {
+ throw new Horde_Exception($result->getMessage());
+ }
+ return $result;
+ }
+
+ /**
+ * Search for events "close to" a given point.
+ *
+ * TODO: If all we really use the geodata for is distance, it really doesn't
+ * make sense to use the GIS extensions since the distance calculations
+ * are done with Euclidian geometry ONLY ... and therefore will give
+ * incorrect results when done on a geocentric coordinate system...
+ * They might be useful if we eventually want to do searches on
+ * MBRs
+ *
+ * @see kronolith/lib/Driver/Kronolith_Driver_Geo#search($criteria)
+ */
+ public function search($criteria)
+ {
+ $point = $criteria['point'];
+ $limit = empty($criteria['limit']) ? 10 : $criteria['limit'];
+ $radius = empty($criteria['radius']) ? 10 : $criteria['radius'];
+
+ /* Allow overriding the default conversion factor */
+ $factor = empty($criteria['factor']) ? $this->_conversionFactor : $criteria['factor'];
+
+ // ... if this works it will be a miracle ;)
+ $sql = "SELECT event_id, "
+ . "GLength(LINESTRINGFromWKB(LineString(coordinates, GeomFromText('POINT(" . $point['lat'] . " " . $point['lon'] . ")')))) * " . $factor . " as distance, "
+ . "x(coordinates) as lat, y(coordinates) as lon FROM kronolith_events_geo HAVING distance < " . $radius . " ORDER BY distance ASC LIMIT " . $limit;
+
+ $results = $this->_db->getAssoc($sql, false, null, DB_FETCHMODE_ASSOC);
+ if ($results instanceof PEAR_Error) {
+ throw new Horde_Exception($results->getMessage());
+ }
+
+ return $results;
+
+ }
+}
\ No newline at end of file
--- /dev/null
+<?php
+/**
+ * General SQL implementation for storing/searching geo location data for events.
+ *
+ * Copyright 2009 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author Michael J. Rubinsky <mrubinsk@horde.org>
+ *
+ * @package Kronolith
+ */
+class Kronolith_Geo_Sql extends Kronolith_Geo
+{
+ protected $_write_db;
+ protected $_db;
+
+ /**
+ * Still needs to return a PEAR_Error since Kronolith_Driver still expects it
+ *
+ * @return mixed boolean || PEAR_Error
+ */
+ public function initialize()
+ {
+ Horde::assertDriverConfig($this->_params, 'calendar', array('phptype'));
+
+ if (!isset($this->_params['database'])) {
+ $this->_params['database'] = '';
+ }
+ if (!isset($this->_params['username'])) {
+ $this->_params['username'] = '';
+ }
+ if (!isset($this->_params['hostspec'])) {
+ $this->_params['hostspec'] = '';
+ }
+ if (!isset($this->_params['table'])) {
+ $this->_params['table'] = 'kronolith_events_geo';
+ }
+
+ /* Connect to the SQL server using the supplied parameters. */
+ $this->_write_db = DB::connect($this->_params,
+ array('persistent' => !empty($this->_params['persistent']),
+ 'ssl' => !empty($this->_params['ssl'])));
+ if (is_a($this->_write_db, 'PEAR_Error')) {
+ return $this->_write_db;
+ }
+ $this->_initConn($this->_write_db);
+
+ /* Check if we need to set up the read DB connection
+ * seperately. */
+ if (!empty($this->_params['splitread'])) {
+ $params = array_merge($this->_params, $this->_params['read']);
+ $this->_db = DB::connect($params,
+ array('persistent' => !empty($params['persistent']),
+ 'ssl' => !empty($params['ssl'])));
+ if (is_a($this->_db, 'PEAR_Error')) {
+ return $this->_db;
+ }
+ $this->_initConn($this->_db);
+ } else {
+ /* Default to the same DB handle for the writer too. */
+ $this->_db = $this->_write_db;
+ }
+
+ return true;
+ }
+
+ protected function _initConn(&$db)
+ {
+ // Set DB portability options.
+ switch ($db->phptype) {
+ case 'mssql':
+ $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS | DB_PORTABILITY_RTRIM);
+ break;
+ default:
+ $db->setOption('portability', DB_PORTABILITY_LOWERCASE | DB_PORTABILITY_ERRORS);
+ }
+
+ /* Handle any database specific initialization code to run. */
+ switch ($db->dbsyntax) {
+ case 'oci8':
+ $query = "ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS'";
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Kronolith_Driver_Sql::_initConn(): user = "%s"; query = "%s"',
+ Horde_Auth::getAuth(), $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ $db->query($query);
+ break;
+
+ case 'pgsql':
+ $query = "SET datestyle TO 'iso'";
+
+ /* Log the query at a DEBUG log level. */
+ Horde::logMessage(sprintf('Kronolith_Driver_Sql::_initConn(): user = "%s"; query = "%s"',
+ Horde_Auth::getAuth(), $query),
+ __FILE__, __LINE__, PEAR_LOG_DEBUG);
+
+ $db->query($query);
+ break;
+ }
+ }
+
+ /**
+ * Set the location of the specified event _id
+ *
+ * @see kronolith/lib/Driver/Kronolith_Driver_Geo#setLocation($event_id, $point)
+ */
+ public function setLocation($event_id, $point)
+ {
+ /* First make sure it doesn't already exist */
+ $sql = "SELECT COUNT(*) FROM kronolith_events_geo WHERE event_id = ('" . $event_id . "')";
+ $count = $this->_db->getOne($sql);
+ if ($count instanceof PEAR_Error) {
+ throw new Horde_Exception($count->getMessage());
+ }
+
+ /* INSERT or UPDATE */
+ $params = array($point['lat'], $point['lng'], $event_id);
+ if ($count) {
+ $sql = 'UPDATE kronolith_events_geo SET event_lat = ?, event_lng = ? WHERE event_id = ?';
+ } else {
+ $sql = 'INSERT into kronolith_events_geo (event_lat, event_lng, event_id) VALUES(?, ?, ?)';
+ }
+ $result = $this->_write_db->query($sql, $params);
+ if ($result instanceof PEAR_Error) {
+ throw new Horde_Exception($result->getMessage());
+ }
+
+ return $result;
+ }
+
+ /**
+ * Get the location of the provided event_id.
+ *
+ * @see kronolith/lib/Driver/Kronolith_Driver_Geo#getLocation($event_id)
+ */
+ public function getLocation($event_id)
+ {
+ $sql = 'SELECT event_lat as lat, event_lng as lng FROM kronolith_events_geo WHERE event_id = ?';
+ $result = $this->_db->getRow($sql, array($event_id), DB_FETCHMODE_ASSOC);
+ if ($result instanceof PEAR_Error) {
+ throw new Horde_Exception($result->getMessage());
+ }
+
+ return $result;
+ }
+
+ /**
+ *
+ * @see kronolith/lib/Driver/Kronolith_Driver_Geo#removeLocation($event_id)
+ */
+ public function removeLocation($event_id)
+ {
+ $sql = 'DELETE FROM kronolith_events_geo WHERE event_id = ?';
+ $this->_write_db->query($sql, array($event_id));
+ }
+
+ /**
+ * Search for events "close to" a given point.
+ *
+ * TODO: If all we really use the geodata for is distance, it really doesn't
+ * make sense to use the GIS extensions since the distance calculations
+ * are done with Euclidian geometry ONLY ... and therefore will give
+ * incorrect results when done on a geocentric coordinate system...
+ * They might be useful if we eventually want to do searches on
+ * MBRs
+ *
+ * @see kronolith/lib/Driver/Kronolith_Driver_Geo#search($criteria)
+ */
+ public function search($criteria)
+ {
+ throw new Horde_Exception(_("Searching requires a GIS enabled database."));
+ }
+}
\ No newline at end of file
'prefs' => _("Options"),
'no_url' => _("You must specify a URL."),
'wrong_auth' => _("The authentication information you specified wasn't accepted."),
+ 'geocode_error' => _("Unable to locate requested address"),
);
for ($i = 1; $i <= 12; ++$i) {
$code['text']['month'][$i - 1] = Horde_Nls::getLangInfo(constant('MON_' . $i));
return self::$_tagger;
}
+ public static function getGeoDriver()
+ {
+ /* Get geolocation data */
+ if ($GLOBALS['conf']['geo']['driver']) {
+ return Kronolith_Geo::factory($GLOBALS['conf']['geo']['driver']);
+ } else {
+ return false;
+ }
+ }
+
/**
* Obtain an internal calendar. Use this where we don't know if we will
* have a Horde_Share or a Kronolith_Resource based calendar.
<form id="kronolithEventForm" action="">
<input id="kronolithEventId" type="hidden" name="id" />
<input id="kronolithEventCalendar" type="hidden" name="cal" />
+<input id="kronolithEventLocationLon" type="hidden" name="lon" />
+<input id="kronolithEventLocationLat" type="hidden" name="lat" />
<div>
<label for="kronolithEventTitle"><?php echo _("Event title") ?>:</label><br />