Repository: geocoder-php/Geocoder (language: php)

Issue:
Administrative level 4 is defined twice
Hi There,

Using the Google API and ReverseQuery with coordinates sometimes  dies  with Geocoder\Exception\InvalidArgument 'Administrative level 4 is defined twice'

Google docs do say this might happen. Is it possible to merge the two lists for the purposes of returning data and therefore not having to generate an Exception?




Discussion / hints:
Hey. 

Thank you for this issue. 
You are correct. What do you suggest we should do if we notice multiple level 4’s?
Hi,

I wonder if there is a way to merge the lists in the level 4s? 
so take this example:

`        {
          "long_name": "Kings Hill",
          "short_name": "Kings Hill",
          "types": [
            "administrative_area_level_4",
            "locality",
            "political"
          ]
        },
        {
          "long_name": "Kings Hill",
          "short_name": "Kings Hill",
          "types": [
            "administrative_area_level_4",
            "political"
          ]
        },`

The types would all be merged and uniqued so the result might be ['locality','political']
Hm.. A bunch of questions comes to my mind.
- Should we always merge?
- Should we only merge if they are exactly the same?
- Should we really correct Google? If Google does not care about this issue, should we?
Closed because of inactivity. 
Same, here **Administrative level 3 is defined twice**. I guess the correct solution would be to get the first administrative area level of the same type.
Same here, with __Administrative level 1 is defined twice__ with the following query:
```
{"text":"\u017delezni\u010dn\u00e1 53 Stupava 90031","bounds":"null","locale":null,"limit":5,"data":[]}
```
and following result:
```
{
   "results":[
      {
         "address_components":[
            {
               "long_name":"53",
               "short_name":"53",
               "types":[
                  "street_number"
               ]
            },
            {
               "long_name":"\u017delezni\u010dn\u00e1",
               "short_name":"\u017delezni\u010dn\u00e1",
               "types":[
                  "route"
               ]
            },
            {
               "long_name":"Stupava",
               "short_name":"Stupava",
               "types":[
                  "political",
                  "sublocality",
                  "sublocality_level_1"
               ]
            },
            {
               "long_name":"Stupava",
               "short_name":"Stupava",
               "types":[
                  "political",
                  "sublocality",
                  "sublocality_level_1"
               ]
            },
            {
               "long_name":"Malacky",
               "short_name":"Malacky",
               "types":[
                  "administrative_area_level_2",
                  "political"
               ]
            },
            {
               "long_name":"Bratislavsk\u00fd kraj",
               "short_name":"Bratislavsk\u00fd kraj",
               "types":[
                  "administrative_area_level_1",
                  "political"
               ]
            },
            {
               "long_name":"Slovakia",
               "short_name":"SK",
               "types":[
                  "country",
                  "political"
               ]
            },
            {
               "long_name":"900 31",
               "short_name":"900 31",
               "types":[
                  "postal_code"
               ]
            }
         ],
         "formatted_address":"\u017delezni\u010dn\u00e1 53, 900 31 Stupava, Slovakia",
         "geometry":{
            "location":{
               "lat":48.2690826,
               "lng":17.0170817
            },
            "location_type":"RANGE_INTERPOLATED",
            "viewport":{
               "northeast":{
                  "lat":48.27043158029149,
                  "lng":17.0184306802915
               },
               "southwest":{
                  "lat":48.26773361970849,
                  "lng":17.0157327197085
               }
            }
         },
         "place_id":"EirFvWVsZXpuacSNbsOhIDUzLCA5MDAgMzEgU3R1cGF2YSwgU2xvdmFraWEiGhIYChQKEgnNEl0zb-1sRxGxKFRFRK8LrBA1",
         "types":[
            "street_address"
         ]
      }
   ],
   "status":"OK"
}
```
Weirdly, there is only one `administrative_area_level_1`.

