Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions docs/Behavior/Geocoder.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ Possible config options are:
- locale (for example DE)
- region (for some providers
- ssl (for some providers)
- address: (array|string, optional) set to the field name that contains the string from where to generate the slug, or a set of field names to concatenate for generating the slug.
- address: (array|string, optional) set to the field name that contains the string from where
to generate the slug, or a set of field names to concatenate for generating the slug.
- overwrite: lat/lng overwrite existing coordinates, defaults to true
- update: what fields to update (key=>value array pairs)
- on: beforeMarshal/afterMarshal/beforeSave (defaults to beforeSave) - Set to false if you only want to use the validation rules etc
Expand All @@ -29,10 +30,10 @@ Possible config options are:
Note that it is usually better to set global configs in your `app.php` using the `Geocoder` key.

## Configure your own Geocoder
By default it will use the GoogleMaps provider.
By default, it will use the GoogleMaps provider.

Please see [geocoder-php/Geocoder](https://github.com/geocoder-php/Geocoder) library on what other providers you can use out of the box.
You can chose from
You can choose from
- 12+ address-based Geocoder providers
- 10+ IP-based Geocoder providers

Expand Down Expand Up @@ -105,6 +106,39 @@ $options = ['coordinates' => $coordinates, 'distance' => 200];

$query = $this->Addresses->find('distance', ...$options);
```
When using the plugin's native `GeoCoordinate` value object, use
```php
$geoCoordinate = new GeoCoordinate(13.3, 19.2);
$coordinates = $geoCoordinate->toGeocoderCoordinates();
$options = ['coordinates' => $coordinates, 'distance' => 200];
```

### Address elements as closure
Sometimes, you need to have more logic for a specific address field.
In this case you can use a closure to make dynamic lookups where needed.

Example: Cities and their Countries when saving a city (cities/add or cities/edit/ID).

```php
$this->addBehavior('Geo.Geocoder', [
'address' => ['street', 'postal_code', 'city', function(City $entity) {
// If there is a matching relation
if ($entity->country && $entity->country->id && $entity->country_id) {
return $entity->country->name;
}
// If there is a virtual or tmp field
if ($entity->get('country_name')) {
return $entity->get('country_name');
}
// Do an actual DB lookup with the ID given in the form
if ($entity->country_id) {
$country = $this->Countries->get($entity->country_id);
return $country->name;
}

return null;
}]]);
```

## Batch geocoding

Expand Down
17 changes: 17 additions & 0 deletions src/Geocoder/GeoCoordinate.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Geo\Geocoder;

use Geocoder\Model\Coordinates;
use InvalidArgumentException;
use JsonSerializable;
use Stringable;
Expand Down Expand Up @@ -59,6 +60,22 @@ public static function fromArray(array $data): static {
return new static($lat, $lng);
}

/**
* @param \Geocoder\Model\Coordinates $coordinates
*
* @return static
*/
public function fromGeocoderCoordinates(Coordinates $coordinates): static {
return new static($coordinates->getLatitude(), $coordinates->getLongitude());
}

/**
* @return \Geocoder\Model\Coordinates
*/
public function toGeocoderCoordinates(): Coordinates {
return new Coordinates($this->latitude, $this->longitude);
}

/**
* @param string $coordinate
*
Expand Down
16 changes: 15 additions & 1 deletion src/Model/Behavior/GeocoderBehavior.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,14 +232,28 @@ public function geocode(EntityInterface $entity) {
$addressData = [];
$dirty = false;
foreach ($addressFields as $field) {
$fieldData = $entity->get($field);
$isClosure = $field instanceof \Closure;
if ($isClosure) {
$fieldData = $field($entity);
} else {
$fieldData = $entity->get($field);
}
if ($fieldData) {
$addressData[] = $fieldData;
}

if ($isClosure) {
if ($fieldData) {
$dirty = true;
}

continue;
}
if ($entity->isDirty($field)) {
$dirty = true;
}
}

if (!$dirty) {
if (
$this->_config['allowEmpty'] ||
Expand Down
67 changes: 67 additions & 0 deletions tests/TestCase/Model/Behavior/GeocoderBehaviorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,73 @@ public function testBasic() {
$entity = $this->_getEntity($data);
$res = $this->Addresses->save($entity);

$this->assertTrue(!empty($res));
$this->assertSame('Bibersfeld', $res->geocoder_result['address_data']);
}
/**
* @return void
*/
public function testAddressClosure() {
$this->Addresses->removeBehavior('Geocoder');
$this->Addresses->addBehavior('Geocoder', [
'address' => [
'street',
'zip',
'city',
function($entity) {
if ($entity->country && $entity->country->id && $entity->country_id) {
return $entity->country->name;
}
if ($entity->get('country_name')) {
return $entity->get('country_name');
}

if ($entity->country_id) {
// One can use this, but we fake it for simplicity of the test
//$country = TableRegistry::getTableLocator('Countries')->get($entity->country_id);
return 'Deutschland';
}

return null;
},
],
]);

$data = [
'street' => 'Krebenweg 22',
'zip' => '74523',
'city' => 'Bibersfeld',
'country_id' => 1,
];
$entity = $this->_getEntity($data);
$res = $this->Addresses->save($entity);

$this->assertTrue(!empty($res['lat']) && !empty($res['lng']));
$this->assertTrue(round($res['lat']) === 49.0 && round($res['lng']) === 10.0);

$this->assertSame('Deutschland', $res->geocoder_result['country']);
$this->assertSame('DE', $res->geocoder_result['countryCode']);
$this->assertSame('Krebenweg 22 74523 Bibersfeld Deutschland', $res->geocoder_result['address_data']);

// inconclusive
$data = [
//'street' => 'Leopoldstraße',
'city' => 'München',
];
$entity = $this->_getEntity($data);
$res = $this->Addresses->save($entity);

$this->assertTrue(!empty($res['lat']) && !empty($res['lng']));
//FIXME
$this->assertStringContainsString('München', $res['formatted_address']);
//$this->assertEquals('München, Deutschland', $res['formatted_address']);

$data = [
'city' => 'Bibersfeld',
];
$entity = $this->_getEntity($data);
$res = $this->Addresses->save($entity);

$this->assertTrue(!empty($res));
}

Expand Down
Binary file not shown.