Skip to content

Commit

Permalink
Add the ability to colorize tracks by speed or elevation.
Browse files Browse the repository at this point in the history
  • Loading branch information
akkana committed Nov 4, 2023
1 parent 0b1e0d6 commit 4346927
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 13 deletions.
87 changes: 77 additions & 10 deletions pytopo/MapWindow.py
Expand Up @@ -43,14 +43,37 @@

from pkg_resources import resource_filename

from enum import Enum

# As of GTK3 there's no longer any HSV support, because cairo is
# solely RGB. Use colorsys instead
# solely RGB. Use colorsys instead.
import colorsys

import traceback

GPS_MARKER_RADIUS=10

# Track colorization styles
class TrackColor(Enum):
SEPARATE_COLORS = 1
SPEED_COLORS = 2
ELEVATION_COLORS = 3


def is_color(color):
try:
return len(color) == 3
except:
return False


def colorize_by_value(val):
"""val is a float between 0 and 1.
Return a color (a triplet of floats between 0 and 1)
from a smooth gradient between blue (low) and red (high).
"""
return (val, 0., 1. - val)


class MapWindow(object):

Expand All @@ -71,6 +94,12 @@ class MapWindow(object):
but if you want to, contact me and I'll help you figure it out.)
"""

TRACK_COLOR_VIEW_MENU = OrderedDict([
("Each track a different color", TrackColor.SEPARATE_COLORS),
("By Elevation", TrackColor.ELEVATION_COLORS),
("By Speed", TrackColor.SPEED_COLORS)
])

def __init__(self, _controller):
"""Initialize variables, but don't create the window yet."""

Expand All @@ -97,6 +126,9 @@ def __init__(self, _controller):
self.selected_track = None
self.selected_waypoint = None

# By default, each track is a different colors
self.track_colorize = TrackColor.SEPARATE_COLORS

# No redraws initially scheduled
self.redraw_scheduled = False

