diff --git a/application/WarehouseShippingNote/WarehouseShippingNoteController.php b/application/WarehouseShippingNote/WarehouseShippingNoteController.php index 1189e61ec..7af7302a1 100644 --- a/application/WarehouseShippingNote/WarehouseShippingNoteController.php +++ b/application/WarehouseShippingNote/WarehouseShippingNoteController.php @@ -436,45 +436,133 @@ class WarehouseShippingNoteController extends TTCrud { die(json_encode(['success' => true, 'status' => 'USER_NO_CAR'])); } - protected function geoAutocompleteAction() { - $search = $this->request->q; - $search = urlencode($search); - $url = "https://nominatim.haid.in/search?q=$search&format=json&addressdetails=1"; - $data = json_decode(file_get_contents($url), true); - $out = []; + /** + * Helper function to parse Google Geocoding API response into the format expected by the frontend. + * @param array $components The address_components array from Google's response. + * @return array The formatted address array. + */ + private function formatGoogleAddress(array $components): array { + $address = [ + 'house_number' => null, + 'road' => null, + 'postcode' => null, + 'city' => null, + 'town' => null, + 'village' => null, + 'residential' => null, + 'hamlet' => null, + ]; - foreach ($data as $entry) { - $parsedDisplayNameParts = []; - foreach (explode(',', $entry['display_name']) as $part) { - // if str_includes Bezirk remove it - if (strpos($part, 'Bezirk') !== false) { - continue; - } - $parsedDisplayNameParts[] = $part; + foreach ($components as $component) { + $types = $component['types']; + if (in_array('street_number', $types)) { + $address['house_number'] = $component['long_name']; } - - if (!empty($out)) { - foreach ($out as $key => $value) { - if ($value['text'] === implode(',', $parsedDisplayNameParts)) { - continue 2; - } + if (in_array('route', $types)) { + $address['road'] = $component['long_name']; + } + if (in_array('postal_code', $types)) { + $address['postcode'] = $component['long_name']; + } + if (in_array('locality', $types)) { + $address['city'] = $component['long_name']; + $address['town'] = $component['long_name']; // Town is often the same as city + } + // A village in Google can be sublocality or sublocality_level_1 + if (in_array('sublocality', $types) || in_array('sublocality_level_1', $types)) { + $address['village'] = $component['long_name']; + } + if (in_array('neighborhood', $types)) { + $address['residential'] = $component['long_name']; + if (!$address['village']) { // Fallback for village if not already set + $address['village'] = $component['long_name']; } } - $out[] = ['value' => $entry['lat'] . "," . $entry['lon'] . (!isset($entry['address']['house_number']) ? ", area" : ""), 'text' => implode(',', $parsedDisplayNameParts)]; } + // If city is null, try to find it in administrative areas + if (!$address['city']) { + foreach ($components as $component) { + if (in_array('administrative_area_level_3', $component['types'])) { + $address['city'] = $component['long_name']; + $address['town'] = $component['long_name']; + break; + } + } + } + return $address; + } + + protected function geoAutocompleteAction() { + $search = urlencode($this->request->q); + $apiKey = TT_GEOCODING_API_SECRET; + // Bias search results for Austria (AT) in German (de) + $url = TT_GEOCODING_API_URL . "?address=$search&key=$apiKey®ion=at&language=de&components=country:AT"; + + $response = @file_get_contents($url); + if ($response === false) { + self::returnJson([]); + return; + } + + $data = json_decode($response, true); + $out = []; + + if ($data['status'] === 'OK') { + foreach ($data['results'] as $entry) { + $lat = $entry['geometry']['location']['lat']; + $lng = $entry['geometry']['location']['lng']; + $text = $entry['formatted_address']; + + $hasHouseNumber = false; + foreach ($entry['address_components'] as $component) { + if (in_array('street_number', $component['types'])) { + $hasHouseNumber = true; + break; + } + } + + $value = $lat . "," . $lng . (!$hasHouseNumber ? ", area" : ""); + + // Deduplication logic from original code + $isDuplicate = false; + foreach ($out as $existing) { + if ($existing['text'] === $text) { + $isDuplicate = true; + break; + } + } + if (!$isDuplicate) { + $out[] = ['value' => $value, 'text' => $text]; + } + } + } self::returnJson($out); } protected function geoReverseAction() { $lat = $this->request->lat; $lon = $this->request->lon; - $url = "https://nominatim.haid.in/reverse?lat=$lat&lon=$lon&format=json&addressdetails=1"; - $data = json_decode(file_get_contents($url), true); - self::returnJson(is_array($data) ? $data : ['data' => $data]); - } + $apiKey = TT_GEOCODING_API_SECRET; + $url = TT_GEOCODING_API_URL . "?latlng=$lat,$lon&key=$apiKey&language=de"; + $response = @file_get_contents($url); + if ($response === false) { + self::returnJson(['address' => [], 'error' => 'REQUEST_FAILED', 'error_message' => 'Could not connect to Google Maps API.']); + return; + } + + $data = json_decode($response, true); + + if ($data['status'] === 'OK' && !empty($data['results'])) { + $addressComponents = $data['results'][0]['address_components']; + $formattedAddress = $this->formatGoogleAddress($addressComponents); + self::returnJson(['address' => $formattedAddress]); + } else { + self::returnJson(['address' => [], 'error' => $data['status'], 'error_message' => $data['error_message'] ?? 'Reverse geocoding failed.']); + } + } //TODO: export this to an api class for openstreetmap protected function getDistanceAction() { @@ -486,7 +574,6 @@ class WarehouseShippingNoteController extends TTCrud { self::returnJson(json_decode($data, true)); } - $from = $this->request->from; $to = $this->request->to; $from = urlencode($from); @@ -497,47 +584,66 @@ class WarehouseShippingNoteController extends TTCrud { return [['lat' => 46.99555015, 'lon' => 15.77507876755547]]; } - $curl = curl_init(); - curl_setopt_array($curl, [ - CURLOPT_URL => "https://nominatim.haid.in/search?q=$address&format=json", - CURLOPT_RETURNTRANSFER => true, - CURLOPT_FOLLOWLOCATION => true, - CURLOPT_ENCODING => "", - CURLOPT_MAXREDIRS => 10, - CURLOPT_TIMEOUT => 30, - CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, - CURLOPT_CUSTOMREQUEST => "GET", - CURLOPT_HTTPHEADER => [ - "accept: application/json", - "accept-language: de-AT,de;q=0.9,en;q=0.8", - "origin: https://routing.openstreetmap.de", - "referer: https://routing.openstreetmap.de/", - "user-agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36" - ], - ]); + $address = urlencode($address); + $apiKey = TT_GEOCODING_API_SECRET; + // Bias search results for Austria + $url = TT_GEOCODING_API_URL . "?address=$address&key=$apiKey®ion=at&components=country:AT"; - $response = curl_exec($curl); - $err = curl_error($curl); - - if ($err) { - die(json_encode(['success' => false, 'message' => 'Error while geocoding'])); + $response = @file_get_contents($url); + if ($response === false) { + die(json_encode(['success' => false, 'message' => 'Error while geocoding: API connection failed.'])); } - curl_close($curl); - return json_decode($response, true); + $data = json_decode($response, true); + + if ($data['status'] === 'OK' && !empty($data['results'])) { + $location = $data['results'][0]['geometry']['location']; + // The OSRM routing engine expects 'lon', so we map Google's 'lng' to 'lon'. + return [['lat' => $location['lat'], 'lon' => $location['lng']]]; + } else { + die(json_encode(['success' => false, 'message' => 'Error while geocoding: ' . ($data['error_message'] ?? $data['status'])])); + } } function route($from, $to) { + // The geocode() function (which you've already updated to use Google) + // will provide the necessary coordinates. It will `die()` on error, so we can + // assume we have valid data if the script continues. $fromData = geocode($from); $toData = geocode($to); + $fromLat = $fromData[0]['lat']; $fromLon = $fromData[0]['lon']; $toLat = $toData[0]['lat']; $toLon = $toData[0]['lon']; - $url = "https://router.project-osrm.org/route/v1/driving/$fromLon,$fromLat;$toLon,$toLat?overview=false"; - $data = json_decode(file_get_contents($url), true); - $distance = $data['routes'][0]['distance']; - return $distance * 2; + + $apiKey = TT_GEOCODING_API_SECRET; + $url = "https://maps.googleapis.com/maps/api/directions/json" . + "?origin={$fromLat},{$fromLon}" . + "&destination={$toLat},{$toLon}" . + "&key={$apiKey}" . + "&mode=driving" . + "®ion=at"; // Bias results for Austria + + $response = @file_get_contents($url); + if ($response === false) { + die(json_encode(['success' => false, 'message' => 'Could not connect to Google Directions API.'])); + } + + $data = json_decode($response, true); + + // Check for a successful response and if a route was found + if ($data['status'] === 'OK' && !empty($data['routes'])) { + // The distance is provided in meters in the first "leg" of the first "route" + $distance = $data['routes'][0]['legs'][0]['distance']['value']; + + // Return the round-trip distance + return $distance * 2; + } else { + // Handle cases where no route is found or another API error occurred + $errorMessage = $data['error_message'] ?? 'No route could be found between the locations.'; + die(json_encode(['success' => false, 'message' => "Routing Error: " . $errorMessage])); + } } $distance = route($from, $to);