From ced5699b8652bafac64cf5d487e63a1f807b0248 Mon Sep 17 00:00:00 2001 From: Jan Martinec Date: Wed, 15 Jan 2014 17:04:12 +0100 Subject: [PATCH 01/14] new implementation for in-house nominatim (OSM) --- src/Geolocation/Nominatim/GeocodingClient.php | 202 +++++++++++++++ .../Nominatim/GeocodingResponse.php | 244 ++++++++++++++++++ 2 files changed, 446 insertions(+) create mode 100644 src/Geolocation/Nominatim/GeocodingClient.php create mode 100644 src/Geolocation/Nominatim/GeocodingResponse.php diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php new file mode 100644 index 0000000..b0c7871 --- /dev/null +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -0,0 +1,202 @@ +base_url = $url; + } + + /** + * Get GPS position for given address + * + * @param Address|string + * @param array + * @param bool + * @return Position|NULL + */ + public function getPosition($address, $options = array(), $fullResult = FALSE) + { + // address + if ($address instanceof Address) + { + $address = (string) $address; + } + elseif (!is_string($address)) + { + throw new InvalidArgumentException('Address should be instance of Address or a string.'); + } + + // bounds + if (!empty($options['bounds']) && $options['bounds'] instanceof Rectangle) + { + /** @var Rectangle $rec */ + $rec = $options['bounds']; + $b = $rec->getLatLonBounds(); + $options['viewboxlbrt'] = "$b[0],$b[1]|$b[2],$b[3]"; + $options['bounded'] = 1; + unset($options['bounds']); + } + + $result = $this->getResponse($address, $options); + + return $fullResult ? $result : $result->getPosition(); + } + + /** + * Get address for given GPS position + * + * @param Position + * @param array + * @param bool + * @return Address|NULL + */ + public function getAddress(Position $position, $options = array(), $fullResult = FALSE) + { + $result = $this->getResponse($position, $options); + + return $fullResult ? $result : $result->getAddress(); + } + + /** + * Get both position and address for given query + * + * @param string|Address|Position + * @param array + * @return array (Position|NULL, Address|NULL) + */ + public function getPositionAndAddress($query, $options = array()) + { + if ($query instanceof Position) + { + /** @var GeocodingResponse $response */ + $response = $this->getAddress($query, $options, TRUE); + if ($response) + { + return array($response->getPosition(), $response->getAddress()); + } + } + else + { + /** @var GeocodingResponse $response */ + $response = $this->getPosition($query, $options, TRUE); + if ($response) + { + return array($response->getPosition(), $response->getAddress()); + } + } + + return array(NULL, NULL); + } + + /** + * Get a full geocoding query result + * + * @param string|Address|Position + * @return GeocodingResponse + */ + public function getResponse($query, $options) + { + if ($query instanceof Position) + { + $options['lat'] = $query->latitude; + $options['lng'] = $query->longitude; + } + else + { + $options['address'] = (string) $query; + } + + return $this->query($options); + } + + /** + * Executes query on The Google Geocoding API + * + * @param string + * @param string + * @param array [bounds, language, region, sensor] + * @return \StdClass + */ + protected function query(array $options) + { + if ($options['lat'] && $options['lng']) { + $method = 'reverse'; + } else { + $method = 'search'; + } + + $options['format'] = 'json'; + $options['addressdetails'] = 1; + // $options['limit'] = 1; // do we need alternatives? + $options['email'] = $this->email; + + $url = $this->base_url . $method . '?' . http_build_query($options); + + $curl = curl_init(); + if ($this->user_name) { + curl_setopt($this->curl, CURLOPT_USERPWD, $this->user_name . ':' . $this->user_pwd); + } + if ($this->ua) { + curl_setopt($this->curl, CURLOPT_USERAGENT,$this->ua); + } + curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($curl, CURLOPT_URL, $url); + $response = curl_exec($curl); + curl_close($curl); + + if (!$response) + { + throw new ConnectionException("Unable to connect to geocoding API."); + } + $payload = @json_decode($response); // @ - intentionally to escalate error to exception + if (!$payload) + { + throw new InvalidResponseException("Unable to parse response from geocoding API."); + } + if ($payload->status != 'OK') + { + throw new InvalidStatusException("Geocoding query failed (status: '{$payload->status}')."); + } + + return new GeocodingResponse($this, $payload->results, $options); + } + +} diff --git a/src/Geolocation/Nominatim/GeocodingResponse.php b/src/Geolocation/Nominatim/GeocodingResponse.php new file mode 100644 index 0000000..e517165 --- /dev/null +++ b/src/Geolocation/Nominatim/GeocodingResponse.php @@ -0,0 +1,244 @@ + stdClass] other found locations */ + private $alternatives; + + /** @var array address components */ + private $components = array(); + + + /** + * @param GoogleGeocodingClient + * @param StdClass[] + * @param array + */ + public function __construct(GeocodingClient $client, array $result, $options) + { + $this->client = $client; + $this->result = array_shift($result); + $this->options = $options; + $this->alternatives = $result; + + foreach ($this->result->address as $type => $component) + { + $this->components[$type] = $component; + } + } + + /** + * @return Address + */ + public function getAddress() + { + $c = $this->components; + + $address = new Address; + $address->language = isset($this->options['language']) ? $this->options['language'] : NULL; + + $address->country = $this->getCountry(); + $address->countryCode = $this->getCountryCode(); + + // state + if (in_array($address->countryCode, self::$countriesWithStates)) + { + if (isset($c['state'])) + { + $address->state = $c['state']; + $address->stateCode = NULL; + } + } + + // region/county + if (isset($c['county'])) + { + $address->region = $c['county']; + $address->regionCode = NULL; + } + + $address->district = isset($c['state_district']) + ? $c['state_district'] : NULL; + + $address->town = isset($c['city']) ? $c['city'] : NULL; + + $address->quarter = isset($c['suburb']) ? $c['suburb'] : NULL; + + $address->neighborhood = isset($c['neighborhood']) ? $c['neighborhood'] : NULL; + + $address->postalCode = isset($c['postcode']) ? $c['postcode'] : NULL; + + $address->street = isset($c['road']) ? $c['road'] : NULL; + + // house and street number (číslo popisné a orientační) + $n = array(); + if (isset($c['house_number'])) + { + $n[] = isset($c['house_number']); + } + if (isset($c['street_number'])) + { + $n[] = isset($c['street_number']); + } + $n = implode('/', $n); + $address->number = $n ? $n : NULL; + + $address->formatedAddress = $this->getFormatedAddress(); + + $address->partialMatch = $this->isPartialMatch(); + + return $address; + } + + /** + * Returns formatted label + * + * @return string + */ + public function getFormatedAddress() + { + return $this->result->display_name; // returns ugly addresses in CZ - @TODO: FIXME maybe? + } + + /** + * Indicates that the response doesn't match exactly the original querys + * + * @return bool + */ + public function isPartialMatch() + { + return false; // no such thing in OSM (yet?) + } + + /** + * Returns GPS position + * + * @return Position + */ + public function getPosition() + { + return new Position( + $this->result->lat, + $this->result->lon + ); + } + + /** + * Returns rectangle area + * + * @return Rectangle + */ + public function getArea() + { + $bounds = $this->result->boundingbox; + + return new Rectangle( + new Position($bounds[0], $bounds[2]), + new Position($bounds[1], $bounds[3]) + ); + } + + /** + * Returns name of country + * + * @return string|NULL + */ + public function getCountry() + { + return isset($this->components['country']) ? $this->components['country'] : NULL; + } + + /** + * Returns short international symbol of country + * + * @return string + */ + public function getCountryCode() + { + return isset($this->components['country_code']) ? $this->components['country_code'] : NULL; + } + + /** + * Returns TRUE if current Address has any alternatives + * (returned by The Google Geocoding API) + * + * @return bool + */ + public function hasAlternatives() + { + return (bool) count($this->getAlternatives()); + } + + /** + * Returns array of alternative Addresses + * (returned by The Google Geocoding API) + * + * @return array [# => Address] + */ + public function getAlternatives() + { + $client = $this->client; + $label = $this->getFormatedAddress(); + $class = get_class($this); + return array_filter(array_map(function ($alternative) use ($client, $label, $class) { + if ($alternative->display_name === $label + || in_array('train_station', $alternative->types) + || in_array('postal_town', $alternative->types) + ) { + return NULL; + } + return new $class($client, array($alternative)); + }, $this->alternatives), function ($alternative) { return (bool) $alternative;}); + } + + /** + * Returns original response + * + * @internal + * @return stdClass + */ + public function getRaw() + { + return $this->result; + } + +} From 8e211dc7a6f7cd462bd9c456ab6d08399398f387 Mon Sep 17 00:00:00 2001 From: Jan Martinec Date: Wed, 15 Jan 2014 17:19:49 +0100 Subject: [PATCH 02/14] add user+pwd --- src/Geolocation/Nominatim/GeocodingClient.php | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index b0c7871..9b3d19e 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -141,7 +141,9 @@ public function getResponse($query, $options) } else { - $options['address'] = (string) $query; + $options['lat'] = null; + $options['lng'] = null; + $options['q'] = ((string) $query); } return $this->query($options); @@ -171,11 +173,20 @@ protected function query(array $options) $url = $this->base_url . $method . '?' . http_build_query($options); $curl = curl_init(); + + /* + + echo "$url\n"; + curl_setopt($curl, CURLOPT_PROXY, '192.168.56.6:8888'); + // */ + + curl_setopt($curl, CURLOPT_HTTPHEADER, Array('Content-Type: application/json; charset=utf-8')); + if ($this->user_name) { - curl_setopt($this->curl, CURLOPT_USERPWD, $this->user_name . ':' . $this->user_pwd); + curl_setopt($curl, CURLOPT_USERPWD, $this->user_name . ':' . $this->user_pwd); } if ($this->ua) { - curl_setopt($this->curl, CURLOPT_USERAGENT,$this->ua); + curl_setopt($curl, CURLOPT_USERAGENT,$this->ua); } curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_URL, $url); @@ -189,7 +200,14 @@ protected function query(array $options) $payload = @json_decode($response); // @ - intentionally to escalate error to exception if (!$payload) { - throw new InvalidResponseException("Unable to parse response from geocoding API."); + if ((is_array($payload) && (count($payload) == 0))) + { + throw new InvalidStatusException("Geocoding query failed (no results)."); + + } else + { + throw new InvalidResponseException("Unable to parse response from geocoding API."); + } } if ($payload->status != 'OK') { From 1909ac8e3bf4fcfe0e3a721fdcff0f99fc897f55 Mon Sep 17 00:00:00 2001 From: Jan Martinec Date: Wed, 15 Jan 2014 17:28:48 +0100 Subject: [PATCH 03/14] check result by response code --- src/Geolocation/Nominatim/GeocodingClient.php | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index 9b3d19e..ef41a57 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -174,12 +174,6 @@ protected function query(array $options) $curl = curl_init(); - /* - - echo "$url\n"; - curl_setopt($curl, CURLOPT_PROXY, '192.168.56.6:8888'); - // */ - curl_setopt($curl, CURLOPT_HTTPHEADER, Array('Content-Type: application/json; charset=utf-8')); if ($this->user_name) { @@ -191,6 +185,7 @@ protected function query(array $options) curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_URL, $url); $response = curl_exec($curl); + $response_code = curl_getinfo($curl, CURLINFO_HTTP_CODE); curl_close($curl); if (!$response) @@ -209,12 +204,12 @@ protected function query(array $options) throw new InvalidResponseException("Unable to parse response from geocoding API."); } } - if ($payload->status != 'OK') + if ($response_code != 200) { throw new InvalidStatusException("Geocoding query failed (status: '{$payload->status}')."); } - return new GeocodingResponse($this, $payload->results, $options); + return new GeocodingResponse($this, $payload, $options); } } From f5b4588f9f723030cc997472b7b6f39f5fbb47ad Mon Sep 17 00:00:00 2001 From: Jan Martinec Date: Wed, 15 Jan 2014 17:30:49 +0100 Subject: [PATCH 04/14] fix lng to lon --- src/Geolocation/Nominatim/GeocodingClient.php | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index ef41a57..ae2bad3 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -137,13 +137,13 @@ public function getResponse($query, $options) if ($query instanceof Position) { $options['lat'] = $query->latitude; - $options['lng'] = $query->longitude; + $options['lon'] = $query->longitude; // NB: not "lng"! } else { $options['lat'] = null; - $options['lng'] = null; - $options['q'] = ((string) $query); + $options['lon'] = null; + $options['q'] = ((string) $query); // NB: not "address"! } return $this->query($options); @@ -159,7 +159,7 @@ public function getResponse($query, $options) */ protected function query(array $options) { - if ($options['lat'] && $options['lng']) { + if ($options['lat'] && $options['lon']) { $method = 'reverse'; } else { $method = 'search'; @@ -167,7 +167,6 @@ protected function query(array $options) $options['format'] = 'json'; $options['addressdetails'] = 1; - // $options['limit'] = 1; // do we need alternatives? $options['email'] = $this->email; $url = $this->base_url . $method . '?' . http_build_query($options); From adc7f9ee4795e8458c0d511bba2819f97404ed12 Mon Sep 17 00:00:00 2001 From: Jan Martinec Date: Wed, 15 Jan 2014 17:41:34 +0100 Subject: [PATCH 05/14] make Nominatim reverse geocoding work --- src/Geolocation/Nominatim/GeocodingClient.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index ae2bad3..6d42552 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -192,6 +192,11 @@ protected function query(array $options) throw new ConnectionException("Unable to connect to geocoding API."); } $payload = @json_decode($response); // @ - intentionally to escalate error to exception + + if ($payload && !is_array($payload)) { + $payload = array($payload); + } + if (!$payload) { if ((is_array($payload) && (count($payload) == 0))) From 34358b00dfc2f12c71b0672ef8e74e3669c35daf Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Sun, 2 Feb 2014 16:53:24 +0100 Subject: [PATCH 06/14] replace the vestigial "Google" stuff with "OSM Nominatim" --- readme.md | 3 ++- src/Geolocation/Nominatim/GeocodingClient.php | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/readme.md b/readme.md index 4203623..242e9f3 100644 --- a/readme.md +++ b/readme.md @@ -17,5 +17,6 @@ namespace `Clevis\Geolocation` #### geocoding: - Address - Geocoder - - Google\GeocodingApiClient ( *IGeocodingService*, *IReverseGeocodingService* ) + - Google\GeocodingClient ( *IGeocodingService*, *IReverseGeocodingService* ) + - Nominatim\GeocodingClient ( *IGeocodingService*, *IReverseGeocodingService* ) - Google\ElevationApiClient ( *IElevationService* ) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index 6d42552..2322a23 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -150,7 +150,7 @@ public function getResponse($query, $options) } /** - * Executes query on The Google Geocoding API + * Executes query on OSM Nominatim API * * @param string * @param string From b6cb4c3d8bbef4f8d4db2b6c2aa83212c3b2d751 Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Sun, 2 Feb 2014 16:58:54 +0100 Subject: [PATCH 07/14] replace the vestigial "Google" stuff with "OSM Nominatim" --- src/Geolocation/Nominatim/GeocodingClient.php | 2 ++ src/Geolocation/Nominatim/GeocodingResponse.php | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index 2322a23..4c8a9d1 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -25,6 +25,8 @@ * Own instances of nominatim are not subject to usage restrictions (except for the license, 'course). * * May be overridden for different nominatim instances (see $base_url) + * + * @author Jan Martinec */ class GeocodingClient extends Object implements IGeocodingService { diff --git a/src/Geolocation/Nominatim/GeocodingResponse.php b/src/Geolocation/Nominatim/GeocodingResponse.php index e517165..b1e2c49 100644 --- a/src/Geolocation/Nominatim/GeocodingResponse.php +++ b/src/Geolocation/Nominatim/GeocodingResponse.php @@ -10,9 +10,9 @@ /** - * Result received from The Google Geocoding API + * Result received from OSM Nominatim API * - * @author Vojtěch Dobeš + * @author Jan Martinec */ class GeocodingResponse extends Object { @@ -199,7 +199,7 @@ public function getCountryCode() /** * Returns TRUE if current Address has any alternatives - * (returned by The Google Geocoding API) + * (returned by OSM Nominatim API) * * @return bool */ @@ -210,7 +210,7 @@ public function hasAlternatives() /** * Returns array of alternative Addresses - * (returned by The Google Geocoding API) + * (returned by OSM Nominatim API) * * @return array [# => Address] */ From 9e8098ce0e42f9c7410293a3f0c5271c1ae605d3 Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Tue, 1 Apr 2014 11:43:06 +0200 Subject: [PATCH 08/14] add geocoder example --- src/index.php | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 src/index.php diff --git a/src/index.php b/src/index.php new file mode 100644 index 0000000..e90123e --- /dev/null +++ b/src/index.php @@ -0,0 +1,38 @@ +getPositionAndAddress($address); +$resultGoogle = $geocoderGoogle->getPositionAndAddress($address); + +echo $resultOSM[1]->formatedAddress , "\n"; +echo $resultGoogle[1]->formatedAddress , "\n"; + +$position = \Clevis\Geolocation\Position::fromString("50°1'57.8\"N 15°46'56.21\"E"); +var_dump($position); + +$resultOSM = $geocoderOSM->getPositionAndAddress($position); +var_dump($resultOSM); +$resultGoogle = $geocoderGoogle->getPositionAndAddress($position); + +var_dump($resultGoogle); + + From fdf64fd1ece8c36f5fe15f4ff69c9b077231db56 Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Tue, 1 Apr 2014 12:14:25 +0200 Subject: [PATCH 09/14] munge addresses to fit with Nominatim (remove ZIP and city parts) --- src/Geolocation/Nominatim/GeocodingClient.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index 6d42552..624188d 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -37,6 +37,8 @@ class GeocodingClient extends Object implements IGeocodingService protected $email = 'info@rekola.cz'; // needed in case of capacity problems (not in case of own instance) protected $ua = 'ReKolaSMS http://rekola.cz/ info@rekola.cz 1.0 2014-01-07'; // User-Agent string; required! + protected $munge_address = true; // Nominatim has problems when ZIP cosdes are included, or city parts ("Praha 3"); this toggle attempts to strip such extraneous info + /** * allows use of other nominatims */ @@ -75,6 +77,11 @@ public function getPosition($address, $options = array(), $fullResult = FALSE) unset($options['bounds']); } + if ($this->munge_address) { + $address = $this->mungeAddress($address); + } + + $result = $this->getResponse($address, $options); return $fullResult ? $result : $result->getPosition(); @@ -216,4 +223,15 @@ protected function query(array $options) return new GeocodingResponse($this, $payload, $options); } + protected function mungeAddress($address) { + $address = preg_replace('/\d{3}\W*\d{2}/','',$address); // no ZIP codes - + $matched = 0; + do { + $address = preg_replace('/,([^\d]*?)([\d]+)/',',\\1',$address, -1, $matched); + } while ($matched > 0); + $address = preg_replace('/(Praha|Brno|Olomouc|Plzeň|Plzen|Ostrava) +\d+/',',\\1',$address, -1, $matched); + $address = preg_replace('/,? +,?/',' ',$address); + + return $address; + } } From c7dd46aab1a9537b8e859b127e6eaa4b4a490060 Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Tue, 1 Apr 2014 18:51:49 +0200 Subject: [PATCH 10/14] geocode strip Praha X case-insensitive --- src/Geolocation/Nominatim/GeocodingClient.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index f260d4a..53c7f1f 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -226,12 +226,12 @@ protected function query(array $options) } protected function mungeAddress($address) { - $address = preg_replace('/\d{3}\W*\d{2}/','',$address); // no ZIP codes - + $address = preg_replace('/\d{3}\W*\d{2}/','',$address); // no ZIP codes - assuming there is no 5-digit house number $matched = 0; - do { + do { // remove all numbers behind the first comma, if any $address = preg_replace('/,([^\d]*?)([\d]+)/',',\\1',$address, -1, $matched); } while ($matched > 0); - $address = preg_replace('/(Praha|Brno|Olomouc|Plzeň|Plzen|Ostrava) +\d+/',',\\1',$address, -1, $matched); + $address = preg_replace('/(Praha|Brno|Olomouc|Plzeň|Plzen|Ostrava) +\d+/i',',\\1',$address, -1, $matched); // remove "Praha 3", as this administrative district division is useless to us; worse, not all address points have it! $address = preg_replace('/,? +,?/',' ',$address); return $address; From 1f30d5a9ca81bda8a6423c45f9dcfbe5f4273f7e Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Sun, 18 May 2014 18:17:43 +0200 Subject: [PATCH 11/14] add viewport biasing --- src/Geolocation/Google/GeocodingClient.php | 44 +++++++++++++++++ src/Geolocation/Nominatim/GeocodingClient.php | 48 ++++++++++++++++++- .../interfaces/IGeocodingService.php | 21 ++++++++ 3 files changed, 111 insertions(+), 2 deletions(-) diff --git a/src/Geolocation/Google/GeocodingClient.php b/src/Geolocation/Google/GeocodingClient.php index 3713fe3..597633b 100644 --- a/src/Geolocation/Google/GeocodingClient.php +++ b/src/Geolocation/Google/GeocodingClient.php @@ -23,6 +23,7 @@ class GeocodingClient extends Object implements IGeocodingService const GOOGLE_URL = 'http://maps.googleapis.com/maps/api/geocode/json?'; + private $viewportBias = null; /** * Get GPS position for given address @@ -139,6 +140,35 @@ public function getResponse($query, $options) return $this->query($options); } + /** + * Set the geocoder to bias on a "rectangle" defined by two Position "corners"; + * following queries will prefer the viewport set this way. + * Note that there are always two areas likely to be defined this way (see the links); + * choosing the correct one is up to the implementation. + * + * @see http://stackoverflow.com/questions/23084764/draw-rectangle-on-map-given-two-opposite-coordinates-determine-which-ones-ar#comment35283253_23084764 + * @see http://i.piskvor.org/test/which.png + * @see http://i.piskvor.org/test/which2.png + * + * @param Position $corner1 + * @param Position $corner2 + * @return boolean true if region biasing is available, false otherwise + */ + public function setBias(Position $corner1, Position $corner2) { + $this->viewportBias = array($corner1,$corner2); + } + + + /** + * Reset the bias set by setBias; following queries will not have a preferred viewport + * + * @return void + */ + public function unsetBias() { + $this->viewportBias = null; + } + + /** * Executes query on The Google Geocoding API * @@ -153,6 +183,20 @@ private function query(array $options) { $options['sensor'] = $options['sensor'] ? 'true' : 'false'; } + + if (is_array($this->viewportBias) && (count($this->viewportBias) == 2)) { + /** @var Position $corner1 */ + $corner1 = $this->viewportBias[0]; + /** @var Position $corner2 */ + $corner2 = $this->viewportBias[1]; + /** @see https://developers.google.com/maps/documentation/geocoding/#Viewports */ + $options['bounds'] = ( + $corner1->latitude . ',' . $corner1->longitude + . '|' + . $corner2->latitude . ',' . $corner2->longitude + ); + } + $url = self::GOOGLE_URL . http_build_query($options); $curl = curl_init(); diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index 53c7f1f..614c83f 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -41,6 +41,8 @@ class GeocodingClient extends Object implements IGeocodingService protected $munge_address = true; // Nominatim has problems when ZIP cosdes are included, or city parts ("Praha 3"); this toggle attempts to strip such extraneous info + private $viewport_bias = null; + /** * allows use of other nominatims */ @@ -158,6 +160,34 @@ public function getResponse($query, $options) return $this->query($options); } + /** + * Set the geocoder to bias on a "rectangle" defined by two Position "corners"; + * following queries will prefer the viewport set this way. + * Note that there are always two areas likely to be defined this way (see the links); + * choosing the correct one is up to the implementation. + * + * @see http://stackoverflow.com/questions/23084764/draw-rectangle-on-map-given-two-opposite-coordinates-determine-which-ones-ar#comment35283253_23084764 + * @see http://i.piskvor.org/test/which.png + * @see http://i.piskvor.org/test/which2.png + * + * @param Position $corner1 + * @param Position $corner2 + * @return boolean true if region biasing is available, false otherwise + */ + public function setBias(Position $corner1, Position $corner2) { + $this->viewport_bias = array($corner1,$corner2); + } + + + /** + * Reset the bias set by setBias; following queries will not have a preferred viewport + * + * @return void + */ + public function unsetBias() { + $this->viewport_bias = null; + } + /** * Executes query on OSM Nominatim API * @@ -174,6 +204,20 @@ protected function query(array $options) $method = 'search'; } + if (is_array($this->viewport_bias) && (count($this->viewport_bias) == 2)) { + /** @var Position $corner1 */ + $corner1 = $this->viewport_bias[0]; + /** @var Position $corner2 */ + $corner2 = $this->viewport_bias[1]; + /** @see http://wiki.openstreetmap.org/wiki/Nominatim */ + $options['viewbox'] = ( + $corner1->latitude . ',' . $corner1->longitude + . ',' + . $corner2->latitude . ',' . $corner2->longitude + ); + $options['bounded'] = '1'; + } + $options['format'] = 'json'; $options['addressdetails'] = 1; $options['email'] = $this->email; @@ -219,7 +263,7 @@ protected function query(array $options) } if ($response_code != 200) { - throw new InvalidStatusException("Geocoding query failed (status: '{$payload->status}')."); + throw new InvalidStatusException("Geocoding query failed (status: '" . $response_code . "')."); } return new GeocodingResponse($this, $payload, $options); @@ -231,7 +275,7 @@ protected function mungeAddress($address) { do { // remove all numbers behind the first comma, if any $address = preg_replace('/,([^\d]*?)([\d]+)/',',\\1',$address, -1, $matched); } while ($matched > 0); - $address = preg_replace('/(Praha|Brno|Olomouc|Plzeň|Plzen|Ostrava) +\d+/i',',\\1',$address, -1, $matched); // remove "Praha 3", as this administrative district division is useless to us; worse, not all address points have it! + $address = preg_replace('/(Praha|Brno|Olomouc|Plzeň|Plzen|Ostrava|Pardubice) +\d+/i',',\\1',$address, -1, $matched); // remove "Praha 3", as this administrative district division is useless to us; worse, not all address points have it! $address = preg_replace('/,? +,?/',' ',$address); return $address; diff --git a/src/Geolocation/interfaces/IGeocodingService.php b/src/Geolocation/interfaces/IGeocodingService.php index 9ec798f..82974da 100644 --- a/src/Geolocation/interfaces/IGeocodingService.php +++ b/src/Geolocation/interfaces/IGeocodingService.php @@ -35,4 +35,25 @@ function getAddress(Position $position, $options = array()); */ function getPositionAndAddress($query); + /** + * Set the geocoder to bias on a "rectangle" defined by two Position "corners"; + * following queries will prefer the viewport set this way. + * Note that there are always two areas likely to be defined this way (see the links); + * choosing the correct one is up to the implementation. + * @see http://stackoverflow.com/questions/23084764/draw-rectangle-on-map-given-two-opposite-coordinates-determine-which-ones-ar#comment35283253_23084764 + * @see http://i.piskvor.org/test/which.png + * @see http://i.piskvor.org/test/which2.png + * + * @param Position $corner1 + * @param Position $corner2 + * @return boolean true if region biasing is available, false otherwise + */ + function setBias(Position $corner1, Position $corner2); + + /** + * Reset the bias set by setBias; following queries will not have a preferred viewport + * @return void + */ + function unsetBias(); + } From 8440de896602fff25c8be101d196df96859f86e8 Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Mon, 11 Aug 2014 21:47:31 +0200 Subject: [PATCH 12/14] use public Nominatim --- src/Geolocation/Nominatim/GeocodingClient.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index 614c83f..08512b6 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -31,10 +31,10 @@ class GeocodingClient extends Object implements IGeocodingService { - protected $base_url = 'http://nominatim.limes.dyndns.org:19401/'; // use our own nominatim instance, instead of the public one + protected $base_url = 'http://nominatim.limes.dyndns.org/'; // use the public nominatim instance - protected $user_name = 'rekolacz'; // set to empty for "no HTTP auth required" - protected $user_pwd = 'rekolacz'; + protected $user_name = ''; // set to empty for "no HTTP auth required" + protected $user_pwd = ''; protected $email = 'info@rekola.cz'; // needed in case of capacity problems (not in case of own instance) protected $ua = 'ReKolaSMS http://rekola.cz/ info@rekola.cz 1.0 2014-01-07'; // User-Agent string; required! From b5235a5a70284e25a74203548207a483f210ec97 Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Mon, 11 Aug 2014 21:48:01 +0200 Subject: [PATCH 13/14] use public Nominatim --- src/Geolocation/Nominatim/GeocodingClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index 08512b6..f6b2031 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -31,7 +31,7 @@ class GeocodingClient extends Object implements IGeocodingService { - protected $base_url = 'http://nominatim.limes.dyndns.org/'; // use the public nominatim instance + protected $base_url = 'http://nominatim.openstreetmap.org'; // use the public nominatim instance protected $user_name = ''; // set to empty for "no HTTP auth required" protected $user_pwd = ''; From 163329fb0598d8684f3f2f425ad2ed45c1b37c3f Mon Sep 17 00:00:00 2001 From: Jan Piskvor Martinec Date: Mon, 11 Aug 2014 21:48:50 +0200 Subject: [PATCH 14/14] use public Nominatim --- src/Geolocation/Nominatim/GeocodingClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Geolocation/Nominatim/GeocodingClient.php b/src/Geolocation/Nominatim/GeocodingClient.php index f6b2031..c96c414 100644 --- a/src/Geolocation/Nominatim/GeocodingClient.php +++ b/src/Geolocation/Nominatim/GeocodingClient.php @@ -31,7 +31,7 @@ class GeocodingClient extends Object implements IGeocodingService { - protected $base_url = 'http://nominatim.openstreetmap.org'; // use the public nominatim instance + protected $base_url = 'http://nominatim.openstreetmap.org/'; // use the public nominatim instance protected $user_name = ''; // set to empty for "no HTTP auth required" protected $user_pwd = '';