// ah, the problem is actually the occurence of two `sublocality_level_1` with exactly the same names.
I submitted a PR (#943), could one of you test and confirm the PR fix the issue ? Thanks !

Fix patch:
diff --git a/src/Provider/GoogleMaps/Model/GoogleAddress.php b/src/Provider/GoogleMaps/Model/GoogleAddress.php
index 3108b39a5..9579f1d37 100644
--- a/src/Provider/GoogleMaps/Model/GoogleAddress.php
+++ b/src/Provider/GoogleMaps/Model/GoogleAddress.php
@@ -516,21 +516,26 @@ public function getSubLocalityLevels()
      */
     public function withSubLocalityLevels(array $subLocalityLevel)
     {
+        $levels = array_filter($subLocalityLevel, function ($level) {
+            return !empty($level['level']) && (!empty($level['name']) || !empty($level['code']));
+        });
+
+        $levelCount = array_count_values(array_column($levels, 'level'));
+
         $subLocalityLevels = [];
-        foreach ($subLocalityLevel as $level) {
-            if (empty($level['level'])) {
-                continue;
-            }
+        foreach ($levelCount as $level => $count) {
+            $_levels = array_filter($levels, function ($l) use ($level) {
+                return $l['level'] === $level;
+            });
 
-            $name = $level['name'] ?? $level['code'] ?? '';
-            if (empty($name)) {
-                continue;
-            }
+            $names = array_filter(array_column($_levels, 'name'), function ($name) { return !empty($name); });
+            $codes = array_filter(array_column($_levels, 'code'), function ($code) { return !empty($code); });
 
-            $subLocalityLevels[] = new AdminLevel($level['level'], $name, $level['code'] ?? null);
-        }
+            $name = count($names) > 0 ? implode(' / ', $names) : implode(' / ', $codes);
+            $code = count($codes) > 0 ? implode(' / ', $codes) : null;
 
-        $subLocalityLevels = array_unique($subLocalityLevels);
+            $subLocalityLevels[] = new AdminLevel($level, $name, $code);
+        }
 
         $new = clone $this;
         $new->subLocalityLevels = new AdminLevelCollection($subLocalityLevels);
diff --git a/src/Provider/GoogleMaps/Tests/.cached_responses/maps.googleapis.com_627b6389804e68f8ee33a3ba990dd5df82902f16 b/src/Provider/GoogleMaps/Tests/.cached_responses/maps.googleapis.com_627b6389804e68f8ee33a3ba990dd5df82902f16
new file mode 100644
index 000000000..99b37413c
--- /dev/null
+++ b/src/Provider/GoogleMaps/Tests/.cached_responses/maps.googleapis.com_627b6389804e68f8ee33a3ba990dd5df82902f16
@@ -0,0 +1,80 @@
+s:2655:"{
+   "results" : [
+      {
+         "address_components" : [
+            {
+               "long_name" : "Rue de Pont-à-Migneloux",
+               "short_name" : "Rue de Pont-à-Migneloux",
+               "types" : [ "route" ]
+            },
+            {
+               "long_name" : "Wayaux",
+               "short_name" : "Wayaux",
+               "types" : [ "political", "sublocality", "sublocality_level_1" ]
+            },
+            {
+               "long_name" : "Les Bons Villers",
+               "short_name" : "Les Bons Villers",
+               "types" : [ "political", "sublocality", "sublocality_level_1" ]
+            },
+            {
+               "long_name" : "Frasnes-lez-Gosselies",
+               "short_name" : "Frasnes-lez-Gosselies",
+               "types" : [ "locality", "political" ]
+            },
+            {
+               "long_name" : "Hainaut",
+               "short_name" : "HT",
+               "types" : [ "administrative_area_level_2", "political" ]
+            },
+            {
+               "long_name" : "Région Wallonne",
+               "short_name" : "Région Wallonne",
+               "types" : [ "administrative_area_level_1", "political" ]
+            },
+            {
+               "long_name" : "Belgium",
+               "short_name" : "BE",
+               "types" : [ "country", "political" ]
+            },
+            {
+               "long_name" : "6210",
+               "short_name" : "6210",
+               "types" : [ "postal_code" ]
+            }
+         ],
+         "formatted_address" : "Rue de Pont-à-Migneloux, 6210 Les Bons Villers, Belgium",
+         "geometry" : {
+            "bounds" : {
+               "northeast" : {
+                  "lat" : 50.49571500000003,
+                  "lng" : 4.473629799999999
+               },
+               "southwest" : {
+                  "lat" : 50.48990779999998,
+                  "lng" : 4.4595167
+               }
+            },
+            "location" : {
+               "lat" : 50.4944514,
+               "lng" : 4.4694362
+            },
+            "location_type" : "GEOMETRIC_CENTER",
+            "viewport" : {
+               "northeast" : {
+                  "lat" : 50.49571500000003,
+                  "lng" : 4.473629799999999
+               },
+               "southwest" : {
+                  "lat" : 50.48990779999998,
+                  "lng" : 4.4595167
+               }
+            }
+         },
+         "place_id" : "EjhSdWUgZGUgUG9udC3DoC1NaWduZWxvdXgsIDYyMTAgTGVzIEJvbnMgVmlsbGVycywgQmVsZ2l1bSIuKiwKFAoSCcPJx0xGKcJHEUw-Q8kfZcEeEhQKEgmHf2F_QCnCRxF02JKWsgNAAA",
+         "types" : [ "route" ]
+      }
+   ],
+   "status" : "OK"
+}
+";
\ No newline at end of file
diff --git a/src/Provider/GoogleMaps/Tests/GoogleMapsTest.php b/src/Provider/GoogleMaps/Tests/GoogleMapsTest.php
index 943fb19d0..9d6291525 100644
--- a/src/Provider/GoogleMaps/Tests/GoogleMapsTest.php
+++ b/src/Provider/GoogleMaps/Tests/GoogleMapsTest.php
@@ -14,7 +14,6 @@
 
 use Geocoder\Exception\InvalidServerResponse;
 use Geocoder\IntegrationTest\BaseTestCase;
-use Geocoder\Location;
 use Geocoder\Model\Address;
 use Geocoder\Model\AddressCollection;
 use Geocoder\Provider\GoogleMaps\Model\GoogleAddress;
@@ -94,7 +93,7 @@ public function testGeocodeWithRealAddress()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(1, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertEqualsWithDelta(48.8630462, $result->getCoordinates()->getLatitude(), 0.001);
@@ -128,7 +127,7 @@ public function testGeocodeBoundsWithRealAddressForNonRooftopLocation()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(1, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertNotNull($result->getBounds());
@@ -156,7 +155,7 @@ public function testReverseWithRealCoordinates()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(5, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertEquals(12, $result->getStreetNumber());
@@ -180,7 +179,7 @@ public function testReverseWithRealCoordinatesAndLocale()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(5, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertEquals(12, $result->getStreetNumber());
@@ -204,7 +203,7 @@ public function testGeocodeWithCityDistrict()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(1, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertEquals('Kalbach-Riedberg', $result->getSubLocality());
@@ -228,7 +227,7 @@ public function testGeocodeWithRealValidApiKey()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(1, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertNotNull($result->getCoordinates()->getLatitude());
@@ -251,7 +250,7 @@ public function testGeocodeWithComponentFiltering()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(1, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertEquals('Malmö', $result->getLocality());
@@ -340,7 +339,7 @@ public function testGeocodePostalTown()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(1, $results);
 
-        /** @var Location $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertEquals('Pontypridd', $result->getLocality());
@@ -558,7 +557,7 @@ public function testGeocodeBoundsWithRealAddressWithViewportOnly()
         $this->assertInstanceOf(AddressCollection::class, $results);
         $this->assertCount(1, $results);
 
-        /** @var \Geocoder\Model\Address $result */
+        /** @var GoogleAddress $result */
         $result = $results->first();
         $this->assertInstanceOf(Address::class, $result);
         $this->assertNotNull($result->getBounds());
@@ -569,6 +568,28 @@ public function testGeocodeBoundsWithRealAddressWithViewportOnly()
         $this->assertEquals(false, $result->isPartialMatch());
     }
 
+    public function testGeocodeDuplicateSubLocalityLevel()
+    {
+        $provider = $this->getGoogleMapsProvider();
+        $results = $provider->geocodeQuery(GeocodeQuery::create('Rue de Pont-A-Migneloux, 6210 Wayaux, Belgique'));
+
+        $this->assertInstanceOf(AddressCollection::class, $results);
+        $this->assertCount(1, $results);
+
+        /** @var GoogleAddress $result */
+        $result = $results->first();
+        $this->assertInstanceOf(Address::class, $result);
+        $this->assertEquals('Rue de Pont-à-Migneloux, 6210 Les Bons Villers, Belgium', $result->getFormattedAddress());
+        $this->assertEquals('Les Bons Villers', $result->getSubLocality());
+        $this->assertCount(2, $result->getAdminLevels());
+        $this->assertEquals('Région Wallonne', $result->getAdminLevels()->get(1)->getName());
+        $this->assertEquals('Hainaut', $result->getAdminLevels()->get(2)->getName());
+        $this->assertInstanceOf('\Geocoder\Model\AdminLevelCollection', $result->getSubLocalityLevels());
+        $this->assertEquals(1, $result->getSubLocalityLevels()->get(1)->getLevel());
+        $this->assertEquals('Wayaux / Les Bons Villers', $result->getSubLocalityLevels()->get(1)->getName());
+        $this->assertEquals('Wayaux / Les Bons Villers', $result->getSubLocalityLevels()->get(1)->getCode());
+    }
+
     private function getGoogleMapsProvider(): GoogleMaps
     {
         if (!isset($_SERVER['GOOGLE_GEOCODING_KEY'])) {


Test patch: