Initial speedometer function created

This commit is contained in:
José Carlos Cuevas 2020-03-08 18:28:57 +01:00
commit bb10bf0009
10 changed files with 341 additions and 0 deletions

0
src/__init__.py Normal file
View file

18
src/distance.py Normal file
View file

@ -0,0 +1,18 @@
#!/usr/bin/env python
from math import radians, sin, cos, atan2, sqrt
EARTH_RADIUS_IN_METERS = 6371007.177356707
def get_meters(lat1, long1, lat2, long2):
lat_diff = radians(abs(lat2 - lat1))
lng_diff = radians(abs(long2 - long1))
a = sin(lat_diff/2) * sin(lat_diff/2) + \
cos(radians(lat1)) * cos(radians(lat2)) * \
sin(lng_diff / 2) * sin(lng_diff / 2)
c = 2 * atan2(sqrt(a), sqrt(1 - a))
return EARTH_RADIUS_IN_METERS * c

34
src/draw/__init__.py Normal file
View file

@ -0,0 +1,34 @@
import cairo
def initialize(width, height):
"""
Creates a Cairo Context and initializes it
"""
surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
ctx = cairo.Context(surface)
ctx.set_antialias(cairo.ANTIALIAS_GOOD)
return ctx
def set_background(ctx, red, green, blue, alpha=1.0):
"""
Sets the background to a color
"""
target = ctx.get_target()
width = target.get_width()
height = target.get_height()
ctx.set_source_rgba(red, green, blue, alpha)
ctx.rectangle(0, 0, width, height)
ctx.fill()
def save_to_file(ctx, filename):
"""
Creates the PNG image out of the given context
"""
target = ctx.get_target()
target.write_to_png(filename)

16
src/draw/elevation.py Normal file
View file

@ -0,0 +1,16 @@
#! /usr/bin/env python
import cairo
def gauge(ctx, posx, posy, current_value, min_value, max_value, opts):
"""
Creates a gauge with the desired options
"""
default_opts = {
"width": 50,
"height": 100,
"color": (0.1, 0.8, 0.1),
"text": "Elevation"
}
final_opts = default_opts.update(opts)

71
src/draw/speedometer.py Normal file
View file

