20 ) ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { return array(); } $gpx_content = wp_remote_retrieve_body( $response ); if ( empty( $gpx_content ) ) { return array(); } // --- Privacy Zone --- $privacy_options = get_option( 'statpress_privacy_options' ); $privacy_enabled = ! empty( $privacy_options['latitude'] ) && ! empty( $privacy_options['longitude'] ) && ! empty( $privacy_options['radius'] ); $privacy_center_lat = $privacy_enabled ? (float) $privacy_options['latitude'] : 0; $privacy_center_lon = $privacy_enabled ? (float) $privacy_options['longitude'] : 0; $privacy_radius_km = $privacy_enabled ? ( (int) $privacy_options['radius'] ) / 1000 : 0; // Convert meters to km libxml_use_internal_errors( true ); $gpx = simplexml_load_string( $gpx_content ); libxml_clear_errors(); if ( false === $gpx ) { return array(); } // Use XPath to be more robust against different GPX structures/namespaces $gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' ); $trackpoints = $gpx->xpath( '//gpx:trkpt' ); if ( empty( $trackpoints ) ) { $trackpoints = $gpx->xpath( '//trkpt' ); // Fallback for files without namespace } $raw_points = array(); $start_time = null; foreach ( $trackpoints as $trkpt ) { if ( isset( $trkpt['lat'], $trkpt['lon'] ) ) { $extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null; $time = isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null; if ( $time && is_null( $start_time ) ) { $start_time = $time; } $hr_val = ( $extensions && isset( $extensions->TrackPointExtension->hr ) ) ? (int) $extensions->TrackPointExtension->hr : null; $cad_val = ( $extensions && isset( $extensions->TrackPointExtension->cad ) ) ? (int) $extensions->TrackPointExtension->cad : null; $raw_points[] = array( 'lat' => (float) $trkpt['lat'], 'lon' => (float) $trkpt['lon'], 'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null, 'time_offset' => $start_time && $time ? $time - $start_time : null, 'hr' => $hr_val, 'cad' => $cad_val, ); } } if ( empty( $raw_points ) ) { return array(); } // Process raw points to calculate profiles $map_points = array(); $profiles = array( 'distance' => array(), 'time' => array(), 'elevation' => array(), 'speed' => array(), 'hr' => array(), 'cadence' => array(), ); $cumulative_distance = 0; $haversine = function( $lat1, $lon1, $lat2, $lon2 ) { $earth_radius = 6371; // in km $dLat = deg2rad( $lat2 - $lat1 ); $dLon = deg2rad( $lon2 - $lon1 ); $a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 ); $c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) ); return $earth_radius * $c; }; foreach ( $raw_points as $i => $point ) { $is_in_privacy_zone = false; if ( $privacy_enabled ) { $distance_from_center = $haversine( $privacy_center_lat, $privacy_center_lon, $point['lat'], $point['lon'] ); if ( $distance_from_center <= $privacy_radius_km ) { $is_in_privacy_zone = true; } } if ( ! $is_in_privacy_zone ) { $map_points[] = array( $point['lat'], $point['lon'] ); $speed = null; if ( $i > 0 ) { $prev_point = $raw_points[ $i - 1 ]; $distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); // km $cumulative_distance += $distance_delta; if ( ! is_null( $point['time_offset'] ) && ! is_null( $prev_point['time_offset'] ) ) { $time_delta = $point['time_offset'] - $prev_point['time_offset']; // seconds if ( $time_delta > 0 ) { $speed = ( $distance_delta * 3600 ) / $time_delta; // km/h } } } $profiles['distance'][] = round( $cumulative_distance, 3 ); $profiles['time'][] = $point['time_offset']; $profiles['elevation'][] = ! is_null( $point['ele'] ) ? round( $point['ele'], 2 ) : null; $profiles['speed'][] = ! is_null( $speed ) ? round( $speed, 1 ) : null; $profiles['hr'][] = $point['hr']; $profiles['cadence'][] = $point['cad']; } } return array( 'points' => $map_points, 'profiles' => $profiles, ); } /** * Fetches and calculates a full summary from a GPX file. * This function parses the entire file to get accurate summary stats, ignoring privacy zones. * * @param string $gpx_url The URL of the GPX file. * @return array An associative array with summary statistics. */ function statpress_calculate_gpx_summary( $gpx_url ) { if ( empty( $gpx_url ) ) { return array(); } $response = wp_remote_get( $gpx_url, array( 'timeout' => 20 ) ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { return array(); } $gpx_content = wp_remote_retrieve_body( $response ); if ( empty( $gpx_content ) ) { return array(); } libxml_use_internal_errors( true ); $gpx = simplexml_load_string( $gpx_content ); libxml_clear_errors(); if ( false === $gpx ) { return array(); } $gpx->registerXPathNamespace( 'gpx', 'http://www.topografix.com/GPX/1/1' ); $trackpoints = $gpx->xpath( '//gpx:trkpt' ); if ( empty( $trackpoints ) ) { $trackpoints = $gpx->xpath( '//trkpt' ); } if ( empty( $trackpoints ) ) { return array(); } $haversine = function( $lat1, $lon1, $lat2, $lon2 ) { $earth_radius = 6371; // km $dLat = deg2rad( $lat2 - $lat1 ); $dLon = deg2rad( $lon2 - $lon1 ); $a = sin( $dLat / 2 ) * sin( $dLat / 2 ) + cos( deg2rad( $lat1 ) ) * cos( deg2rad( $lat2 ) ) * sin( $dLon / 2 ) * sin( $dLon / 2 ); $c = 2 * atan2( sqrt( $a ), sqrt( 1 - $a ) ); return $earth_radius * $c; }; $stats = array( 'distance' => 0, 'total_elevation_gain' => 0, 'total_elevation_loss' => 0, 'min_altitude' => null, 'max_altitude' => null, 'max_speed' => 0, 'max_heart_rate' => 0, 'max_cadence' => 0, 'hr_sum' => 0, 'hr_count' => 0, 'cad_sum' => 0, 'cad_count' => 0, 'speed_sum' => 0, 'speed_count' => 0, 'start_time' => null, 'end_time' => null, ); $prev_point = null; foreach ( $trackpoints as $trkpt ) { $point = array( 'lat' => (float) $trkpt['lat'], 'lon' => (float) $trkpt['lon'], 'ele' => isset( $trkpt->ele ) ? (float) $trkpt->ele : null, 'time' => isset( $trkpt->time ) ? strtotime( (string) $trkpt->time ) : null, ); if ( is_null( $stats['start_time'] ) && ! is_null( $point['time'] ) ) { $stats['start_time'] = $point['time']; } $stats['end_time'] = $point['time']; if ( ! is_null( $point['ele'] ) ) { if ( is_null( $stats['min_altitude'] ) || $point['ele'] < $stats['min_altitude'] ) { $stats['min_altitude'] = $point['ele']; } if ( is_null( $stats['max_altitude'] ) || $point['ele'] > $stats['max_altitude'] ) { $stats['max_altitude'] = $point['ele']; } } $extensions = $trkpt->extensions ? $trkpt->extensions->children( 'gpxtpx', true ) : null; if ( $extensions ) { if ( isset( $extensions->TrackPointExtension->hr ) ) { $hr = (int) $extensions->TrackPointExtension->hr; $stats['hr_sum'] += $hr; $stats['hr_count']++; if ( $hr > $stats['max_heart_rate'] ) { $stats['max_heart_rate'] = $hr; } } if ( isset( $extensions->TrackPointExtension->cad ) ) { $cad = (int) $extensions->TrackPointExtension->cad; $stats['cad_sum'] += $cad; $stats['cad_count']++; if ( $cad > $stats['max_cadence'] ) { $stats['max_cadence'] = $cad; } } } if ( $prev_point ) { $distance_delta = $haversine( $prev_point['lat'], $prev_point['lon'], $point['lat'], $point['lon'] ); $stats['distance'] += $distance_delta; if ( ! is_null( $point['ele'] ) && ! is_null( $prev_point['ele'] ) ) { $ele_delta = $point['ele'] - $prev_point['ele']; if ( $ele_delta > 0 ) { $stats['total_elevation_gain'] += $ele_delta; } else { $stats['total_elevation_loss'] += abs( $ele_delta ); } } if ( ! is_null( $point['time'] ) && ! is_null( $prev_point['time'] ) ) { $time_delta = $point['time'] - $prev_point['time']; if ( $time_delta > 0 ) { $speed = ( $distance_delta * 3600 ) / $time_delta; // km/h $stats['speed_sum'] += $speed; $stats['speed_count']++; if ( $speed > $stats['max_speed'] ) { $stats['max_speed'] = $speed; } } } } $prev_point = $point; } $duration_sec = ( ! is_null( $stats['end_time'] ) && ! is_null( $stats['start_time'] ) ) ? ( $stats['end_time'] - $stats['start_time'] ) : 0; return array( 'distance' => round( $stats['distance'], 2 ), 'duration' => gmdate( 'H:i:s', $duration_sec ), 'avg_speed' => ( $stats['distance'] > 0 && $duration_sec > 0 ) ? round( ( $stats['distance'] / ( $duration_sec / 3600 ) ), 1 ) : 0, 'max_speed' => round( $stats['max_speed'], 1 ), 'total_elevation_gain' => round( $stats['total_elevation_gain'] ), 'total_elevation_loss' => round( $stats['total_elevation_loss'] ), 'min_altitude' => ! is_null( $stats['min_altitude'] ) ? round( $stats['min_altitude'] ) : null, 'max_altitude' => ! is_null( $stats['max_altitude'] ) ? round( $stats['max_altitude'] ) : null, 'avg_heart_rate' => $stats['hr_count'] > 0 ? round( $stats['hr_sum'] / $stats['hr_count'] ) : 0, 'max_heart_rate' => $stats['max_heart_rate'], 'avg_cadence' => $stats['cad_count'] > 0 ? round( $stats['cad_sum'] / $stats['cad_count'] ) : 0, 'max_cadence' => $stats['max_cadence'], ); }