Expand Down Expand Up @@ -461,7 +493,12 @@ def draw_trackpoint_segment(self, start, linecolor, linewidth=2,
Stop drawing if we reach another start string, and return the index
of that string. Return None if we reach the end of the list.
"""
self.cr.set_source_rgb (*linecolor)
if self.track_colorize == TrackColor.SEPARATE_COLORS and \
is_color(linecolor):
self.cr.set_source_rgb (*linecolor)
elif self.track_colorize == TrackColor.SPEED_COLORS or\
self.track_colorize == TrackColor.ELEVATION_COLORS:
linewidth *= 2

cur_x = None
cur_y = None
Expand All @@ -482,6 +519,28 @@ def draw_trackpoint_segment(self, start, linecolor, linewidth=2,
if self.trackpoints.is_attributes(pt):
continue

# If specified, colorize according to speed or altitude:
try:
if self.track_colorize == TrackColor.SPEED_COLORS:
speedfrac = float(pt.speed) / self.trackpoints.max_speed
linecolor = colorize_by_value(speedfrac)
self.cr.set_source_rgb (*linecolor)

elif (self.track_colorize == TrackColor.ELEVATION_COLORS and
self.trackpoints.min_ele and self.trackpoints.max_ele):
elefrac = ((pt.ele - self.trackpoints.min_ele) /
(self.trackpoints.max_ele
- self.trackpoints.min_ele))
linecolor = colorize_by_value(elefrac)
# print("(ele)", int(elefrac * 100),
# ": Setting color to (%.2f, %.2f, %.2f)"
# % linecolor)
self.cr.set_source_rgb (*linecolor)

except Exception as e:
# print("can't set color:", e)
pass

x = int((pt.lon - self.center_lon) * self.collection.xscale
+ self.win_width / 2)
y = int((self.center_lat - pt.lat) * self.collection.yscale
Expand Down Expand Up @@ -1106,12 +1165,12 @@ def context_menu(self, event):
("Remove point from track", self.remove_trackpoint),
("Undo", self.undo),
("Save GPX or GeoJSON...", self.save_all_tracks_as),
# ("Save Area as GPX...", self.save_area_tracks_as),

("View", SEPARATOR),
("Zoom here...", self.zoom),
(draw_track_label, self.toggle_track_drawing),
(show_waypoint_label, self.toggle_show_waypoints),
("Colorize tracks", None),

("Rest", SEPARATOR),
("Download Area...", self.download_area),
Expand Down Expand Up @@ -1141,6 +1200,17 @@ def context_menu(self, event):

item.set_submenu(submenu)

elif itemname == "Colorize tracks":
submenu = gtk.Menu()
for name in self.TRACK_COLOR_VIEW_MENU:
subitem = gtk.MenuItem(name)
subitem.connect("activate", self.change_track_colorize,
self.TRACK_COLOR_VIEW_MENU[name])
submenu.append(subitem)
subitem.show()

item.set_submenu(submenu)

elif contextmenu[itemname]:
item.connect("activate", contextmenu[itemname])

Expand All @@ -1163,6 +1233,10 @@ def context_menu(self, event):
# Not since the epoch.
menu.popup(None, None, None, None, button, t)

def change_track_colorize(self, widget, whichcolorize):
self.track_colorize = whichcolorize
self.draw_map()

def change_collection(self, widget, name):
if self.collection:
savezoom = self.collection.zoomlevel
Expand Down Expand Up @@ -1407,13 +1481,6 @@ def save_all_tracks_as(self, widget):
"""Prompt for a filename to save all tracks and waypoints."""
return self.save_tracks_as(widget, False)

def save_area_tracks_as(self, widget):
"""Prompt for a filename to save all tracks and waypoints,
then let the user drag out an area with the mouse
and save all tracks that are completely within that area.
"""
return self.save_tracks_as(widget, True)

def save_tracks_as(self, widget, select_area=False):
"""Prompt for a filename to save all tracks and waypoints.
Then either let the user drag out an area with the mouse
Expand Down
36 changes: 33 additions & 3 deletions pytopo/TrackPoints.py
Expand Up @@ -193,6 +193,10 @@ def __init__(self):
# in case there are no track- or waypoints
self.outer_bbox = BoundingBox()

self.max_speed = 0
self.min_ele = 0
self.max_ele = 0

# Remember which files each set of points came from
self.srcfiles = {}

Expand Down Expand Up @@ -250,8 +254,11 @@ def attributes(self, trackindex):
"""
# Currently attributes are represented by a dictionary
# as the next item after the name in the trackpoints list.
if self.is_attributes(self.points[trackindex + 1]):
return self.points[trackindex + 1]
try:
if self.is_attributes(self.points[trackindex + 1]):
return self.points[trackindex + 1]
except:
pass
return None

def handle_track_point(self, lat, lon, ele=None, speed=None,
Expand All @@ -266,6 +273,20 @@ def handle_track_point(self, lat, lon, ele=None, speed=None,
point = GeoPoint(lat, lon, ele=ele, speed=speed, timestamp=timestamp,
name=waypoint_name, attrs=attrs)

try:
if point.ele:
if not self.min_ele or point.ele < self.min_ele:
self.min_ele = point.ele
if point.ele > self.max_ele:
self.max_ele = point.ele
except:
pass

try:
self.max_speed = max(self.max_speed, float(speed))
except:
pass

if waypoint_name:
self.waypoints.append(point)
else:
Expand Down Expand Up @@ -429,9 +450,14 @@ def GPX_point_coords(self, pointnode):
"""
lat = float(pointnode.getAttribute("lat"))
lon = float(pointnode.getAttribute("lon"))
ele = self.get_DOM_text(pointnode, "ele")
time = self.get_DOM_text(pointnode, "time")

ele = self.get_DOM_text(pointnode, "ele")
try:
ele = float(ele)
except:
ele = 0.

# Python dom and minidom have no easy way to combine sub-nodes
# into a dictionary, or to serialize them.
# Also nodes are mostly undocumented.
Expand All @@ -447,6 +473,10 @@ def GPX_point_coords(self, pointnode):
# Old versions of osmand used a <speed> node, but now it's inside
# <extensions><osmand:speed>.
speed = self.get_DOM_text(pointnode, SPEEDRE)
try:
speed = float(speed)
except:
speed = None

# If there were no extra attributes, pass None, not an empty dict.
if not attrs:
Expand Down

0 comments on commit 4346927

Please sign in to comment.