| 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) { |
|---|
| 51 | $count = count($markers); |
|---|
| 52 | |
|---|
| 53 | /* Calculate average lat and lon of markers. */ |
|---|
| 54 | $lat_sum = $lon_sum = 0; |
|---|
| 55 | foreach ($markers as $marker) { |
|---|
| 56 | $lat_sum += $marker['lat']; |
|---|
| 57 | $lon_sum += $marker['lon']; |
|---|
| 58 | } |
|---|
| 59 | $lat_avg = $lat_sum / $count; |
|---|
| 60 | $lon_avg = $lon_sum / $count; |
|---|
| 61 | |
|---|
| 62 | return array( |
|---|
| 63 | 'count' => $count, |
|---|
| 64 | 'lat' => $lat_avg, |
|---|
| 65 | 'lon' => $lon_avg |
|---|
| 66 | ); |
|---|
| 67 | } |
|---|
| 68 | |
|---|
| 69 | /** |
|---|
| 70 | * |
|---|
| 71 | * @param $markers Array of lat and lon locations. |
|---|
| 72 | */ |
|---|
| 73 | public function getMarkers(array $markers) { |
|---|
| 74 | $clustered = array(); |
|---|
| 75 | |
|---|
| 76 | /* Loop until all markers have been compared. */ |
|---|
| 77 | while (count($markers)) { |
|---|
| 78 | $marker = array_pop($markers); |
|---|
| 79 | $cluster = array(); |
|---|
| 80 | |
|---|
| 81 | /* Compare against all markers which are left. */ |
|---|
| 82 | foreach ($markers as $key => $target) { |
|---|
| 83 | $pixels = $this->pixelDistance($marker['lat'], $marker['lon'], $target['lat'], $target['lon'], $this->zoom); |
|---|
| 84 | |
|---|
| 85 | /* If two markers are closer than given distance remove */ |
|---|
| 86 | /* target marker from array and add it to cluster. */ |
|---|
| 87 | if ($this->distance > $pixels) { |
|---|
| 88 | unset($markers[$key]); |
|---|
| 89 | $cluster[] = $target; |
|---|
| 90 | } |
|---|
| 91 | } |
|---|
| 92 | |
|---|
| 93 | /* If a marker has been added to cluster, add also the one */ |
|---|
| 94 | /* we were comparing to and remove the original from array. */ |
|---|
| 95 | if (count($cluster) > 0) { |
|---|
| 96 | $cluster[] = $marker; |
|---|
| 97 | $clustered[] = $this->getCluster($cluster); |
|---|
| 98 | } else { |
|---|
| 99 | $clustered[] = $marker; |
|---|
| 100 | } |
|---|
| 101 | } |
|---|
| 102 | return $clustered; |
|---|
| 103 | } |
|---|
| 104 | } |
|---|