andydefer / laravel-locationiq
Laravel SDK for integrating LocationIQ and Nominatim geospatial services (balance, timezone, directions, reverse geocoding).
Requires
- php: ^8.2
- andydefer/laravel-nemesis: ^1.22
- andydefer/php-locationiq: ^0.1.0
Requires (Dev)
- barryvdh/laravel-ide-helper: ^3.6
- larastan/larastan: ^3.8
- laravel/pint: ^1.26
- orchestra/testbench: ^10.8
- phpunit/phpunit: ^12.5
- rector/rector: *
- symfony/var-dumper: ^7.0
- vimeo/psalm: ^6.14
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
Intégration Laravel du SDK LocationIQ/Nominatim pour le géocodage, les itinéraires et les fuseaux horaires.
Table des matières
- Introduction
- Installation
- Configuration
- Routes exposées
- Utilisation
- Contrats et extension
- Gestion des erreurs
- Tests
- Sécurité
- Compatibilité
1. Introduction
Objectif
andydefer/laravel-locationiq expose les opérations LocationIQ et Nominatim (solde de quota, fuseau horaire, itinéraires, reverse geocoding) sous forme d'endpoints HTTP Laravel prêts à l'emploi.
Le package s'appuie sur andydefer/php-locationiq, qui contient toute la logique métier et les objets typés (Record, Response, Data, Graph, Collection). Laravel LocationIQ n'ajoute que la couche transport : routes, validation, injection.
Ce que le package fournit
- 4 endpoints HTTP : solde de quota, fuseau horaire, itinéraires, reverse geocoding.
- 3
FormRequest: validation stricte des payloads et construction desRecordtypés du SDK. - 4
Action: résolvent le client LocationIQ ou Nominatim via la configuration, appellent l'opération correspondante et renvoient laDatatypée. En cas d'erreur, l'action propage unErrorResponseDatanormalisé. - Une configuration unique : clé API, URL régionale, URL Nominatim, User-Agent, profil de directions par défaut.
- Deux points d'extension : remplacer le client LocationIQ (
locationiq_client_fqcn) ou le client Nominatim (nominatim_client_fqcn) sans modifier le package.
Ce que le package ne fait pas
- Aucune authentification. Les routes sont publiées sans middleware.
- Aucune persistance. Le package ne touche pas à la base de données.
- Aucune mise en cache. À implémenter côté application hôte selon les besoins.
2. Installation
composer require andydefer/laravel-locationiq
Le LocationIqServiceProvider est découvert automatiquement par Laravel.
3. Configuration
3.1 Publier les fichiers
php artisan vendor:publish --tag=laravel-locationiq-config php artisan vendor:publish --tag=laravel-locationiq-routes
- Config publiée dans
config/locationiq.php. - Routes publiées dans
routes/locationiq.php.
3.2 Charger les routes
Les routes ne sont pas chargées automatiquement. Charge-les explicitement depuis ton application :
// routes/api.php require base_path('routes/locationiq.php');
Tu contrôles ainsi le préfixe, les middlewares et l'ordre de chargement.
3.3 Fichier de configuration
<?php declare(strict_types=1); use AndyDefer\PhpLocationIq\Enums\LocationIqBaseUrl; use AndyDefer\PhpLocationIq\Enums\NominatimBaseUrl; use AndyDefer\PhpLocationIq\LocationIqClient; use AndyDefer\PhpLocationIq\NominatimClient; return [ 'api_key' => env('LOCATIONIQ_API_KEY', ''), 'base_url' => env('LOCATIONIQ_BASE_URL', LocationIqBaseUrl::US1->value), 'locationiq_client_fqcn' => LocationIqClient::class, 'nominatim' => [ 'base_url' => env('NOMINATIM_BASE_URL', NominatimBaseUrl::PUBLIC->value), 'user_agent' => env('NOMINATIM_USER_AGENT', 'andydefer/laravel-locationiq'), 'accept_language' => env('NOMINATIM_ACCEPT_LANGUAGE', 'fr'), ], 'nominatim_client_fqcn' => NominatimClient::class, 'directions' => [ 'profile' => env('LOCATIONIQ_DIRECTIONS_PROFILE', 'driving'), 'accept_language' => env('LOCATIONIQ_ACCEPT_LANGUAGE'), ], ];
3.4 Clés de configuration
| Clé | Type | Défaut | Description |
|---|---|---|---|
api_key |
string |
'' |
Clé API LocationIQ |
base_url |
string |
https://us1.locationiq.com |
URL régionale LocationIQ (us1, eu1) |
locationiq_client_fqcn |
class-string |
LocationIqClient::class |
Implémentation de LocationIqClientInterface |
nominatim.base_url |
string |
https://nominatim.openstreetmap.org |
URL Nominatim |
nominatim.user_agent |
string |
andydefer/laravel-locationiq |
User-Agent obligatoire pour Nominatim |
nominatim.accept_language |
string |
fr |
Langue préférée des résultats Nominatim |
nominatim_client_fqcn |
class-string |
NominatimClient::class |
Implémentation de NominatimClientInterface |
directions.profile |
string |
driving |
Profil par défaut (driving, walking) |
directions.accept_language |
string|null |
null |
Langue préférée des réponses LocationIQ |
3.5 Variables d'environnement
LOCATIONIQ_API_KEY=pk.xxxxxxxxxxxxxxxxxxxxxxxx LOCATIONIQ_BASE_URL=us1 NOMINATIM_USER_AGENT=MonApp/1.0 (contact@exemple.com) NOMINATIM_ACCEPT_LANGUAGE=fr LOCATIONIQ_DIRECTIONS_PROFILE=driving
3.6 Cas concret : configuration Afya (RDC)
// config/locationiq.php return [ 'api_key' => env('LOCATIONIQ_API_KEY', ''), 'base_url' => LocationIqBaseUrl::EU1->value, 'nominatim' => [ 'base_url' => NominatimBaseUrl::PUBLIC->value, 'user_agent' => 'AfyaMedical/1.0 (ops@afya-medical.com)', 'accept_language' => 'fr', ], 'directions' => [ 'profile' => 'driving', ], ];
4. Routes exposées
Le fichier routes/locationiq.php déclare quatre endpoints, tous en POST, sous le préfixe locationiq.
| Méthode | URI | Nom | Description |
|---|---|---|---|
| POST | /locationiq/balance |
locationiq.balance |
Récupérer le solde de requêtes du jour |
| POST | /locationiq/timezone |
locationiq.timezone |
Résoudre le fuseau horaire d'un point GPS |
| POST | /locationiq/directions |
locationiq.directions |
Calculer un itinéraire |
| POST | /locationiq/reverse |
locationiq.reverse |
Reverse geocoding via Nominatim |
5. Utilisation
5.1 Récupérer le solde de quota
Requête :
curl -X POST https://app.test/locationiq/balance
Aucun paramètre.
Réponse succès (200) :
{
"balance": {
"day": 30000
}
}
5.2 Résoudre un fuseau horaire
Requête :
curl -X POST https://app.test/locationiq/timezone \ -H "Content-Type: application/json" \ -d '{ "lat": 19.0760, "lon": 72.8777, "timestamp": 1609459200 }'
Paramètres :
| Champ | Type | Requis | Contrainte |
|---|---|---|---|
lat |
numeric |
✅ | Entre -90 et 90 |
lon |
numeric |
✅ | Entre -180 et 180 |
timestamp |
integer |
❌ | Unix timestamp, ≥ 0 |
Réponse succès (200) :
{
"timezone": {
"name": "Asia/Kolkata",
"nowInDst": false,
"offsetSeconds": 19800,
"shortName": "IST",
"fullName": "India Standard Time"
}
}
5.3 Calculer un itinéraire
Requête :
curl -X POST https://app.test/locationiq/directions \ -H "Content-Type: application/json" \ -d '{ "coordinates": [ [15.3222, -4.3250], [15.4446, -4.3858] ], "profile": "driving", "overview": "full", "steps": true, "alternatives": false, "geometries": "polyline" }'
Paramètres :
| Champ | Type | Requis | Contrainte |
|---|---|---|---|
coordinates |
array |
✅ | 2 à 25 paires [lon, lat] |
coordinates.* |
array |
✅ | Exactement 2 valeurs |
coordinates.*.0 |
numeric |
✅ | Entre -180 et 180 (longitude) |
coordinates.*.1 |
numeric |
✅ | Entre -90 et 90 (latitude) |
profile |
DirectionsProfile |
❌ | driving, walking |
overview |
OverviewType |
❌ | simplified, full, false |
steps |
boolean |
❌ | Inclure les étapes détaillées |
alternatives |
boolean |
❌ | Retourner des itinéraires alternatifs |
geometries |
GeometriesType |
❌ | polyline, polyline6, geojson |
Réponse succès (200) :
{
"directions": {
"code": "ok",
"waypoints": [...],
"routes": [...]
}
}
Polymorphisme géré : LocationIQ retourne tantôt un objet unique, tantôt un tableau. Le SDK normalise toujours en collection typée.
5.4 Reverse geocoding
Requête :
curl -X POST https://app.test/locationiq/reverse \ -H "Content-Type: application/json" \ -d '{ "lat": -4.3617, "lon": 15.2183, "format": "jsonv2", "accept_language": "fr", "zoom": 18, "address_details": true }'
Paramètres :
| Champ | Type | Requis | Contrainte |
|---|---|---|---|
lat |
numeric |
✅ | Entre -90 et 90 |
lon |
numeric |
✅ | Entre -180 et 180 |
format |
NominatimFormat |
❌ | json, jsonv2, geojson, geocodejson |
accept_language |
string |
❌ | Code ISO 639-1, max 10 caractères |
zoom |
integer |
❌ | Entre 0 et 18 |
address_details |
boolean |
❌ | Inclure le bloc address |
Réponse succès (200) :
{
"reverse": {
"licence": "Data © OpenStreetMap contributors",
"osmType": "way",
"osmId": 434892314,
"location": { "longitude": 15.2185794, "latitude": -4.3619926 },
"category": "highway",
"type": "residential",
"placeRank": 26,
"importance": 0.0534,
"addressType": "road",
"name": "",
"displayName": "Kasi, Lukunga, Ngaliema, Kinshasa, République démocratique du Congo",
"address": {
"cityDistrict": "Kasi",
"city": "Lukunga",
"municipality": "Ngaliema",
"state": "Kinshasa",
"iso3166Lvl4": "CD-KN",
"country": "République démocratique du Congo",
"countryCode": "cd"
},
"boundingBox": {
"minLatitude": -4.3631191,
"maxLatitude": -4.3619147,
"minLongitude": 15.2171727,
"maxLongitude": 15.2186610
}
}
}
6. Contrats et extension
6.1 LocationIqConfigInterface
Contrat de la configuration. Utilisé par le ServiceProvider pour construire les clients et par les Action pour les résoudre.
<?php declare(strict_types=1); namespace AndyDefer\LaravelLocationIq\Contracts; use AndyDefer\PhpLocationIq\Contracts\LocationIqClientInterface; use AndyDefer\PhpLocationIq\Contracts\NominatimClientInterface; use AndyDefer\PhpLocationIq\Enums\LocationIqBaseUrl; use AndyDefer\PhpLocationIq\Enums\NominatimBaseUrl; interface LocationIqConfigInterface { public function getApiKey(): string; public function getBaseUrl(): LocationIqBaseUrl; /** * @return class-string<LocationIqClientInterface> */ public function getLocationIqClientFqcn(): string; public function getNominatimBaseUrl(): NominatimBaseUrl; public function getUserAgent(): string; /** * @return class-string<NominatimClientInterface> */ public function getNominatimClientFqcn(): string; public function getDefaultDirectionsProfile(): string; public function getDefaultAcceptLanguage(): ?string; }
6.2 ErrorDescribable
Contrat hérité du package andydefer/laravel-nemesis. Tout enum d'erreur qui l'implémente expose une API cohérente :
getHttpStatusCode(): HttpStatusCodegetMessage(): stringgetLabel(): stringtoResponseData(): ErrorResponseDatatoJsonResponseFactory(): ResponseFactory
6.3 ErrorCode
Enum qui implémente ErrorDescribable. Couvre les codes d'erreur LocationIQ et Nominatim.
enum ErrorCode: string implements ErrorDescribable { case INVALID_REQUEST = 'INVALID_REQUEST'; case INVALID_KEY = 'INVALID_KEY'; case ACCESS_RESTRICTED = 'ACCESS_RESTRICTED'; case UNABLE_TO_GEOCODE = 'UNABLE_TO_GEOCODE'; case RATE_LIMITED_DAY = 'RATE_LIMITED_DAY'; case INVALID_OPTIONS = 'INVALID_OPTIONS'; case INVALID_COORDINATES = 'INVALID_COORDINATES'; case MISSING_API_KEY = 'MISSING_API_KEY'; case UNKNOWN_ERROR = 'UNKNOWN_ERROR'; public static function fromApiMessage(string $message): self; }
6.4 Remplacer le client LocationIQ
// config/locationiq.php 'locationiq_client_fqcn' => \App\Geo\AfyaLocationIqClient::class,
Contraintes :
- La classe doit implémenter
LocationIqClientInterface. - Son constructeur doit être auto-résolvable par le conteneur Laravel.
6.5 Remplacer le client Nominatim
// config/locationiq.php 'nominatim_client_fqcn' => \App\Geo\AfyaNominatimClient::class,
Contraintes :
- La classe doit implémenter
NominatimClientInterface. - Son constructeur doit être auto-résolvable par le conteneur Laravel.
6.6 Binder les interfaces manuellement
$this->app->bind( LocationIqClientInterface::class, AfyaLocationIqClient::class, ); $this->app->bind( NominatimClientInterface::class, AfyaNominatimClient::class, );
L'option bind n'a d'effet que si les clés locationiq_client_fqcn et nominatim_client_fqcn restent égales à leurs valeurs par défaut.
7. Gestion des erreurs
Le package distingue trois niveaux.
7.1 Validation (HTTP 422)
Les FormRequest rejettent les payloads invalides avant toute exécution.
| Situation | Message type |
|---|---|
lat manquant |
The lat field is required. |
lat hors bornes |
The lat field must be between -90 and 90. |
lon hors bornes |
The lon field must be between -180 and 180. |
timestamp négatif |
The timestamp field must be at least 0. |
coordinates moins de 2 paires |
The coordinates field must have at least 2 items. |
coordinates plus de 25 paires |
The coordinates field must not have more than 25 items. |
coordinates.* non conforme |
The coordinates.0 field must contain 2 items. |
profile invalide |
The selected profile is invalid. |
overview invalide |
The selected overview is invalid. |
geometries invalide |
The selected geometries is invalid. |
format invalide |
The selected format is invalid. |
zoom hors bornes |
The zoom field must be between 0 and 18. |
accept_language trop long |
The accept language field must not be greater than 10 characters. |
7.2 Erreurs LocationIQ
L'action map le message brut vers un ErrorCode, puis renvoie un ErrorResponseData avec le bon code HTTP.
{
"errorCode": "INVALID_KEY",
"message": "Invalid Key",
"status": 401
}
Correspondances message → code :
| Message brut LocationIQ | ErrorCode |
HTTP |
|---|---|---|
Invalid Request |
INVALID_REQUEST |
422 |
Invalid Key |
INVALID_KEY |
401 |
Access restricted |
ACCESS_RESTRICTED |
403 |
Unable to geocode |
UNABLE_TO_GEOCODE |
404 |
Rate Limited Day |
RATE_LIMITED_DAY |
429 |
InvalidOptions |
INVALID_OPTIONS |
422 |
Unknown error - Please try again after some time |
UNKNOWN_ERROR |
500 |
LocationIQ peut renvoyer
InvalidOptionsdans le champcodeavec un HTTP 200 sur l'endpoint Directions. Le SDK détecte ce cas et le mappe enINVALID_OPTIONS(422).
7.3 Erreurs SDK
Les Action capturent les exceptions levées par le SDK et les transforment en ErrorResponseData.
| Exception SDK | ErrorCode |
HTTP |
|---|---|---|
InvalidArgumentException (coordonnées Directions hors bornes) |
INVALID_COORDINATES |
422 |
RuntimeException (erreur réseau Guzzle) |
Non capturée → 500 Laravel | 500 |
8. Tests
composer test
Le package fournit un IntegrationTestCase basé sur orchestra/testbench. Chaque test enregistre le vrai endpoint via action_route(...) et remplace les clients HTTP bas niveau par des mocks (MockLocationIqClient, MockNominatimClient).
Structure d'un test
Les tests suivent la convention AAA :
public function test_it_returns_day_balance_on_success(): void { // Arrange: the API will answer 200 with a positive balance $this->locationIqClient->addBalanceSuccessResponse(30000); // Act: hit the route $response = $this->postJson('/api/locationiq-balance'); // Assert: 200 and balance.day exposed $response->assertOk(); $response->assertJsonPath('balance.day', 30000); }
Restreindre les valeurs en test
Si tu veux tester avec une configuration alternative :
$this->app->instance(LocationIqConfigInterface::class, new RestrictedLocationIqConfig);
9. Sécurité
- Routes non protégées par défaut. Ajoute tes middlewares (
auth:sanctum,throttle:60,1, signature HMAC…) dans le fichier publié. LOCATIONIQ_API_KEYcôté serveur uniquement. Ne jamais l'exposer au frontend.NOMINATIM_USER_AGENTobligatoire en production. Nominatim peut bloquer les requêtes sans User-Agent identifiable.- Rate limit Nominatim : 1 requête/seconde sur l'instance publique. Prévoir un throttling côté application.
- Validation stricte : toutes les entrées (coordonnées, formats, profils, langues) sont validées par les
FormRequestavant tout appel HTTP. - Restriction des formats Nominatim : seuls
json,jsonv2,geojsonetgeocodejsonsont acceptés. - Restriction des profils Directions : seuls
drivingetwalkingsont acceptés. - Limite de coordonnées : 2 à 25 par requête Directions (limite imposée par l'API).
- Aucune donnée sensible persistée. Le package ne stocke rien.
10. Compatibilité
| Version | Support |
|---|---|
| PHP 8.2+ | ✅ Complet |
| Laravel 10 | ✅ Complet |
| Laravel 11 | ✅ Complet |
| Laravel 12 | ✅ Complet |
Licence
MIT © Andy Defer