Untitled
unknown
php
2 years ago
16 kB
5
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...