2 namespace App\Controller;
4 use App\Controller\AppController;
5 use Avmaps\Controller\Component\SimpleMapsComponent;
9 * Categories Controller
11 * @property \App\Model\Table\CategoriesTable $Categories
13 class CategoriesController extends AppController
15 // keeps track of the API key to be used for our google queries, if one is known.
16 private $api_key = null;
18 public function initialize()
22 // load up the request handler to make processing easier on ajax requests.
23 $this->loadComponent('RequestHandler');
25 // configure the request handler to deal with json encoding.
26 $this->RequestHandler->config('inputTypeMap.json', ['json_decode', true]);
28 $this->api_key = SimpleMapsComponent::getGoogleAPIKey();
30 // allow simple access to location db.
31 $this->loadModel ( 'Locations' );
40 * @return \Cake\Network\Response|null
42 public function index()
44 $categories = $this->paginate($this->Categories);
46 $this->set(compact('categories'));
47 $this->set('_serialize', ['categories']);
53 * @param string|null $id Category id.
54 * @return \Cake\Network\Response|null
55 * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
57 public function view($id = null)
59 $category = $this->Categories->get($id, [
60 'contain' => ['Locations']
63 $this->set('category', $category);
64 $this->set('_serialize', ['category']);
66 // query all the locations belonging to this category but only retrieve part of the info.
67 $locationBits = $this->Categories->CategoriesLocations->find('list', [
68 'conditions' => [ 'category_id' => $id ],
69 'contain' => ['Locations'],
70 'valueField' => function ($row) {
71 return $row->location->get('lat')
72 . ',' . $row->location->get('lng')
73 . ' ' . $row->location->get('location');
76 $this->set('locationBits', $locationBits);
80 * queries the locations to display on a map given an "id" to lookup in categories table.
82 public function map($id = null)
84 $category = $this->Categories->get($id, [
85 'contain' => ['Locations']
88 $locationsFound = $this->Categories->getLocationsInCategory($id);
89 $this->set('locationsInCategory', $locationsFound);
91 // grab the flags in requests.
92 $parms = $this->request->getQueryParams();
94 // we will cluster by default, but they can also pass the clustering flag with 0 or 1.
95 $clustering = (array_key_exists('clustering', $parms) && ($parms['clustering'] == '1'))
96 || !array_key_exists('clustering', $parms);
97 //Log::Debug('clustering is: ' . var_export($clustering, true));
99 // set up the selected items in our config chooser.
102 array_push($selectedList, 'clustering');
104 $this->set('selectedList', $selectedList);
106 // pass the pre-loading function in for the map element.
107 $this->set('dbProcessor', $this);
108 $this->set('preloader', '$this->MapDisplay->addMarkers($dbProcessor, $locationsInCategory, $category );');
110 if ($this->request->is(['post', 'put'])) {
112 $postData = $this->request->getData('view_options')['_ids'];
114 $new_clustering = is_array($postData) && in_array('clustering', $postData);
116 if ($clustering != $new_clustering) {
117 $this->redirect(['action' => 'map',
120 'clustering' => $new_clustering,
126 $this->set('category', $category);
128 // $this->set('_serialize', ['category']);
132 * processes a location row by extracting the important information into an array.
133 * separates the db structure from the pieces needed for google maps.
135 public function processRow(& $location_row)
138 'lat' => $location_row ['location'] ['lat'],
139 'lng' => $location_row ['location'] ['lng'],
140 'title' => $location_row ['location'] ['name'],
141 // kludge below adds extra space on content, since someone is not left justifying these.
142 'content' => h ( $location_row ['location'] ['location'] ) . ' ',
146 public function extractCategoryImage(& $category)
148 return $category->image;
153 * attempts to jump to the center of the category, given a particular interpretation of the
154 * category. if possible we will decide that the category is a state name, and then attempt
155 * to jump to the center of that state.
156 * will also show markers in the category.
158 public function center($id = null)
160 $category = $this->Categories->get($id, [
161 'contain' => ['Locations']
164 // grab the flags in requests.
165 $parms = $this->request->getQueryParams();
167 // we will cluster by default, but they can also pass the clustering flag with 0 or 1.
168 $clustering = (array_key_exists('clustering', $parms) && ($parms['clustering'] == '1'))
169 || !array_key_exists('clustering', $parms);
170 //Log::Debug('clustering is: ' . var_export($clustering, true));
172 // set up the selected items in our config chooser.
175 array_push($selectedList, 'clustering');
177 $this->set('selectedList', $selectedList);
180 // interpret the category name as a state if possible.
181 if (substr($category->name, 0, 3) == 'US-') {
182 // bingo, we have a US prefix on the string, so decide what state they mean.
183 $state = substr($category->name, 3, 2);
186 // perform the lookup to get the readable name. otherwise a state like indiana (IN) will
187 // not get geocoded properly. probably others as well.
188 $state = SimpleMapsComponent::lookupStateFromAbbreviation($state);
190 $this->set('state', $state);
192 Log::debug('state was found as: ' . $state);
194 if ($state !== null) {
195 $centeredPoint = SimpleMapsComponent::geocode($state . " USA", [
196 'key' => $this->api_key
198 $mapCenter = [ 'lat' => $centeredPoint[0], 'lng' => $centeredPoint[1] ] ;
199 $this->set('mapCenter', $mapCenter);
200 Log::Debug('computed a map center of: ' . var_export($mapCenter, true));
204 // thoughts on algorithm: is the center of the state according to google what we want to rely on?
205 // we could at least start there, record the lat/longs and then edit them later as we decide.
211 $radius = 40; // miles from center to show markers.
212 $lat = $mapCenter['lat'];
213 $long = $mapCenter['lng'];
216 Log::Debug('given lat=' . $lat . ' long='. $long . ' radius=' . $radius);
218 Log::Debug('loading locations in category within radius');
220 // compute the lat/long bounding box for our search.
221 $bounds = SimpleMapsComponent::calculateLatLongBoundingBox ( $lat, $long, $radius );
224 Log::Debug("failed to calculate the bounding box!");
226 Log::Debug("bounding box: " . var_export($bounds, true));
229 // query all the locations belonging to this category within the box.
230 $locationsInCategory = $this->Categories->getLocationsInCategoryInBox($id,
231 $bounds [0], $bounds [1], $bounds [2], $bounds [3] );
233 $this->set('locationsInCategory', $locationsInCategory);
235 $this->set('dbProcessor', $this);
236 $this->set('preloader', '$this->MapDisplay->addMarkers($dbProcessor, $locationsInCategory, $category );');
238 if ($this->request->is(['post', 'put'])) {
240 $postData = $this->request->getData('view_options')['_ids'];
242 $new_clustering = is_array($postData) && in_array('clustering', $postData);
244 if ($clustering != $new_clustering) {
245 $this->redirect(['action' => 'map',
248 'clustering' => $new_clustering,
254 $this->set('category', $category);
255 // $this->set('_serialize', ['category','selectedList']);
261 * @return \Cake\Network\Response|null Redirects on successful add, renders view otherwise.
263 public function add()
265 $category = $this->Categories->newEntity();
266 if ($this->request->is('post')) {
267 $category = $this->Categories->patchEntity($category, $this->request->getData());
268 if ($this->Categories->save($category)) {
269 $this->Flash->success(__('The category has been saved.'));
271 return $this->redirect(['action' => 'index']);
273 $this->Flash->error(__('The category could not be saved. Please, try again.'));
275 $this->set(compact('category'));
276 $this->set('_serialize', ['category']);
282 * @param string|null $id Category id.
283 * @return \Cake\Network\Response|null Redirects on successful edit, renders view otherwise.
284 * @throws \Cake\Network\Exception\NotFoundException When record not found.
286 public function edit($id = null)
288 $category = $this->Categories->get($id, [
291 if ($this->request->is(['patch', 'post', 'put'])) {
292 $category = $this->Categories->patchEntity($category, $this->request->getData());
293 if ($this->Categories->save($category)) {
294 $this->Flash->success(__('The category has been saved.'));
296 return $this->redirect(['action' => 'index']);
298 $this->Flash->error(__('The category could not be saved. Please, try again.'));
300 $this->set(compact('category'));
301 $this->set('_serialize', ['category']);
307 * @param string|null $id Category id.
308 * @return \Cake\Network\Response|null Redirects to index.
309 * @throws \Cake\Datasource\Exception\RecordNotFoundException When record not found.
311 public function delete($id = null)
313 $this->request->allowMethod(['post', 'delete']);
314 $category = $this->Categories->get($id);
315 if ($this->Categories->delete($category)) {
316 $this->Flash->success(__('The category has been deleted.'));
318 $this->Flash->error(__('The category could not be deleted. Please, try again.'));
321 return $this->redirect(['action' => 'index']);
325 * provides ajax friendly responses. expects to be passed 'category' in post data.
326 * requires bounding box for lookup be passed as 'sw_lat', 'sw_lng', 'ne_lat', 'ne_lng'.
328 public function lookupajax()
330 $reqData = $this->request->getData();
332 Log::Debug('got to lookup with data: ' . var_export($reqData, true));
334 // check that this is a post method, since we don't support anything else.
335 if (! $this->request->is(['post'])) {
336 die('this is a post method');
339 if (array_key_exists('action', $reqData)) {
340 $action = $reqData['action'];
342 if (strcasecmp($action, 'lookupBox') == 0) {
343 $this->findLocationsInCategoryWithinBounds($reqData);
344 } else if (strcasecmp($action, 'getInfo') == 0) {
345 $this->getInfoOnLocation($reqData);
347 die('lookupajax call was given unknown action: ' . $action);
351 die('lookupajax call has no action specified');
355 public function findLocationsInCategoryWithinBounds($reqData)
357 if (array_key_exists('category', $reqData)) {
358 $id = $reqData['category'];
360 // throw an exception here?
361 $message = 'failed to find category id in request data';
362 Log::Debug($message);
366 $category = $this->Categories->get($id, [
367 'contain' => ['Locations']
369 $this->set('category', $category);
371 if (array_key_exists('sw_lat', $reqData)) {
372 $sw_lat = $reqData['sw_lat'];
374 if (array_key_exists('sw_lng', $reqData)) {
375 $sw_lng = $reqData['sw_lng'];
377 if (array_key_exists('ne_lat', $reqData)) {
378 $ne_lat = $reqData['ne_lat'];
380 if (array_key_exists('ne_lng', $reqData)) {
381 $ne_lng = $reqData['ne_lng'];
384 //temp! fails over to using whole range.
385 if ($sw_lat === null) { $sw_lat = -90; }
386 if ($sw_lng === null) { $sw_lng = -180; }
387 if ($ne_lat === null) { $ne_lat = 90; }
388 if ($ne_lng === null) { $ne_lng = 180; }
391 if (array_key_exists('start', $reqData)) {
392 $start = $reqData['start'];
395 if (array_key_exists('end', $reqData)) {
396 $end = $reqData['end'];
399 // lookup the locations inside that box and store for view.
400 $locationsToSerialize = $this->Categories->getChewedLocationsInCategoryInBox($id, $sw_lat, $sw_lng, $ne_lat, $ne_lng, $start, $end);
401 //Log::debug('before encoding, php array looks like: ' . var_export($locationsToSerialize, true));
402 Log::Debug("returning json now...");
403 $encoded = json_encode($locationsToSerialize);
404 // Log::debug('encoded json is: ' . var_export($encoded, true));
408 public function getInfoOnLocation($reqData)
410 if (array_key_exists('id', $reqData)) {
411 $id = $reqData['id'];
413 // throw an exception here?
414 $message = 'failed to find location id in request data';
415 Log::Debug($message);
419 $location = $this->Locations->get($id, [
421 return die(json_encode($location));