1<?php 2/* 3 * Copyright (c) 2011-2014 Mark C. Prins <mprins@users.sf.net> 4 * 5 * Permission to use, copy, modify, and distribute this software for any 6 * purpose with or without fee is hereby granted, provided that the above 7 * copyright notice and this permission notice appear in all copies. 8 * 9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 */ 17 18/** 19 * DokuWiki Plugin spatialhelper (Search component). 20 * 21 * @license BSD license 22 * @author Mark Prins 23 */ 24class helper_plugin_spatialhelper_search extends DokuWiki_Plugin { 25 /** 26 * spatial index. 27 * 28 * @var array 29 */ 30 protected $spatial_idx = array(); 31 /** 32 * handle to the geoPHP plugin. 33 */ 34 protected $geophp; 35 /** 36 * Precision, Distance of Adjacent Cell in Meters. 37 * 38 * @see https://stackoverflow.com/questions/13836416/geohash-and-max-distance 39 * 40 * @var float 41 */ 42 private $precision = array( 43 5003530, 44 625441, 45 123264, 46 19545, 47 3803, 48 610, 49 118, 50 19, 51 3.7, 52 0.6 53 ); 54 55 /** 56 * constructor; initialize/load spatial index. 57 */ 58 public function __construct() { 59 // parent::__construct (); 60 global $conf; 61 62 if(!$geophp = plugin_load('helper', 'geophp')) { 63 $message = 'helper_plugin_spatialhelper_search::spatialhelper_search: geophp plugin is not available.'; 64 msg($message, -1); 65 } 66 67 $idx_dir = $conf ['indexdir']; 68 if(!@file_exists($idx_dir . '/spatial.idx')) { 69 $indexer = plugin_load('helper', 'spatialhelper_index'); 70 } 71 72 $this->spatial_idx = unserialize(io_readFile($fn = $idx_dir . '/spatial.idx', false)); 73 } 74 75 /** 76 * Find locations based on the coordinate pair. 77 * 78 * @param float $lat 79 * The y coordinate (or latitude) 80 * @param float $lon 81 * The x coordinate (or longitude) 82 */ 83 public function findNearbyLatLon(float $lat, float $lon): array { 84 $geometry = new Point($lon, $lat); 85 return $this->findNearby($geometry->out('geohash'), $geometry); 86 } 87 88 /** 89 * finds nearby elements in the index based on the geohash. 90 * returns a list of documents and the bunding box. 91 * 92 * @param string $geohash 93 * @param Point $p 94 * optional point 95 * @return array of ... 96 */ 97 public function findNearby(string $geohash, Point $p = null): array { 98 $_geohashClass = new Geohash(); 99 if(!$p) { 100 $decodedPoint = $_geohashClass->read($geohash); 101 } else { 102 $decodedPoint = $p; 103 } 104 105 // find adjacent blocks 106 $adjacent = array(); 107 $adjacent ['center'] = $geohash; 108 $adjacent ['top'] = $_geohashClass->adjacent($adjacent ['center'], 'top'); 109 $adjacent ['bottom'] = $_geohashClass->adjacent($adjacent ['center'], 'bottom'); 110 $adjacent ['right'] = $_geohashClass->adjacent($adjacent ['center'], 'right'); 111 $adjacent ['left'] = $_geohashClass->adjacent($adjacent ['center'], 'left'); 112 $adjacent ['topleft'] = $_geohashClass->adjacent($adjacent ['left'], 'top'); 113 $adjacent ['topright'] = $_geohashClass->adjacent($adjacent ['right'], 'top'); 114 $adjacent ['bottomright'] = $_geohashClass->adjacent($adjacent ['right'], 'bottom'); 115 $adjacent ['bottomleft'] = $_geohashClass->adjacent($adjacent ['left'], 'bottom'); 116 dbglog($adjacent, "adjacent geo hashes:"); 117 118 // find all the pages in the index that overlap with the adjacent hashes 119 $docIds = array(); 120 foreach($adjacent as $adjHash) { 121 if(is_array($this->spatial_idx)) { 122 foreach($this->spatial_idx as $_geohash => $_docIds) { 123 if(strpos($_geohash, $adjHash) !== false) { 124 // dbglog ( "Found adjacent geo hash: $adjHash in $_geohash" ); 125 // if $adjHash similar to geohash 126 $docIds = array_merge($docIds, $_docIds); 127 } 128 } 129 } 130 } 131 $docIds = array_unique($docIds); 132 dbglog($docIds, "found docIDs"); 133 134 // create associative array of pages + calculate distance 135 $pages = array(); 136 $media = array(); 137 $indexer = plugin_load('helper', 'spatialhelper_index'); 138 139 foreach($docIds as $id) { 140 if(strpos($id, 'media__', 0) === 0) { 141 $id = substr($id, strlen('media__')); 142 if(auth_quickaclcheck($id) >= /*AUTH_READ*/ 1) { 143 $point = $indexer->getCoordsFromExif($id); 144 $line = new LineString( 145 [ 146 $decodedPoint, 147 $point 148 ] 149 ); 150 $media [] = array( 151 'id' => $id, 152 'distance' => (int) ($line->greatCircleLength()), 153 'lat' => $point->y(), 154 'lon' => $point->x() 155 // optionally add other meta such as tag, description... 156 ); 157 } 158 } else { 159 if(auth_quickaclcheck($id) >= /*AUTH_READ*/ 1) { 160 $geotags = p_get_metadata($id, 'geo'); 161 $point = new Point($geotags ['lon'], $geotags ['lat']); 162 $line = new LineString( 163 [ 164 $decodedPoint, 165 $point 166 ] 167 ); 168 $pages [] = array( 169 'id' => $id, 170 'distance' => (int) ($line->greatCircleLength()), 171 'description' => p_get_metadata($id, 'description')['abstract'], 172 'lat' => $geotags ['lat'], 173 'lon' => $geotags ['lon'] 174 // optionally add other meta such as tag... 175 ); 176 } 177 } 178 } 179 180 // sort all the pages/media using distance 181 usort( 182 $pages, static function ($a, $b) { 183 return strnatcmp($a ['distance'], $b ['distance']); 184 } 185 ); 186 usort( 187 $media, static function ($a, $b) { 188 return strnatcmp($a ['distance'], $b ['distance']); 189 } 190 ); 191 192 return array( 193 'pages' => $pages, 194 'media' => $media, 195 'lat' => $decodedPoint->y(), 196 'lon' => $decodedPoint->x(), 197 'geohash' => $geohash, 198 'precision' => $this->precision [strlen($geohash)] 199 ); 200 } 201} 202