├── geo.php ├── lib ├── geo.php └── geo │ └── point.php ├── package.json └── readme.md /geo.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | 9 | use Kirby\Geo; 10 | 11 | /** 12 | * Autoloader for all Kirby GEO Classes 13 | */ 14 | load([ 15 | 'kirby\\geo' => __DIR__ . DS . 'lib' . DS . 'geo.php', 16 | 'kirby\\geo\\point' => __DIR__ . DS . 'lib' . DS . 'geo' . DS . 'point.php' 17 | ]); 18 | 19 | /** 20 | * Creates a class alias for the GEO class, to make it more usable 21 | */ 22 | class_alias('Kirby\\Geo', 'Geo'); 23 | 24 | /** 25 | * Adds a new radius filter to all collections 26 | */ 27 | collection::$filters['radius'] = function($collection, $field, $options) { 28 | 29 | $origin = geo::point(a::get($options, 'lat'), a::get($options, 'lng')); 30 | $radius = intval(a::get($options, 'radius')); 31 | $unit = a::get($options, 'unit', 'km') === 'km' ? 'km' : 'mi'; 32 | 33 | if(!$origin) { 34 | throw new Exception('Invalid geo point for radius filter. You must specify valid lat and lng values'); 35 | } 36 | 37 | if($radius === 0) { 38 | throw new Exception('Invalid radius value for radius filter. You must specify a valid integer value'); 39 | } 40 | 41 | foreach($collection->data as $key => $item) { 42 | 43 | $value = collection::extractValue($item, $field); 44 | 45 | // skip invalid points 46 | if(!is_string($value) and !is_a($value, 'Field')) { 47 | unset($collection->$key); 48 | continue; 49 | } 50 | 51 | try { 52 | $point = geo::point((string)$value); 53 | } catch(Exception $e) { 54 | unset($collection->$key); 55 | continue; 56 | } 57 | 58 | $distance = geo::distance($origin, $point, $unit); 59 | 60 | if($distance > $radius) { 61 | unset($collection->$key); 62 | } 63 | 64 | } 65 | 66 | return $collection; 67 | 68 | }; 69 | 70 | /** 71 | * Adds a new field method "coordinates", 72 | * which can be used to convert a field with 73 | * comma separated lat and long values to a Kirby Geo Point 74 | */ 75 | field::$methods['coordinates'] = function($field) { 76 | return geo::point($field->value); 77 | }; 78 | 79 | /** 80 | * Adds a new field method "distance", 81 | * which can be used to calculate the distance between a 82 | * field with comma separated lat and long values and a 83 | * valid Kirby Geo Point 84 | */ 85 | field::$methods['distance'] = function($field, $point, $unit = 'km') { 86 | if(!is_a($point, 'Kirby\\Geo\\Point')) { 87 | throw new Exception('You must pass a valid Geo Point object to measure the distance'); 88 | } 89 | return geo::distance($field->coordinates(), $point, $unit); 90 | }; 91 | 92 | /** 93 | * Same as distance, but will return a human readable version 94 | * of the distance instead of a long float 95 | */ 96 | field::$methods['niceDistance'] = function($field, $point, $unit = 'km') { 97 | if(!is_a($point, 'Kirby\\Geo\\Point')) { 98 | throw new Exception('You must pass a valid Geo Point object to measure the distance'); 99 | } 100 | return geo::niceDistance($field->coordinates(), $point, $unit); 101 | }; -------------------------------------------------------------------------------- /lib/geo.php: -------------------------------------------------------------------------------- 1 | 15 | * @license MIT 16 | * @link https://getkirby.com 17 | */ 18 | class Geo { 19 | 20 | /** 21 | * Creates a new Kirby Geo Point object 22 | * 23 | * @see Kirby\Geo\Point::make 24 | * @return Kirby\Geo\Point 25 | */ 26 | public static function point() { 27 | return call_user_func_array('Kirby\\Geo\\Point::make', func_get_args()); 28 | } 29 | 30 | /** 31 | * Converts Miles to Kilometers 32 | * 33 | * @param int|float $miles 34 | * @return float 35 | */ 36 | public static function milesToKilometers($miles) { 37 | return $miles * 1.60934; 38 | } 39 | 40 | /** 41 | * Converts Kilometers to Miles 42 | * 43 | * @param int|float $kilometers 44 | * @return float 45 | */ 46 | public static function kilometersToMiles($kilometers) { 47 | return $kilometers * 0.621371; 48 | } 49 | 50 | /** 51 | * Calculates the distance between to Kirby Geo Points 52 | * 53 | * @param Kirby\Geo\Point|string $a 54 | * @param Kirby\Geo\Point|string $b 55 | * @param null|string $unit ("km", "mi") 56 | * @return float 57 | */ 58 | public static function distance($a, $b, $unit = 'km') { 59 | 60 | if(!is_a($a, 'Kirby\\Geo\\Point')) { 61 | $a = geo::point($a); 62 | } 63 | 64 | if(!is_a($b, 'Kirby\\Geo\\Point')) { 65 | $b = geo::point($b); 66 | } 67 | 68 | $theta = $a->lng() - $b->lng(); 69 | $dist = sin(deg2rad($a->lat())) * sin(deg2rad($b->lat())) + cos(deg2rad($a->lat())) * cos(deg2rad($b->lat())) * cos(deg2rad($theta)); 70 | $dist = acos($dist); 71 | $dist = rad2deg($dist); 72 | $miles = $dist * 60 * 1.1515; 73 | 74 | if(strtolower($unit) === 'km') { 75 | return static::milesToKilometers($miles); 76 | } else { 77 | return $miles; 78 | } 79 | 80 | } 81 | 82 | /** 83 | * Calculates the distance between to Kirby Geo Points 84 | * and returns the result in a a human readable format 85 | * 86 | * @param Kirby\Geo\Point|string $a 87 | * @param Kirby\Geo\Point|string $b 88 | * @param null|string $unit ("km", "mi") 89 | * @return string 90 | */ 91 | public static function niceDistance($a, $b, $unit = 'km') { 92 | return number_format(static::distance($a, $b, $unit), 2) . ' ' . strtolower($unit); 93 | } 94 | 95 | /** 96 | * Returns a Kirby Geo Point for a given address 97 | * 98 | * @param string $address 99 | * @param array $components Additional component info for the Google Geo Locator 100 | * @return Kirby\Geo\Point 101 | */ 102 | public static function locate($address, $components = []) { 103 | 104 | foreach($components as $key => $component) { 105 | $components[$key] = strtolower($key) . ':' . urlencode(strtolower($component)); 106 | } 107 | 108 | $string = str_replace(' ', '+', urlencode($address)); 109 | $url = 'https://maps.googleapis.com/maps/api/geocode/json?address=' . $string . '&components=' . implode('|', $components) . '&sensor=false'; 110 | $response = remote::get($url); 111 | 112 | if($response->error()) { 113 | throw new Exception('The Google geocoder call failed'); 114 | } 115 | 116 | $content = json_decode($response->content(), true); 117 | $results = a::get($content, 'results', []); 118 | $first = a::first($results, []); 119 | $geometry = a::get($first, 'geometry', []); 120 | $location = a::get($geometry, 'location', []); 121 | 122 | return static::point([ 123 | 'lat' => str_replace(',', '.', a::get($location, 'lat')), 124 | 'lng' => str_replace(',', '.', a::get($location, 'lng')), 125 | ]); 126 | 127 | } 128 | 129 | } -------------------------------------------------------------------------------- /lib/geo/point.php: -------------------------------------------------------------------------------- 1 | 13 | * @license MIT 14 | * @link https://getkirby.com 15 | */ 16 | class Point { 17 | 18 | /** 19 | * Latitude 20 | * 21 | * @var float 22 | * @access protected 23 | */ 24 | protected $lat; 25 | 26 | /** 27 | * Latitude 28 | * 29 | * @var float 30 | * @access protected 31 | */ 32 | protected $lng; 33 | 34 | /** 35 | * Creates a new Point object 36 | * 37 | * @param string|float $lat 38 | * @param string|float $lng 39 | */ 40 | public function __construct($lat, $lng) { 41 | 42 | if(!is_numeric($lat) or !is_numeric($lng)) { 43 | throw new Exception('Invalid Geo Point values'); 44 | } 45 | 46 | $this->lat = floatval($lat); 47 | $this->lng = floatval($lng); 48 | 49 | } 50 | 51 | /** 52 | * Static method to create a new Geo Point 53 | * This can be used with various combinations of values 54 | * 55 | * 1.) static::make($lat, $lng) 56 | * 2.) static::make("$lat,$lng") 57 | * 3.) static::make([$lat, $lng]) 58 | * 4.) static::make(['lat' => $lat, 'lng' => $lng]) 59 | * 60 | * @return Kirby\Geo\Point 61 | */ 62 | public static function make() { 63 | 64 | $args = func_get_args(); 65 | $count = count($args); 66 | 67 | switch($count) { 68 | case 1: 69 | if(is_string($args[0])) { 70 | $parts = str::split($args[0]); 71 | if(count($parts) === 2) { 72 | return new static($parts[0], $parts[1]); 73 | } 74 | } else if(is_array($args[0])) { 75 | 76 | $array = $args[0]; 77 | 78 | if(isset($array['lat']) and isset($array['lng'])) { 79 | return new static($array['lat'], $array['lng']); 80 | } else if(count($array) === 2) { 81 | return new static(a::first($array), a::last($array)); 82 | } 83 | 84 | } 85 | break; 86 | case 2: 87 | return new static($args[0], $args[1]); 88 | break; 89 | } 90 | 91 | throw new Exception('Invalid Geo Point values'); 92 | 93 | } 94 | 95 | /** 96 | * Returns the latitude value of the point 97 | * 98 | * @return float 99 | */ 100 | public function lat() { 101 | return $this->lat; 102 | } 103 | 104 | /** 105 | * Returns the longituted value of the point 106 | * 107 | * @return float 108 | */ 109 | public function lng() { 110 | return $this->lng; 111 | } 112 | 113 | /** 114 | * Returns the longituted value of the point 115 | * 116 | * @return float 117 | */ 118 | public function long() { 119 | return $this->lng(); 120 | } 121 | 122 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "geo", 3 | "description": "Kirby GEO Plugin", 4 | "author": "Bastian Allgeier ", 5 | "license": "MIT", 6 | "version": "1.0.0", 7 | "type": "kirby-plugin" 8 | } -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Kirby 2 GEO Plugin 2 | 3 | This plugin adds basic geo search and conversion functionalities to Kirby 4 | 5 | ## Geo Class Option 6 | 7 | ### geo::point 8 | 9 | Creates a new Kirby Geo Point 10 | 11 | Example: 12 | 13 | ```php 14 | geo::point(49.4883333, 8.4647222); 15 | geo::point('49.4883333, 8.4647222'); 16 | geo::point([49.4883333, 8.4647222]); 17 | geo::point(['lat' => 49.4883333, 'lng' => 8.4647222]); 18 | ``` 19 | 20 | Afterwards you can get the latitude and longitude values of the point like this: 21 | 22 | ```php 23 | $point = geo::point(49.4883333, 8.4647222); 24 | echo $point->lat(); 25 | echo $point->lng(); 26 | ``` 27 | 28 | ### geo::distance 29 | 30 | Returns the distance between two geo points. 31 | 32 | ```php 33 | $mannheim = geo::point(49.4883333, 8.4647222); 34 | $hamburg = geo::point(53.553436, 9.992247); 35 | 36 | echo 'The distance between Mannheim and Hamburg is: ' . geo::distance($mannheim, $hamburg); 37 | ``` 38 | 39 | You can also return the distance in miles instead of kilometers 40 | 41 | ```php 42 | echo 'The distance between Mannheim and Hamburg is: ' . geo::distance($mannheim, $hamburg, 'mi'); 43 | ``` 44 | 45 | ### geo::niceDistance 46 | 47 | Returns the distance between two geo points in a human readable way (i.e. 461.32 km) 48 | 49 | ```php 50 | $mannheim = geo::point(49.4883333, 8.4647222); 51 | $hamburg = geo::point(53.553436, 9.992247); 52 | 53 | echo 'The distance between Mannheim and Hamburg is: ' . geo::niceDistance($mannheim, $hamburg); 54 | ``` 55 | 56 | You can also return the "nice distance" in miles instead of kilometers 57 | 58 | ```php 59 | echo 'The distance between Mannheim and Hamburg is: ' . geo::niceDistance($mannheim, $hamburg, 'mi'); 60 | ``` 61 | 62 | ### geo::locate 63 | 64 | Runs the Google geo locator to find the latitude and longitude for a certain address 65 | 66 | ```php 67 | $mannheim = geo::locate('Mannheim, Germany'); 68 | 69 | echo $mannheim->lat(); 70 | echo $mannheim->lng(); 71 | ``` 72 | 73 | ### geo::kilometersToMiles 74 | 75 | Converts kilometers into miles: 76 | 77 | ```php 78 | echo geo::kilometersToMiles(1000); 79 | ``` 80 | 81 | ### geo::milesToKilometers 82 | 83 | Converts miles into kilometers: 84 | 85 | ```php 86 | echo geo::milesToKilometers(1000); 87 | ``` 88 | 89 | ## Radius Filter 90 | 91 | The plugin automatically adds a new filter for all collections, which can be used to do a radius search: 92 | 93 | ```php 94 | $addresses = page('addresses')->children()->filterBy('location', 'radius', [ 95 | 'lat' => 49.4883333, 96 | 'lng' => 8.4647222, 97 | 'radius' => 10 98 | ]); 99 | ``` 100 | 101 | To make this work, the location field for each address page must be in the following format: 102 | 103 | ``` 104 | location: {lat},{lng} 105 | ``` 106 | 107 | or with a real life example: 108 | 109 | ``` 110 | location: 49.4883333,8.4647222 111 | ``` 112 | 113 | You can also filter in miles 114 | 115 | ```php 116 | $addresses = page('addresses')->children()->filterBy('location', 'radius', [ 117 | 'lat' => 49.4883333, 118 | 'lng' => 8.4647222, 119 | 'radius' => 10, 120 | 'unit' => 'mi' 121 | ]); 122 | ``` 123 | 124 | ## Field Methods 125 | 126 | The plugin also adds a set of field methods, which can be handy to work with locations 127 | 128 | ### coordinates 129 | 130 | Converts a field with the value format {lat},{lng} into a valid Kirby Geo Point Object: 131 | 132 | ```php 133 | $page->location()->coordinates()->lat(); 134 | $page->location()->coordinates()->lng(); 135 | ``` 136 | 137 | ### distance 138 | 139 | Calculates the distance between a location field and another Kirby Geo Point: 140 | 141 | ```php 142 | $hamburg = geo::point(53.553436, 9.992247); 143 | 144 | echo $page->location()->distance($hamburg); 145 | ``` 146 | 147 | Of course you can run this in miles again: 148 | 149 | ```php 150 | $hamburg = geo::point(53.553436, 9.992247); 151 | 152 | echo $page->location()->distance($hamburg, 'mi'); 153 | ``` 154 | 155 | ### niceDistance 156 | 157 | Returns the distance in a more human friendly format: 158 | 159 | ```php 160 | $hamburg = geo::point(53.553436, 9.992247); 161 | 162 | echo $page->location()->niceDistance($hamburg); 163 | ``` 164 | 165 | ## License 166 | 167 | 168 | 169 | ## Author 170 | 171 | Bastian Allgeier 172 | --------------------------------------------------------------------------------