1<?php
2
3namespace geoPHP\Adapter;
4
5use geoPHP\Geometry\Geometry;
6use geoPHP\Geometry\GeometryCollection;
7use geoPHP\Geometry\Point;
8use geoPHP\Geometry\MultiPoint;
9use geoPHP\Geometry\LineString;
10use geoPHP\Geometry\Polygon;
11use geoPHP\Geometry\MultiPolygon;
12
13/*
14 * (c) Camptocamp <info@camptocamp.com>
15 * (c) Patrick Hayes
16 *
17 * This code is open-source and licenced under the Modified BSD License.
18 * For the full copyright and license information, please view the LICENSE
19 * file that was distributed with this source code.
20 */
21
22/**
23 * PHP Google Geocoder Adapter
24 *
25 *
26 * @package    geoPHP
27 * @author     Patrick Hayes <patrick.d.hayes@gmail.com>
28 */
29class GoogleGeocode implements GeoAdapter
30{
31
32    /** @var \stdClass $result */
33    protected $result;
34
35    /**
36     * Makes a geocoding (lat/lon lookup) with an address string or array geometry objects
37     *
38     * @param string|string[]     $address        Address to geocode
39     * @param string              $apiKey         Your application's Google Maps Geocoding API key
40     * @param string              $returnType     Type of Geometry to return. Can either be 'points' or 'bounds' (polygon)
41     * @param array|bool|Geometry $bounds         Limit the search area to within this region.
42     *        For example by default geocoding "Cairo" will return the location of Cairo Egypt.
43     *        If you pass a polygon of Illinois, it will return Cairo IL.
44     * @param boolean             $returnMultiple - Return all results in a multipoint or multipolygon
45     *
46     * @return Geometry|GeometryCollection
47     * @throws \Exception If geocoding fails
48     */
49    public function read($address, $apiKey = null, $returnType = 'point', $bounds = false, $returnMultiple = false)
50    {
51        if (is_array($address)) {
52            $address = join(',', $address);
53        }
54
55        if (gettype($bounds) == 'object') {
56            $bounds = $bounds->getBBox();
57        }
58        if (gettype($bounds) == 'array') {
59            $boundsString = '&bounds=' . $bounds['miny'] . ',' . $bounds['minx'] . '|' . $bounds['maxy'] . ',' . $bounds['maxx'];
60        } else {
61            $boundsString = '';
62        }
63
64        $url = "http://maps.googleapis.com/maps/api/geocode/json";
65        $url .= '?address=' . urlencode($address);
66        $url .= $boundsString . ($apiKey ? '&key=' . $apiKey : '');
67        $this->result = json_decode(@file_get_contents($url));
68
69        if ($this->result->status == 'OK') {
70            if (!$returnMultiple) {
71                if ($returnType == 'point') {
72                    return $this->getPoint();
73                }
74                if ($returnType == 'bounds' || $returnType == 'polygon') {
75                    return $this->getPolygon();
76                }
77            } else {
78                if ($returnType == 'point') {
79                    $points = [];
80                    foreach ($this->result->results as $delta => $item) {
81                        $points[] = $this->getPoint($delta);
82                    }
83                    return new MultiPoint($points);
84                }
85                if ($returnType == 'bounds' || $returnType == 'polygon') {
86                    $polygons = [];
87                    foreach ($this->result->results as $delta => $item) {
88                        $polygons[] = $this->getPolygon($delta);
89                    }
90                    return new MultiPolygon($polygons);
91                }
92            }
93        } elseif ($this->result->status == 'ZERO_RESULTS') {
94            return null;
95        } else {
96            if ($this->result->status) {
97                throw new \Exception(
98                    'Error in Google Reverse Geocoder: '
99                        . $this->result->status
100                    . (isset($this->result->error_message) ? '. ' . $this->result->error_message : '')
101                );
102            } else {
103                throw new \Exception('Unknown error in Google Reverse Geocoder');
104            }
105        }
106        return false;
107    }
108
109    /**
110     * Makes a Reverse Geocoding (address lookup) with the (center) point of Geometry
111     * Detailed documentation of response values can be found in:
112     *
113     * @see https://developers.google.com/maps/documentation/geocoding/intro#ReverseGeocoding
114     *
115     * @param Geometry $geometry
116     * @param string   $apiKey     Your application's Google Maps Geocoding API key
117     * @param string   $returnType Should be either 'string' or 'array' or 'both'
118     * @param string   $language   The language in which to return results. If not set, geocoder tries to use the native language of the domain.
119     *
120     * @return string|Object[]|null A formatted address or array of address components
121     * @throws \Exception If geocoding fails
122     */
123    public function write(Geometry $geometry, $apiKey = null, $returnType = 'string', $language = null)
124    {
125        $centroid = $geometry->centroid();
126        $lat = $centroid->y();
127        $lon = $centroid->x();
128
129        $url = "http://maps.googleapis.com/maps/api/geocode/json";
130        /** @noinspection SpellCheckingInspection */
131        $url .= '?latlng=' . $lat . ',' . $lon;
132        $url .= ($language ? '&language=' . $language : '') . ($apiKey ? '&key=' . $apiKey : '');
133
134        $this->result = json_decode(@file_get_contents($url));
135
136        if ($this->result->status == 'OK') {
137            if ($returnType == 'string') {
138                return $this->result->results[0]->formatted_address;
139            } elseif ($returnType == 'array') {
140                return $this->result->results[0]->address_components;
141            } elseif ($returnType == 'full') {
142                return $this->result->results[0];
143            }
144        } elseif ($this->result->status == 'ZERO_RESULTS') {
145            if ($returnType == 'string') {
146                return '';
147            }
148            if ($returnType == 'array') {
149                return $this->result->results;
150            }
151        } else {
152            if ($this->result->status) {
153                throw new \Exception(
154                    'Error in Google Reverse Geocoder: '
155                        . $this->result->status
156                    . (isset($this->result->error_message) ? '. ' . $this->result->error_message : '')
157                );
158            } else {
159                throw new \Exception('Unknown error in Google Reverse Geocoder');
160            }
161        }
162        return false;
163    }
164
165    private function getPoint($delta = 0)
166    {
167        $lat = $this->result->results[$delta]->geometry->location->lat;
168        $lon = $this->result->results[$delta]->geometry->location->lng;
169        return new Point($lon, $lat);
170    }
171
172    private function getPolygon($delta = 0)
173    {
174        $points = [
175                $this->getTopLeft($delta),
176                $this->getTopRight($delta),
177                $this->getBottomRight($delta),
178                $this->getBottomLeft($delta),
179                $this->getTopLeft($delta),
180        ];
181        $outerRing = new LineString($points);
182        return new Polygon([$outerRing]);
183    }
184
185    private function getTopLeft($delta = 0)
186    {
187        $lat = $this->result->results[$delta]->geometry->bounds->northeast->lat;
188        $lon = $this->result->results[$delta]->geometry->bounds->southwest->lng;
189        return new Point($lon, $lat);
190    }
191
192    private function getTopRight($delta = 0)
193    {
194        $lat = $this->result->results[$delta]->geometry->bounds->northeast->lat;
195        $lon = $this->result->results[$delta]->geometry->bounds->northeast->lng;
196        return new Point($lon, $lat);
197    }
198
199    private function getBottomLeft($delta = 0)
200    {
201        $lat = $this->result->results[$delta]->geometry->bounds->southwest->lat;
202        $lon = $this->result->results[$delta]->geometry->bounds->southwest->lng;
203        return new Point($lon, $lat);
204    }
205
206    private function getBottomRight($delta = 0)
207    {
208        $lat = $this->result->results[$delta]->geometry->bounds->southwest->lat;
209        $lon = $this->result->results[$delta]->geometry->bounds->northeast->lng;
210        return new Point($lon, $lat);
211    }
212}
213