From c815b76118bb3448f3b4acacd33ccdcd90d73a5c Mon Sep 17 00:00:00 2001 From: Nagy Attila Gabor Date: Tue, 24 Oct 2023 19:00:33 +0200 Subject: [PATCH 1/4] [Pelias] Refactor address instantiation into separate method This is a minor refactor, but can help customization of this provider a lot. By having a separate method for Address instantiation one might easily override what fields from Pelias make into the output --- src/Provider/Pelias/Pelias.php | 83 +++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 37 deletions(-) diff --git a/src/Provider/Pelias/Pelias.php b/src/Provider/Pelias/Pelias.php index 3bf9a79cb..a473918a9 100644 --- a/src/Provider/Pelias/Pelias.php +++ b/src/Provider/Pelias/Pelias.php @@ -132,48 +132,57 @@ protected function executeQuery(string $url): AddressCollection $results = []; foreach ($locations as $location) { - if (isset($location['bbox'])) { - $bounds = [ - 'south' => $location['bbox'][3], - 'west' => $location['bbox'][2], - 'north' => $location['bbox'][1], - 'east' => $location['bbox'][0], - ]; - } else { - $bounds = [ - 'south' => null, - 'west' => null, - 'north' => null, - 'east' => null, - ]; - } + $results[] = $this->buildAddress($location); + } - $props = $location['properties']; + return new AddressCollection($results); + } - $adminLevels = []; - foreach (['region', 'county', 'locality', 'macroregion', 'country'] as $i => $component) { - if (isset($props[$component])) { - $adminLevels[] = ['name' => $props[$component], 'level' => $i + 1]; - } - } + /** + * Build the Address object from the the Feature + * @param array $location the Feature array + * @return Address the address object + */ + protected function buildAddress(array $location) + { + if (isset($location['bbox'])) { + $bounds = [ + 'south' => $location['bbox'][3], + 'west' => $location['bbox'][2], + 'north' => $location['bbox'][1], + 'east' => $location['bbox'][0], + ]; + } else { + $bounds = [ + 'south' => null, + 'west' => null, + 'north' => null, + 'east' => null, + ]; + } - $results[] = Address::createFromArray([ - 'providedBy' => $this->getName(), - 'latitude' => $location['geometry']['coordinates'][1], - 'longitude' => $location['geometry']['coordinates'][0], - 'bounds' => $bounds, - 'streetNumber' => isset($props['housenumber']) ? $props['housenumber'] : null, - 'streetName' => isset($props['street']) ? $props['street'] : null, - 'subLocality' => isset($props['neighbourhood']) ? $props['neighbourhood'] : null, - 'locality' => isset($props['locality']) ? $props['locality'] : null, - 'postalCode' => isset($props['postalcode']) ? $props['postalcode'] : null, - 'adminLevels' => $adminLevels, - 'country' => isset($props['country']) ? $props['country'] : null, - 'countryCode' => isset($props['country_a']) ? strtoupper($props['country_a']) : null, - ]); + $props = $location['properties']; + $adminLevels = []; + foreach (['region', 'county', 'locality', 'macroregion', 'country'] as $i => $component) { + if (isset($props[$component])) { + $adminLevels[] = ['name' => $props[$component], 'level' => $i + 1]; + } } - return new AddressCollection($results); + return Address::createFromArray([ + 'providedBy' => $this->getName(), + 'latitude' => $location['geometry']['coordinates'][1], + 'longitude' => $location['geometry']['coordinates'][0], + 'bounds' => $bounds, + 'streetNumber' => isset($props['housenumber']) ? $props['housenumber'] : null, + 'streetName' => isset($props['street']) ? $props['street'] : null, + 'subLocality' => isset($props['neighbourhood']) ? $props['neighbourhood'] : null, + 'locality' => isset($props['locality']) ? $props['locality'] : null, + 'postalCode' => isset($props['postalcode']) ? $props['postalcode'] : null, + 'adminLevels' => $adminLevels, + 'country' => isset($props['country']) ? $props['country'] : null, + 'countryCode' => isset($props['country_a']) ? strtoupper($props['country_a']) : null, + ]); } /** From 9c2af7f4410c50dd4280503ed46c9003f9b726f3 Mon Sep 17 00:00:00 2001 From: Nagy Attila Gabor Date: Tue, 24 Oct 2023 19:16:32 +0200 Subject: [PATCH 2/4] [Pelias] return more information from the pelias result set This change introduces a new address type for pelias. This exposes the following properties from the pelias feature: - layer - confidence - source - match_type - accuracy These values can be very useful when deciding if the result is accurate enough or not. --- src/Provider/Pelias/CHANGELOG.md | 6 + src/Provider/Pelias/Model/PeliasAddress.php | 175 ++++++++++++++++++ src/Provider/Pelias/Pelias.php | 8 +- .../Pelias/Tests/CustomPropertiesTest.php | 117 ++++++++++++ 4 files changed, 305 insertions(+), 1 deletion(-) create mode 100644 src/Provider/Pelias/Model/PeliasAddress.php create mode 100644 src/Provider/Pelias/Tests/CustomPropertiesTest.php diff --git a/src/Provider/Pelias/CHANGELOG.md b/src/Provider/Pelias/CHANGELOG.md index 0ae88d80a..4ff48cd89 100644 --- a/src/Provider/Pelias/CHANGELOG.md +++ b/src/Provider/Pelias/CHANGELOG.md @@ -8,6 +8,12 @@ The change log describes what is "Added", "Removed", "Changed" or "Fixed" betwee - Add support for PHP 8.1 - Add GitHub Actions workflow +- Returns the following pelias properties as well: + - layer + - confidence + - source + - match_type + - accuracy ### Removed diff --git a/src/Provider/Pelias/Model/PeliasAddress.php b/src/Provider/Pelias/Model/PeliasAddress.php new file mode 100644 index 000000000..c658904d6 --- /dev/null +++ b/src/Provider/Pelias/Model/PeliasAddress.php @@ -0,0 +1,175 @@ +layer = $data['layer'] ?? null; + $address->confidence = $data['confidence'] ?? null; + $address->matchType = $data['match_type'] ?? null; + $address->source = $data['source'] ?? null; + $address->accuracy = $data['accuracy'] ?? null; + return $address; + } + + /** + * Get the pelias layer returned + * + * @return string|null + */ + public function getLayer() + { + return $this->layer; + } + + /** + * Get confidence score from pelias + * + * @return float|null + */ + public function getConfidence() + { + return $this->confidence; + } + + /** + * Get match type from pelias + * + * @return string|null + */ + public function getMatchType() + { + return $this->matchType; + } + + /** + * Get data source from pelias + * + * @return string|null + */ + public function getSource() + { + return $this->source; + } + + /** + * Get accuracy from pelias + * + * @return string|null + */ + public function getAccuracy() + { + return $this->accuracy; + } + + /** + * Set the pelias layer returned + * @param string|null $layer name of the pelias layer + * @return PeliasAddress + */ + public function withLayer(string $layer = null) + { + $new = clone $this; + $new->layer = $layer; + + return $new; + } + + /** + * Set confidence score from pelias + * @param float|null $confidence confidence level as a float + * @return PeliasAddress + */ + public function withConfidence(float $confidence = null) + { + $new = clone $this; + $new->confidence = $confidence; + + return $new; + } + + /** + * Set match type from pelias + * @param string|null $matchType precision of the match like "exact" + * @return PeliasAddress + */ + public function withMatchType(string $matchType = null) + { + $new = clone $this; + $new->matchType = $matchType; + + return $new; + } + + /** + * Set data source from pelias + * @param string|null $source address source from pelias + * @return PeliasAddress + */ + public function withSource(string $source = null) + { + $new = clone $this; + $new->source = $source; + + return $new; + } + + /** + * Set accuracy from pelias + * @param string|null $accuracy accuracy level from pelias like "point" + * @return PeliasAddress + */ + public function withAccuracy(string $accuracy = null) + { + $new = clone $this; + $new->accuracy = $accuracy; + + return $new; + } + +} \ No newline at end of file diff --git a/src/Provider/Pelias/Pelias.php b/src/Provider/Pelias/Pelias.php index a473918a9..a0a7cded5 100644 --- a/src/Provider/Pelias/Pelias.php +++ b/src/Provider/Pelias/Pelias.php @@ -19,6 +19,7 @@ use Geocoder\Http\Provider\AbstractHttpProvider; use Geocoder\Model\Address; use Geocoder\Model\AddressCollection; +use Geocoder\Provider\Pelias\Model\PeliasAddress; use Geocoder\Provider\Provider; use Geocoder\Query\GeocodeQuery; use Geocoder\Query\ReverseQuery; @@ -169,7 +170,7 @@ protected function buildAddress(array $location) } } - return Address::createFromArray([ + return PeliasAddress::createFromArray([ 'providedBy' => $this->getName(), 'latitude' => $location['geometry']['coordinates'][1], 'longitude' => $location['geometry']['coordinates'][0], @@ -182,6 +183,11 @@ protected function buildAddress(array $location) 'adminLevels' => $adminLevels, 'country' => isset($props['country']) ? $props['country'] : null, 'countryCode' => isset($props['country_a']) ? strtoupper($props['country_a']) : null, + 'layer' => $props['layer'] ?? null, + 'confidence' => $props['confidence'] ?? null, + 'match_type' => $props['match_type'] ?? null, + 'source' => $props['source'] ?? null, + 'accuracy' => $props['accuracy'] ?? null, ]); } diff --git a/src/Provider/Pelias/Tests/CustomPropertiesTest.php b/src/Provider/Pelias/Tests/CustomPropertiesTest.php new file mode 100644 index 000000000..1eecc1f3f --- /dev/null +++ b/src/Provider/Pelias/Tests/CustomPropertiesTest.php @@ -0,0 +1,117 @@ +getMockedHttpClient($response), 'http://localhost/'); + $result = $provider->geocodeQuery(GeocodeQuery::create('foobar')); + + $this->assertInstanceOf(Collection::class, $result); + $this->assertEquals(1, $result->count()); + $address = $result->get(0); + + $this->assertInstanceOf(PeliasAddress::class, $address); + + $this->assertEquals('openaddresses', $address->getSource()); + $this->assertEquals('address', $address->getLayer()); + $this->assertEquals(1, $address->getConfidence()); + $this->assertEquals('exact', $address->getMatchType()); + $this->assertEquals('point', $address->getAccuracy()); + } + + public function testWithSource() + { + $address = new PeliasAddress('Pelias', new AdminLevelCollection()); + $newAddress = $address->withSource('openaddresses'); + $this->assertEquals('openaddresses', $newAddress->getSource()); + $this->assertNull($address->getSource()); + } + + public function testWithLayer() + { + $address = new PeliasAddress('Pelias', new AdminLevelCollection()); + $newAddress = $address->withLayer('address'); + $this->assertEquals('address', $newAddress->getLayer()); + $this->assertNull($address->getLayer()); + } + + public function testWithConfidence() + { + $address = new PeliasAddress('Pelias', new AdminLevelCollection()); + $newAddress = $address->withConfidence(1); + $this->assertEquals(1, $newAddress->getConfidence()); + $this->assertNull($address->getConfidence()); + } + + public function testWithMatchType() + { + $address = new PeliasAddress('Pelias', new AdminLevelCollection()); + $newAddress = $address->withMatchType('exact'); + $this->assertEquals('exact', $newAddress->getMatchType()); + $this->assertNull($address->getMatchType()); + } + + public function testWithAccuracy() + { + $address = new PeliasAddress('Pelias', new AdminLevelCollection()); + $newAddress = $address->withAccuracy('point'); + $this->assertEquals('point', $newAddress->getAccuracy()); + $this->assertNull($address->getAccuracy()); + } + +} From 9a19d62d8083f2c09cb322a89f17bb2235150045 Mon Sep 17 00:00:00 2001 From: Nagy Attila Gabor Date: Thu, 26 Oct 2023 11:05:20 +0200 Subject: [PATCH 3/4] [Pelias] Updated code to use null coalescing operator and typehinting wherever possible This change does not affect the way this provider behaves, it's only syntactic sugar. --- src/Provider/Pelias/Model/PeliasAddress.php | 10 +++---- src/Provider/Pelias/Pelias.php | 33 ++++++++------------- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/Provider/Pelias/Model/PeliasAddress.php b/src/Provider/Pelias/Model/PeliasAddress.php index c658904d6..5e7c1129b 100644 --- a/src/Provider/Pelias/Model/PeliasAddress.php +++ b/src/Provider/Pelias/Model/PeliasAddress.php @@ -20,31 +20,31 @@ class PeliasAddress extends Address * The pelias layer returned * @var string|null */ - private $layer; + private ?string $layer = null; /** * Confidence score from pelias * @var float|null */ - private $confidence; + private ?float $confidence = null; /** * Match type from pelias * @var string|null */ - private $matchType; + private ?string $matchType = null; /** * Data source from pelias * @var string|null */ - private $source; + private ?string $source = null; /** * Accuracy from pelias * @var string|null */ - private $accuracy; + private ?string $accuracy = null; public static function createFromArray(array $data) { diff --git a/src/Provider/Pelias/Pelias.php b/src/Provider/Pelias/Pelias.php index a0a7cded5..449251933 100644 --- a/src/Provider/Pelias/Pelias.php +++ b/src/Provider/Pelias/Pelias.php @@ -146,21 +146,12 @@ protected function executeQuery(string $url): AddressCollection */ protected function buildAddress(array $location) { - if (isset($location['bbox'])) { - $bounds = [ - 'south' => $location['bbox'][3], - 'west' => $location['bbox'][2], - 'north' => $location['bbox'][1], - 'east' => $location['bbox'][0], - ]; - } else { - $bounds = [ - 'south' => null, - 'west' => null, - 'north' => null, - 'east' => null, - ]; - } + $bounds = [ + 'south' => $location['bbox'][3] ?? null, + 'west' => $location['bbox'][2] ?? null, + 'north' => $location['bbox'][1] ?? null, + 'east' => $location['bbox'][0] ?? null, + ]; $props = $location['properties']; $adminLevels = []; @@ -175,13 +166,13 @@ protected function buildAddress(array $location) 'latitude' => $location['geometry']['coordinates'][1], 'longitude' => $location['geometry']['coordinates'][0], 'bounds' => $bounds, - 'streetNumber' => isset($props['housenumber']) ? $props['housenumber'] : null, - 'streetName' => isset($props['street']) ? $props['street'] : null, - 'subLocality' => isset($props['neighbourhood']) ? $props['neighbourhood'] : null, - 'locality' => isset($props['locality']) ? $props['locality'] : null, - 'postalCode' => isset($props['postalcode']) ? $props['postalcode'] : null, + 'streetNumber' => $props['housenumber'] ?? null, + 'streetName' => $props['street'] ?? null, + 'subLocality' => $props['neighbourhood'] ?? null, + 'locality' => $props['locality'] ?? null, + 'postalCode' => $props['postalcode'] ?? null, 'adminLevels' => $adminLevels, - 'country' => isset($props['country']) ? $props['country'] : null, + 'country' => $props['country'] ?? null, 'countryCode' => isset($props['country_a']) ? strtoupper($props['country_a']) : null, 'layer' => $props['layer'] ?? null, 'confidence' => $props['confidence'] ?? null, From f124341c6f3e33d6a20b14f171450508687ae6dd Mon Sep 17 00:00:00 2001 From: Nagy Attila Gabor Date: Fri, 27 Oct 2023 23:15:10 +0200 Subject: [PATCH 4/4] [Pelias] Fixed phpstan and cs-fixed errors, add documentation to the new fields --- src/Provider/Pelias/Model/PeliasAddress.php | 77 +++++++++++-------- src/Provider/Pelias/Pelias.php | 10 ++- .../Pelias/Tests/CustomPropertiesTest.php | 14 ++-- 3 files changed, 59 insertions(+), 42 deletions(-) diff --git a/src/Provider/Pelias/Model/PeliasAddress.php b/src/Provider/Pelias/Model/PeliasAddress.php index 5e7c1129b..4242546f2 100644 --- a/src/Provider/Pelias/Model/PeliasAddress.php +++ b/src/Provider/Pelias/Model/PeliasAddress.php @@ -17,32 +17,37 @@ class PeliasAddress extends Address { /** - * The pelias layer returned - * @var string|null + * The pelias layer returned. + * + * @see https://github.com/pelias/documentation/blob/master/result_quality.md#layer */ private ?string $layer = null; /** - * Confidence score from pelias - * @var float|null + * Confidence score from pelias. + * + * @see https://github.com/pelias/documentation/blob/master/result_quality.md#confidence */ private ?float $confidence = null; /** - * Match type from pelias - * @var string|null + * Match type from pelias. + * + * @see https://github.com/pelias/documentation/blob/master/result_quality.md#match_type */ private ?string $matchType = null; /** - * Data source from pelias - * @var string|null + * Data source from pelias. + * + * @see https://github.com/pelias/documentation/blob/master/data-sources.md */ private ?string $source = null; /** - * Accuracy from pelias - * @var string|null + * Accuracy from pelias. + * + * @see https://github.com/pelias/documentation/blob/master/result_quality.md#accuracy */ private ?string $accuracy = null; @@ -54,53 +59,54 @@ public static function createFromArray(array $data) $address->matchType = $data['match_type'] ?? null; $address->source = $data['source'] ?? null; $address->accuracy = $data['accuracy'] ?? null; + return $address; } /** - * Get the pelias layer returned + * Get the pelias layer returned. * - * @return string|null - */ + * @return string|null + */ public function getLayer() { return $this->layer; } /** - * Get confidence score from pelias + * Get confidence score from pelias. * - * @return float|null - */ + * @return float|null + */ public function getConfidence() { return $this->confidence; } /** - * Get match type from pelias + * Get match type from pelias. * - * @return string|null - */ + * @return string|null + */ public function getMatchType() { return $this->matchType; } /** - * Get data source from pelias + * Get data source from pelias. * - * @return string|null - */ + * @return string|null + */ public function getSource() { return $this->source; } /** - * Get accuracy from pelias + * Get accuracy from pelias. * - * @return string|null + * @return string|null */ public function getAccuracy() { @@ -108,8 +114,10 @@ public function getAccuracy() } /** - * Set the pelias layer returned + * Set the pelias layer returned. + * * @param string|null $layer name of the pelias layer + * * @return PeliasAddress */ public function withLayer(string $layer = null) @@ -121,8 +129,10 @@ public function withLayer(string $layer = null) } /** - * Set confidence score from pelias + * Set confidence score from pelias. + * * @param float|null $confidence confidence level as a float + * * @return PeliasAddress */ public function withConfidence(float $confidence = null) @@ -134,8 +144,10 @@ public function withConfidence(float $confidence = null) } /** - * Set match type from pelias + * Set match type from pelias. + * * @param string|null $matchType precision of the match like "exact" + * * @return PeliasAddress */ public function withMatchType(string $matchType = null) @@ -147,8 +159,10 @@ public function withMatchType(string $matchType = null) } /** - * Set data source from pelias + * Set data source from pelias. + * * @param string|null $source address source from pelias + * * @return PeliasAddress */ public function withSource(string $source = null) @@ -160,8 +174,10 @@ public function withSource(string $source = null) } /** - * Set accuracy from pelias + * Set accuracy from pelias. + * * @param string|null $accuracy accuracy level from pelias like "point" + * * @return PeliasAddress */ public function withAccuracy(string $accuracy = null) @@ -171,5 +187,4 @@ public function withAccuracy(string $accuracy = null) return $new; } - -} \ No newline at end of file +} diff --git a/src/Provider/Pelias/Pelias.php b/src/Provider/Pelias/Pelias.php index 449251933..d2f33538b 100644 --- a/src/Provider/Pelias/Pelias.php +++ b/src/Provider/Pelias/Pelias.php @@ -140,11 +140,13 @@ protected function executeQuery(string $url): AddressCollection } /** - * Build the Address object from the the Feature - * @param array $location the Feature array - * @return Address the address object + * Build the Address object from the the Feature. + * + * @param array $location the Feature array + * + * @return PeliasAddress the address object */ - protected function buildAddress(array $location) + protected function buildAddress(array $location): PeliasAddress { $bounds = [ 'south' => $location['bbox'][3] ?? null, diff --git a/src/Provider/Pelias/Tests/CustomPropertiesTest.php b/src/Provider/Pelias/Tests/CustomPropertiesTest.php index 1eecc1f3f..c6bc18857 100644 --- a/src/Provider/Pelias/Tests/CustomPropertiesTest.php +++ b/src/Provider/Pelias/Tests/CustomPropertiesTest.php @@ -26,7 +26,7 @@ protected function getCacheDir(): string return __DIR__.'/.cached_responses'; } - public function testPeliasAddressReturned() + public function testPeliasAddressReturned(): void { $response = ' { @@ -63,6 +63,7 @@ public function testPeliasAddressReturned() $this->assertInstanceOf(Collection::class, $result); $this->assertEquals(1, $result->count()); + /** @var PeliasAddress $address */ $address = $result->get(0); $this->assertInstanceOf(PeliasAddress::class, $address); @@ -74,7 +75,7 @@ public function testPeliasAddressReturned() $this->assertEquals('point', $address->getAccuracy()); } - public function testWithSource() + public function testWithSource(): void { $address = new PeliasAddress('Pelias', new AdminLevelCollection()); $newAddress = $address->withSource('openaddresses'); @@ -82,7 +83,7 @@ public function testWithSource() $this->assertNull($address->getSource()); } - public function testWithLayer() + public function testWithLayer(): void { $address = new PeliasAddress('Pelias', new AdminLevelCollection()); $newAddress = $address->withLayer('address'); @@ -90,7 +91,7 @@ public function testWithLayer() $this->assertNull($address->getLayer()); } - public function testWithConfidence() + public function testWithConfidence(): void { $address = new PeliasAddress('Pelias', new AdminLevelCollection()); $newAddress = $address->withConfidence(1); @@ -98,7 +99,7 @@ public function testWithConfidence() $this->assertNull($address->getConfidence()); } - public function testWithMatchType() + public function testWithMatchType(): void { $address = new PeliasAddress('Pelias', new AdminLevelCollection()); $newAddress = $address->withMatchType('exact'); @@ -106,12 +107,11 @@ public function testWithMatchType() $this->assertNull($address->getMatchType()); } - public function testWithAccuracy() + public function testWithAccuracy(): void { $address = new PeliasAddress('Pelias', new AdminLevelCollection()); $newAddress = $address->withAccuracy('point'); $this->assertEquals('point', $newAddress->getAccuracy()); $this->assertNull($address->getAccuracy()); } - }