= 0x20 && $i < strlen($encoded)); $dlng = ($result & 1) ? ~($result >> 1) : ($result >> 1); $ll[$j] = $previous[$j] + $dlng; $previous[$j] = $ll[$j]; } $decoded[] = [ 'lat' => $ll[0] * $inv, 'lng' => $ll[1] * $inv ]; } return $decoded; } /** * Encode array of coordinates to polyline string * Implementation of Google's Encoded Polyline Algorithm Format * * @param array $coordinates Array of coordinates with 'lat' and 'lng' keys * @return string The encoded polyline string */ public static function encodePolyline($coordinates) { $encoded = ''; $previous = [0, 0]; foreach ($coordinates as $point) { $lat = $point['lat']; $lng = $point['lng']; $current = [ intval(round($lat * 1e5)), intval(round($lng * 1e5)) ]; for ($j = 0; $j < 2; $j++) { $curr = $current[$j]; $prev = $previous[$j]; $value = $curr - $prev; $value = ($value << 1) ^ ($value >> 31); $chunks = []; while ($value >= 0x20) { $chunks[] = (0x20 | ($value & 0x1f)) + 63; $value >>= 5; } $chunks[] = $value + 63; foreach ($chunks as $chunk) { $encoded .= chr($chunk); } $previous[$j] = $current[$j]; } } return $encoded; } /** * Calculate distance between two coordinates using Haversine formula * Returns distance in meters * * @param float $lat1 Starting latitude * @param float $lng1 Starting longitude * @param float $lat2 Ending latitude * @param float $lng2 Ending longitude * @return float Distance in meters */ public static function calculateDistance($lat1, $lng1, $lat2, $lng2) { $earth_radius = 6371000; // Earth's radius in meters $dLat = deg2rad($lat2 - $lat1); $dLng = deg2rad($lng2 - $lng1); $a = sin($dLat / 2) * sin($dLat / 2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng / 2) * sin($dLng / 2); $c = 2 * atan2(sqrt($a), sqrt(1 - $a)); $distance = $earth_radius * $c; return round($distance, 2); } /** * Calculate total distance of a route from coordinates array * * @param array $coordinates Array of coordinates with 'lat' and 'lng' keys * @return float Total distance in meters */ public static function calculateTotalDistance($coordinates) { if (count($coordinates) < 2) { return 0; } $total_distance = 0; for ($i = 0; $i < count($coordinates) - 1; $i++) { $current = $coordinates[$i]; $next = $coordinates[$i + 1]; $segment_distance = self::calculateDistance( $current['lat'], $current['lng'], $next['lat'], $next['lng'] ); $total_distance += $segment_distance; } return round($total_distance, 2); } /** * Calculate elevation gain and loss from coordinates with elevation data * * @param array $coordinates Array of coordinates with 'lat', 'lng', and 'elevation' keys * @return array ['gain' => float, 'loss' => float] */ public static function calculateElevationChange($coordinates) { $elevation_gain = 0; $elevation_loss = 0; for ($i = 0; $i < count($coordinates) - 1; $i++) { $current_elevation = $coordinates[$i]['elevation'] ?? null; $next_elevation = $coordinates[$i + 1]['elevation'] ?? null; if ($current_elevation !== null && $next_elevation !== null) { $change = $next_elevation - $current_elevation; if ($change > 0) { $elevation_gain += $change; } else { $elevation_loss += abs($change); } } } return [ 'gain' => round($elevation_gain, 2), 'loss' => round($elevation_loss, 2) ]; } /** * Simplify polyline using Douglas-Peucker algorithm * Reduces number of points while maintaining shape accuracy * * @param array $coordinates Array of coordinates * @param float $tolerance Distance tolerance in degrees * @return array Simplified coordinates array */ public static function simplifyPolyline($coordinates, $tolerance = 0.00001) { if (count($coordinates) < 3) { return $coordinates; } $dmax = 0; $index = 0; for ($i = 1; $i < count($coordinates) - 1; $i++) { $d = self::pointLineDistance( $coordinates[$i], $coordinates[0], $coordinates[count($coordinates) - 1] ); if ($d > $dmax) { $dmax = $d; $index = $i; } } if ($dmax > $tolerance) { $rec1 = self::simplifyPolyline( array_slice($coordinates, 0, $index + 1), $tolerance ); $rec2 = self::simplifyPolyline( array_slice($coordinates, $index), $tolerance ); $result = array_merge(array_slice($rec1, 0, -1), $rec2); } else { $result = [ $coordinates[0], $coordinates[count($coordinates) - 1] ]; } return $result; } /** * Calculate perpendicular distance from point to line */ private static function pointLineDistance($point, $lineStart, $lineEnd) { $px = $point['lat']; $py = $point['lng']; $x1 = $lineStart['lat']; $y1 = $lineStart['lng']; $x2 = $lineEnd['lat']; $y2 = $lineEnd['lng']; $numerator = abs(($y2 - $y1) * $px - ($x2 - $x1) * $py + $x2 * $y1 - $y2 * $x1); $denominator = sqrt(pow($y2 - $y1, 2) + pow($x2 - $x1, 2)); return $denominator != 0 ? $numerator / $denominator : 0; } /** * Validate polyline string format * * @param string $polyline The encoded polyline * @return bool True if valid polyline format */ public static function validatePolyline($polyline) { if (!is_string($polyline) || empty($polyline)) { return false; } // Check that all characters are valid ASCII printable for ($i = 0; $i < strlen($polyline); $i++) { $char_code = ord($polyline[$i]); if ($char_code < 63 || $char_code > 126) { return false; } } return true; } } ?>