Skip to content

Commit c74db68

Browse files
cylon-vNyholm
authored andcommitted
Add PickPoint provider. (#721)
Fixed style. Apply review notes.
1 parent 511af45 commit c74db68

19 files changed

+476
-0
lines changed

phpunit.xml.dist

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
<server name="OPENCAGE_API_KEY" value="YOUR_GEOCODING_KEY" />
2828
<server name="MAPZEN_API_KEY" value="YOUR_MAPZEN_API_KEY" />
2929
<server name="IPINFODB_API_KEY" value="YOUR_API_KEY" />
30+
<server name="PICKPOINT_API_KEY" value="YOUR_API_KEY" />
3031
<!--<server name="MAXMIND_API_KEY" value="YOUR_API_KEY" />-->
3132
</php>
3233

src/Provider/PickPoint/.gitattributes

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.gitattributes export-ignore
2+
.travis.yml export-ignore
3+
phpunit.xml.dist export-ignore
4+
Tests/ export-ignore

src/Provider/PickPoint/.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
vendor/
2+
composer.lock
3+
phpunit.xml

src/Provider/PickPoint/.travis.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
language: php
2+
sudo: false
3+
4+
php: 7.0
5+
6+
7+
install:
8+
- composer update --prefer-stable --prefer-dist
9+
10+
script:
11+
- composer test-ci
12+
13+
after_success:
14+
- wget https://scrutinizer-ci.com/ocular.phar
15+
- php ocular.phar code-coverage:upload --format=php-clover build/coverage.xml
16+

src/Provider/PickPoint/CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Change Log
2+
3+
The change log describes what is "Added", "Removed", "Changed" or "Fixed" between each release.
4+
5+
## 4.0.0
6+
7+
First release of this library.

src/Provider/PickPoint/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License (MIT)
2+
3+
Copyright (c) 2017 — Vladimir Kalinkin <[email protected]>
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

src/Provider/PickPoint/PickPoint.php

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the Geocoder package.
7+
* For the full copyright and license information, please view the LICENSE
8+
* file that was distributed with this source code.
9+
*
10+
* @license MIT License
11+
*/
12+
13+
namespace Geocoder\Provider\PickPoint;
14+
15+
use Geocoder\Collection;
16+
use Geocoder\Exception\InvalidServerResponse;
17+
use Geocoder\Exception\InvalidCredentials;
18+
use Geocoder\Location;
19+
use Geocoder\Model\AddressBuilder;
20+
use Geocoder\Model\AddressCollection;
21+
use Geocoder\Query\GeocodeQuery;
22+
use Geocoder\Query\ReverseQuery;
23+
use Geocoder\Http\Provider\AbstractHttpProvider;
24+
use Geocoder\Provider\Provider;
25+
use Http\Client\HttpClient;
26+
27+
/**
28+
* @author Vladimir Kalinkin <[email protected]>
29+
*/
30+
final class PickPoint extends AbstractHttpProvider implements Provider
31+
{
32+
/**
33+
* @var string
34+
*/
35+
const BASE_API_URL = 'https://api.pickpoint.io/v1';
36+
37+
/**
38+
* @var string
39+
*/
40+
private $apiKey;
41+
42+
/**
43+
* @param HttpClient $client an HTTP adapter
44+
* @param string $apiKey an API key
45+
*/
46+
public function __construct(HttpClient $client, string $apiKey)
47+
{
48+
if (empty($apiKey)) {
49+
throw new InvalidCredentials('No API key provided.');
50+
}
51+
52+
$this->apiKey = $apiKey;
53+
parent::__construct($client);
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function geocodeQuery(GeocodeQuery $query): Collection
60+
{
61+
$address = $query->getText();
62+
63+
$url = sprintf($this->getGeocodeEndpointUrl(), urlencode($address), $query->getLimit());
64+
$content = $this->executeQuery($url, $query->getLocale());
65+
66+
$doc = new \DOMDocument();
67+
if (!@$doc->loadXML($content) || null === $doc->getElementsByTagName('searchresults')->item(0)) {
68+
throw InvalidServerResponse::create($url);
69+
}
70+
71+
$searchResult = $doc->getElementsByTagName('searchresults')->item(0);
72+
$places = $searchResult->getElementsByTagName('place');
73+
74+
if (null === $places || 0 === $places->length) {
75+
return new AddressCollection([]);
76+
}
77+
78+
$results = [];
79+
foreach ($places as $place) {
80+
$results[] = $this->xmlResultToArray($place, $place);
81+
}
82+
83+
return new AddressCollection($results);
84+
}
85+
86+
/**
87+
* {@inheritdoc}
88+
*/
89+
public function reverseQuery(ReverseQuery $query): Collection
90+
{
91+
$coordinates = $query->getCoordinates();
92+
$longitude = $coordinates->getLongitude();
93+
$latitude = $coordinates->getLatitude();
94+
$url = sprintf($this->getReverseEndpointUrl(), $latitude, $longitude, $query->getData('zoom', 18));
95+
$content = $this->executeQuery($url, $query->getLocale());
96+
97+
$doc = new \DOMDocument();
98+
if (!@$doc->loadXML($content) || $doc->getElementsByTagName('error')->length > 0) {
99+
return new AddressCollection([]);
100+
}
101+
102+
$searchResult = $doc->getElementsByTagName('reversegeocode')->item(0);
103+
$addressParts = $searchResult->getElementsByTagName('addressparts')->item(0);
104+
$result = $searchResult->getElementsByTagName('result')->item(0);
105+
106+
return new AddressCollection([$this->xmlResultToArray($result, $addressParts)]);
107+
}
108+
109+
/**
110+
* @param \DOMElement $resultNode
111+
* @param \DOMElement $addressNode
112+
*
113+
* @return Location
114+
*/
115+
private function xmlResultToArray(\DOMElement $resultNode, \DOMElement $addressNode): Location
116+
{
117+
$builder = new AddressBuilder($this->getName());
118+
119+
foreach (['state', 'county'] as $i => $tagName) {
120+
if (null !== ($adminLevel = $this->getNodeValue($addressNode->getElementsByTagName($tagName)))) {
121+
$builder->addAdminLevel($i + 1, $adminLevel, '');
122+
}
123+
}
124+
125+
// get the first postal-code when there are many
126+
$postalCode = $this->getNodeValue($addressNode->getElementsByTagName('postcode'));
127+
if (!empty($postalCode)) {
128+
$postalCode = current(explode(';', $postalCode));
129+
}
130+
$builder->setPostalCode($postalCode);
131+
$builder->setStreetName($this->getNodeValue($addressNode->getElementsByTagName('road')) ?: $this->getNodeValue($addressNode->getElementsByTagName('pedestrian')));
132+
$builder->setStreetNumber($this->getNodeValue($addressNode->getElementsByTagName('house_number')));
133+
$builder->setLocality($this->getNodeValue($addressNode->getElementsByTagName('city')));
134+
$builder->setSubLocality($this->getNodeValue($addressNode->getElementsByTagName('suburb')));
135+
$builder->setCountry($this->getNodeValue($addressNode->getElementsByTagName('country')));
136+
$builder->setCountryCode(strtoupper($this->getNodeValue($addressNode->getElementsByTagName('country_code'))));
137+
$builder->setCoordinates($resultNode->getAttribute('lat'), $resultNode->getAttribute('lon'));
138+
139+
$boundsAttr = $resultNode->getAttribute('boundingbox');
140+
if ($boundsAttr) {
141+
$bounds = [];
142+
list($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']) = explode(',', $boundsAttr);
143+
$builder->setBounds($bounds['south'], $bounds['north'], $bounds['west'], $bounds['east']);
144+
}
145+
146+
return $builder->build();
147+
}
148+
149+
/**
150+
* {@inheritdoc}
151+
*/
152+
public function getName(): string
153+
{
154+
return 'pickpoint';
155+
}
156+
157+
/**
158+
* @param string $url
159+
* @param string|null $locale
160+
*
161+
* @return string
162+
*/
163+
private function executeQuery(string $url, string $locale = null): string
164+
{
165+
if (null !== $locale) {
166+
$url = sprintf('%s&accept-language=%s', $url, $locale);
167+
}
168+
169+
return $this->getUrlContents($url);
170+
}
171+
172+
private function getGeocodeEndpointUrl(): string
173+
{
174+
return self::BASE_API_URL.'/forward?q=%s&format=xml&addressdetails=1&limit=%d&key='.$this->apiKey;
175+
}
176+
177+
private function getReverseEndpointUrl(): string
178+
{
179+
return self::BASE_API_URL.'/reverse?format=xml&lat=%F&lon=%F&addressdetails=1&zoom=%d&key='.$this->apiKey;
180+
}
181+
182+
private function getNodeValue(\DOMNodeList $element)
183+
{
184+
return $element->length ? $element->item(0)->nodeValue : null;
185+
}
186+
}

src/Provider/PickPoint/Readme.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# PickPoint Geocoder provider
2+
[![Build Status](https://travis-ci.org/geocoder-php/pickpoint-provider.svg?branch=master)](http://travis-ci.org/geocoder-php/pickpoint-provider)
3+
[![Latest Stable Version](https://poser.pugx.org/geocoder-php/pickpoint-provider/v/stable)](https://packagist.org/packages/geocoder-php/pickpoint-provider)
4+
[![Total Downloads](https://poser.pugx.org/geocoder-php/pickpoint-provider/downloads)](https://packagist.org/packages/geocoder-php/pickpoint-provider)
5+
[![Monthly Downloads](https://poser.pugx.org/geocoder-php/pickpoint-provider/d/monthly.png)](https://packagist.org/packages/geocoder-php/pickpoint-provider)
6+
[![Code Coverage](https://img.shields.io/scrutinizer/coverage/g/geocoder-php/pickpoint-provider.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/pickpoint-provider)
7+
[![Quality Score](https://img.shields.io/scrutinizer/g/geocoder-php/pickpoint-provider.svg?style=flat-square)](https://scrutinizer-ci.com/g/geocoder-php/pickpoint-provider)
8+
[![Software License](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE)
9+
10+
This is the PickPoint provider from the PHP Geocoder. This is a **READ ONLY** repository. See the
11+
[main repo](https://github.com/geocoder-php/Geocoder) for information and documentation.
12+
13+
### Install
14+
15+
```bash
16+
composer require geocoder-php/pickpoint-provider
17+
```
18+
19+
### Contribute
20+
21+
Contributions are very welcome! Send a pull request to the [main repository](https://github.com/geocoder-php/Geocoder) or
22+
report any issues you find on the [issue tracker](https://github.com/geocoder-php/Geocoder/issues).
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
s:1025:"<?xml version="1.0" encoding="UTF-8" ?>
2+
<reversegeocode timestamp='Tue, 27 Jun 17 20:19:20 +0000' attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright' querystring='format=xml&amp;lat=48.860000&amp;lon=2.350000&amp;addressdetails=1&amp;zoom=18&amp;key=pqxQyozFtbW9wx2xr93h'>
3+
<result place_id="7270079" osm_type="node" osm_id="700309516" ref="Bistrot Beaubourg" lat="48.8600408" lon="2.3499857" boundingbox="48.8599408,48.8601408,2.3498857,2.3500857">Bistrot Beaubourg, 25, Rue Quincampoix, Beaubourg, St-Merri, 3e, Paris, Île-de-France, France métropolitaine, 75004, France</result><addressparts><cafe>Bistrot Beaubourg</cafe><house_number>25</house_number><pedestrian>Rue Quincampoix</pedestrian><neighbourhood>Beaubourg</neighbourhood><suburb>St-Merri</suburb><city_district>3e</city_district><city>Paris</city><county>Paris</county><state>Île-de-France</state><country>France</country><postcode>75004</postcode><country_code>fr</country_code></addressparts></reversegeocode>";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
s:414:"<?xml version="1.0" encoding="UTF-8" ?>
2+
<searchresults timestamp='Tue, 27 Jun 17 20:19:19 +0000' attribution='Data © OpenStreetMap contributors, ODbL 1.0. http://www.openstreetmap.org/copyright' querystring='2001:0db8:0000:0042:0000:8a2e:0370:7334' polygon='false' more_url='http://127.0.0.1/search.php?q=2001%3A0db8%3A0000%3A0042%3A0000%3A8a2e%3A0370%3A7334&amp;addressdetails=1&amp;format=xml'>
3+
</searchresults>";

0 commit comments

Comments
 (0)