retry(0) // wir handeln retry selbst ->get('https://maps.googleapis.com/maps/api/directions/json', [ 'origin' => $origin, 'destination' => $destination, 'mode' => $mode, 'language' => 'de', 'region' => 'at', 'key' => $key, ]); if (! $response->successful()) { $lastErr = 'HTTP '.$response->status(); Log::warning('Directions API HTTP-Fehler', ['attempt' => $attempt, 'status' => $response->status()]); if ($attempt < 2) { usleep(400_000); continue; } return null; } $data = $response->json(); $status = $data['status'] ?? null; // ZERO_RESULTS oder UNKNOWN_ERROR sind oft transient bei ÖPNV - retry if (in_array($status, ['ZERO_RESULTS', 'UNKNOWN_ERROR', 'OVER_QUERY_LIMIT'], true) && $attempt < 2) { Log::info('Directions transient '.$status.', retry'); usleep(500_000); continue; } break; } catch (\Throwable $e) { $lastErr = $e->getMessage(); Log::warning('Directions exception', ['attempt' => $attempt, 'e' => $lastErr]); if ($attempt < 2) { usleep(400_000); continue; } return null; } } try { if ($status !== 'OK' || empty($data['routes'])) { Log::warning('Directions: keine Route nach '.($attempt ?? 1).' Versuchen', [ 'status' => $status, 'msg' => $data['error_message'] ?? $lastErr, 'origin' => $origin, 'destination' => $destination, ]); return null; } $route = $data['routes'][0]; $leg = $route['legs'][0]; return [ 'distance_text' => $leg['distance']['text'] ?? '', 'distance_m' => $leg['distance']['value'] ?? 0, 'duration_text' => $leg['duration']['text'] ?? '', 'duration_s' => $leg['duration']['value'] ?? 0, 'start_address' => $leg['start_address'] ?? '', 'end_address' => $leg['end_address'] ?? '', 'start_location' => $leg['start_location'] ?? null, 'end_location' => $leg['end_location'] ?? null, 'bounds' => $route['bounds'] ?? null, 'geojson' => $this->decodePolyline($route['overview_polyline']['points'] ?? ''), 'mode' => $mode, 'steps' => $this->normalizeSteps($leg['steps'] ?? []), ]; } catch (\Throwable $e) { Log::warning('RouteService exception', ['e' => $e->getMessage()]); return null; } } /** * Google-Steps → schlankeres Format für die Sidebar mit Transit-Details. */ protected function normalizeSteps(array $steps): array { $out = []; foreach ($steps as $s) { $mode = $s['travel_mode'] ?? 'WALKING'; $instruction = strip_tags(str_replace( [''], [' '], (string) ($s['html_instructions'] ?? ''), )); $instruction = trim((string) preg_replace('/\s+/u', ' ', $instruction)); $entry = [ 'mode' => $mode, 'instruction' => $instruction, 'distance' => $s['distance']['text'] ?? '', 'duration' => $s['duration']['text'] ?? '', ]; if ($mode === 'TRANSIT' && ! empty($s['transit_details'])) { $td = $s['transit_details']; $line = $td['line'] ?? []; $entry['transit'] = [ 'line' => $line['short_name'] ?? ($line['name'] ?? ''), 'line_name' => $line['name'] ?? '', 'color' => $line['color'] ?? '#666', 'text_color' => $line['text_color'] ?? '#fff', 'vehicle' => $line['vehicle']['type'] ?? 'BUS', 'departure_stop' => $td['departure_stop']['name'] ?? '', 'arrival_stop' => $td['arrival_stop']['name'] ?? '', 'departure_time' => $td['departure_time']['text'] ?? '', 'arrival_time' => $td['arrival_time']['text'] ?? '', 'num_stops' => (int) ($td['num_stops'] ?? 0), 'headsign' => $td['headsign'] ?? '', ]; } $out[] = $entry; } return $out; } /** * Google Polyline-Format → GeoJSON LineString-Feature. * https://developers.google.com/maps/documentation/utilities/polylinealgorithm */ protected function decodePolyline(string $encoded): array { $points = []; $index = $lat = $lng = 0; $len = strlen($encoded); while ($index < $len) { $shift = $result = 0; do { $b = ord($encoded[$index++]) - 63; $result |= ($b & 0x1f) << $shift; $shift += 5; } while ($b >= 0x20 && $index < $len); $lat += ($result & 1) ? ~($result >> 1) : ($result >> 1); $shift = $result = 0; do { $b = ord($encoded[$index++]) - 63; $result |= ($b & 0x1f) << $shift; $shift += 5; } while ($b >= 0x20 && $index < $len); $lng += ($result & 1) ? ~($result >> 1) : ($result >> 1); $points[] = [$lng / 1e5, $lat / 1e5]; } return [ 'type' => 'Feature', 'geometry' => ['type' => 'LineString', 'coordinates' => $points], 'properties' => new \stdClass, ]; } }