App developed by Aleksandr Kunin
Deutsch translate by Tatiana Holm
Spanish translate by Ekaterina Larina
Logo designed by Julia Makoveeva
def distance a, b
rad_per_deg = Math::PI/180 # PI / 180
rkm = 6371 # Радиус земли в километрах
rm = rkm * 1000
dlon_rad = (b[1]-a[1]) * rad_per_deg
dlat_rad = (b[0]-a[0]) * rad_per_deg
lat1_rad, lon1_rad = a.map! {|i| i * rad_per_deg }
lat2_rad, lon2_rad = b.map! {|i| i * rad_per_deg }
a = Math.sin(dlat_rad/2)**2 + Math.cos(lat1_rad) * Math.cos(lat2_rad) * Math.sin(dlon_rad/2)**2
c = 2 * Math.asin(Math.sqrt(a))
rm * c # Расстояние в метрах
end
def process_track_points track_points
# Пока не придумал что делать с 5 Гц и 10 Гц файлами - оставляю только первую запись по дате создания
track_points.uniq!{ |x| DateTime.strptime(x[:point_created_at], '%Y-%m-%dT%H:%M:%S') }
min_h = track_points.min_by{ |x| x[:elevation] }[:elevation]
# Уменьшим высоту во всех точках на минимальную. (корректировка относительно уровня земли)
track_points.each do |x|
x[:elevation] -= min_h
end
min_h = track_points.min_by{ |x| x[:elevation] }[:elevation]
max_h = track_points.max_by{ |x| x[:elevation] }[:elevation]
# Расчет дистанции и времени полета
fl_time = 0
track_points.each_index do |i|
point = track_points[i]
point[:distance] = 0 if i == 0
if i > 0
prev_point = track_points.at(i-1)
datetime_1 = DateTime.strptime(point[:point_created_at], '%Y-%m-%dT%H:%M:%S')
datetime_2 = DateTime.strptime(prev_point[:point_created_at], '%Y-%m-%dT%H:%M:%S')
fl_time_diff = (datetime_1 - datetime_2) * 1.days
fl_time += fl_time_diff
point[:distance] = calc_distance [prev_point[:latitude], prev_point[:longitude]], [point[:latitude], point[:longitude]]
point[:h_speed] = point[:distance] / fl_time_diff * 3.6
point[:v_speed] = (prev_point[:elevation] - point[:elevation]) / fl_time_diff * 3.6
end
point[:fl_time] = fl_time
end
# Медианный фильтр для расстояния и высоты
track_points.each_index do |i|
point = track_points[i]
median_start = [0, i-1].max
median_end = [track_points.count-1, i+1].min
median_points = [track_points[median_start], point, track_points[median_end]]
point[:distance] = median_points.map { |x| x[:distance] }.sort[1]
point[:elevation] = median_points.map { |x| x[:elevation] }.sort[1]
point[:h_speed] = median_points.map { |x| x[:h_speed] || 0 }.sort[1]
point[:v_speed] = median_points.map { |x| x[:v_speed] || 0 }.sort[1]
end
self.ff_start = 0
self.ff_end = fl_time
# Развернем массив и найдем точку после достижения максимальной высоты и набору скорости в 25 км/ч
track_points.reverse!
start_point = track_points.detect { |x| x[:elevation] >= (max_h - 15) }
self.ff_start = start_point[:fl_time] if start_point.present?
track_points.reverse!
start_point = track_points.detect { |x| (x[:fl_time] > self.ff_start && x[:v_speed] > 25) }
self.ff_start = start_point[:fl_time] if start_point.present?
# Найдем первую точку ниже минимума (предполагаю Земли) + 50 метров
end_point = track_points.detect { |x| x[:elevation] < (min_h + 50) }
self.ff_end = end_point[:fl_time] if end_point.present?
track_points
end
function set_chart_data() {
var dist_data = [],
elev_data = [],
heights_data = [],
h_speed = [],
v_speed = [],
gr = [],
avg_h_speed = 0,
avg_v_speed = 0,
avg_gr = 0,
min_h_speed = 0,
max_h_speed = 0,
min_v_speed = 0,
max_v_speed = 0,
min_gr = 0,
max_gr = 0,
fl_time = 0,
dist = 0,
elev = 0;
max_val = typeof range_from !== 'undefined' ? range_from : 100000;
min_val = typeof range_to !== 'undefined' ? range_to : 0;
var isFirst = true,
isLast = false;
for (var index in charts_data) {
var current_point = charts_data[index];
var point = {};
isLast = true;
if (current_point.elevation <= max_val && current_point.elevation >= min_val) {
point = clone(current_point);
// Корректировка выбранного диапазона
if (isFirst) {
isFirst = false;
if (current_point.elevation != max_val && charts_data.hasOwnProperty(index-1)) {
point.elevation_diff = max_val - current_point.elevation;
var k = point.elevation_diff / current_point.elevation_diff;
point.distance = Math.round(current_point.distance * k);
point.fl_time = Math.round(current_point.fl_time * k);
point.elevation = current_point.elevation;
point.v_speed = current_point.v_speed;
point.h_speed = current_point.h_speed;
}
}
isLast = false;
dist += point.distance;
elev += point.elevation_diff;
elev_data.push([fl_time, elev]);
dist_data.push([fl_time, dist]);
heights_data.push([fl_time, point.elevation]);
h_speed.push([fl_time, point.h_speed]);
v_speed.push([fl_time, point.v_speed]);
gr.push([fl_time, point.glrat]);
fl_time += point.fl_time;
min_h_speed = min_h_speed == 0 || min_h_speed > point.h_speed ? point.h_speed : min_h_speed;
max_h_speed = max_h_speed == 0 || max_h_speed < point.h_speed ? point.h_speed : max_h_speed;
min_v_speed = min_v_speed == 0 || min_v_speed > point.v_speed ? point.v_speed : min_v_speed;
max_v_speed = max_v_speed == 0 || max_v_speed < point.v_speed ? point.v_speed : max_v_speed;
min_gr = min_gr == 0 || min_gr > point.glrat ? point.glrat : min_gr;
max_gr = max_gr == 0 || max_gr < point.glrat ? point.glrat : max_gr;
}
if (isLast && elev_data.length > 0) {
if (current_point.elevation <= min_val && charts_data.hasOwnProperty(index - 1)) {
point = clone(current_point);
prev_point = charts_data[index - 1];
point.elevation_diff = prev_point.elevation - min_val;
var k = point.elevation_diff / current_point.elevation_diff;
point.fl_time = current_point.fl_time * k;
point.distance = Math.round(current_point.distance * k);
dist += point.distance;
elev += point.elevation_diff;
elev_data.push([fl_time, elev]);
dist_data.push([fl_time, dist]);
heights_data.push([fl_time, point.elevation]);
h_speed.push([fl_time, point.h_speed]);
v_speed.push([fl_time, point.v_speed]);
gr.push([fl_time, point.glrat]);
}
break;
}
}
var ed_chart = $('#elevation_distance_chart').highcharts();
ed_chart.series[0].setData(elev_data);
ed_chart.series[1].setData(dist_data);
ed_chart.series[2].setData(heights_data);
var sp_chart = $('#speeds_chart').highcharts();
sp_chart.series[0].setData(h_speed);
sp_chart.series[1].setData(v_speed);
var gr_chart = $('#glideratio_chart').highcharts();
gr_chart.series[0].setData(gr);
var ad_chart = $('#all_data_chart').highcharts();
ad_chart.series[0].setData(h_speed);
ad_chart.series[1].setData(v_speed);
ad_chart.series[2].setData(gr);
ad_chart.series[3].setData(heights_data);
ad_chart.series[4].setData(dist_data);
ad_chart.series[5].setData(elev_data);
$('#dd_distance').text(dist.toString() + ' м');
$('#dd_elevation').text(elev.toString() + ' м');
$('#dd_fl_time').text(fl_time.toString() + ' с');
$('#p_min_v_speed').text(min_v_speed.toFixed(0) + '...');
$('#p_max_v_speed').text('...' + max_v_speed.toFixed(0));
$('#p_avg_v_speed').text(Math.round(elev / fl_time * 3.6));
$('#p_min_h_speed').text(min_h_speed.toFixed(0) + '...');
$('#p_max_h_speed').text('...' + max_h_speed.toFixed(0));
$('#p_avg_h_speed').text(Math.round(dist / fl_time * 3.6).toString());
$('#p_min_gr').text(min_gr.toFixed(2) + '...');
$('#p_max_gr').text('...' + max_gr.toFixed(2));
$('#p_avg_gr').text((dist / elev).toFixed(2));
};
GPX icon made by Freepik from
www.flaticon.com
is licensed by
CC BY 3.0