| 1 | <?php |
|---|
| 2 | |
|---|
| 3 | /** |
|---|
| 4 | * gets several positions and returns a clustered array |
|---|
| 5 | * |
|---|
| 6 | * @author Torben Brodt |
|---|
| 7 | * @license GNU General Public License <http://opensource.org/licenses/gpl-3.0.html> |
|---|
| 8 | */ |
|---|
| 9 | class GmapCluster { |
|---|
| 10 | |
|---|
| 11 | const OFFSET = 268435456; |
|---|
| 12 | const RADIUS = 85445659.4471; /* $self::OFFSET / pi() */ |
|---|
| 13 | |
|---|
| 14 | protected $distance; |
|---|
| 15 | protected $zoom; |
|---|
| 16 | |
|---|
| 17 | /** |
|---|
| 18 | * |
|---|
| 19 | * @param $distance Distance in pixel inside which markers will be clustered. |
|---|
| 20 | * @param $zoom Current map zoom level. |
|---|
| 21 | */ |
|---|
| 22 | public function __construct($distance, $zoom) { |
|---|
| 23 | $this->distance = $distance; |
|---|
| 24 | $this->zoom = $zoom; |
|---|
| 25 | } |
|---|
| 26 | |
|---|
| 27 | protected function lonToX($lon) { |
|---|
| 28 | return round(self::OFFSET + self::RADIUS * $lon * pi() / 180); |
|---|
| 29 | } |
|---|
| 30 | |
|---|
| 31 | protected function latToY($lat) { |
|---|
| 32 | return round(self::OFFSET - self::RADIUS * log((1 + sin($lat * pi() / 180)) / (1 - sin($lat * pi() / 180))) / 2); |
|---|
| 33 | } |
|---|
| 34 | |
|---|
| 35 | protected function pixelDistance($lat1, $lon1, $lat2, $lon2, $zoom) { |
|---|
| 36 | $x1 = $this->lonToX($lon1); |
|---|
| 37 | $y1 = $this->latToY($lat1); |
|---|
| 38 | |
|---|
| 39 | $x2 = $this->lonToX($lon2); |
|---|
| 40 | $y2 = $this->latToY($lat2); |
|---|
| 41 | |
|---|
| 42 | return sqrt(pow(($x1-$x2),2) + pow(($y1-$y2),2)) >> (21 - $zoom); |
|---|
| 43 | } |
|---|
| 44 | |
|---|
| 45 | /** |
|---|
| 46 | * Return average center of markers |
|---|
| 47 | * |
|---|
| 48 | * @return object Google_Maps_Coordinate |
|---|
| 49 | */ |
|---|
| 50 | protected function getCluster(array $markers, $return_ids = false) { |
|---|
| 51 | $count = count($markers); |
|---|
| 52 | $ids = array(); |
|---|
| 53 | |
|---|
| 54 | /* Calculate average lat and lon of markers. */ |
|---|
| 55 | $lat_sum = $lon_sum = 0; |
|---|
| 56 | foreach ($markers as $marker) { |
|---|
| 57 | $lat_sum += $marker['lat']; |
|---|
| 58 | $lon_sum += $marker['lon']; |
|---|
| 59 | if($return_ids) { |
|---|
| 60 | $ids[] = $marker['id']; |
|---|
| 61 | } |
|---|
| 62 | } |
|---|
| 63 | $lat_avg = $lat_sum / $count; |
|---|
| 64 | $lon_avg = $lon_sum / $count; |
|---|
| 65 | |
|---|
| 66 | $data = array( |
|---|
| 67 | 'count' => $count, |
|---|
| 68 | 'lat' => $lat_avg, |
|---|
| 69 | 'lon' => $lon_avg |
|---|
| 70 | ); |
|---|
| 71 | if(count($ids)) { |
|---|
| 72 | $data['ids'] = $ids; |
|---|
| 73 | } |
|---|
| 74 | |
|---|
| 75 | return $data; |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | /** |
|---|
| 79 | * |
|---|
| 80 | * @param $markers Array of lat and lon locations. |
|---|
| 81 | */ |
|---|
| 82 | public function getMarkers(array $markers, $pick = array()) { |
|---|
| 83 | $clustered = array(); |
|---|
| 84 | |
|---|
| 85 | /* Loop until all markers have been compared. */ |
|---|
| 86 | while (count($markers)) { |
|---|
| 87 | $marker = array_pop($markers); |
|---|
| 88 | $cluster = array(); |
|---|
| 89 | |
|---|
| 90 | /* Compare against all markers which are left. */ |
|---|
| 91 | foreach ($markers as $key => $target) { |
|---|
| 92 | $pixels = $this->pixelDistance($marker['lat'], $marker['lon'], $target['lat'], $target['lon'], $this->zoom); |
|---|
| 93 | |
|---|
| 94 | /* If two markers are closer than given distance remove */ |
|---|
| 95 | /* target marker from array and add it to cluster. */ |
|---|
| 96 | if ($this->distance > $pixels) { |
|---|
| 97 | unset($markers[$key]); |
|---|
| 98 | $cluster[] = $target; |
|---|
| 99 | } |
|---|
| 100 | } |
|---|
| 101 | |
|---|
| 102 | /* If a marker has been added to cluster, add also the one */ |
|---|
| 103 | /* we were comparing to and remove the original from array. */ |
|---|
| 104 | if (count($cluster) > 0) { |
|---|
| 105 | $cluster[] = $marker; |
|---|
| 106 | $clustered[] = $this->getCluster($cluster); |
|---|
| 107 | } else { |
|---|
| 108 | $clustered[] = $marker; |
|---|
| 109 | } |
|---|
| 110 | } |
|---|
| 111 | return $clustered; |
|---|
| 112 | } |
|---|
| 113 | |
|---|
| 114 | /** |
|---|
| 115 | * |
|---|
| 116 | * @param $markers Array of lat and lon locations. |
|---|
| 117 | */ |
|---|
| 118 | public function getIDs(array $markers, $lat, $lon) { |
|---|
| 119 | |
|---|
| 120 | /* Loop until all markers have been compared. */ |
|---|
| 121 | while (count($markers)) { |
|---|
| 122 | $marker = array_pop($markers); |
|---|
| 123 | $cluster = array(); |
|---|
| 124 | |
|---|
| 125 | /* Compare against all markers which are left. */ |
|---|
| 126 | foreach ($markers as $key => $target) { |
|---|
| 127 | $pixels = $this->pixelDistance($marker['lat'], $marker['lon'], $target['lat'], $target['lon'], $this->zoom); |
|---|
| 128 | |
|---|
| 129 | /* If two markers are closer than given distance remove */ |
|---|
| 130 | /* target marker from array and add it to cluster. */ |
|---|
| 131 | if ($this->distance > $pixels) { |
|---|
| 132 | unset($markers[$key]); |
|---|
| 133 | $cluster[] = $target; |
|---|
| 134 | } |
|---|
| 135 | } |
|---|
| 136 | |
|---|
| 137 | /* If a marker has been added to cluster, add also the one */ |
|---|
| 138 | /* we were comparing to and remove the original from array. */ |
|---|
| 139 | if (count($cluster) > 0) { |
|---|
| 140 | $cluster[] = $marker; |
|---|
| 141 | $cluster = $this->getCluster($cluster, true); |
|---|
| 142 | if(abs($cluster['lat'] - $lat) < 0.000000001 && abs($cluster['lon'] - $lon) < 0.000000001) { |
|---|
| 143 | return $cluster['ids']; |
|---|
| 144 | } |
|---|
| 145 | } else { |
|---|
| 146 | if(abs($marker['lat'] - $lat) < 0.000000001 && abs($marker['lon'] - $lon) < 0.000000001) { |
|---|
| 147 | return array($marker['id']); |
|---|
| 148 | } |
|---|
| 149 | } |
|---|
| 150 | } |
|---|
| 151 | return null; |
|---|
| 152 | } |
|---|
| 153 | } |
|---|