Skip to content

Commit 267bd9d

Browse files
committed
Merge pull request #133 from heiglandreas/feature/addLocationBasedSearch
Adds location based search of usergroups
2 parents 86ec192 + 0fe763c commit 267bd9d

File tree

5 files changed

+346
-1
lines changed

5 files changed

+346
-1
lines changed

Diff for: src/module/Phpug/config/module.config.php

+1
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,7 @@
444444
'Phpug\Api\Rest\Twitter' => 'Phpug\Api\Rest\TwitterController',
445445
'Phpug\Api\v1\Usergroup' => 'Phpug\Api\v1\UsergroupController',
446446
'Phpug\Api\v1\Calendar' => 'Phpug\Api\v1\CalendarController',
447+
'Phpug\Api\v1\Location' => 'Phpug\Api\v1\LocationController',
447448
'Phpug\Controller\MentoringController' => 'Phpug\Controller\MentoringController',
448449
'Phpug\Controller\EventCacheController' => 'Phpug\Controller\EventCacheController',
449450
'Phpug\Controller\TwitterController' => 'Phpug\Controller\TwitterController',

Diff for: src/module/Phpug/src/Phpug/Api/v1/CalendarController.php

+47-1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
namespace Phpug\Api\v1;
3333

34+
use Phpug\ORM\Query\AST\Functions\DistanceFrom;
3435
use Zend\Mvc\Controller\AbstractActionController;
3536
use Sabre\VObject;
3637
use Zend\Json\Json;
@@ -64,10 +65,16 @@ public function listAction()
6465
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
6566
$result = $em->getRepository('Phpug\Entity\Cache')->findBy(array('type' => 'event'));
6667
$calendar = new VObject\Component\VCalendar();
68+
$affectedUGs = $this->findGroupsWithinRangeAndDistance();
6769
foreach ($result as $cal) {
6870
if (! $cal->getGRoup()) {
6971
continue;
7072
}
73+
74+
if ($affectedUGs && ! in_array($cal->getGroup()->getShortname(), $affectedUGs)) {
75+
continue;
76+
}
77+
7178
try {
7279
$ical = VObject\Reader::read($cal->getCache());
7380
foreach ($ical->children as $event) {
@@ -81,7 +88,7 @@ public function listAction()
8188
} catch(\Exception $e){}
8289

8390
}
84-
91+
8592
$viewModel = $this->getViewModel();
8693

8794
return $viewModel->setVariable('calendar', new \Phpug\Wrapper\SabreVCalendarWrapper($calendar));
@@ -114,4 +121,43 @@ protected function setAcceptHeaderAccordingToParameters()
114121

115122
return $this;
116123
}
124+
125+
protected function findGroupsWithinRangeAndDistance()
126+
{
127+
$lat = $this->params()->fromQuery('latitude', null);
128+
$lon = $this->params()->fromQuery('longitude', null);
129+
$distance = $this->params()->fromQuery('distance', null);
130+
$number = $this->params()->fromQuery('count', null);
131+
132+
if (! $lat || ! $lon) {
133+
return array();
134+
}
135+
/** @var \Doctrine\ORM\EntityManager $em */
136+
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
137+
DistanceFrom::setLatitudeField('latitude');
138+
DistanceFrom::setLongitudeField('longitude');
139+
DistanceFrom::setRadius(6367);
140+
$em->getConfiguration()->addCustomNumericFunction('DISTANCEFROM', 'Phpug\ORM\Query\AST\Functions\DistanceFrom');
141+
142+
$qs = 'SELECT p, DISTANCEFROM(' . (float) $lat . ',' . (float) $lon . ') AS distance FROM \Phpug\Entity\Usergroup p WHERE p.state = 1 ';
143+
144+
145+
if ($distance) {
146+
$qs .= ' AND DISTANCEFROM(' . (float) $lat . ',' . (float) $lon . ') <= ' . (float) $distance;
147+
}
148+
149+
$qs .= ' ORDER BY distance';
150+
151+
$query = $em->createQuery($qs);
152+
if ($number) {
153+
$query->setMaxResults($number);
154+
}
155+
156+
$res = array();
157+
foreach ($query->getResult() as $result) {
158+
$res[] = $result[0]->getShortname();
159+
}
160+
161+
return $res;
162+
}
117163
}
+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
<?php
2+
/**
3+
* Copyright (c)2014-2014 heiglandreas
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIBILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*
23+
* @category
24+
* @author Andreas Heigl<[email protected]>
25+
* @copyright ©2014-2014 Andreas Heigl
26+
* @license http://www.opesource.org/licenses/mit-license.php MIT-License
27+
* @version 0.0
28+
* @since 04.02.14
29+
* @link https://github.com/heiglandreas/
30+
*/
31+
32+
namespace Phpug\Api\v1;
33+
34+
use Phpug\ORM\Query\AST\Functions\DistanceFrom;
35+
use Zend\Mvc\Controller\AbstractActionController;
36+
use Sabre\VObject;
37+
use Zend\Json\Json;
38+
39+
class LocationController extends AbstractActionController
40+
{
41+
private $acceptCriteria = array(
42+
'Zend\View\Model\JsonModel' => array(
43+
'application/json',
44+
'text/html',
45+
),
46+
'Zend\View\Model\FeedModel' => array('application/rss+xml'),
47+
);
48+
49+
/**
50+
* Get a list of groups that are nearest to the given coordinates.
51+
*
52+
* Coordinates are given via the parameters <var>lat</var and <var>lon</var>,
53+
* the maximum distance from the current location is given via <var>distance</var>
54+
* and the maximum number of entries is given via the parameter <var>max</var>
55+
*
56+
* This method will then return the maximum number of entries within the given
57+
* range. If less than the maximum number of entries is found within the distance
58+
* only that number of entries will be returned. When no distance is given or the
59+
* distance is given as "0" the maximum number of entries will be retrieved.
60+
*
61+
* @return mixed|\Zend\View\Model\ModelInterface
62+
* @throws \UnexpectedValueException
63+
*/
64+
public function nextGroupsAction()
65+
{
66+
$adapter = $this->getAdapter();
67+
$response = $this->getResponse();
68+
$viewModel = $this->getViewModel();
69+
70+
Json::$useBuiltinEncoderDecoder = true;
71+
72+
// Get Latitude, Longitude, distance and/or number of groups to retrieve
73+
$latitude = $this->params()->fromQuery('latitude');
74+
$longitude = $this->params()->fromQuery('longitude');
75+
$distance = $this->params()->fromQuery('distance', null);
76+
$number = $this->params()->fromQuery('count', null);
77+
78+
79+
//$this->sorter = $this->getServiceManager('Phpug\Sorter\Usergroup\Distance');
80+
$groups = $this->findGroupsWithinRangeAndDistance($latitude, $longitude, $distance, $number);
81+
$return = array(
82+
'currentLocation' => array(
83+
'latitude' => $latitude,
84+
'longitude' => $longitude,
85+
),
86+
'groups' => array(),
87+
);
88+
// $hydrator = $this->getServiceManager('Phpug\Hydrator\Usergroup');
89+
foreach ($groups as $group) {
90+
$grp = array(
91+
'name' => $group[0]->getName(),
92+
'latitude' => $group[0]->getLatitude(),
93+
'longitude' => $group[0]->getLongitude(),
94+
'shortname' => $group[0]->getShortname(),
95+
'distance' => $group['distance'],
96+
'icalendar_url' => $group[0]->getIcalendar_url(),
97+
'url' => $group[0]->getUrl(),
98+
'contacts' => array(),
99+
'uri' => '',
100+
);
101+
102+
foreach ($group[0]->getContacts() as $contact) {
103+
$grp['contacts'][] = array(
104+
'service' => $contact->getServiceName(),
105+
'name' => $contact->getName(),
106+
'uri' => $contact->getUrl(),
107+
);
108+
}
109+
110+
$return['groups'][] = $grp;
111+
}
112+
$viewModel->setVariable('groups', $return);
113+
114+
return $viewModel;
115+
}
116+
117+
protected function getAdapter()
118+
{
119+
$format = $this->params()->fromQuery('format', null);
120+
switch ($format) {
121+
case 'sphp':
122+
$contentType = 'text/plain';
123+
$adapter = '\Zend\Serializer\Adapter\PhpSerialize';
124+
break;
125+
case 'json':
126+
default:
127+
$contentType = 'application/json';
128+
$adapter = '\Zend\Serializer\Adapter\Json';
129+
break;
130+
}
131+
$this->getResponse()->getHeaders()->addHeaderLine('Content-Type', $contentType);
132+
133+
return new $adapter;
134+
}
135+
136+
protected function getViewModel()
137+
{
138+
return $this->acceptableViewModelSelector($this->acceptCriteria);
139+
}
140+
141+
protected function findGroupsWithinRangeAndDistance($lat, $lon, $distance = null, $number = null)
142+
{
143+
/** @var \Doctrine\ORM\EntityManager $em */
144+
$em = $this->getServiceLocator()->get('doctrine.entitymanager.orm_default');
145+
DistanceFrom::setLatitudeField('latitude');
146+
DistanceFrom::setLongitudeField('longitude');
147+
DistanceFrom::setRadius(6367);
148+
$em->getConfiguration()->addCustomNumericFunction('DISTANCEFROM', 'Phpug\ORM\Query\AST\Functions\DistanceFrom');
149+
150+
$qs = 'SELECT p, DISTANCEFROM(' . (float) $lat . ',' . (float) $lon . ') AS distance FROM \Phpug\Entity\Usergroup p WHERE p.state = 1 ';
151+
152+
153+
if ($distance) {
154+
$qs .= ' AND DISTANCEFROM(' . (float) $lat . ',' . (float) $lon . ') <= ' . (float) $distance;
155+
}
156+
157+
$qs .= ' ORDER BY distance';
158+
159+
$query = $em->createQuery($qs);
160+
if ($number) {
161+
$query->setMaxResults($number);
162+
}
163+
164+
return $query->getResult();
165+
166+
167+
}
168+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
/**
3+
* Copyright (c)2015-2015 heiglandreas
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIBILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*
23+
* @category
24+
* @author Andreas Heigl<[email protected]>
25+
* @copyright ©2015-2015 Andreas Heigl
26+
* @license http://www.opesource.org/licenses/mit-license.php MIT-License
27+
* @version 0.0
28+
* @since 25.03.15
29+
* @link https://github.com/heiglandreas/
30+
*/
31+
32+
namespace Phpug\ORM\Query\AST\Functions;
33+
34+
35+
use Doctrine\ORM\Query\AST\Functions\FunctionNode;
36+
use Doctrine\ORM\Query\Lexer;
37+
38+
/**
39+
* DistanceFromFunction ::= "DISTANCEFROM" "(" ArithmeticPrimary "," ArithmeticPrimary ")"
40+
*
41+
* This distance is calculated acording to http://www.movable-type.co.uk/scripts/gis-faq-5.1.html
42+
*
43+
* @see http://www.movable-type.co.uk/scripts/gis-faq-5.1.html
44+
*/
45+
class DistanceFrom extends FunctionNode
46+
{
47+
protected static $latitudeField = 'latitude';
48+
protected static $longitudeField = 'longitude';
49+
50+
/**
51+
* This is the radius of the sphere.
52+
*
53+
* For the earth use 6367 for distance results in kilometers or
54+
* ?? for results in miles.
55+
*
56+
* @var float
57+
*/
58+
protected static $radius = 6367;
59+
60+
protected $latitude = null;
61+
62+
protected $longitude = null;
63+
64+
65+
/**
66+
* @param \Doctrine\ORM\Query\SqlWalker $sqlWalker
67+
*
68+
* @return string
69+
*/
70+
public function getSql(\Doctrine\ORM\Query\SqlWalker $sqlWalker)
71+
{
72+
return sprintf(
73+
'(asin(sqrt(pow(sin((%2$s*0.017453293-%4$f*0.017453293)/2),2) + cos(%2$s*0.017453293) * cos(%4$f*0.017453293) * pow(sin((%3$s*0.017453293-%5$f*0.017453293)/2),2))) * %1$f)',
74+
self::getRadius(),
75+
self::getLatitudeField(),
76+
self::getLongitudeField(),
77+
$this->latitude->dispatch($sqlWalker),
78+
$this->longitude->dispatch($sqlWalker)
79+
);
80+
}
81+
82+
/**
83+
* @param \Doctrine\ORM\Query\Parser $parser
84+
*
85+
* @return void
86+
*/
87+
public function parse(\Doctrine\ORM\Query\Parser $parser)
88+
{
89+
$parser->match(Lexer::T_IDENTIFIER);
90+
$parser->match(Lexer::T_OPEN_PARENTHESIS);
91+
$this->latitude = $parser->ArithmeticPrimary();
92+
$parser->match(Lexer::T_COMMA);
93+
$this->longitude = $parser->ArithmeticPrimary();
94+
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
95+
96+
}
97+
98+
public static function getRadius()
99+
{
100+
return self::$radius;
101+
}
102+
103+
public static function getLatitudeField()
104+
{
105+
return self::$latitudeField;
106+
}
107+
108+
public static function getLongitudeField()
109+
{
110+
return self::$longitudeField;
111+
}
112+
113+
public static function setLongitudeField($longitude)
114+
{
115+
self::$longitudeField = (string) $longitude;
116+
}
117+
118+
public static function setLatitudeField($latitude)
119+
{
120+
self::$latitudeField = (string) $latitude;
121+
}
122+
123+
public static function setRadius($radius)
124+
{
125+
self::$radius = (float) $radius;
126+
}
127+
128+
129+
}

Diff for: src/module/Phpug/src/Phpug/Wrapper/SabreVCalendarWrapper.php

+1
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public function getEvents(\DateInterval $interval)
6262
$now = new \DateTime();
6363
$then = (new \DateTime())->add($interval);
6464
$this->object->expand($now, $then);
65+
$return = array();
6566
foreach ($this->object->children as $item) {
6667
if (! $item instanceof VEvent) {
6768
continue;

0 commit comments

Comments
 (0)