@ -0,0 +1,71 @@
#! /usr/bin/env python
"""
This module draws a speedometer in the position required
with the speed given. It also needs a maximum speed
"""
import math
import cairo
def speedometer(ctx, posx, posy, radius, speed, max_speed):
# Draw the exterior of the speedometer
init_arc = -(5 * math.pi) / 4
finish_arc = math.pi / 4
ctx.new_sub_path()
ctx.arc(posx, posy, radius, init_arc, finish_arc)
ctx.set_source_rgb(1, 1, 1)
ctx.set_line_width(radius * 0.1)
ctx.stroke()
# Draw the interior of the speedometer
ctx.new_sub_path()
ctx.arc(posx, posy, radius * 0.5, 0, 2 * math.pi)
ctx.set_source_rgb(1, 1, 1)
ctx.set_line_width(radius * 0.1)
ctx.stroke()
# Draw the speed number
speed_text = f"{int(round(speed))}"
unit_text = "km/h"
ctx.select_font_face("Sans", cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_BOLD)
# The speed itself
ctx.set_font_size(radius * 0.4)
x_bearing, y_bearing, width, height, x_advance, y_advance = \
ctx.text_extents(speed_text)
x = posx - (width / 2 + x_bearing)
y = posy
ctx.move_to(x, y)
ctx.set_source_rgb(1, 1, 1)
ctx.show_text(speed_text)
ctx.stroke()
# The unit
ctx.select_font_face("Sans", cairo.FONT_SLANT_NORMAL,
cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(radius * 0.15)
x_bearing, y_bearing, width, height, x_advance, y_advance = \
ctx.text_extents(unit_text)
x = posx - (width / 2 + x_bearing)
y = y + (radius * 0.2)
ctx.move_to(x, y)
ctx.show_text(unit_text)
ctx.stroke()
# The curve indicating the speed
point_rad = (6 / max_speed) * speed
curve_finish_arc = init_arc + ((point_rad * math.pi) / 4)
ctx.new_sub_path()
ctx.arc(posx, posy, radius - (radius * 0.2), init_arc, curve_finish_arc)
ctx.set_source_rgb(1, 0.1, 0.1)
ctx.set_line_width(radius * 0.3)
ctx.stroke()

78
src/parser.py Normal file
View file

@ -0,0 +1,78 @@
import datetime
import re
import xml.etree.ElementTree as ET
from distance import get_meters
from track import Track
def namespace(element):
m = re.match(r'\{.*\}', element.tag)
if m:
name_space = m.group(0)
name_space = name_space.replace('{', '')
name_space = name_space.replace('}', '')
return name_space
return ''
def parse_gpx(filename, fps=30.0):
root = ET.parse(filename).getroot()
print("Default namespace is {}".format(namespace(root)))
ns = {'def': namespace(root)}
initial_time = datetime.datetime.strptime(root.find('def:metadata', ns).find('def:time', ns).text, "%Y-%m-%dT%H:%M:%S.%fZ")
track_data = root.find('def:trk', ns)
lat = None
lon = None
data = Track()
prev_velocity = 0.0
prev_elevation = None
max_speed = 0.0
max_elevation = 0.0
print("Calculating interpolation at {} fps".format(fps))
for segment in track_data.findall('def:trkseg', ns):
for point in segment.findall('def:trkpt', ns):
new_lat = float(point.attrib['lat'])
new_lon = float(point.attrib['lon'])
elevation = int(point.find('def:ele', ns).text)
if not prev_elevation:
prev_elevation = elevation
if lat is None:
lat = new_lat
if lon is None:
lon = new_lon
meters = get_meters(lat, lon, new_lat, new_lon)
km_hour = meters * 3.6
if elevation > max_elevation:
max_elevation = elevation
if km_hour > max_speed:
max_speed = km_hour
# Begin interpolation
delta_v = (km_hour - prev_velocity) / fps
delta_e = (elevation - prev_elevation) / fps
for count in range(0, int(fps)):
data.add_point(prev_velocity + (count * delta_v), prev_elevation + (count * delta_e))
# Prepare next iteration
lat = new_lat
lon = new_lon
prev_velocity = km_hour
prev_elevation = elevation
data.set_maximum('speed', max_speed)
data.set_maximum('elevation', max_elevation)
return data

59
src/processor.py Normal file
View file

@ -0,0 +1,59 @@
#!/usr/bin/env python
import argparse
import math
import os
from parser import parse_gpx
import draw
from draw.speedometer import speedometer
FPS = 30.0
STILLS_PATH = './stills'
def create_progress_bar(current_frame, last_frame, bar_size=10):
percent = current_frame / last_frame
fill_bars = math.floor(bar_size * percent)
return ("" * fill_bars) + ("" * (bar_size - fill_bars))
def create_args_parser():
parser = argparse.ArgumentParser(description="Creates frames based off a GPX file for overlays")
parser.add_argument('filename', type=str, help="GPX file to parse")
parser.add_argument('stills', type=str, help="The folder where to store the still images")
parser.add_argument('--fps', dest='fps', default=30, help="Frames per second to generate")
return parser
def main(filename, fps, stills_path):
data = parse_gpx(filename, fps)
max_speed = data.get_maximum('speed')
max_elevation = data.get_maximum('elevation')
print(f"Replaying at {fps} FPS")
print(f"Saving to {stills_path}")
if not os.path.exists(stills_path):
os.makedirs(stills_path)
frame = 0
last_frame = data.length()
for elem in data.get_datapoints():
ctx = draw.initialize(1920, 1080)
draw.set_background(ctx, 0, 0, 0, 0.0)
progress_bar = create_progress_bar(frame, last_frame, 60)
print(f"Current Frame: {frame} {progress_bar} {(frame/last_frame) * 100.0:.2f} %", end="\r")
speedometer(ctx, 200, 900, 150, elem[0], max_speed)
draw.save_to_file(ctx, os.path.join(stills_path, f"still-{frame:08}.png"))
frame += 1
if __name__ == "__main__":
parser = create_args_parser()
options = parser.parse_args()
main(options.filename, options.fps, options.stills)

28
src/test.py Normal file
View file

@ -0,0 +1,28 @@
#!/usr/bin/env python
import math
from parser import parse_gpx
from time import sleep
FPS = 30.0
def create_speed_bar(current_speed, max_speed, bar_size=10):
percent = current_speed / max_speed
fill_bars = math.floor(bar_size * percent)
return ("" * fill_bars) + ("" * (bar_size - fill_bars))
if __name__ == "__main__":
data = parse_gpx('../data/2019_08_08_1.gpx', FPS)
max_speed = data.get_maximum('speed')
print(f"Replaying at {FPS} FPS")
for elem in data.get_datapoints():
speed_bar = create_speed_bar(elem[0], max_speed, 50)
print("Speed: {} {:.0f} km/h - Elevation: {:.0f} meters ".format(speed_bar, elem[0], elem[1]), end="\r")
sleep(1/FPS)

11
src/test_draw.py Normal file
View file

@ -0,0 +1,11 @@
#! /usr/bin/env python
import draw
from draw.speedometer import speedometer
if __name__ == "__main__":
ctx = draw.initialize(1920, 1080)
draw.set_background(ctx, 0, 0, 0, 0.0)
speedometer(ctx, 200, 900, 150, 0, 100)
draw.save_to_file(ctx, "test.png")

26
src/track.py Normal file
View file

@ -0,0 +1,26 @@
class Track:
"""
Holds all the data generated from processing the GPX file
"""
def __init__(self):
self.datapoints = []
self.maximums = {}
def add_point(self, speed, elevation):
self.datapoints.append((speed, elevation))
def set_datapoins(self, datapoints):
self.datapoints = datapoints
def get_datapoints(self):
return self.datapoints
def length(self):
return len(self.datapoints)
def set_maximum(self, key, value):
self.maximums[key] = value
def get_maximum(self, key):
return self.maximums.get(key, 0)