Untitled
unknown
php
3 years ago
16 kB
8
Indexable
<?php
namespace App\Http\Utility;
use App\DTO\LocationDto;
use App\DTO\MarkerDto;
use Geocoder\Collection;
use Geocoder\Provider\GoogleMaps\GoogleMaps;
use Geocoder\Query\ReverseQuery;
use Geocoder\StatefulGeocoder;
use Http\Client\Curl\Client;
use Illuminate\Http\Client\RequestException;
use Illuminate\Http\Client\Response;
use Illuminate\Http\Exceptions\HttpResponseException;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
class Geocode
{
/**
* Minimal distance in kilometers that 2 markers can be when checking entire route
*
* @var int
*/
private static $markerMinDistance = 20;
/**
* Maximum distance in kilometers that 2 markers can be when checking entire route
*
* @var int
*/
private static $markerMaxDistance = 50;
/**
* Maximum distance in kilometers for origin point and polyline segment to take segment into consideration
*
* @var int
*/
private static $segmentTolerance = 20;
/**
* Minimum distance in kilometers between points of delivery routes and origin, where route is taken into consideration
*
* @var int
*/
private static $minMarkerDistanceRadius = 8;
/**
* Value for detour radius that the driver designated while making his route
*
* @var int
*/
public $detourRadius = 40;
/**
* @var LocationDto
*/
public $origin = [];
/**
* @var LocationDto
*/
public $destination = [];
/**
* @var array
*/
public $provinces = [];
/**
* @var array
*/
public $countries = [];
/**
* @param string $origin
* @param string $destination
* @param bool|null $avoid_tolls
* @param array|null $vias
* @return string
* @throws \Exception
*/
public function googleMapsRouteUrl (string $origin, string $destination, bool|null $avoid_tolls = false, array|null $vias = []): string
{
$google_maps_api_key = config('googlemaps.google_maps_api_key');
if (empty($google_maps_api_key)) {
throw new HttpResponseException(response()->json([
'message' => 'Google maps key is not set up.'
], JsonResponse::HTTP_BAD_REQUEST));
}
$url = 'https://maps.googleapis.com/maps/api/directions/json?origin=' . urlencode($origin) . '&destination=' . urlencode($destination);
if ($avoid_tolls) {
$url .= '&avoid=tolls';
}
if ($vias && count($vias)) {
$viaList = '';
$url .= '&waypoints=';
foreach ($vias as $location) {
$viaList .= 'via:' . $location . '|';
}
$viaList = rtrim($viaList, '|');
$url .= urlencode($viaList);
}
$url .= '&language=en&key=' . $google_maps_api_key;
return $url;
}
/**
* @param string $address
* @return string
* @throws \Exception
*/
public function googleMapsAddressUrl (string $address)
{
$google_maps_api_key = config('googlemaps.google_geocode_api_key');
if (empty($google_maps_api_key)) {
throw new HttpResponseException(response()->json([
'message' => 'Google maps key is not set up.'
], JsonResponse::HTTP_BAD_REQUEST));
}
return 'https://maps.google.com/maps/api/geocode/json?address=' . urlencode($address) . '&language=en&key=' . $google_maps_api_key;
}
/**
* @param string $url
* @return mixed
* @throws \Exception
*/
public function googleMapsFetchUrl (string $url)
{
$response = Http::get($url)->object();
if ($response->status !== "OK") {
throw new HttpResponseException(response()->json([
'message' => trans('response.geocode.empty')
], JsonResponse::HTTP_BAD_REQUEST));
}
return $response;
}
/**
* @param array $coordinates
* @return void
* @throws \Geocoder\Exception\Exception
*/
public function geocodeLocation (array $coordinates)
{
$this->provinces = [];
$this->countries = [];
/*Storage::put('geocode_rmp_coords.json', \Illuminate\Support\Collection::make($coordinates)->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));*/
$province_markers = $this->callAWS(GeocodeUrls::routeMarkerProvince(), ["markers" => $coordinates])->object()->route_marker_province;
/*Storage::put('geocode_test.json', \Illuminate\Support\Collection::make($province_markers)->toJson(JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
dump('geocode');
exit;*/
$province_marker_count = count($province_markers) - 1;
foreach ($province_markers as $cnt => $marker) {
$geocode_data = $this->geocodeQuery($marker->lat, $marker->lng);
if ($geocode_data->isEmpty()) {
continue;
}
if ($cnt === 0) {
$this->origin = new LocationDto();
$this->locationFiller($this->origin, $geocode_data);
}
if ($cnt === $province_marker_count) {
$this->destination = new LocationDto();
$this->locationFiller($this->destination, $geocode_data);
}
$this->countryFiller($geocode_data);
$this->provinceFiller($geocode_data);
}
}
/**
* @param float $latitude
* @param float $longitude
* @return Collection
* @throws \Geocoder\Exception\Exception
*/
public function geocodeQuery (float $latitude, float $longitude): Collection
{
$google_geocode_api_key = config('googlemaps.google_geocode_api_key', '');
$httpClient = new Client();
$provider = new GoogleMaps($httpClient, null, $google_geocode_api_key);
$geocoder = new StatefulGeocoder($provider);
$geocoder_query = ReverseQuery::fromCoordinates($latitude, $longitude)
->withData('result_type', 'administrative_area_level_1|locality')->withLocale('en');
return $geocoder->reverseQuery($geocoder_query);
}
/**
* @param LocationDto $location_data
* @param Collection $data
* @return void
*/
public function locationFiller (LocationDto $location_data, Collection $data): void
{
$location_data->town = $data->first()->getLocality();
if ($data->first()->getCountry()) {
$location_data->country = $data->first()->getCountry()->getName();
}
foreach ($data as $value) {
if (! $value->getAdminLevels()->count()) {
continue;
}
if (in_array($value->getAdminLevels()->first()->getLevel(), [1, 2])) {
$location_data->province = $value->getAdminLevels()->first()->getName();
break;
}
}
}
/**
* @param Collection $data
* @return void
*/
private function countryFiller (Collection $data): void
{
if (! $data->first()->getCountry()) {
return;
}
$name = $data->first()->getCountry()->getName();
if (! in_array($name, $this->countries)) {
$this->countries[] = $name;
}
}
/**
* @param Collection $data
* @return void
*/
private function provinceFiller (Collection $data): void
{
$name = '';
if ($data->first()->getAdminLevels()->count()) {
$name = $data->first()->getAdminLevels()->first()->getName();
}
if ($data->has(1)) {
$name = $data->get(1)->getAdminLevels()->first()->getName();
}
if (! $name) {
return;
}
if (! in_array($name, $this->provinces)) {
$this->provinces[] = $name;
$this->provinceSpecialCases($name);
}
}
/**
* @param string $province
* @return void
*/
private function provinceSpecialCases (string $province): void
{
// TODO: We'll need to extend this for easier implementation of edge cases
$special_case = ['Zagreb County', 'City of Zagreb'];
if ($match = array_search($province, $special_case)) {
unset($special_case[$match]);
$this->provinces[] = array_shift($special_case);
}
}
/**
* @param array $coordinates
* @return Response|JsonResponse
*/
public function polylineEncode (array $coordinates)
{
return $this->callAWS(GeocodeUrls::encode(), $coordinates);
}
/**
* @param array $polyline
* @return Response|JsonResponse
*/
public function polylineDecode (string $polyline)
{
return $this->callAWS(GeocodeUrls::decode(), $polyline);
}
/**
* Because every end_location marker is same as start_location of the next marker
* we're only taking the end_location markers except of the first one
*
* @param array|null $steps
* @return array
*/
public function markerList (array|null $steps)
{
return $this->callAWS(GeocodeUrls::markerList(), ["steps" => $steps])->object()->marker_list;
}
/**
* @param array $coordinates
* @return false|string
*/
public function routeMarkerFullPath (array $coordinates)
{
$polylines = $this->callAWS(GeocodeUrls::routeMarkerFullPath(), ["markers" => $coordinates])->object()->polylines;
if (! is_string($polylines)) {
$polylines = json_encode($polylines);
}
return $polylines;
}
/**
* @param string $polylines
* @param MarkerDto $targetMarker
* @param int $detour_radius
* @param bool $polyline_continue
* @return array|bool
*/
public function shipmentCheck (string $polylines, MarkerDto $targetMarker, int $detour_radius): array|bool
{
if (empty($polylines)) {
return false;
}
$this->detourRadius = $detour_radius;
return $this->callAWS(GeocodeUrls::shipmentCheck(), [
"shipment_check_input" => [
'polylines' => json_decode($polylines),
'target_marker' => $targetMarker
]
])->object()->polylines;
}
/**
* @return JsonResponse|string
*/
private function getToken (): JsonResponse|string
{
$response = Http::asForm()->post(GeocodeUrls::token(),
[
'username' => env("FAST_API_GEO_USER"),
'password' => env("FAST_API_GEO_PASS"),
],
);
if ($response->status() !== JsonResponse::HTTP_OK) {
return response()->json([
'message' => trans('response.geocode.connection')
], JsonResponse::HTTP_BAD_REQUEST);
}
$token = $response->json('access_token');
if (! isset($token)) {
return response()->json([
'message' => trans('response.not_found', ['item' => 'Token'])
], JsonResponse::HTTP_NOT_FOUND);
}
if (! Cache::add('aws_auth', $token)) {
Cache::put('aws_auth', $token);
}
return $token;
}
/**
* @param string $url
* @param array|string|null $payload
* @param string $type
* @return JsonResponse|Response
*/
private function callAWS (string $url, array|string|null $payload, string $type = 'post')
{
if (! in_array($type, ['post', 'get'])) {
return response()->json([
'message' => trans('response.geocode.connection_type')
], JsonResponse::HTTP_BAD_REQUEST);
}
if (! is_string($payload)) {
$payload["geo_marker_options"] = [
"marker_min_distance" => self::$markerMinDistance,
"marker_max_distance" => self::$markerMaxDistance,
"segment_tolerance" => self::$segmentTolerance,
"min_marker_distance_radius" => max($this->detourRadius, self::$minMarkerDistanceRadius)
];
} else {
$payload = [
"encoded_polyline" => $payload,
];
}
try {
if (! Cache::has('aws_auth')) {
$this->getToken();
}
$request = Http::withToken(Cache::get('aws_auth'))->retry(1, 100)->withoutRedirecting()->acceptJson();
if ($type === 'get') {
$payload = null;
}
$response = $request->$type($url, $payload);
if ($response->status() === JsonResponse::HTTP_UNAUTHORIZED) {
throw new RequestException($response);
}
if ($response->status() !== JsonResponse::HTTP_OK) {
throw new \Exception();
}
} catch (RequestException $e) {
if ($type === 'get') {
$payload = null;
}
$response = $request->withToken($this->getToken())->retry(3, 100)->acceptJson()->$type($url, $payload);
} catch (\Exception $e) {
error_log("URL: " . $url . PHP_EOL);
error_log("HTTP code: " . $response->getStatusCode() . PHP_EOL);
error_log("Response body: " . $response->getBody()->getContents() . PHP_EOL);
return response()->json([
'message' => trans('response.geocode.connection_error')
], JsonResponse::HTTP_BAD_REQUEST);
}
return $response;
}
}
<?php
namespace App\Http\Utility;
enum GeocodeUrls: string
{
case TOKEN = 'auth/token';
case ENCODE = 'polyline-encoder/encode';
case DECODE = 'polyline-encoder/decode';
case CREATE_MARKER = 'geocode/create_marker';
case MARKER_LIST = 'geocode/marker-list';
case ROUTE_MARKER_PROVINCE = 'geocode/route-marker-province';
case ROUTE_MARKER_FULL_PATH = 'geocode/route-marker-full-path';
case SHIPMENT_CHECK = 'geocode/shipment-check';
/**
* @return string
*/
public static function token(): string
{
return env("FAST_API_GEO_BASE_URL") . self::TOKEN->value;
}
/**
* @return string
*/
public static function encode(): string
{
return env("FAST_API_GEO_BASE_URL") . self::ENCODE->value;
}
/**
* @return string
*/
public static function decode(): string
{
return env("FAST_API_GEO_BASE_URL") . self::DECODE->value;
}
/**
* @return string
*/
public static function createMarker(): string
{
return env("FAST_API_GEO_BASE_URL") . self::CREATE_MARKER->value;
}
/**
* @return string
*/
public static function markerList(): string
{
return env("FAST_API_GEO_BASE_URL") . self::MARKER_LIST->value;
}
/**
* @return string
*/
public static function routeMarkerProvince(): string
{
return env("FAST_API_GEO_BASE_URL") . self::ROUTE_MARKER_PROVINCE->value;
}
/**
* @return string
*/
public static function routeMarkerFullPath(): string
{
return env("FAST_API_GEO_BASE_URL") . self::ROUTE_MARKER_FULL_PATH->value;
}
/**
* @return string
*/
public static function shipmentCheck(): string
{
return env("FAST_API_GEO_BASE_URL") . self::SHIPMENT_CHECK->value;
}
}
Editor is loading...