Repository: geocoder-php/Geocoder (php)

Issue:
[Photon] missing support for multiple layers
#1245 introduced `layer` parameter for photon.

Photon supports filtering by [multiple layers](https://github.com/komoot/photon?tab=readme-ov-file#filter-results-by-layer).

https://photon.komoot.io/reverse?lat=49.001831&lon=21.239311&layer=city&limit=5
https://photon.komoot.io/reverse?lat=49.001831&lon=21.239311&layer=city&layer=district&limit=5

However it's not possible to filter with multiple layers with `Geocoder\Provider\Photon\Photon`.

Pull request description:
feat: [Photon] support multiple layers
Resolves #1249 

Patch:
diff --git a/src/Provider/Photon/Photon.php b/src/Provider/Photon/Photon.php
index c8efa767..d9d423c4 100644
--- a/src/Provider/Photon/Photon.php
+++ b/src/Provider/Photon/Photon.php
@@ -68,12 +68,12 @@ final class Photon extends AbstractHttpProvider implements Provider
             .'/api?'
             .http_build_query([
                 'q' => $address,
-                'layer' => $query->getData('layer'),
                 'limit' => $query->getLimit(),
                 'lang' => $query->getLocale(),
                 'lat' => $query->getData('lat'),
                 'lon' => $query->getData('lon'),
             ]);
+        $url .= $this->buildLayerFilterQuery($query->getData('layer'));
         $osmTagFilters = $this->buildOsmTagFilterQuery($query->getData('osm_tag'));
         if (!empty($osmTagFilters)) {
             $url .= $osmTagFilters;
@@ -109,11 +109,11 @@ final class Photon extends AbstractHttpProvider implements Provider
             .http_build_query([
                 'lat' => $latitude,
                 'lon' => $longitude,
-                'layer' => $query->getData('layer'),
                 'radius' => $query->getData('radius'),
                 'limit' => $query->getLimit(),
                 'lang' => $query->getLocale(),
             ]);
+        $url .= $this->buildLayerFilterQuery($query->getData('layer'));
         $osmTagFilters = $this->buildOsmTagFilterQuery($query->getData('osm_tag'));
         if (!empty($osmTagFilters)) {
             $url .= $osmTagFilters;
@@ -177,6 +177,25 @@ final class Photon extends AbstractHttpProvider implements Provider
         return 'photon';
     }
 
+    /**
+     * @param string|string[]|null $layers
+     */
+    private function buildLayerFilterQuery(mixed $layers): string
+    {
+        $query = '';
+        if (null === $layers) {
+            return $query;
+        }
+        if (is_string($layers)) {
+            return '&layer='.urlencode($layers);
+        }
+        foreach ($layers as $layer) {
+            $query .= '&layer='.urlencode($layer);
+        }
+
+        return $query;
+    }
+
     /**
      * @param string|array<int, string>|null $filters
      */


Test patch:
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_4d9e19f668373f9804a90f41f1a499a74ded2594 b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_4d9e19f668373f9804a90f41f1a499a74ded2594
new file mode 100644
index 00000000..bc7c5a59
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_4d9e19f668373f9804a90f41f1a499a74ded2594
@@ -0,0 +1,1 @@
+s:359:"{"features":[{"geometry":{"coordinates":[21.2392122,49.0000074],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":388255,"extent":[20.870461,49.185185,21.485864,48.810739],"country":"Slovensko","osm_key":"place","countrycode":"SK","osm_value":"city","name":"Prešov","state":"Prešovský kraj","type":"city"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_1452fda1b72428b0f5de90de04038275fd7f7e7e b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_6ab584da133fb849cb0cffafad872b65d3da3de1
similarity index 100%
rename from src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_1452fda1b72428b0f5de90de04038275fd7f7e7e
rename to src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_6ab584da133fb849cb0cffafad872b65d3da3de1
diff --git a/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ed91365025e794205300bed4ad45119eeeed5f3f b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ed91365025e794205300bed4ad45119eeeed5f3f
new file mode 100644
index 00000000..19cda6b8
--- /dev/null
+++ b/src/Provider/Photon/Tests/.cached_responses/photon.komoot.io_ed91365025e794205300bed4ad45119eeeed5f3f
@@ -0,0 +1,1 @@
+s:703:"{"features":[{"geometry":{"coordinates":[21.2392122,49.0000074],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":388255,"extent":[20.870461,49.185185,21.485864,48.810739],"country":"Slovensko","osm_key":"place","countrycode":"SK","osm_value":"city","name":"Prešov","state":"Prešovský kraj","type":"city"}},{"geometry":{"coordinates":[21.2392122,49.0000074],"type":"Point"},"type":"Feature","properties":{"osm_type":"R","osm_id":2320257,"extent":[21.1569866,49.0468381,21.3354271,48.9449997],"country":"Slovensko","osm_key":"place","city":"Prešov","countrycode":"SK","osm_value":"city","name":"Prešov","state":"Prešovský kraj","type":"district"}}],"type":"FeatureCollection"}";
\ No newline at end of file
diff --git a/src/Provider/Photon/Tests/PhotonTest.php b/src/Provider/Photon/Tests/PhotonTest.php
index 58466bcb..3afd7486 100644
--- a/src/Provider/Photon/Tests/PhotonTest.php
+++ b/src/Provider/Photon/Tests/PhotonTest.php
@@ -213,6 +213,32 @@ class PhotonTest extends BaseTestCase
         $this->assertEquals('Berlin', $result->getLocality());
     }
 
+    public function testReverseQueryWithMultipleLayers(): void
+    {
+        $provider = Photon::withKomootServer($this->getHttpClient());
+        $reverseQuery = ReverseQuery::fromCoordinates(49.001831, 21.239311)
+            ->withData('layer', 'city')
+            ->withLimit(2);
+
+        $results = $provider->reverseQuery($reverseQuery);
+        $this->assertInstanceOf(AddressCollection::class, $results);
+        $this->assertCount(1, $results);
+        $result0 = $results->get(0);
+        $this->assertInstanceOf(PhotonAddress::class, $result0);
+        $this->assertEquals('city', $result0->getType());
+
+        $reverseQuery = $reverseQuery->withData('layer', ['city', 'district']);
+        $results = $provider->reverseQuery($reverseQuery);
+        $this->assertInstanceOf(AddressCollection::class, $results);
+        $this->assertCount(2, $results);
+        $result0 = $results->get(0);
+        $this->assertInstanceOf(PhotonAddress::class, $result0);
+        $this->assertEquals('city', $result0->getType());
+        $result1 = $results->get(1);
+        $this->assertInstanceOf(PhotonAddress::class, $result1);
+        $this->assertEquals('district', $result1->getType());
+    }
+
     public function testGeocodeQueryWithBbox(): void
     {
         // Germany