From 80daa1eb52352a9002aecd05c4d6dc1b4e510228 Mon Sep 17 00:00:00 2001 From: Ptosiek <16878205+Ptosiek@users.noreply.github.com> Date: Wed, 4 Oct 2023 13:06:11 -0700 Subject: [PATCH] fix loader_tcx to handle missing distance easier to understand what's happening remove commented code that preloaded course more pythonic code for loading course list move things around, rename a bit list courses should not be in tcx_loader fix missing notes when inserting start/end points --- .gitignore | 1 + modules/config.py | 42 +- modules/{logger/loader_tcx.py => course.py} | 493 +- modules/gui_pyqt.py | 2 +- modules/loaders/__init__.py | 1 + modules/loaders/tcx.py | 168 + modules/logger_core.py | 8 +- modules/pyqt/graph/pyqt_base_map.py | 3 +- modules/pyqt/graph/pyqt_course_profile.py | 4 +- modules/pyqt/graph/pyqt_map.py | 162 +- modules/pyqt/menu/pyqt_course_menu_widget.py | 6 +- modules/pyqt/pyqt_cuesheet_widget.py | 12 +- modules/sensor/sensor_gps.py | 104 +- tests/data/tcx/Mt_Angel_Abbey.tcx | 8986 ++++++++++++++++++ tests/test_loader.py | 12 + 15 files changed, 9538 insertions(+), 466 deletions(-) rename modules/{logger/loader_tcx.py => course.py} (63%) create mode 100644 modules/loaders/__init__.py create mode 100644 modules/loaders/tcx.py create mode 100644 tests/data/tcx/Mt_Angel_Abbey.tcx create mode 100644 tests/test_loader.py diff --git a/.gitignore b/.gitignore index 0ae6237e..c47999a0 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ /log/ /screenshots/ /maptile/* +/courses/.current /courses/ridewithgps/* /courses/*html* /fonts/* diff --git a/modules/config.py b/modules/config.py index 535aa7f9..34d22c0e 100644 --- a/modules/config.py +++ b/modules/config.py @@ -7,6 +7,7 @@ import shutil import traceback import math +from glob import glob import numpy as np import oyaml as yaml @@ -93,7 +94,7 @@ class Config: # courses G_COURSE_DIR = "courses" - G_COURSE_FILE_PATH = os.path.join(G_COURSE_DIR, "course.tcx") + G_COURSE_FILE_PATH = os.path.join(G_COURSE_DIR, ".current") G_CUESHEET_DISPLAY_NUM = 3 # max: 5 G_CUESHEET_SCROLL = False G_OBEXD_CMD = "/usr/libexec/bluetooth/obexd" @@ -1327,3 +1328,42 @@ def get_lon_lat_from_tile_xy(z, x, y): lat = math.degrees(math.atan(math.sinh(math.pi * (1 - 2 * y / n)))) return lon, lat + + def get_courses(self): + dirs = sorted( + glob(os.path.join(self.G_COURSE_DIR, "*.tcx")), + key=lambda f: os.stat(f).st_mtime, + reverse=True, + ) + + # heavy: delayed updates required + # def get_course_info(c): + # pattern = { + # "name": re.compile(r"(?P[\s\S]*?)"), + # "distance_meters": re.compile( + # r"(?P[\s\S]*?)" + # ), + # # "track": re.compile(r'(?P[\s\S]*?)'), + # # "altitude": re.compile(r'(?P[^<]*)'), + # } + # info = {} + # with open(c, "r", encoding="utf-8_sig") as f: + # tcx = f.read() + # match_name = pattern["name"].search(tcx) + # if match_name: + # info["name"] = match_name.group("text").strip() + # + # match_distance_meter = pattern["distance_meters"].search(tcx) + # if match_distance_meter: + # info["distance"] = float(match_distance_meter.group("text").strip()) + # return info + + return [ + { + "path": f, + "name": os.path.basename(f), + # **get_course_info(f) + } + for f in dirs + if os.path.isfile(f) and f != self.G_COURSE_FILE_PATH + ] diff --git a/modules/logger/loader_tcx.py b/modules/course.py similarity index 63% rename from modules/logger/loader_tcx.py rename to modules/course.py index 78669342..e935c684 100644 --- a/modules/logger/loader_tcx.py +++ b/modules/course.py @@ -1,15 +1,16 @@ import os -import glob import json -import shutil import re +import shutil from math import factorial -from crdp import rdp +import oyaml import numpy as np +from crdp import rdp from logger import app_logger +from modules.loaders import TcxLoader from modules.utils.timer import Timer, log_timers POLYLINE_DECODER = False @@ -20,8 +21,39 @@ except ImportError: pass +LOADERS = {"tcx": TcxLoader} + + +class CoursePoints: + name = None + type = None + altitude = None + distance = None + latitude = None + longitude = None + notes = None + + def __init__(self): + self.reset() + + @property + def is_set(self): + # https://developer.garmin.com/fit/file-types/course/ + # no field is mandatory, but they will be zeroes/empty anyway so len will not be 0 is coursePoints are set + return bool(len(self.name)) + + def reset(self): + self.name = [] + self.type = [] + self.altitude = [] + self.distance = [] + self.latitude = [] + self.longitude = [] + self.notes = [] -class LoaderTcx: + +# we have mutable attributes but course is supposed to be a singleton anyway +class Course: config = None # for course @@ -31,22 +63,17 @@ class LoaderTcx: latitude = np.array([]) longitude = np.array([]) + course_points = None + + # calculated points_diff = np.array([]) azimuth = np.array([]) slope = np.array([]) slope_smoothing = np.array([]) colored_altitude = np.array([]) + # [start_index, end_index, distance, average_grade, volume(=dist*average), cat] climb_segment = [] - # for course points - point_name = np.array([]) - point_latitude = np.array([]) - point_longitude = np.array([]) - point_type = np.array([]) - point_notes = np.array([]) - point_distance = np.array([]) - point_altitude = np.array([]) - html_remove_pattern = [ re.compile(r"\"), re.compile(r"\<.+?\>"), @@ -55,6 +82,16 @@ class LoaderTcx: def __init__(self, config): super().__init__() self.config = config + self.course_points = CoursePoints() + + def __str__(self): + return f"Course:\n" f"{oyaml.dump(self.info, allow_unicode=True)}\n" + + @property + def is_set(self): + # we keep checking distance as it's how it was done in the original code, + # but we can load tcx file with no distance in it load (it gets populated as np.zeros in load) + return bool(len(self.distance)) def reset(self, delete_course_file=False, replace=False): # for course @@ -70,18 +107,10 @@ def reset(self, delete_course_file=False, replace=False): self.slope = np.array([]) self.slope_smoothing = np.array([]) self.colored_altitude = np.array([]) - self.climb_segment = ( - [] - ) # [start_index, end_index, distance, average_grade, volume(=dist*average), cat] - - # for course points - self.point_name = np.array([]) - self.point_latitude = np.array([]) - self.point_longitude = np.array([]) - self.point_type = np.array([]) - self.point_notes = np.array([]) - self.point_distance = np.array([]) - self.point_altitude = np.array([]) + self.climb_segment = [] + + if self.course_points: + self.course_points.reset() if delete_course_file: if os.path.exists(self.config.G_COURSE_FILE_PATH): @@ -89,19 +118,52 @@ def reset(self, delete_course_file=False, replace=False): if not replace and self.config.G_THINGSBOARD_API["STATUS"]: self.config.network.api.send_livetrack_course_reset() - def load(self): + def load(self, file=None): + # if file is given, copy it to self.config.G_COURSE_FILE_PATH firsthand, we are loading a new course + if file: + _, ext = os.path.splitext(file) + shutil.copy2(file, self.config.G_COURSE_FILE_PATH) + if ext: + os.setxattr( + self.config.G_COURSE_FILE_PATH, "user.ext", ext[1:].encode() + ) + self.reset() timers = [ - Timer(auto_start=False, text="read_tcx : {0:.3f} sec"), + Timer(auto_start=False, text="read_file : {0:.3f} sec"), Timer(auto_start=False, text="downsample : {0:.3f} sec"), Timer(auto_start=False, text="calc_slope_smoothing: {0:.3f} sec"), Timer(auto_start=False, text="modify_course_points: {0:.3f} sec"), ] with timers[0]: - self.read_tcx() - + # get loader based on the extension + if os.path.exists(self.config.G_COURSE_FILE_PATH): + # get file extension in order to find the correct loader + # extension was set in custom attributes as the current course is always + # loaded from '.current' + try: + ext = os.getxattr( + self.config.G_COURSE_FILE_PATH, "user.ext" + ).decode() + if ext in LOADERS: + course_data, course_points_data = LOADERS[ext].load_file( + self.config.G_COURSE_FILE_PATH + ) + if course_data: + for k, v in course_data.items(): + setattr(self, k, v) + if course_points_data: + for k, v in course_points_data.items(): + setattr(self.course_points, k, v) + else: + app_logger.warning(f".{ext} files are not handled") + except (AttributeError, OSError) as e: + app_logger.error( + f"Incorrect course file: {e}. Please reload the course and make sure your file" + f"has a proper extension set" + ) with timers[1]: self.downsample() @@ -111,9 +173,6 @@ def load(self): with timers[3]: self.modify_course_points() - if not len(self.latitude): - return - app_logger.info("[logger] Loading course:") log_timers(timers, text_total="total : {0:.3f} sec") @@ -158,199 +217,19 @@ async def search_route(self, x1, y1, x2, y2): if self.config.G_THINGSBOARD_API["STATUS"]: self.config.network.api.send_livetrack_course_load() - def get_courses(self): - dir_list = sorted( - glob.glob(os.path.join(self.config.G_COURSE_DIR, "*.tcx")), - key=lambda f: os.stat(f).st_mtime, - reverse=True, - ) - file_list = [ - f - for f in dir_list - if os.path.isfile(f) and f != self.config.G_COURSE_FILE_PATH - ] - - courses = [] - for c in file_list: - info = { - "path": c, - "name": os.path.basename(c), - } - # heavy: delayed updates required - # pattern = { - # "name": re.compile(r"(?P[\s\S]*?)"), - # "distance_meters": re.compile( - # r"(?P[\s\S]*?)" - # ), - # # "track": re.compile(r'(?P[\s\S]*?)'), - # # "altitude": re.compile(r'(?P[^<]*)'), - # } - # with open(c, "r", encoding="utf-8_sig") as f: - # tcx = f.read() - # match_name = pattern["name"].search(tcx) - # if match_name: - # info["name"] = match_name.group("text").strip() - # - # match_distance_meter = pattern["distance_meters"].search(tcx) - # if match_distance_meter: - # info["distance"] = float(match_distance_meter.group("text").strip()) - - courses.append(info) - - return courses - def get_ridewithgps_privacycode(self, route_id): privacy_code = None filename = ( self.config.G_RIDEWITHGPS_API["URL_ROUTE_DOWNLOAD_DIR"] + "course-{route_id}.json" ).format(route_id=route_id) + with open(filename, "r") as json_file: json_contents = json.load(json_file) if "privacy_code" in json_contents["route"]: privacy_code = json_contents["route"]["privacy_code"] - return privacy_code - - def set_course(self, file): - shutil.copy(file, self.config.G_COURSE_FILE_PATH) - self.load() - def read_tcx(self): - if not os.path.exists(self.config.G_COURSE_FILE_PATH): - return - app_logger.info(f"loading {self.config.G_COURSE_FILE_PATH}") - - # read with regex - pattern = { - "name": re.compile(r"(?P[\s\S]*?)"), - "distance_meters": re.compile( - r"(?P[\s\S]*?)" - ), - "track": re.compile(r"(?P[\s\S]*?)"), - "latitude": re.compile( - r"(?P[^<]*)" - ), - "longitude": re.compile( - r"(?P[^<]*)" - ), - "altitude": re.compile(r"(?P[^<]*)"), - "distance": re.compile(r"(?P[^<]*)"), - "course_point": re.compile(r"(?P[\s\S]+)"), - "course_name": re.compile(r"(?P[^<]*)"), - "course_point_type": re.compile(r"(?P[^<]*)"), - "course_notes": re.compile(r"(?P[^<]*)"), - } - - with open(self.config.G_COURSE_FILE_PATH, "r", encoding="utf-8_sig") as f: - tcx = f.read() - - match_name = pattern["name"].search(tcx) - if match_name: - self.info["Name"] = match_name.group("text").strip() - - match_distance_meter = pattern["distance_meters"].search(tcx) - if match_distance_meter: - self.info["DistanceMeters"] = round( - float(match_distance_meter.group("text").strip()) / 1000, 1 - ) - - match_track = pattern["track"].search(tcx) - if match_track: - track = match_track.group("text") - self.latitude = np.array( - [ - float(m.group("text").strip()) - for m in pattern["latitude"].finditer(track) - ] - ) - self.longitude = np.array( - [ - float(m.group("text").strip()) - for m in pattern["longitude"].finditer(track) - ] - ) - self.altitude = np.array( - [ - float(m.group("text").strip()) - for m in pattern["altitude"].finditer(track) - ] - ) - self.distance = np.array( - [ - float(m.group("text").strip()) - for m in pattern["distance"].finditer(track) - ] - ) - - match_course = pattern["course_point"].search(tcx) - if match_course: - course_point = match_course.group("text") - self.point_name = [ - m.group("text").strip() - for m in pattern["course_name"].finditer(course_point) - ] - self.point_latitude = np.array( - [ - float(m.group("text").strip()) - for m in pattern["latitude"].finditer(course_point) - ] - ) - self.point_longitude = np.array( - [ - float(m.group("text").strip()) - for m in pattern["longitude"].finditer(course_point) - ] - ) - self.point_type = [ - m.group("text").strip() - for m in pattern["course_point_type"].finditer(course_point) - ] - self.point_notes = [ - m.group("text").strip() - for m in pattern["course_notes"].finditer(course_point) - ] - - check_course = False - if not ( - len(self.latitude) - == len(self.longitude) - == len(self.altitude) - == len(self.distance) - ): - app_logger.error("Can not parse course") - check_course = True - if not ( - len(self.point_name) - == len(self.point_latitude) - == len(self.point_longitude) - == len(self.point_type) - ): - app_logger.error("Can not parse course point") - check_course = True - if check_course: - self.distance = np.array([]) - self.altitude = np.array([]) - self.latitude = np.array([]) - self.longitude = np.array([]) - self.point_name = np.array([]) - self.point_latitude = np.array([]) - self.point_longitude = np.array([]) - self.point_type = np.array([]) - return - - # delete 'Straight' of course points - if len(self.point_type): - ptype = np.array(self.point_type) - not_straight_cond = np.where(ptype != "Straight", True, False) - self.point_type = list(ptype[not_straight_cond]) - if len(self.point_name): - self.point_name = list(np.array(self.point_name)[not_straight_cond]) - if len(self.point_latitude): - self.point_latitude = np.array(self.point_latitude)[not_straight_cond] - if len(self.point_longitude): - self.point_longitude = np.array(self.point_longitude)[not_straight_cond] - if len(self.point_notes): - self.point_notes = list(np.array(self.point_notes)[not_straight_cond]) + return privacy_code async def get_google_route_from_mapstogpx(self, url): json_routes = await self.config.network.api.get_google_route_from_mapstogpx(url) @@ -361,76 +240,79 @@ async def get_google_route_from_mapstogpx(self, url): self.latitude = np.array([p["lat"] for p in json_routes["points"]]) self.longitude = np.array([p["lng"] for p in json_routes["points"]]) - self.point_name = [] - self.point_latitude = [] - self.point_longitude = [] - self.point_distance = [] - self.point_type = [] - self.point_notes = [] + point_name = [] + point_latitude = [] + point_longitude = [] + point_distance = [] + point_type = [] + point_notes = [] - self.point_distance.append(0) + point_distance.append(0) cp = [p for p in json_routes["points"] if len(p) > 2] cp_n = len(cp) - 1 cp_i = -1 + for p in cp: cp_i += 1 # skip if ("step" in p and p["step"] in ["straight", "merge", "keep"]) or ( "step" not in p and cp_i not in [0, cp_n] ): - self.point_distance[-1] = round(p["dist"]["total"] / 1000, 1) + point_distance[-1] = round(p["dist"]["total"] / 1000, 1) continue - self.point_latitude.append(p["lat"]) - self.point_longitude.append(p["lng"]) + point_latitude.append(p["lat"]) + point_longitude.append(p["lng"]) if "dist" in p: dist = round(p["dist"]["total"] / 1000, 1) - self.point_distance.append(dist) + point_distance.append(dist) turn_str = "" + if "step" in p: turn_str = p["step"] if turn_str[-4:] == "left": turn_str = "Left" elif turn_str[-5:] == "right": turn_str = "Right" - self.point_name.append(turn_str) - self.point_type.append(turn_str) + + point_name.append(turn_str) + point_type.append(turn_str) text = "" + if "dir" in p: text = self.remove_html_tag(p["dir"]) - self.point_notes.append(text) - self.point_name[0] = "Start" - self.point_name[-1] = "End" + point_notes.append(text) + + point_name[0] = "Start" + point_name[-1] = "End" # print(self.point_name) # print(self.point_type) # print(self.point_notes) # print(self.point_distance) - self.point_latitude = np.array(self.point_latitude) - self.point_longitude = np.array(self.point_longitude) - self.point_distance = np.array(self.point_distance) + self.course_points.name = point_name + self.course_points.type = point_type + self.course_points.notes = point_notes + self.course_points.latitude = np.array(point_latitude) + self.course_points.longitude = np.array(point_longitude) + self.course_points.distance = np.array(point_distance) check_course = False if not (len(self.latitude) == len(self.longitude)): - print("ERROR parse course") + app_logger.warning("ERROR parse course") check_course = True if check_course: self.latitude = np.array([]) self.longitude = np.array([]) - self.point_name = np.array([]) - self.point_latitude = np.array([]) - self.point_longitude = np.array([]) - self.point_distance = np.array([]) - self.point_type = np.array([]) - self.point_notes = [] + self.course_points.reset() return async def get_google_route(self, x1, y1, x2, y2): @@ -446,12 +328,7 @@ async def get_google_route(self, x1, y1, x2, y2): # points = np.array(polyline.decode(json_routes["routes"][0]["overview_polyline"]["points"])) points_detail = [] - self.point_name = [] - self.point_latitude = [] - self.point_longitude = [] - self.point_distance = [] - self.point_type = [] - self.point_notes = [] + self.course_points.reset() dist = 0 pre_dist = 0 @@ -480,19 +357,21 @@ async def get_google_route(self, x1, y1, x2, y2): turn_str = "Left" elif turn_str[-5:] == "right": turn_str = "Right" - self.point_type.append(turn_str) - self.point_latitude.append(step["start_location"]["lat"]) - self.point_longitude.append(step["start_location"]["lng"]) - self.point_distance.append(dist) - self.point_notes.append(self.remove_html_tag(step["html_instructions"])) - self.point_name.append(turn_str) + self.course_points.type.append(turn_str) + self.course_points.latitude.append(step["start_location"]["lat"]) + self.course_points.longitude.append(step["start_location"]["lng"]) + self.course_points.distance.append(dist) + self.course_points.notes.append( + self.remove_html_tag(step["html_instructions"]) + ) + self.course_points.name.append(turn_str) points_detail = np.array(points_detail) self.latitude = np.array(points_detail)[:, 0] self.longitude = np.array(points_detail)[:, 1] - self.point_latitude = np.array(self.point_latitude) - self.point_longitude = np.array(self.point_longitude) - self.point_distance = np.array(self.point_distance) + self.course_points.latitude = np.array(self.course_points.latitude) + self.course_points.longitude = np.array(self.course_points.longitude) + self.course_points.distance = np.array(self.course_points.distance) def remove_html_tag(self, text): res = text.replace(" ", "") @@ -580,6 +459,7 @@ def downsample(self): app_logger.info(f"downsampling:{len_lat} -> {len(self.latitude)}") + # make route colors by slope for MapWidget, CourseProfileWidget def calc_slope_smoothing(self): # parameters course_n = len(self.distance) @@ -738,26 +618,26 @@ def calc_slope_smoothing(self): self.colored_altitude = np.array(self.config.G_SLOPE_COLOR)[slope_smoothing_cat] def modify_course_points(self): - # make route colors by slope for MapWidget, CourseProfileWidget + course_points = self.course_points - len_pnt_lat = len(self.point_latitude) - len_pnt_dist = len(self.point_distance) - len_pnt_alt = len(self.point_altitude) + len_pnt_lat = len(course_points.latitude) + len_pnt_dist = len(course_points.distance) + len_pnt_alt = len(course_points.altitude) len_dist = len(self.distance) len_alt = len(self.altitude) # calculate course point distance if not len_pnt_dist and len_dist: - self.point_distance = np.empty(len_pnt_lat) + course_points.distance = np.empty(len_pnt_lat) if not len_pnt_alt and len_alt: - self.point_altitude = np.zeros(len_pnt_lat) + course_points.altitude = np.zeros(len_pnt_lat) min_index = 0 for i in range(len_pnt_lat): b_a_x = self.points_diff[0][min_index:] b_a_y = self.points_diff[1][min_index:] - lon_diff = self.point_longitude[i] - self.longitude[min_index:] - lat_diff = self.point_latitude[i] - self.latitude[min_index:] + lon_diff = course_points.longitude[i] - self.longitude[min_index:] + lat_diff = course_points.latitude[i] - self.latitude[min_index:] p_a_x = lon_diff[:-1] p_a_y = lat_diff[:-1] inner_p = (b_a_x * p_a_x + b_a_y * p_a_y) / self.points_diff_sum_of_squares[ @@ -784,7 +664,10 @@ def modify_course_points(self): * inner_p[j] ) dist_diff_h = self.config.get_dist_on_earth( - h_lon, h_lat, self.point_longitude[i], self.point_latitude[i] + h_lon, + h_lat, + course_points.longitude[i], + course_points.latitude[i], ) if ( @@ -822,47 +705,57 @@ def modify_course_points(self): min_index = min_index + min_j if not len_pnt_dist and len_dist: - self.point_distance[i] = self.distance[min_index] + min_dist_delta + course_points.distance[i] = self.distance[min_index] + min_dist_delta if not len_pnt_alt and len_alt: - self.point_altitude[i] = self.altitude[min_index] + min_alt_delta + course_points.altitude[i] = self.altitude[min_index] + min_alt_delta # add climb tops # if len(self.climb_segment): # min_index = 0 # for i in range(len(self.climb_segment)): - # diff_dist = np.abs(self.point_distance - self.climb_segment[i]['course_point_distance']) + # diff_dist = np.abs(course_points.distance - self.climb_segment[i]['course_point_distance']) # min_index = np.where(diff_dist == np.min(diff_dist))[0][0]+1 - # self.point_name.insert(min_index, "Top of Climb") - # self.point_latitude = np.insert(self.point_latitude, min_index, self.climb_segment[i]['course_point_latitude']) - # self.point_longitude = np.insert(self.point_longitude, min_index, self.climb_segment[i]['course_point_longitude']) - # self.point_type.insert(min_index, "Summit") - # self.point_distance = np.insert(self.point_distance, min_index, self.climb_segment[i]['course_point_distance']) - # self.point_altitude = np.insert(self.point_altitude, min_index, self.climb_segment[i]['course_point_altitude']) + # course_points.name.insert(min_index, "Top of Climb") + # course_points.latitude = np.insert(course_points._latitude, min_index, self.climb_segment[i]['course_point_latitude']) + # course_points.longitude = np.insert(course_points.longitude, min_index, self.climb_segment[i]['course_point_longitude']) + # course_points.type.insert(min_index, "Summit") + # course_points.distance = np.insert(course_points.distance, min_index, self.climb_segment[i]['course_point_distance']) + # course_points.altitude = np.insert(course_points.altitude, min_index, self.climb_segment[i]['course_point_altitude']) - len_pnt_dist = len(self.point_distance) - len_pnt_alt = len(self.point_altitude) + len_pnt_dist = len(course_points.distance) + len_pnt_alt = len(course_points.latitude) # add start course point - if len_pnt_lat and len_pnt_dist and len_dist and self.point_distance[0] != 0.0: - self.point_name.insert(0, "Start") - self.point_latitude = np.insert(self.point_latitude, 0, self.latitude[0]) - self.point_longitude = np.insert(self.point_longitude, 0, self.longitude[0]) - self.point_type.insert(0, "") + if ( + len_pnt_lat + and len_pnt_dist + and len_dist + # TODO do not use float + and course_points.distance[0] != 0.0 + ): + course_points.name.insert(0, "Start") + course_points.latitude = np.insert( + course_points.latitude, 0, self.latitude[0] + ) + course_points.longitude = np.insert( + course_points.longitude, 0, self.longitude[0] + ) + course_points.type.insert(0, "") + course_points.notes.insert(0, "") if len_pnt_dist and len_dist: - self.point_distance = np.insert(self.point_distance, 0, 0.0) + course_points.distance = np.insert(course_points.distance, 0, 0.0) if len_pnt_alt and len_alt: - self.point_altitude = np.insert( - self.point_altitude, 0, self.altitude[0] + course_points.altitude = np.insert( + course_points.altitude, 0, self.altitude[0] ) # add end course point - # print(self.point_latitude, self.latitude, self.point_longitude, self.longitude) end_distance = None - if len(self.latitude) and len(self.point_longitude): + if len(self.latitude) and len(course_points.longitude): end_distance = self.config.get_dist_on_earth_array( self.longitude[-1], self.latitude[-1], - self.point_longitude[-1], - self.point_latitude[-1], + course_points.longitude[-1], + course_points.latitude[-1], ) if ( len_pnt_lat @@ -871,18 +764,26 @@ def modify_course_points(self): and end_distance is not None and end_distance > 5 ): - self.point_name.append("End") - self.point_latitude = np.append(self.point_latitude, self.latitude[-1]) - self.point_longitude = np.append(self.point_longitude, self.longitude[-1]) - self.point_type.append("") + course_points.name.append("End") + course_points.latitude = np.append( + course_points.latitude, self.latitude[-1] + ) + course_points.longitude = np.append( + course_points.longitude, self.longitude[-1] + ) + course_points.type.append("") + course_points.notes.append("") if len_pnt_dist and len_dist: - self.point_distance = np.append(self.point_distance, self.distance[-1]) + course_points.distance = np.append( + course_points.distance, self.distance[-1] + ) if len_pnt_alt and len_alt: - self.point_altitude = np.append(self.point_altitude, self.altitude[-1]) + course_points.altitude = np.append( + course_points.altitude, self.altitude[-1] + ) - self.point_name = np.array(self.point_name) - self.point_type = np.array(self.point_type) - self.point_name = np.array(self.point_name) + course_points.name = np.array(course_points.name) + course_points.type = np.array(course_points.type) @staticmethod def savitzky_golay(y, window_size, order, deriv=0, rate=1): diff --git a/modules/gui_pyqt.py b/modules/gui_pyqt.py index a95855fb..308b5433 100644 --- a/modules/gui_pyqt.py +++ b/modules/gui_pyqt.py @@ -392,7 +392,7 @@ def delay_init(self): self.main_page.addWidget(self.map_widget) elif ( k == "CUESHEET" - and len(self.config.logger.course.point_name) + and self.config.logger.course.course_points.is_set and self.config.G_COURSE_INDEXING and self.config.G_CUESHEET_DISPLAY_NUM ): diff --git a/modules/loaders/__init__.py b/modules/loaders/__init__.py new file mode 100644 index 00000000..67a1d1af --- /dev/null +++ b/modules/loaders/__init__.py @@ -0,0 +1 @@ +from .tcx import TcxLoader diff --git a/modules/loaders/tcx.py b/modules/loaders/tcx.py new file mode 100644 index 00000000..2ded2bbc --- /dev/null +++ b/modules/loaders/tcx.py @@ -0,0 +1,168 @@ +import os +import re +from collections import defaultdict + +import numpy as np + +from logger import app_logger + +POLYLINE_DECODER = False +try: + import polyline + + POLYLINE_DECODER = True +except ImportError: + pass + +patterns = { + "name": re.compile(r"(?P[\s\S]*?)"), + "distance_meters": re.compile( + r"(?P[\s\S]*?)" + ), + "track": re.compile(r"(?P[\s\S]*?)"), + "latitude": re.compile(r"(?P[^<]*)"), + "longitude": re.compile(r"(?P[^<]*)"), + "altitude": re.compile(r"(?P[^<]*)"), + "distance": re.compile(r"(?P[^<]*)"), + "course_point": re.compile(r"(?P[\s\S]+)"), + "course_name": re.compile(r"(?P[^<]*)"), + "course_point_type": re.compile(r"(?P[^<]*)"), + "course_notes": re.compile(r"(?P[^<]*)"), +} + + +class TcxLoader: + config = None + + @classmethod + def load_file(cls, file): + if not os.path.exists(file): + return None, None + app_logger.info(f"[{cls.__name__}]: loading {file}") + + # should just return a Course object + course = { + "info": {}, + "latitude": None, + "longitude": None, + "altitude": None, + "distance": None, + } + course_points = defaultdict(list) + + with open(file, "r", encoding="utf-8_sig") as f: + tcx = f.read() + + match_name = patterns["name"].search(tcx) + if match_name: + course["info"]["Name"] = match_name.group("text").strip() + + match_distance_meter = patterns["distance_meters"].search(tcx) + if match_distance_meter: + course["info"]["DistanceMeters"] = round( + float(match_distance_meter.group("text").strip()) / 1000, 1 + ) + + match_track = patterns["track"].search(tcx) + if match_track: + track = match_track.group("text") + course["latitude"] = np.array( + [ + float(m.group("text").strip()) + for m in patterns["latitude"].finditer(track) + ] + ) + course["longitude"] = np.array( + [ + float(m.group("text").strip()) + for m in patterns["longitude"].finditer(track) + ] + ) + course["altitude"] = np.array( + [ + float(m.group("text").strip()) + for m in patterns["altitude"].finditer(track) + ] + ) + course["distance"] = np.array( + [ + float(m.group("text").strip()) + for m in patterns["distance"].finditer(track) + ] + ) + + match_course_point = patterns["course_point"].search(tcx) + + if match_course_point: + course_point = match_course_point.group("text") + course_points["name"] = [ + m.group("text").strip() + for m in patterns["course_name"].finditer(course_point) + ] + course_points["latitude"] = np.array( + [ + float(m.group("text").strip()) + for m in patterns["latitude"].finditer(course_point) + ] + ) + course_points["longitude"] = np.array( + [ + float(m.group("text").strip()) + for m in patterns["longitude"].finditer(course_point) + ] + ) + course_points["type"] = [ + m.group("text").strip() + for m in patterns["course_point_type"].finditer(course_point) + ] + course_points["notes"] = [ + m.group("text").strip() + for m in patterns["course_notes"].finditer(course_point) + ] + + valid_course = True + if len(course["latitude"]) != len(course["longitude"]): + app_logger.error("Could not parse course") + valid_course = False + if not ( + len(course["latitude"]) + == len(course["altitude"]) + == len(course["distance"]) + ): + app_logger.warning( + f"Course has missing data: points {len(course['latitude'])} altitude {len(course['altitude'])} " + f"distance {len(course['distance'])}" + ) + if not ( + len(course_points["name"]) + == len(course_points["latitude"]) + == len(course_points["longitude"]) + == len(course_points["type"]) + ): + app_logger.error("Could not parse course points") + valid_course = False + + if not valid_course: + course["distance"] = np.array([]) + course["altitude"] = np.array([]) + course["latitude"] = np.array([]) + course["longitude"] = np.array([]) + course_points = defaultdict(list) + else: + # delete 'Straight' from course points + if len(course_points["type"]): + ptype = np.array(course_points["type"]) + not_straight_cond = np.where(ptype != "Straight", True, False) + course_points["type"] = list(ptype[not_straight_cond]) + + for key in ["name", "latitude", "longitude", "notes"]: + if len(course_points[key]): + # TODO, probably not necessary but kept so logic is 1-1 + # we should avoid to mix data types here (or using typings) + course_points[key] = np.array(course_points[key])[ + not_straight_cond + ] + if key in ["name", "notes"]: + course_points[key] = list(course_points[key]) + + return course, course_points diff --git a/modules/logger_core.py b/modules/logger_core.py index 73d6a4df..de3ebfc0 100644 --- a/modules/logger_core.py +++ b/modules/logger_core.py @@ -98,13 +98,13 @@ def start_coroutine(self): traceback.print_exc() def delay_init(self): - from . import sensor_core - from .logger import loader_tcx + from .course import Course from .logger import logger_csv from .logger import logger_fit + from . import sensor_core self.sensor = sensor_core.SensorCore(self.config) - self.course = loader_tcx.LoaderTcx(self.config) + self.course = Course(self.config) self.logger_csv = logger_csv.LoggerCsv(self.config) self.logger_fit = logger_fit.LoggerFit(self.config) @@ -477,7 +477,7 @@ def reset_course(self, delete_course_file=False, replace=False): self.sensor.sensor_gps.reset_course_index() def set_new_course(self, course_file): - self.course.set_course(course_file) + self.course.load(course_file) async def record_log(self): # need to detect location delta for smart recording diff --git a/modules/pyqt/graph/pyqt_base_map.py b/modules/pyqt/graph/pyqt_base_map.py index adf476fa..c1175afb 100644 --- a/modules/pyqt/graph/pyqt_base_map.py +++ b/modules/pyqt/graph/pyqt_base_map.py @@ -175,7 +175,7 @@ async def zoom_minus(self): await self.update_extra() def get_max_zoom(self): - if not len(self.config.logger.course.distance): + if not self.config.logger.course.is_set: return if self.config.G_MAX_ZOOM != 0: @@ -190,7 +190,6 @@ def get_max_zoom(self): while z / 1000 > dist: z /= 2 self.config.G_MAX_ZOOM = z - # print("MAX_ZOOM", self.config.G_MAX_ZOOM, dist) def load_course(self): pass diff --git a/modules/pyqt/graph/pyqt_course_profile.py b/modules/pyqt/graph/pyqt_course_profile.py index 0382f33b..fbd080ae 100644 --- a/modules/pyqt/graph/pyqt_course_profile.py +++ b/modules/pyqt/graph/pyqt_course_profile.py @@ -49,7 +49,7 @@ def setup_ui_extra(self): # load course profile and display def load_course(self): - if not len(self.config.logger.course.distance) or not len( + if not self.config.logger.course.is_set or not len( self.config.logger.course.altitude ): return @@ -126,7 +126,7 @@ def init_course(self): self.resizeEvent(None) async def update_extra(self): - if not len(self.config.logger.course.distance) or not len( + if not self.config.logger.course.is_set or not len( self.config.logger.course.altitude ): return diff --git a/modules/pyqt/graph/pyqt_map.py b/modules/pyqt/graph/pyqt_map.py index 78bd9d22..5a604aed 100644 --- a/modules/pyqt/graph/pyqt_map.py +++ b/modules/pyqt/graph/pyqt_map.py @@ -230,7 +230,7 @@ def reset_map(self): def init_cuesheet_and_instruction(self): # init cuesheet_widget if ( - len(self.config.logger.course.point_name) + self.config.logger.course.course_points.is_set and self.config.G_CUESHEET_DISPLAY_NUM and self.config.G_COURSE_INDEXING ): @@ -252,7 +252,7 @@ def init_cuesheet_and_instruction(self): def resizeEvent(self, event): if ( - not len(self.config.logger.course.point_name) + not self.config.logger.course.course_points.is_set or not self.config.G_CUESHEET_DISPLAY_NUM or not self.config.G_COURSE_INDEXING ): @@ -286,86 +286,90 @@ def switch_lock(self): super().switch_lock() def load_course(self): - if not len(self.config.logger.course.latitude): - return - timers = [ Timer(auto_start=False, text="course plot : {0:.3f} sec"), Timer(auto_start=False, text="course points: {0:.3f} sec"), ] + course = self.config.logger.course with timers[0]: if self.course_plot is not None: self.plot.removeItem(self.course_plot) - self.course_plot = CoursePlotItem( - x=self.config.logger.course.longitude, - y=self.get_mod_lat_np(self.config.logger.course.latitude), - brushes=self.config.logger.course.colored_altitude, - width=6, - ) - self.course_plot.setZValue(20) - self.plot.addItem(self.course_plot) - - # test - if not self.config.G_IS_RASPI: - if self.plot_verification is not None: - self.plot.removeItem(self.plot_verification) - self.plot_verification = pg.ScatterPlotItem(pxMode=True) - self.plot_verification.setZValue(25) - test_points = [] - for i in range(len(self.config.logger.course.longitude)): - p = { - "pos": [ - self.config.logger.course.longitude[i], - self.get_mod_lat(self.config.logger.course.latitude[i]), - ], - "size": 2, - "pen": {"color": "w", "width": 1}, - "brush": pg.mkBrush(color=(255, 0, 0)), - } - test_points.append(p) - self.plot_verification.setData(test_points) - self.plot.addItem(self.plot_verification) + if not len(course.latitude): + app_logger.warning("Course has no points") + else: + self.course_plot = CoursePlotItem( + x=course.longitude, + y=self.get_mod_lat_np(course.latitude), + brushes=course.colored_altitude, + width=6, + ) + self.course_plot.setZValue(20) + self.plot.addItem(self.course_plot) + + # test + if not self.config.G_IS_RASPI: + if self.plot_verification is not None: + self.plot.removeItem(self.plot_verification) + self.plot_verification = pg.ScatterPlotItem(pxMode=True) + self.plot_verification.setZValue(25) + test_points = [] + for i in range(len(course.longitude)): + p = { + "pos": [ + course.longitude[i], + self.get_mod_lat(course.latitude[i]), + ], + "size": 2, + "pen": {"color": "w", "width": 1}, + "brush": pg.mkBrush(color=(255, 0, 0)), + } + test_points.append(p) + self.plot_verification.setData(test_points) + self.plot.addItem(self.plot_verification) with timers[1]: - # course point - if not len(self.config.logger.course.point_longitude): - app_logger.warning("Point_longitude is empty") - return + # course points + course_points = course.course_points if self.course_points_plot is not None: self.plot.removeItem(self.course_points_plot) - self.course_points_plot = pg.ScatterPlotItem( - pxMode=True, symbol="t", size=12 - ) - self.course_points_plot.setZValue(40) - self.course_points = [] - - for i in reversed(range(len(self.config.logger.course.point_longitude))): - color = (255, 0, 0) - symbol = "t" - if self.config.logger.course.point_type[i] == "Left": - symbol = "t3" - elif self.config.logger.course.point_type[i] == "Right": - symbol = "t2" - cp = { - "pos": [ - self.config.logger.course.point_longitude[i], - self.get_mod_lat(self.config.logger.course.point_latitude[i]), - ], - "pen": {"color": color, "width": 1}, - "symbol": symbol, - "brush": pg.mkBrush(color=color), - } - self.course_points.append(cp) - self.course_points_plot.setData(self.course_points) - self.plot.addItem(self.course_points_plot) + + if not len(course_points.longitude): + app_logger.warning("No course points found") + + else: + self.course_points_plot = pg.ScatterPlotItem( + pxMode=True, symbol="t", size=12 + ) + self.course_points_plot.setZValue(40) + formatted_course_points = [] + + for i in reversed(range(len(course_points.longitude))): + color = (255, 0, 0) + symbol = "t" + if course_points.type[i] == "Left": + symbol = "t3" + elif course_points.type[i] == "Right": + symbol = "t2" + cp = { + "pos": [ + course_points.longitude[i], + self.get_mod_lat(course_points.latitude[i]), + ], + "pen": {"color": color, "width": 1}, + "symbol": symbol, + "brush": pg.mkBrush(color=color), + } + formatted_course_points.append(cp) + self.course_points_plot.setData(formatted_course_points) + self.plot.addItem(self.course_points_plot) app_logger.info("Plotting course:") log_timers(timers, text_total=f"total : {0:.3f} sec") async def update_extra(self): - # t = datetime.datetime.utcnow() + course = self.config.logger.course # display current position if len(self.location): @@ -383,12 +387,10 @@ async def update_extra(self): # recent point(from log or pre_point) / course start / dummy if len(self.tracks_lon) and len(self.tracks_lat): self.point["pos"] = [self.tracks_lon_pos, self.tracks_lat_pos] - elif len(self.config.logger.course.longitude) and len( - self.config.logger.course.latitude - ): + elif len(course.longitude) and len(course.latitude): self.point["pos"] = [ - self.config.logger.course.longitude[0], - self.config.logger.course.latitude[0], + course.longitude[0], + course.latitude[0], ] else: self.point["pos"] = [ @@ -417,7 +419,7 @@ async def update_extra(self): x_move = y_move = 0 if ( self.lock_status - and len(self.config.logger.course.distance) + and len(course.distance) and self.gps_values["on_course_status"] ): index = self.gps_sensor.get_index_with_distance_cutoff( @@ -425,8 +427,8 @@ async def update_extra(self): # get some forward distance [m] self.get_width_distance(self.map_pos["y"], self.map_area["w"]) / 1000, ) - x2 = self.config.logger.course.longitude[index] - y2 = self.config.logger.course.latitude[index] + x2 = course.longitude[index] + y2 = course.latitude[index] x_delta = x2 - self.map_pos["x"] y_delta = y2 - self.map_pos["y"] # slide from center @@ -488,14 +490,12 @@ async def update_extra(self): self.center_point.setData(self.center_point_location) self.plot.addItem(self.center_point) - # print("\tpyqt_graph : update_extra init : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") - # t = datetime.datetime.utcnow() - # set x and y ranges x_start = self.map_pos["x"] - self.map_area["w"] / 2 x_end = x_start + self.map_area["w"] y_start = self.map_pos["y"] - self.map_area["h"] / 2 y_end = y_start + self.map_area["h"] + if not np.isnan(x_start) and not np.isnan(x_end): self.plot.setXRange(x_start, x_end, padding=0) if not np.isnan(y_start) and not np.isnan(y_end): @@ -505,9 +505,8 @@ async def update_extra(self): if not np.any(np.isnan([x_start, x_end, y_start, y_end])): await self.draw_map_tile(x_start, x_end, y_start, y_end) - # print("\tpyqt_graph : update_extra map : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") - # t = datetime.datetime.utcnow() + # TODO shouldn't be there but does not plot if removed ! if not self.course_loaded: self.load_course() self.course_loaded = True @@ -516,21 +515,14 @@ async def update_extra(self): x_start, x_end, y_start, y_end, auto_zoom=True ) - # print("\tpyqt_graph : update_extra cuesheet : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") - # t = datetime.datetime.utcnow() - # draw track self.get_track() self.track_plot.setData(self.tracks_lon, self.tracks_lat) - # print("\tpyqt_graph : update_extra track : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") - # t = datetime.datetime.utcnow() # draw scale self.draw_scale(x_start, y_start) # draw map attribution self.draw_map_attribution(x_start, y_start) - # print("\tpyqt_graph : update_extra draw map : ", (datetime.datetime.utcnow()-t).total_seconds(), "sec") - # t = datetime.datetime.utcnow() def get_track(self): # get track from SQL @@ -998,7 +990,7 @@ async def update_cuesheet_and_instruction( self, x_start, x_end, y_start, y_end, auto_zoom=False ): if ( - not len(self.config.logger.course.point_name) + not self.config.logger.course.course_points.is_set or not self.config.G_CUESHEET_DISPLAY_NUM or not self.config.G_COURSE_INDEXING ): diff --git a/modules/pyqt/menu/pyqt_course_menu_widget.py b/modules/pyqt/menu/pyqt_course_menu_widget.py index 5d742125..92d6d6d6 100644 --- a/modules/pyqt/menu/pyqt_course_menu_widget.py +++ b/modules/pyqt/menu/pyqt_course_menu_widget.py @@ -71,7 +71,7 @@ def google_directions_api_setting_menu(self): self.change_page("Google Directions API mode", preprocess=True) def onoff_course_cancel_button(self): - status = bool(len(self.config.logger.course.distance)) + status = self.config.logger.course.is_set self.buttons["Cancel Course"].onoff_button(status) def cancel_course(self, replace=False): @@ -214,7 +214,7 @@ def preprocess_extra(self): self.page_name_label.setText(self.list_type) async def list_local_courses(self): - courses = self.config.logger.course.get_courses() + courses = self.config.get_courses() for c in courses: course_item = CourseListItemWidget(self, self.list_type, c) self.add_list_item(course_item) @@ -238,7 +238,7 @@ def set_course(self, course_file=None): self.course_file = course_file # exist course: cancel and set new course - if len(self.config.logger.course.distance): + if self.config.logger.course.is_set: self.config.gui.show_dialog( self.cancel_and_set_new_course, "Replace this course?" ) diff --git a/modules/pyqt/pyqt_cuesheet_widget.py b/modules/pyqt/pyqt_cuesheet_widget.py index 6f11a8d5..6ba447e4 100644 --- a/modules/pyqt/pyqt_cuesheet_widget.py +++ b/modules/pyqt/pyqt_cuesheet_widget.py @@ -135,26 +135,24 @@ def resizeEvent(self, event): @qasync.asyncSlot() async def update_display(self): - if ( - not len(self.config.logger.course.point_distance) - or not self.config.G_CUESHEET_DISPLAY_NUM - ): + course_points = self.config.logger.course.course_points + if not course_points.is_set or not self.config.G_CUESHEET_DISPLAY_NUM: return cp_i = self.gps_values["course_point_index"] # cuesheet for i, cuesheet_item in enumerate(self.cuesheet): - if cp_i + i > len(self.config.logger.course.point_distance) - 1: + if cp_i + i > len(course_points.distance) - 1: cuesheet_item.reset() continue dist = cuesheet_item.dist_num = ( - self.config.logger.course.point_distance[cp_i + i] * 1000 + course_points.distance[cp_i + i] * 1000 - self.gps_values["course_distance"] ) if dist < 0: continue dist_text = f"{dist / 1000:4.1f}km " if dist > 1000 else f"{dist:6.0f}m " cuesheet_item.dist.setText(dist_text) - name_text = self.config.logger.course.point_type[cp_i + i] + name_text = course_points.type[cp_i + i] cuesheet_item.name.setText(name_text) diff --git a/modules/sensor/sensor_gps.py b/modules/sensor/sensor_gps.py index c58adcb6..ce04895d 100644 --- a/modules/sensor/sensor_gps.py +++ b/modules/sensor/sensor_gps.py @@ -642,6 +642,7 @@ def set_time(self): return True def get_course_index(self): + course = self.config.logger.course if not self.config.G_COURSE_INDEXING: self.values["on_course_status"] = False return @@ -660,7 +661,7 @@ def get_course_index(self): if self.config.G_IS_RASPI and self.config.G_STOPWATCH_STATUS != "START": return - course_n = len(self.config.logger.course.longitude) + course_n = len(course.longitude) if course_n == 0: return @@ -676,23 +677,19 @@ def get_course_index(self): start, -self.config.G_GPS_SEARCH_RANGE ) - b_a_x = self.config.logger.course.points_diff[0] - b_a_y = self.config.logger.course.points_diff[1] - lon_diff = self.values["lon"] - self.config.logger.course.longitude - lat_diff = self.values["lat"] - self.config.logger.course.latitude + b_a_x = course.points_diff[0] + b_a_y = course.points_diff[1] + lon_diff = self.values["lon"] - course.longitude + lat_diff = self.values["lat"] - course.latitude p_a_x = lon_diff[0:-1] p_a_y = lat_diff[0:-1] p_b_x = lon_diff[1:] p_b_y = lat_diff[1:] - inner_p = ( - b_a_x * p_a_x + b_a_y * p_a_y - ) / self.config.logger.course.points_diff_sum_of_squares + inner_p = (b_a_x * p_a_x + b_a_y * p_a_y) / course.points_diff_sum_of_squares - azimuth_diff = np.full(len(self.config.logger.course.azimuth), np.nan) + azimuth_diff = np.full(len(course.azimuth), np.nan) if not np.isnan(self.values["track"]): - azimuth_diff = ( - self.values["track"] - self.config.logger.course.azimuth - ) % 360 + azimuth_diff = (self.values["track"] - course.azimuth) % 360 dist_diff = np.where( inner_p <= 0.0, @@ -700,8 +697,7 @@ def get_course_index(self): np.where( inner_p >= 1.0, np.sqrt(p_b_x**2 + p_b_y**2), - np.abs(b_a_x * p_a_y - b_a_y * p_a_x) - / self.config.logger.course.points_diff_dist, + np.abs(b_a_x * p_a_y - b_a_y * p_a_x) / course.points_diff_dist, ), ) @@ -749,7 +745,7 @@ def get_course_index(self): # check azimuth # print("i:{}, s:{}, m:{}, azimuth_diff:{}".format(i, s, m, azimuth_diff[m])) # print("self.values['track']:{}, m:{}".format(self.values['track'], m)) - # print(self.config.logger.course.azimuth) + # print(course.azimuth) # print("azimuth_diff:{}".format(azimuth_diff)) if np.isnan(azimuth_diff[m]): # GPS is lost(return start finally) @@ -763,26 +759,25 @@ def get_course_index(self): else: # go backward # print("self.values['track']:{}, m:{}".format(self.values['track'], m)) - # print(self.config.logger.course.azimuth) + # print(course.azimuth) # print("azimuth_diff:{}".format(azimuth_diff)) continue # print("i:{}, s:{}, m:{}, azimuth_diff:{}, course_index:{}, course_point_index:{}".format(i, s, m, azimuth_diff[m], self.values['course_index'], self.values['course_point_index'])) # print("\t lat_lon: {}, {}".format(self.values['lat'], self.values['lon'])) - # print("\t course: {}, {}".format(self.config.logger.course.latitude[self.values['course_index']], self.config.logger.course.longitude[self.values['course_index']])) - # print("\t course_point: {}, {}".format(self.config.logger.course.point_latitude[self.values['course_point_index']], self.config.logger.course.point_longitude[self.values['course_point_index']])) + # print("\t course: {}, {}".format(course.latitude[self.values['course_index']], course.longitude[self.values['course_index']])) + # print("\t course_point: {}, {}".format(course.course_points.latitude[self.values['course_point_index']], course.course_points.longitude[self.values['course_point_index']])) # grade check if available grade = self.config.logger.sensor.values["integrated"]["grade"] if not np.isnan(grade) and (grade > self.config.G_SLOPE_CUTOFF[0]) != ( - self.config.logger.course.slope_smoothing[m] - > self.config.G_SLOPE_CUTOFF[0] + course.slope_smoothing[m] > self.config.G_SLOPE_CUTOFF[0] ): continue if m == 0 and inner_p[0] <= 0.0: app_logger.info(f"before start of course: {start} -> {m}") app_logger.info( - f"\t {self.values['lat']} {self.values['lon']} / {self.config.logger.course.latitude[m]} {self.config.logger.course.longitude[m]}" + f"\t {self.values['lat']} {self.values['lon']} / {course.latitude[m]} {course.longitude[m]}" ) self.values["on_course_status"] = False self.values["course_distance"] = 0 @@ -792,32 +787,22 @@ def get_course_index(self): elif m == len(dist_diff) - 1 and inner_p[-1] >= 1.0: app_logger.info(f"after end of course {start} -> {m}") app_logger.info( - f"\t {self.values['lat']} {self.values['lon']} / {self.config.logger.course.latitude[m]} {self.config.logger.course.longitude[m]}", + f"\t {self.values['lat']} {self.values['lon']} / {course.latitude[m]} {course.longitude[m]}", ) self.values["on_course_status"] = False m = course_n - 1 - self.values["course_distance"] = ( - self.config.logger.course.distance[-1] * 1000 - ) + self.values["course_distance"] = course.distance[-1] * 1000 self.values["course_altitude"] = np.nan self.values["course_index"] = m return h_lon = ( - self.config.logger.course.longitude[m] - + ( - self.config.logger.course.longitude[m + 1] - - self.config.logger.course.longitude[m] - ) - * inner_p[m] + course.longitude[m] + + (course.longitude[m + 1] - course.longitude[m]) * inner_p[m] ) h_lat = ( - self.config.logger.course.latitude[m] - + ( - self.config.logger.course.latitude[m + 1] - - self.config.logger.course.latitude[m] - ) - * inner_p[m] + course.latitude[m] + + (course.latitude[m + 1] - course.latitude[m]) * inner_p[m] ) dist_diff_h = self.config.get_dist_on_earth( h_lon, h_lat, self.values["lon"], self.values["lat"] @@ -827,8 +812,8 @@ def get_course_index(self): # print("dist_diff_h: {:.0f} > cutoff {}[m]".format(dist_diff_h, self.config.G_GPS_ON_ROUTE_CUTOFF)) # print("\t i:{}, s:{}, m:{}, azimuth_diff:{}, course_point_index:{}".format(i, s, m, azimuth_diff[m], self.values['course_point_index'])) # print("\t h_lon/h_lat: {}, {}, lat_lon: {}, {}".format(h_lat, h_lon, self.values['lat'], self.values['lon'])) - # print("\t course[m]: {}, {}".format(self.config.logger.course.latitude[m], self.config.logger.course.longitude[m])) - # print("\t course[m+1]: {}, {}".format(self.config.logger.course.latitude[m+1], self.config.logger.course.longitude[m+1])) + # print("\t course[m]: {}, {}".format(course.latitude[m], course.longitude[m])) + # print("\t course[m+1]: {}, {}".format(course.latitude[m+1], course.longitude[m+1])) continue # stay forward while self.config.G_GPS_KEEP_ON_COURSE_CUTOFF if search_indexes is except forward @@ -849,59 +834,48 @@ def get_course_index(self): self.values["on_course_status"] = True dist_diff_course = self.config.get_dist_on_earth( - self.config.logger.course.longitude[m], - self.config.logger.course.latitude[m], + course.longitude[m], + course.latitude[m], self.values["lon"], self.values["lat"], ) self.values["course_distance"] = ( - self.config.logger.course.distance[m] * 1000 + dist_diff_course + course.distance[m] * 1000 + dist_diff_course ) - if len(self.config.logger.course.altitude): + if len(course.altitude): alt_diff_course = 0 - if m + 1 < len(self.config.logger.course.altitude): + if m + 1 < len(course.altitude): alt_diff_course = ( - ( - self.config.logger.course.altitude[m + 1] - - self.config.logger.course.altitude[m] - ) - / ( - ( - self.config.logger.course.distance[m + 1] - - self.config.logger.course.distance[m] - ) - * 1000 - ) + (course.altitude[m + 1] - course.altitude[m]) + / ((course.distance[m + 1] - course.distance[m]) * 1000) * dist_diff_course ) - self.values["course_altitude"] = ( - self.config.logger.course.altitude[m] + alt_diff_course - ) + self.values["course_altitude"] = course.altitude[m] + alt_diff_course # print("search: ", (datetime.datetime.utcnow()-t).total_seconds(), "sec, index:", m) self.values["course_index"] = m - if len(self.config.logger.course.point_distance): + if len(course.course_points.distance): cp_m = np.abs( - self.config.logger.course.point_distance + course.course_points.distance - self.values["course_distance"] / 1000 ).argmin() # specify next points for displaying in cuesheet widget if ( - self.config.logger.course.point_distance[cp_m] + course.course_points.distance[cp_m] < self.values["course_distance"] / 1000 ): cp_m += 1 - if cp_m >= len(self.config.logger.course.point_distance): - cp_m = len(self.config.logger.course.point_distance) - 1 + if cp_m >= len(course.course_points.distance): + cp_m = len(course.course_points.distance) - 1 self.values["course_point_index"] = cp_m if i >= penalty_index: app_logger.info(f"{s_state[i]} {start} -> {m}") app_logger.info( - f"\t {self.values['lat']} {self.values['lon']} / {self.config.logger.course.latitude[m]} {self.config.logger.course.longitude[m]}" + f"\t {self.values['lat']} {self.values['lon']} / {course.latitude[m]} {course.longitude[m]}" ) app_logger.info(f"\t azimuth_diff: {azimuth_diff[m]}") @@ -910,7 +884,7 @@ def get_course_index(self): self.values["on_course_status"] = False def get_index_with_distance_cutoff(self, start, search_range): - if self.config.logger is None or not len(self.config.logger.course.distance): + if self.config.logger is None or not self.config.logger.course.is_set: return 0 dist_to = self.config.logger.course.distance[start] + search_range diff --git a/tests/data/tcx/Mt_Angel_Abbey.tcx b/tests/data/tcx/Mt_Angel_Abbey.tcx new file mode 100644 index 00000000..6b542165 --- /dev/null +++ b/tests/data/tcx/Mt_Angel_Abbey.tcx @@ -0,0 +1,8986 @@ + + + + + + + Mt-Angel-Abbey + + + + + + + Mt-Angel-Abbey + + 946 + 67455.5 + + 44.93903 + -123.02959 + + + 44.93903 + -123.02959 + + Active + + + + + + 44.93903 + -123.02959 + + 51.1 + 0 + + + + + 44.939047 + -123.029581 + + 51.1 + 2.056 + + + + + 44.93948 + -123.02936 + + 51.1 + 53.271 + + + + + 44.93987 + -123.02916 + + 51.1 + 99.457 + + + + + 44.93996 + -123.02912 + + 51.1 + 109.96 + + + + + 44.94005 + -123.02907 + + 51.1 + 120.726 + + + + + 44.94113 + -123.02854 + + 51.2 + 247.997 + + + + + 44.94103 + -123.02813 + + 51.4 + 282.167 + + + + + 44.94084 + -123.02737 + + 51.7 + 345.677 + + + + + 44.94074 + -123.02699 + + 51.9 + 377.622 + + + + + 44.94065 + -123.02665 + + 52.1 + 406.224 + + + + + 44.940654 + -123.026648 + + 52.1 + 406.696 + + + + + 44.94061 + -123.02647 + + 52.2 + 421.553 + + + + + 44.94054 + -123.02619 + + 52.3 + 444.951 + + + + + 44.94037 + -123.02554 + + 52.7 + 499.553 + + + + + 44.94034 + -123.02541 + + 52.7 + 510.327 + + + + + 44.94032 + -123.02532 + + 52.8 + 517.76 + + + + + 44.94028 + -123.02518 + + 52.8 + 529.656 + + + + + 44.94014 + -123.02464 + + 53.0 + 574.971 + + + + + 44.94009 + -123.02445 + + 53.1 + 590.943 + + + + + 44.94001 + -123.02433 + + 53.1 + 603.932 + + + + + 44.93994 + -123.02406 + + 53.2 + 626.589 + + + + + 44.93977 + -123.02342 + + 53.5 + 680.453 + + + + + 44.93968 + -123.02305 + + 53.4 + 711.282 + + + + + 44.93957 + -123.02263 + + 53.3 + 746.569 + + + + + 44.9394599 + -123.02219 + + 51.5 + 783.339 + + + + + 44.9393699 + -123.02182 + + 53.8 + 814.167 + + + + + 44.93924 + -123.02132 + + 54.0 + 856.14 + + + + + 44.93919 + -123.02111 + + 54.0 + 873.598 + + + + + 44.93917 + -123.02102 + + 54.1 + 881.031 + + + + + 44.93916 + -123.02097 + + 54.1 + 885.125 + + + + + 44.93916 + -123.02094 + + 54.1 + 887.489 + + + + + 44.93918 + -123.02086 + + 54.1 + 894.175 + + + + + 44.93918 + -123.02081 + + 54.1 + 898.115 + + + + + 44.93918 + -123.02075 + + 54.1 + 902.843 + + + + + 44.93919 + -123.0206 + + 54.1 + 914.715 + + + + + 44.93922 + -123.02026 + + 54.1 + 941.713 + + + + + 44.93925 + -123.01985 + + 54.0 + 974.192 + + + + + 44.93927 + -123.01951 + + 54.1 + 1001.076 + + + + + 44.93924 + -123.01943 + + 54.2 + 1008.21 + + + + + 44.93929 + -123.01883 + + 54.5 + 1055.815 + + + + + 44.93934 + -123.01811 + + 55.1 + 1112.822 + + + + + 44.93941 + -123.01808 + + 54.9 + 1120.965 + + + + + 44.93959 + -123.018 + + 54.3 + 1141.97 + + + + + 44.93983 + -123.0179 + + 53.6 + 1169.824 + + + + + 44.94017 + -123.01776 + + 53.2 + 1209.248 + + + + + 44.94029 + -123.01771 + + 53.0 + 1223.175 + + + + + 44.94057 + -123.01759 + + 52.6 + 1255.747 + + + + + 44.94085 + -123.01748 + + 52.2 + 1288.099 + + + + + 44.94104 + -123.01739 + + 51.9 + 1310.407 + + + + + 44.94115 + -123.01733 + + 51.8 + 1323.533 + + + + + 44.94217 + -123.01681 + + 51.5 + 1444.244 + + + + + 44.9432 + -123.0163 + + 52.6 + 1565.741 + + + + + 44.94381 + -123.01599 + + 53.5 + 1637.904 + + + + + 44.94412 + -123.01583 + + 53.9 + 1674.644 + + + + + 44.9443399 + -123.01572 + + 54.1 + 1700.622 + + + + + 44.94449 + -123.01565 + + 54.2 + 1718.208 + + + + + 44.94512 + -123.01532 + + 54.5 + 1793.003 + + + + + 44.94609 + -123.01484 + + 54.6 + 1907.414 + + + + + 44.94802 + -123.01386 + + 54.7 + 2135.712 + + + + + 44.9482799 + -123.01373 + + 54.6 + 2166.414 + + + + + 44.94873 + -123.0135 + + 54.7 + 2219.684 + + + + + 44.94915 + -123.0133 + + 54.6 + 2269.021 + + + + + 44.9498 + -123.01296 + + 54.6 + 2346.178 + + + + + 44.9505 + -123.0126 + + 54.6 + 2429.102 + + + + + 44.95121 + -123.01225 + + 54.5 + 2512.81 + + + + + 44.95146 + -123.01212 + + 54.5 + 2542.464 + + + + + 44.95192 + -123.01188 + + 54.4 + 2597.05 + + + + + 44.95262 + -123.01152 + + 54.2 + 2679.974 + + + + + 44.95331 + -123.01118 + + 54.0 + 2761.32 + + + + + 44.9536 + -123.01103 + + 53.9 + 2795.697 + + + + + 44.95377 + -123.01094 + + 53.9 + 2815.906 + + + + + 44.95386 + -123.0109 + + 53.9 + 2826.409 + + + + + 44.95396 + -123.01087 + + 53.8 + 2837.789 + + + + + 44.95405 + -123.01086 + + 53.8 + 2847.838 + + + + + 44.95414 + -123.01085 + + 53.8 + 2857.888 + + + + + 44.9546599 + -123.01083 + + 53.5 + 2915.795 + + + + + 44.95521 + -123.0108 + + 53.3 + 2977.066 + + + + + 44.9553299 + -123.01076 + + 53.2 + 2990.791 + + + + + 44.95534 + -123.01076 + + 53.2 + 2991.904 + + + + + 44.95536 + -123.01076 + + 53.2 + 2994.131 + + + + + 44.95541 + -123.01075 + + 53.2 + 2999.752 + + + + + 44.95571 + -123.01075 + + 53.0 + 3033.148 + + + + + 44.95626 + -123.01072 + + 52.8 + 3094.419 + + + + + 44.95622 + -123.00994 + + 53.2 + 3156.024 + + + + + 44.95622 + -123.00955 + + 53.2 + 3186.746 + + + + + 44.95621 + -123.0092 + + 53.3 + 3214.339 + + + + + 44.95621 + -123.00905 + + 53.3 + 3226.156 + + + + + 44.9562 + -123.00897 + + 53.4 + 3232.555 + + + + + 44.95619 + -123.00882 + + 53.4 + 3244.424 + + + + + 44.95618 + -123.00857 + + 53.4 + 3264.149 + + + + + 44.95614 + -123.00837 + + 53.5 + 3280.521 + + + + + 44.95612 + -123.00821 + + 53.6 + 3293.32 + + + + + 44.95608 + -123.00804 + + 53.7 + 3307.432 + + + + + 44.956 + -123.00775 + + 53.8 + 3331.951 + + + + + 44.95596 + -123.00764 + + 53.9 + 3341.694 + + + + + 44.95592 + -123.00754 + + 54.0 + 3350.743 + + + + + 44.95584 + -123.00734 + + 54.1 + 3368.84 + + + + + 44.9558 + -123.00723 + + 54.2 + 3378.583 + + + + + 44.95574 + -123.00713 + + 54.2 + 3388.911 + + + + + 44.95565 + -123.00696 + + 54.4 + 3405.635 + + + + + 44.95554 + -123.00678 + + 54.5 + 3424.37 + + + + + 44.9554199 + -123.00658 + + 54.7 + 3445.026 + + + + + 44.95522 + -123.00626 + + 54.9 + 3478.658 + + + + + 44.95493 + -123.00579 + + 55.3 + 3527.781 + + + + + 44.9546599 + -123.00535 + + 55.6 + 3573.659 + + + + + 44.95437 + -123.0049 + + 56.1 + 3621.605 + + + + + 44.95421 + -123.00463 + + 56.4 + 3649.347 + + + + + 44.95415 + -123.0045 + + 56.5 + 3661.574 + + + + + 44.95409 + -123.00436 + + 56.7 + 3674.467 + + + + + 44.95404 + -123.00422 + + 56.8 + 3686.821 + + + + + 44.9540099 + -123.00409 + + 56.9 + 3697.593 + + + + + 44.95398 + -123.00391 + + 57.2 + 3712.161 + + + + + 44.95396 + -123.0037 + + 57.4 + 3728.853 + + + + + 44.95396 + -123.00338 + + 57.9 + 3754.062 + + + + + 44.95396 + -123.0031 + + 58.3 + 3776.12 + + + + + 44.95395 + -123.00225 + + 59.3 + 3843.09 + + + + + 44.95394 + -123.00079 + + 60.5 + 3958.11 + + + + + 44.95393 + -122.99961 + + 60.8 + 4051.075 + + + + + 44.95393 + -122.99904 + + 60.8 + 4095.978 + + + + + 44.95393 + -122.9986 + + 60.9 + 4130.64 + + + + + 44.95393 + -122.9977 + + 61.3 + 4201.54 + + + + + 44.9539199 + -122.99634 + + 62.1 + 4308.683 + + + + + 44.9539199 + -122.9952 + + 62.3 + 4398.49 + + + + + 44.9539199 + -122.99465 + + 62.5 + 4441.817 + + + + + 44.9539199 + -122.99388 + + 62.3 + 4502.476 + + + + + 44.95391 + -122.99363 + + 62.0 + 4522.202 + + + + + 44.95391 + -122.99306 + + 61.9 + 4567.105 + + + + + 44.95391 + -122.99266 + + 62.0 + 4598.616 + + + + + 44.9539199 + -122.99201 + + 61.7 + 4649.834 + + + + + 44.95388 + -122.99184 + + 61.7 + 4663.947 + + + + + 44.95388 + -122.99172 + + 61.7 + 4673.4 + + + + + 44.95387 + -122.99147 + + 61.9 + 4693.126 + + + + + 44.95387 + -122.99106 + + 61.9 + 4725.425 + + + + + 44.95388 + -122.99074 + + 61.9 + 4750.658 + + + + + 44.95389 + -122.98928 + + 62.7 + 4865.679 + + + + + 44.95389 + -122.98879 + + 64.2 + 4904.28 + + + + + 44.95389 + -122.98862 + + 64.2 + 4917.672 + + + + + 44.95389 + -122.98861 + + 64.2 + 4918.46 + + + + + 44.95389 + -122.98858 + + 64.2 + 4920.823 + + + + + 44.95391 + -122.98854 + + 64.2 + 4924.681 + + + + + 44.95391 + -122.98834 + + 64.0 + 4940.437 + + + + + 44.95391 + -122.98816 + + 63.9 + 4954.617 + + + + + 44.95391 + -122.98761 + + 63.8 + 4997.945 + + + + + 44.95391 + -122.98746 + + 63.7 + 5009.761 + + + + + 44.95391 + -122.98678 + + 63.5 + 5063.33 + + + + + 44.95391 + -122.98615 + + 63.4 + 5112.96 + + + + + 44.95391 + -122.98586 + + 63.3 + 5135.805 + + + + + 44.95391 + -122.98502 + + 63.2 + 5201.979 + + + + + 44.95391 + -122.98495 + + 63.2 + 5207.493 + + + + + 44.95391 + -122.98478 + + 63.2 + 5220.885 + + + + + 44.9539 + -122.9844 + + 63.2 + 5250.841 + + + + + 44.95391 + -122.9837 + + 63.0 + 5305.997 + + + + + 44.9539199 + -122.98316 + + 62.9 + 5348.551 + + + + + 44.9539199 + -122.98265 + + 62.8 + 5388.728 + + + + + 44.9539199 + -122.9825 + + 62.8 + 5400.545 + + + + + 44.9539199 + -122.98245 + + 62.8 + 5404.484 + + + + + 44.9539199 + -122.98182 + + 62.7 + 5454.113 + + + + + 44.9539199 + -122.98142 + + 62.7 + 5485.624 + + + + + 44.9539199 + -122.98105 + + 62.8 + 5514.772 + + + + + 44.9539199 + -122.98019 + + 63.0 + 5582.521 + + + + + 44.9539199 + -122.97966 + + 63.3 + 5624.273 + + + + + 44.9539199 + -122.97939 + + 63.5 + 5645.543 + + + + + 44.9539199 + -122.97905 + + 63.9 + 5672.327 + + + + + 44.95393 + -122.97853 + + 64.1 + 5713.307 + + + + + 44.95393 + -122.97753 + + 64.5 + 5792.084 + + + + + 44.95393 + -122.97696 + + 64.6 + 5836.988 + + + + + 44.9539199 + -122.9741 + + 63.9 + 6062.294 + + + + + 44.95394 + -122.97346 + + 63.5 + 6112.761 + + + + + 44.95397 + -122.97322 + + 63.4 + 6131.96 + + + + + 44.954 + -122.973 + + 63.2 + 6149.61 + + + + + 44.95405 + -122.97277 + + 63.0 + 6168.565 + + + + + 44.9541899 + -122.97204 + + 62.5 + 6228.146 + + + + + 44.95432 + -122.97133 + + 62.2 + 6285.92 + + + + + 44.95442 + -122.9708 + + 62.0 + 6329.13 + + + + + 44.95464 + -122.96962 + + 61.5 + 6425.259 + + + + + 44.95493 + -122.968 + + 61.8 + 6556.896 + + + + + 44.9549499 + -122.96774 + + 62.0 + 6577.499 + + + + + 44.95496 + -122.96755 + + 62.0 + 6592.508 + + + + + 44.95496 + -122.9673 + + 62.0 + 6612.202 + + + + + 44.9549499 + -122.96644 + + 61.7 + 6679.958 + + + + + 44.9549499 + -122.96547 + + 61.9 + 6756.371 + + + + + 44.95494 + -122.96414 + + 62.4 + 6861.149 + + + + + 44.95494 + -122.96271 + + 62.4 + 6973.799 + + + + + 44.95494 + -122.96238 + + 62.5 + 6999.796 + + + + + 44.95492 + -122.96087 + + 62.9 + 7118.768 + + + + + 44.95491 + -122.9596 + + 63.1 + 7218.82 + + + + + 44.9549 + -122.95895 + + 63.1 + 7270.037 + + + + + 44.9549 + -122.95885 + + 63.1 + 7277.915 + + + + + 44.95595 + -122.95911 + + 63.0 + 7396.58 + + + + + 44.95624 + -122.95921 + + 62.9 + 7429.81 + + + + + 44.95652 + -122.95929 + + 62.8 + 7461.61 + + + + + 44.95681 + -122.95937 + + 62.7 + 7494.502 + + + + + 44.95697 + -122.95945 + + 62.7 + 7513.395 + + + + + 44.95737 + -122.95955 + + 62.5 + 7558.614 + + + + + 44.96064 + -122.96042 + + 59.8 + 7929.021 + + + + + 44.96107 + -122.96052 + + 59.1 + 7977.532 + + + + + 44.96131 + -122.96057 + + 58.8 + 8004.537 + + + + + 44.96146 + -122.9606 + + 58.8 + 8021.402 + + + + + 44.96156 + -122.96061 + + 58.7 + 8032.561 + + + + + 44.9617 + -122.96062 + + 58.7 + 8048.166 + + + + + 44.96181 + -122.96062 + + 58.7 + 8060.411 + + + + + 44.96197 + -122.96062 + + 58.8 + 8078.222 + + + + + 44.96213 + -122.96061 + + 59.4 + 8096.05 + + + + + 44.96224 + -122.9606 + + 59.9 + 8108.321 + + + + + 44.96234 + -122.96058 + + 60.3 + 8119.563 + + + + + 44.96249 + -122.96056 + + 61.2 + 8136.335 + + + + + 44.96262 + -122.96053 + + 61.9 + 8150.999 + + + + + 44.9628299 + -122.96049 + + 62.6 + 8174.587 + + + + + 44.96303 + -122.96044 + + 63.1 + 8197.196 + + + + + 44.96446 + -122.96011 + + 61.5 + 8358.49 + + + + + 44.96737 + -122.95942 + + 59.0 + 8686.955 + + + + + 44.96813 + -122.95924 + + 56.8 + 8772.737 + + + + + 44.9685599 + -122.95915 + + 59.7 + 8821.126 + + + + + 44.96937 + -122.95896 + + 58.9 + 8912.528 + + + + + 44.96966 + -122.95888 + + 57.4 + 8945.419 + + + + + 44.96996 + -122.95881 + + 56.4 + 8979.267 + + + + + 44.97002 + -122.95876 + + 56.0 + 8987.02 + + + + + 44.97023 + -122.95871 + + 55.8 + 9010.726 + + + + + 44.9704 + -122.95868 + + 55.9 + 9029.798 + + + + + 44.97049 + -122.95867 + + 56.0 + 9039.847 + + + + + 44.97058 + -122.95867 + + 56.2 + 9049.866 + + + + + 44.97063 + -122.95866 + + 56.4 + 9055.487 + + + + + 44.97068 + -122.95867 + + 56.5 + 9061.109 + + + + + 44.97077 + -122.95867 + + 56.5 + 9071.127 + + + + + 44.97079 + -122.95868 + + 56.5 + 9073.489 + + + + + 44.97085 + -122.9587 + + 56.5 + 9080.351 + + + + + 44.97089 + -122.95871 + + 56.5 + 9084.873 + + + + + 44.97093 + -122.95873 + + 56.4 + 9089.596 + + + + + 44.97097 + -122.95875 + + 56.5 + 9094.319 + + + + + 44.97121 + -122.95891 + + 56.6 + 9123.858 + + + + + 44.9714 + -122.95906 + + 56.5 + 9148.084 + + + + + 44.9715 + -122.95913 + + 56.3 + 9160.506 + + + + + 44.97163 + -122.9592 + + 56.0 + 9175.992 + + + + + 44.9717 + -122.95923 + + 56.0 + 9184.135 + + + + + 44.97177 + -122.95925 + + 55.9 + 9192.085 + + + + + 44.97186 + -122.95926 + + 55.9 + 9202.134 + + + + + 44.97195 + -122.95926 + + 55.8 + 9212.153 + + + + + 44.97202 + -122.95924 + + 55.8 + 9220.103 + + + + + 44.97208 + -122.95923 + + 55.8 + 9226.828 + + + + + 44.97214 + -122.95921 + + 55.9 + 9233.691 + + + + + 44.97218 + -122.95919 + + 55.9 + 9238.414 + + + + + 44.97225 + -122.95917 + + 55.8 + 9246.364 + + + + + 44.97235 + -122.95911 + + 55.9 + 9258.457 + + + + + 44.9732 + -122.95853 + + 55.8 + 9363.526 + + + + + 44.97356 + -122.95829 + + 55.8 + 9407.834 + + + + + 44.9737 + -122.9582 + + 55.9 + 9424.954 + + + + + 44.97381 + -122.95813 + + 56.0 + 9438.383 + + + + + 44.9739099 + -122.95808 + + 56.0 + 9450.191 + + + + + 44.97407 + -122.95801 + + 56.0 + 9468.835 + + + + + 44.97415 + -122.95798 + + 56.1 + 9478.049 + + + + + 44.97426 + -122.95794 + + 56.1 + 9490.693 + + + + + 44.97436 + -122.95791 + + 56.0 + 9502.072 + + + + + 44.97446 + -122.95788 + + 55.7 + 9513.452 + + + + + 44.97459 + -122.95785 + + 55.2 + 9528.115 + + + + + 44.97489 + -122.95777 + + 54.9 + 9562.1 + + + + + 44.9769099 + -122.95729 + + 55.2 + 9790.119 + + + + + 44.98233 + -122.95602 + + 57.4 + 10401.698 + + + + + 44.98307 + -122.95585 + + 56.3 + 10485.155 + + + + + 44.98364 + -122.95572 + + 55.8 + 10549.427 + + + + + 44.98684 + -122.95497 + + 56.3 + 10910.508 + + + + + 44.9875 + -122.9548 + + 53.6 + 10985.188 + + + + + 44.99097 + -122.95394 + + 58.7 + 11377.353 + + + + + 44.99236 + -122.95362 + + 58.5 + 11534.124 + + + + + 44.99429 + -122.95318 + + 57.9 + 11751.744 + + + + + 44.99458 + -122.95312 + + 57.8 + 11784.37 + + + + + 44.99499 + -122.95303 + + 57.7 + 11830.557 + + + + + 44.99516 + -122.95299 + + 57.7 + 11849.742 + + + + + 44.99531 + -122.95295 + + 57.7 + 11866.734 + + + + + 44.99542 + -122.95292 + + 57.5 + 11879.204 + + + + + 44.99555 + -122.95287 + + 57.5 + 11894.202 + + + + + 44.99564 + -122.95283 + + 57.5 + 11904.703 + + + + + 44.99579 + -122.95275 + + 57.5 + 11922.549 + + + + + 44.99592 + -122.95269 + + 57.5 + 11937.772 + + + + + 44.9960499 + -122.95261 + + 57.4 + 11953.554 + + + + + 44.99623 + -122.95249 + + 57.3 + 11975.707 + + + + + 44.99646 + -122.95231 + + 57.2 + 12004.97 + + + + + 44.99685 + -122.95198 + + 57.1 + 12055.562 + + + + + 44.99705 + -122.95181 + + 57.1 + 12081.538 + + + + + 44.9978399 + -122.95117 + + 57.0 + 12182.889 + + + + + 44.99861 + -122.95054 + + 57.1 + 12281.916 + + + + + 44.99906 + -122.95016 + + 57.1 + 12340.261 + + + + + 44.99993 + -122.94944 + + 57.6 + 12452.472 + + + + + 45.00011 + -122.94929 + + 56.9 + 12475.73 + + + + + 45.00053 + -122.94898 + + 57.1 + 12528.468 + + + + + 45.00072 + -122.94887 + + 57.0 + 12551.323 + + + + + 45.00082 + -122.94881 + + 57.0 + 12563.415 + + + + + 45.00095 + -122.94876 + + 57.0 + 12578.412 + + + + + 45.0012 + -122.94864 + + 57.0 + 12607.801 + + + + + 45.00132 + -122.94861 + + 57.1 + 12621.366 + + + + + 45.00155 + -122.94856 + + 57.0 + 12647.27 + + + + + 45.00176 + -122.94852 + + 57.1 + 12670.858 + + + + + 45.00183 + -122.94851 + + 57.1 + 12678.69 + + + + + 45.0019 + -122.9485 + + 57.0 + 12686.522 + + + + + 45.00207 + -122.94849 + + 57.0 + 12705.463 + + + + + 45.0022 + -122.94849 + + 57.0 + 12719.934 + + + + + 45.00237 + -122.94848 + + 57.0 + 12738.875 + + + + + 45.00273 + -122.94847 + + 56.9 + 12778.957 + + + + + 45.00325 + -122.94847 + + 57.0 + 12836.843 + + + + + 45.00379 + -122.94846 + + 57.0 + 12896.961 + + + + + 45.00426 + -122.94846 + + 57.0 + 12949.28 + + + + + 45.00454 + -122.94847 + + 57.2 + 12980.46 + + + + + 45.00458 + -122.94848 + + 57.3 + 12984.981 + + + + + 45.0047 + -122.94851 + + 57.3 + 12998.547 + + + + + 45.00479 + -122.94835 + + 57.3 + 13014.639 + + + + + 45.00489 + -122.94818 + + 57.2 + 13032.045 + + + + + 45.00506 + -122.94785 + + 57.2 + 13064.181 + + + + + 45.00541 + -122.94713 + + 57.0 + 13132.952 + + + + + 45.00569 + -122.94653 + + 56.7 + 13189.535 + + + + + 45.00598 + -122.94586 + + 56.0 + 13251.365 + + + + + 45.0063699 + -122.94487 + + 56.9 + 13340.562 + + + + + 45.00648 + -122.94462 + + 56.8 + 13363.737 + + + + + 45.00757 + -122.94189 + + 56.9 + 13610.495 + + + + + 45.00871 + -122.9391 + + 57.3 + 13864.11 + + + + + 45.00897 + -122.93842 + + 57.3 + 13924.952 + + + + + 45.00904 + -122.93825 + + 57.2 + 13940.435 + + + + + 45.00907 + -122.93814 + + 57.3 + 13949.714 + + + + + 45.00912 + -122.93799 + + 57.2 + 13962.766 + + + + + 45.00916 + -122.9378 + + 57.0 + 13978.368 + + + + + 45.0092 + -122.93761 + + 57.3 + 13993.97 + + + + + 45.00923 + -122.93741 + + 57.0 + 14010.061 + + + + + 45.0092799 + -122.93696 + + 57.0 + 14045.911 + + + + + 45.00962 + -122.93389 + + 57.2 + 14290.471 + + + + + 45.009661 + -122.933553 + + 57.4 + 14317.383 + + + + + 45.00965 + -122.93356 + + 57.4 + 14318.726 + + + + + 45.0097 + -122.93313 + + 57.2 + 14353.022 + + + + + 45.00979 + -122.93297 + + 57.2 + 14369.113 + + + + + 45.0098399 + -122.93292 + + 57.2 + 14375.93 + + + + + 45.00999 + -122.93281 + + 57.2 + 14394.738 + + + + + 45.01008 + -122.9328 + + 57.1 + 14404.788 + + + + + 45.01066 + -122.9328 + + 56.4 + 14469.353 + + + + + 45.010661 + -122.93279 + + 56.4 + 14470.148 + + + + + 45.01254 + -122.93279 + + 55.5 + 14679.316 + + + + + 45.01327 + -122.9328 + + 55.1 + 14760.582 + + + + + 45.0142499 + -122.93282 + + 54.3 + 14869.686 + + + + + 45.0147 + -122.9328 + + 53.8 + 14919.805 + + + + + 45.01549 + -122.93283 + + 51.0 + 15007.778 + + + + + 45.01673 + -122.93283 + + 56.3 + 15145.813 + + + + + 45.01681 + -122.93283 + + 56.5 + 15154.719 + + + + + 45.02219 + -122.9329 + + 50.2 + 15753.64 + + + + + 45.0237399 + -122.9329 + + 49.0 + 15926.184 + + + + + 45.0244 + -122.93289 + + 48.5 + 15999.659 + + + + + 45.0248599 + -122.93289 + + 48.1 + 16050.865 + + + + + 45.02656 + -122.93289 + + 46.5 + 16240.107 + + + + + 45.02709 + -122.93287 + + 46.4 + 16299.127 + + + + + 45.02726 + -122.93276 + + 46.4 + 16319.936 + + + + + 45.02743 + -122.93257 + + 46.4 + 16344.053 + + + + + 45.0275 + -122.93237 + + 46.3 + 16361.612 + + + + + 45.02754 + -122.9321 + + 46.3 + 16383.316 + + + + + 45.02752 + -122.92855 + + 47.6 + 16662.626 + + + + + 45.02757 + -122.92833 + + 47.7 + 16680.808 + + + + + 45.02762 + -122.92813 + + 47.9 + 16697.499 + + + + + 45.0277699 + -122.92798 + + 47.8 + 16717.946 + + + + + 45.02802 + -122.92781 + + 47.8 + 16748.823 + + + + + 45.02886 + -122.92782 + + 47.2 + 16842.334 + + + + + 45.03069 + -122.92782 + + 46.5 + 17046.047 + + + + + 45.03069 + -122.9278 + + 46.4 + 17047.621 + + + + + 45.03071 + -122.92778 + + 46.4 + 17050.347 + + + + + 45.03075 + -122.92777 + + 46.5 + 17054.869 + + + + + 45.03087 + -122.92772 + + 46.5 + 17068.794 + + + + + 45.03091 + -122.92769 + + 46.6 + 17073.834 + + + + + 45.03096 + -122.92765 + + 46.6 + 17080.228 + + + + + 45.03099 + -122.9276 + + 46.6 + 17085.388 + + + + + 45.03105 + -122.92752 + + 46.6 + 17094.565 + + + + + 45.03109 + -122.92743 + + 46.6 + 17102.929 + + + + + 45.03114 + -122.9273 + + 46.6 + 17114.573 + + + + + 45.0311499 + -122.92721 + + 46.5 + 17121.74 + + + + + 45.03117 + -122.92713 + + 46.4 + 17128.416 + + + + + 45.03118 + -122.92712 + + 46.4 + 17129.779 + + + + + 45.03119 + -122.92711 + + 46.4 + 17131.143 + + + + + 45.03119 + -122.92321 + + 48.2 + 17437.961 + + + + + 45.03119 + -122.92268 + + 48.1 + 17479.657 + + + + + 45.03119 + -122.91679 + + 52.6 + 17943.032 + + + + + 45.03117 + -122.91311 + + 53.3 + 18232.551 + + + + + 45.03119 + -122.9131 + + 53.1 + 18234.913 + + + + + 45.03121 + -122.91308 + + 53.0 + 18237.639 + + + + + 45.03126 + -122.91294 + + 52.9 + 18249.98 + + + + + 45.03137 + -122.91279 + + 52.2 + 18266.985 + + + + + 45.03145 + -122.91272 + + 51.8 + 18277.456 + + + + + 45.03152 + -122.91267 + + 51.4 + 18286.185 + + + + + 45.0316 + -122.91264 + + 51.0 + 18295.398 + + + + + 45.03165 + -122.91263 + + 51.1 + 18301.019 + + + + + 45.03198 + -122.9126 + + 51.3 + 18337.83 + + + + + 45.03298 + -122.91257 + + 53.4 + 18449.174 + + + + + 45.03413 + -122.91258 + + 50.9 + 18577.193 + + + + + 45.03538 + -122.91257 + + 48.0 + 18716.344 + + + + + 45.0379099 + -122.9126 + + 44.8 + 18997.99 + + + + + 45.0379 + -122.91258 + + 44.7 + 18999.918 + + + + + 45.03792 + -122.91256 + + 44.8 + 19002.644 + + + + + 45.03812 + -122.91248 + + 44.9 + 19025.78 + + + + + 45.03819 + -122.91241 + + 44.9 + 19035.321 + + + + + 45.03829 + -122.91227 + + 45.1 + 19050.98 + + + + + 45.03832 + -122.91221 + + 45.2 + 19056.762 + + + + + 45.03836 + -122.91211 + + 45.4 + 19065.801 + + + + + 45.03839 + -122.91203 + + 45.3 + 19072.925 + + + + + 45.0384 + -122.91192 + + 45.3 + 19081.649 + + + + + 45.0384 + -122.91188 + + 45.3 + 19084.795 + + + + + 45.03841 + -122.91186 + + 45.3 + 19086.723 + + + + + 45.03843 + -122.91186 + + 45.3 + 19088.949 + + + + + 45.03843 + -122.91154 + + 45.4 + 19114.121 + + + + + 45.03843 + -122.91015 + + 46.2 + 19223.46 + + + + + 45.03843 + -122.90909 + + 46.3 + 19306.841 + + + + + 45.03843 + -122.90865 + + 46.3 + 19341.452 + + + + + 45.03845 + -122.90835 + + 45.8 + 19365.156 + + + + + 45.0384699 + -122.9082 + + 45.8 + 19377.163 + + + + + 45.03855 + -122.90801 + + 45.5 + 19394.561 + + + + + 45.0386 + -122.90789 + + 45.3 + 19405.519 + + + + + 45.03868 + -122.90778 + + 45.2 + 19417.936 + + + + + 45.03874 + -122.90772 + + 45.2 + 19426.114 + + + + + 45.03887 + -122.90762 + + 45.1 + 19442.585 + + + + + 45.03892 + -122.90759 + + 45.2 + 19448.631 + + + + + 45.03902 + -122.90756 + + 44.6 + 19460.01 + + + + + 45.03908 + -122.90754 + + 44.5 + 19466.872 + + + + + 45.03931 + -122.90752 + + 45.0 + 19492.524 + + + + + 45.04141 + -122.90755 + + 46.1 + 19726.305 + + + + + 45.04159 + -122.90756 + + 46.2 + 19746.358 + + + + + 45.04166 + -122.90749 + + 46.1 + 19755.899 + + + + + 45.04174 + -122.90742 + + 46.1 + 19766.369 + + + + + 45.04184 + -122.90733 + + 46.2 + 19779.562 + + + + + 45.04189 + -122.90723 + + 46.1 + 19789.197 + + + + + 45.04195 + -122.90712 + + 46.4 + 19800.128 + + + + + 45.04197 + -122.90706 + + 46.3 + 19805.346 + + + + + 45.04198 + -122.90699 + + 46.3 + 19810.963 + + + + + 45.04199 + -122.90695 + + 46.3 + 19814.301 + + + + + 45.04201 + -122.90694 + + 46.3 + 19816.662 + + + + + 45.0420299 + -122.90252 + + 48.0 + 20164.331 + + + + + 45.04204 + -122.90132 + + 49.0 + 20258.725 + + + + + 45.04204 + -122.90119 + + 49.1 + 20268.951 + + + + + 45.04205 + -122.90103 + + 48.8 + 20281.585 + + + + + 45.0421 + -122.90084 + + 48.7 + 20297.533 + + + + + 45.04212 + -122.90076 + + 48.7 + 20304.207 + + + + + 45.04218 + -122.9006 + + 47.6 + 20318.455 + + + + + 45.04221 + -122.90052 + + 47.4 + 20325.579 + + + + + 45.04332 + -122.89867 + + 40.9 + 20516.477 + + + + + 45.04475 + -122.89638 + + 40.4 + 20756.856 + + + + + 45.04519 + -122.89562 + + 40.6 + 20834.136 + + + + + 45.04531 + -122.8954 + + 39.8 + 20855.996 + + + + + 45.04543 + -122.89512 + + 38.7 + 20881.753 + + + + + 45.04566 + -122.89443 + + 43.1 + 20941.76 + + + + + 45.04578 + -122.89362 + + 42.4 + 21006.853 + + + + + 45.0457899 + -122.89306 + + 43.3 + 21050.911 + + + + + 45.0457899 + -122.89213 + + 43.7 + 21124.057 + + + + + 45.04577 + -122.88888 + + 50.2 + 21379.684 + + + + + 45.04577 + -122.88769 + + 51.8 + 21473.279 + + + + + 45.04578 + -122.88714 + + 52.3 + 21516.552 + + + + + 45.04582 + -122.88673 + + 52.4 + 21549.105 + + + + + 45.04589 + -122.88586 + + 53.2 + 21617.974 + + + + + 45.04607 + -122.88381 + + 53.5 + 21780.449 + + + + + 45.04618 + -122.88244 + + 50.6 + 21888.894 + + + + + 45.04618 + -122.88223 + + 49.4 + 21905.411 + + + + + 45.04618 + -122.88201 + + 48.4 + 21922.714 + + + + + 45.04614 + -122.88174 + + 47.3 + 21944.411 + + + + + 45.04608 + -122.88145 + + 46.1 + 21968.178 + + + + + 45.04595 + -122.88089 + + 44.3 + 22014.539 + + + + + 45.04584 + -122.88046 + + 47.1 + 22050.508 + + + + + 45.0457899 + -122.88017 + + 50.8 + 22073.986 + + + + + 45.0457899 + -122.87979 + + 53.6 + 22103.873 + + + + + 45.04582 + -122.87927 + + 54.8 + 22144.908 + + + + + 45.04595 + -122.87763 + + 57.2 + 22274.705 + + + + + 45.046 + -122.87689 + + 57.3 + 22333.173 + + + + + 45.04601 + -122.8765 + + 57.6 + 22363.867 + + + + + 45.04602 + -122.87613 + + 57.9 + 22392.989 + + + + + 45.04601 + -122.87566 + + 57.9 + 22429.972 + + + + + 45.04601 + -122.87497 + + 57.9 + 22484.241 + + + + + 45.046 + -122.86814 + + 58.6 + 23021.429 + + + + + 45.04601 + -122.86717 + + 58.3 + 23097.729 + + + + + 45.04607 + -122.86702 + + 58.3 + 23111.286 + + + + + 45.04613 + -122.86691 + + 58.1 + 23122.216 + + + + + 45.04618 + -122.86682 + + 58.2 + 23131.221 + + + + + 45.04622 + -122.86676 + + 58.1 + 23137.709 + + + + + 45.04627 + -122.86671 + + 58.2 + 23144.524 + + + + + 45.04633 + -122.86666 + + 58.4 + 23152.275 + + + + + 45.04636 + -122.86663 + + 58.4 + 23156.364 + + + + + 45.04641 + -122.8666 + + 58.5 + 23162.409 + + + + + 45.04646 + -122.86658 + + 58.6 + 23168.193 + + + + + 45.04652 + -122.86656 + + 58.5 + 23175.055 + + + + + 45.04659 + -122.86655 + + 58.4 + 23182.887 + + + + + 45.0467299 + -122.86654 + + 58.3 + 23198.491 + + + + + 45.05025 + -122.86655 + + 56.5 + 23590.334 + + + + + 45.0505 + -122.86655 + + 56.4 + 23618.164 + + + + + 45.05049 + -122.86578 + + 55.9 + 23678.731 + + + + + 45.05051 + -122.86178 + + 58.1 + 23993.318 + + + + + 45.05051 + -122.86047 + + 58.1 + 24096.343 + + + + + 45.05051 + -122.86019 + + 58.1 + 24118.364 + + + + + 45.05051 + -122.85998 + + 58.1 + 24134.879 + + + + + 45.05048 + -122.85972 + + 58.0 + 24155.598 + + + + + 45.05042 + -122.85949 + + 57.7 + 24174.88 + + + + + 45.05033 + -122.85932 + + 57.2 + 24191.587 + + + + + 45.05021 + -122.85913 + + 56.6 + 24211.63 + + + + + 45.0497 + -122.85844 + + 52.7 + 24290.166 + + + + + 45.04955 + -122.85812 + + 51.1 + 24320.368 + + + + + 45.04951 + -122.85764 + + 47.8 + 24358.38 + + + + + 45.04953 + -122.85733 + + 45.7 + 24382.862 + + + + + 45.0495 + -122.84941 + + 57.5 + 25005.749 + + + + + 45.04949 + -122.84732 + + 57.1 + 25170.124 + + + + + 45.0495 + -122.84667 + + 57.5 + 25221.256 + + + + + 45.04951 + -122.84647 + + 57.4 + 25237.024 + + + + + 45.04956 + -122.84612 + + 57.4 + 25265.108 + + + + + 45.04968 + -122.84594 + + 57.2 + 25284.572 + + + + + 45.04984 + -122.84576 + + 57.2 + 25307.323 + + + + + 45.04999 + -122.84569 + + 57.3 + 25324.905 + + + + + 45.05017 + -122.84568 + + 57.1 + 25344.958 + + + + + 45.05176 + -122.84568 + + 56.8 + 25521.955 + + + + + 45.05285 + -122.84568 + + 57.1 + 25643.292 + + + + + 45.0537599 + -122.84568 + + 56.1 + 25744.592 + + + + + 45.05428 + -122.84551 + + 55.4 + 25804.002 + + + + + 45.05496 + -122.84524 + + 54.9 + 25882.62 + + + + + 45.05532 + -122.84489 + + 54.1 + 25931.236 + + + + + 45.05559 + -122.84465 + + 54.3 + 25966.727 + + + + + 45.05586 + -122.8445 + + 55.0 + 25999.015 + + + + + 45.05639 + -122.84441 + + 55.8 + 26058.437 + + + + + 45.05701 + -122.84434 + + 56.3 + 26127.673 + + + + + 45.0581 + -122.84421 + + 56.7 + 26249.441 + + + + + 45.0584 + -122.84408 + + 56.7 + 26284.366 + + + + + 45.05854 + -122.84392 + + 56.8 + 26304.395 + + + + + 45.0586399 + -122.84372 + + 56.7 + 26323.663 + + + + + 45.05867 + -122.84359 + + 56.6 + 26334.417 + + + + + 45.0587 + -122.84334 + + 56.7 + 26354.357 + + + + + 45.05866 + -122.84298 + + 56.5 + 26383.013 + + + + + 45.05862 + -122.84274 + + 56.2 + 26402.404 + + + + + 45.05857 + -122.84243 + + 55.9 + 26427.408 + + + + + 45.0585 + -122.84198 + + 55.4 + 26463.641 + + + + + 45.05843 + -122.84153 + + 54.5 + 26499.874 + + + + + 45.0583699 + -122.84112 + + 54.5 + 26532.798 + + + + + 45.05835 + -122.84092 + + 54.7 + 26548.682 + + + + + 45.05835 + -122.84061 + + 55.4 + 26573.058 + + + + + 45.05838 + -122.84047 + + 55.8 + 26584.563 + + + + + 45.05844 + -122.84029 + + 55.7 + 26600.213 + + + + + 45.05849 + -122.84019 + + 55.9 + 26609.847 + + + + + 45.05866 + -122.84 + + 56.0 + 26633.958 + + + + + 45.05938 + -122.83956 + + 56.0 + 26721.257 + + + + + 45.0595799 + -122.83942 + + 55.9 + 26746.094 + + + + + 45.05972 + -122.83927 + + 55.9 + 26765.638 + + + + + 45.05988 + -122.83911 + + 55.7 + 26787.445 + + + + + 45.06001 + -122.83891 + + 55.2 + 26808.816 + + + + + 45.0602 + -122.83859 + + 54.9 + 26841.687 + + + + + 45.0604299 + -122.83828 + + 55.0 + 26877.038 + + + + + 45.06133 + -122.83695 + + 53.5 + 27021.863 + + + + + 45.06187 + -122.83616 + + 53.6 + 27108.304 + + + + + 45.06199 + -122.83592 + + 54.5 + 27131.424 + + + + + 45.06207 + -122.83564 + + 55.1 + 27155.173 + + + + + 45.0621 + -122.83522 + + 55.2 + 27188.366 + + + + + 45.0621099 + -122.83466 + + 55.1 + 27232.412 + + + + + 45.0621099 + -122.83388 + + 54.1 + 27293.743 + + + + + 45.06212 + -122.8334 + + 53.3 + 27331.501 + + + + + 45.06215 + -122.83315 + + 52.7 + 27351.44 + + + + + 45.06224 + -122.83284 + + 52.2 + 27377.793 + + + + + 45.06241 + -122.83238 + + 51.1 + 27418.614 + + + + + 45.06266 + -122.83179 + + 50.1 + 27472.712 + + + + + 45.0627799 + -122.83149 + + 50.2 + 27499.82 + + + + + 45.06288 + -122.83105 + + 49.9 + 27536.164 + + + + + 45.06292 + -122.83085 + + 48.3 + 27552.507 + + + + + 45.06297 + -122.83051 + + 45.2 + 27579.814 + + + + + 45.063 + -122.82991 + + 42.8 + 27627.109 + + + + + 45.06286 + -122.82885 + + 49.2 + 27711.899 + + + + + 45.06274 + -122.82837 + + 48.1 + 27751.935 + + + + + 45.06242 + -122.82735 + + 48.4 + 27839.691 + + + + + 45.06246 + -122.82723 + + 48.2 + 27850.124 + + + + + 45.0624899 + -122.82717 + + 48.3 + 27855.904 + + + + + 45.06254 + -122.82712 + + 48.3 + 27862.718 + + + + + 45.0626 + -122.82706 + + 48.7 + 27870.896 + + + + + 45.0627 + -122.827 + + 49.3 + 27882.986 + + + + + 45.06309 + -122.82706 + + 51.4 + 27926.656 + + + + + 45.06311 + -122.82705 + + 51.5 + 27929.017 + + + + + 45.06394 + -122.82707 + + 52.8 + 28021.425 + + + + + 45.06404 + -122.82707 + + 52.8 + 28032.557 + + + + + 45.06435 + -122.82708 + + 52.6 + 28067.075 + + + + + 45.06464 + -122.82708 + + 52.3 + 28099.357 + + + + + 45.06482 + -122.82707 + + 52.2 + 28119.41 + + + + + 45.06492 + -122.82704 + + 52.1 + 28130.789 + + + + + 45.0649299 + -122.82437 + + 45.1 + 28340.721 + + + + + 45.06492 + -122.82348 + + 44.8 + 28410.706 + + + + + 45.0649 + -122.82246 + + 45.1 + 28490.934 + + + + + 45.06491 + -122.82216 + + 44.7 + 28514.548 + + + + + 45.0649299 + -122.8216 + + 44.0 + 28558.635 + + + + + 45.06496 + -122.82126 + + 42.9 + 28585.575 + + + + + 45.065 + -122.82089 + + 42.1 + 28615.005 + + + + + 45.06524 + -122.81918 + + 41.7 + 28752.082 + + + + + 45.06526 + -122.81828 + + 41.0 + 28822.879 + + + + + 45.06525 + -122.81753 + + 41.2 + 28881.858 + + + + + 45.06523 + -122.81473 + + 42.3 + 29102.018 + + + + + 45.0652199 + -122.81256 + + 43.1 + 29272.638 + + + + + 45.0652199 + -122.81194 + + 46.7 + 29321.385 + + + + + 45.06526 + -122.81179 + + 47.8 + 29333.991 + + + + + 45.06528 + -122.81174 + + 48.1 + 29338.509 + + + + + 45.06532 + -122.81164 + + 49.2 + 29347.545 + + + + + 45.06542 + -122.81148 + + 50.0 + 29364.343 + + + + + 45.06556 + -122.81139 + + 51.1 + 29381.459 + + + + + 45.06718 + -122.81136 + + 51.8 + 29561.811 + + + + + 45.0673 + -122.81135 + + 51.7 + 29575.192 + + + + + 45.06743 + -122.81133 + + 51.7 + 29589.749 + + + + + 45.06755 + -122.81122 + + 51.4 + 29605.662 + + + + + 45.06763 + -122.81101 + + 51.5 + 29624.421 + + + + + 45.06765 + -122.81082 + + 51.8 + 29639.524 + + + + + 45.06765 + -122.81053 + + 52.3 + 29662.324 + + + + + 45.06764 + -122.80628 + + 50.4 + 29996.467 + + + + + 45.0676599 + -122.80575 + + 51.4 + 30038.196 + + + + + 45.0676599 + -122.80507 + + 52.0 + 30091.658 + + + + + 45.06767 + -122.80416 + + 52.3 + 30163.212 + + + + + 45.06768 + -122.80211 + + 52.6 + 30324.39 + + + + + 45.06768 + -122.8012 + + 52.4 + 30395.935 + + + + + 45.06772 + -122.80009 + + 52.8 + 30483.318 + + + + + 45.06765 + -122.79991 + + 53.1 + 30499.473 + + + + + 45.06763 + -122.79979 + + 53.1 + 30509.167 + + + + + 45.06759 + -122.79968 + + 53.1 + 30518.894 + + + + + 45.06755 + -122.79952 + + 53.2 + 30532.239 + + + + + 45.06755 + -122.79857 + + 53.5 + 30606.929 + + + + + 45.06754 + -122.79756 + + 54.1 + 30686.345 + + + + + 45.06756 + -122.79657 + + 55.0 + 30764.212 + + + + + 45.06757 + -122.79554 + + 58.2 + 30845.199 + + + + + 45.06757 + -122.79527 + + 59.3 + 30866.427 + + + + + 45.06761 + -122.79336 + + 62.8 + 31016.66 + + + + + 45.0676 + -122.79282 + + 62.9 + 31059.13 + + + + + 45.06748 + -122.79237 + + 63.6 + 31096.948 + + + + + 45.06717 + -122.79176 + + 64.7 + 31156.032 + + + + + 45.06656 + -122.79085 + + 65.0 + 31254.672 + + + + + 45.06607 + -122.79008 + + 64.8 + 31336.161 + + + + + 45.06577 + -122.78959 + + 65.8 + 31387.146 + + + + + 45.06573 + -122.78953 + + 65.8 + 31393.633 + + + + + 45.06552 + -122.7892 + + 66.8 + 31428.557 + + + + + 45.06532 + -122.78887 + + 68.0 + 31462.745 + + + + + 45.0650199 + -122.78818 + + 69.8 + 31526.451 + + + + + 45.06429 + -122.78648 + + 76.4 + 31682.879 + + + + + 45.06407 + -122.78576 + + 80.1 + 31744.56 + + + + + 45.06398 + -122.78539 + + 81.8 + 31775.328 + + + + + 45.0638999 + -122.78503 + + 82.9 + 31805.002 + + + + + 45.06387 + -122.78492 + + 83.1 + 31814.273 + + + + + 45.06384 + -122.78495 + + 83.3 + 31818.361 + + + + + 45.06382 + -122.78496 + + 83.5 + 31820.723 + + + + + 45.06379 + -122.78495 + + 83.7 + 31824.154 + + + + + 45.06321 + -122.78465 + + 87.5 + 31892.892 + + + + + 45.0618 + -122.78371 + + 98.3 + 32066.383 + + + + + 45.06133 + -122.78317 + + 103.7 + 32133.764 + + + + + 45.06105 + -122.7825 + + 107.0 + 32194.977 + + + + + 45.06089 + -122.78186 + + 111.2 + 32248.359 + + + + + 45.06056 + -122.78035 + + 119.8 + 32372.645 + + + + + 45.05991 + -122.77991 + + 125.3 + 32452.848 + + + + + 45.05969 + -122.77986 + + 125.8 + 32477.652 + + + + + 45.0596699 + -122.77995 + + 125.7 + 32485.071 + + + + + 45.05942 + -122.77984 + + 125.9 + 32514.214 + + + + + 45.05923 + -122.77966 + + 126.5 + 32539.663 + + + + + 45.05894 + -122.77925 + + 128.9 + 32585.287 + + + + + 45.05886 + -122.77916 + + 129.6 + 32596.662 + + + + + 45.05867 + -122.77904 + + 131.1 + 32619.822 + + + + + 45.05857 + -122.779 + + 131.6 + 32631.39 + + + + + 45.05858 + -122.77889 + + 132.3 + 32640.111 + + + + + 45.05857 + -122.77881 + + 132.7 + 32646.499 + + + + + 45.0585 + -122.77863 + + 134.1 + 32662.657 + + + + + 45.05845 + -122.77846 + + 135.2 + 32677.137 + + + + + 45.05844 + -122.77813 + + 137.1 + 32703.11 + + + + + 45.05845 + -122.77804 + + 137.7 + 32710.274 + + + + + 45.05821 + -122.77742 + + 142.3 + 32765.868 + + + + + 45.0582 + -122.77728 + + 143.1 + 32776.932 + + + + + 45.05822 + -122.77721 + + 143.2 + 32782.87 + + + + + 45.05813 + -122.77698 + + 144.2 + 32803.546 + + + + + 45.05814 + -122.77693 + + 144.3 + 32807.631 + + + + + 45.05811 + -122.77689 + + 144.5 + 32812.22 + + + + + 45.0580799 + -122.77686 + + 144.7 + 32816.307 + + + + + 45.05792 + -122.77689 + + 145.5 + 32834.275 + + + + + 45.05788 + -122.77691 + + 145.6 + 32838.997 + + + + + 45.05787 + -122.77691 + + 145.6 + 32840.11 + + + + + 45.05793 + -122.776896 + + 145.5 + 32846.879 + + + + + 45.0580799 + -122.77686 + + 144.7 + 32863.816 + + + + + 45.05811 + -122.77689 + + 144.5 + 32867.904 + + + + + 45.05814 + -122.77693 + + 144.3 + 32872.492 + + + + + 45.05813 + -122.77698 + + 144.2 + 32876.578 + + + + + 45.05822 + -122.77721 + + 143.2 + 32897.254 + + + + + 45.0582 + -122.77728 + + 143.1 + 32903.191 + + + + + 45.05821 + -122.77742 + + 142.3 + 32914.256 + + + + + 45.0584 + -122.77793 + + 138.4 + 32959.595 + + + + + 45.05845 + -122.77804 + + 137.7 + 32969.881 + + + + + 45.05844 + -122.77813 + + 137.1 + 32977.045 + + + + + 45.05845 + -122.77846 + + 135.2 + 33003.017 + + + + + 45.0585 + -122.77863 + + 134.1 + 33017.499 + + + + + 45.05857 + -122.77881 + + 132.7 + 33033.656 + + + + + 45.05858 + -122.77889 + + 132.3 + 33040.044 + + + + + 45.05857 + -122.779 + + 131.6 + 33048.765 + + + + + 45.0588399 + -122.77915 + + 129.7 + 33081.053 + + + + + 45.05894 + -122.77925 + + 128.9 + 33094.682 + + + + + 45.05923 + -122.77966 + + 126.5 + 33140.305 + + + + + 45.05942 + -122.77984 + + 125.9 + 33165.756 + + + + + 45.05965 + -122.77995 + + 125.8 + 33192.78 + + + + + 45.0600499 + -122.78004 + + 124.6 + 33237.867 + + + + + 45.06028 + -122.7802 + + 122.6 + 33266.394 + + + + + 45.06059 + -122.78047 + + 119.3 + 33306.911 + + + + + 45.06084 + -122.78116 + + 115.4 + 33367.887 + + + + + 45.0608999 + -122.78149 + + 113.6 + 33394.68 + + + + + 45.06092 + -122.78212 + + 109.6 + 33444.269 + + + + + 45.06098 + -122.78233 + + 108.0 + 33462.081 + + + + + 45.06102 + -122.78245 + + 107.5 + 33472.514 + + + + + 45.0611 + -122.78262 + + 106.5 + 33488.576 + + + + + 45.06207 + -122.78398 + + 96.0 + 33640.546 + + + + + 45.06259 + -122.78433 + + 91.3 + 33704.641 + + + + + 45.06354 + -122.78488 + + 85.4 + 33818.894 + + + + + 45.06378 + -122.785 + + 83.7 + 33847.228 + + + + + 45.06385 + -122.78498 + + 83.2 + 33855.177 + + + + + 45.06401 + -122.78564 + + 80.8 + 33910.042 + + + + + 45.06424 + -122.78637 + + 77.1 + 33972.891 + + + + + 45.06429 + -122.7865 + + 76.3 + 33984.529 + + + + + 45.06422 + -122.78651 + + 76.7 + 33992.361 + + + + + 45.06387 + -122.78671 + + 78.2 + 34034.377 + + + + + 45.06359 + -122.78682 + + 78.6 + 34066.724 + + + + + 45.0633 + -122.78684 + + 79.0 + 34099.044 + + + + + 45.05868 + -122.78672 + + 65.8 + 34613.424 + + + + + 45.05821 + -122.78668 + + 64.4 + 34665.838 + + + + + 45.05805 + -122.78665 + + 64.2 + 34683.805 + + + + + 45.05703 + -122.78626 + + 63.0 + 34801.419 + + + + + 45.05676 + -122.78617 + + 62.9 + 34832.297 + + + + + 45.05657 + -122.78613 + + 62.9 + 34853.68 + + + + + 45.05639 + -122.78612 + + 62.7 + 34873.733 + + + + + 45.04569 + -122.78619 + + 64.1 + 36064.857 + + + + + 45.04561 + -122.78615 + + 64.1 + 36074.302 + + + + + 45.04549 + -122.786 + + 64.1 + 36092.124 + + + + + 45.04506 + -122.78533 + + 63.4 + 36163.316 + + + + + 45.04472 + -122.78483 + + 62.4 + 36217.897 + + + + + 45.04421 + -122.78415 + + 62.2 + 36295.895 + + + + + 45.04304 + -122.78273 + + 63.3 + 36467.469 + + + + + 45.04243 + -122.782 + + 63.8 + 36556.395 + + + + + 45.04233 + -122.78201 + + 63.6 + 36567.555 + + + + + 45.04223 + -122.78209 + + 63.8 + 36580.342 + + + + + 45.04219 + -122.78215 + + 63.8 + 36586.831 + + + + + 45.04217 + -122.78686 + + 61.7 + 36957.309 + + + + + 45.04215 + -122.78963 + + 60.5 + 37175.198 + + + + + 45.04215 + -122.78964 + + 60.5 + 37175.985 + + + + + 45.04211 + -122.79373 + + 62.7 + 37497.72 + + + + + 45.04209 + -122.80197 + + 60.9 + 38145.853 + + + + + 45.04209 + -122.80201 + + 60.8 + 38148.999 + + + + + 45.04181 + -122.80199 + + 60.1 + 38180.208 + + + + + 45.04063 + -122.80179 + + 59.2 + 38312.503 + + + + + 45.03695 + -122.80108 + + 60.1 + 38725.946 + + + + + 45.0367 + -122.80096 + + 60.9 + 38755.333 + + + + + 45.03627 + -122.80059 + + 61.1 + 38811.355 + + + + + 45.0361 + -122.8004 + + 61.3 + 38835.469 + + + + + 45.03589 + -122.80008 + + 61.2 + 38869.823 + + + + + 45.03582 + -122.79994 + + 61.3 + 38883.314 + + + + + 45.0354699 + -122.79902 + + 61.3 + 38965.507 + + + + + 45.03539 + -122.79885 + + 61.2 + 38981.574 + + + + + 45.03525 + -122.79867 + + 60.8 + 39002.631 + + + + + 45.03516 + -122.79858 + + 60.7 + 39014.898 + + + + + 45.03507 + -122.79853 + + 60.5 + 39025.662 + + + + + 45.03468 + -122.79839 + + 58.4 + 39070.451 + + + + + 45.03284 + -122.79803 + + 57.9 + 39277.226 + + + + + 45.0324 + -122.79796 + + 58.4 + 39326.515 + + + + + 45.03235 + -122.79797 + + 58.7 + 39332.137 + + + + + 45.03223 + -122.79809 + + 58.3 + 39348.494 + + + + + 45.03187 + -122.7986 + + 58.3 + 39405.201 + + + + + 45.03181 + -122.7987 + + 58.1 + 39415.521 + + + + + 45.03173 + -122.79891 + + 57.9 + 39434.29 + + + + + 45.03165 + -122.7992 + + 58.0 + 39458.781 + + + + + 45.0316 + -122.7993 + + 58.0 + 39468.418 + + + + + 45.03156 + -122.79934 + + 58.0 + 39473.87 + + + + + 45.03145 + -122.79936 + + 58.5 + 39486.216 + + + + + 45.03121 + -122.79933 + + 59.1 + 39513.036 + + + + + 45.02949 + -122.79864 + + 61.3 + 39712.051 + + + + + 45.02782 + -122.79797 + + 62.5 + 39905.283 + + + + + 45.02746 + -122.79784 + + 61.9 + 39946.642 + + + + + 45.02672 + -122.79768 + + 60.6 + 40029.974 + + + + + 45.02623 + -122.79765 + + 61.4 + 40084.571 + + + + + 45.02603 + -122.79764 + + 62.2 + 40106.849 + + + + + 45.02604 + -122.8034 + + 59.4 + 40560.039 + + + + + 45.02606 + -122.80378 + + 59.8 + 40590.019 + + + + + 45.02611 + -122.80401 + + 60.1 + 40608.952 + + + + + 45.02617 + -122.8042 + + 60.6 + 40625.325 + + + + + 45.02634 + -122.80451 + + 61.3 + 40656.196 + + + + + 45.02648 + -122.80466 + + 61.6 + 40675.745 + + + + + 45.02694 + -122.80501 + + 62.0 + 40733.886 + + + + + 45.02705 + -122.80514 + + 62.0 + 40749.841 + + + + + 45.02715 + -122.8053 + + 61.8 + 40766.646 + + + + + 45.02723 + -122.80547 + + 61.9 + 40782.714 + + + + + 45.02728 + -122.80562 + + 61.7 + 40795.762 + + + + + 45.02734 + -122.80596 + + 61.5 + 40823.334 + + + + + 45.02737 + -122.80682 + + 60.9 + 40891.078 + + + + + 45.02748 + -122.81496 + + 59.4 + 41531.623 + + + + + 45.0275 + -122.81583 + + 59.5 + 41600.108 + + + + + 45.02586 + -122.81588 + + 59.8 + 41782.713 + + + + + 45.0247699 + -122.81596 + + 60.1 + 41904.214 + + + + + 45.0239199 + -122.81604 + + 60.9 + 41999.044 + + + + + 45.01881 + -122.81662 + + 60.1 + 42569.711 + + + + + 45.01842 + -122.81665 + + 60.1 + 42613.189 + + + + + 45.01841 + -122.81665 + + 60.1 + 42614.302 + + + + + 45.01436 + -122.81698 + + 61.3 + 43065.891 + + + + + 45.01206 + -122.81717 + + 60.9 + 43322.361 + + + + + 45.01212 + -122.81883 + + 60.6 + 43453.169 + + + + + 45.01211 + -122.81924 + + 60.4 + 43485.455 + + + + + 45.01158 + -122.82277 + + 59.6 + 43769.455 + + + + + 45.0115 + -122.82336 + + 59.8 + 43816.733 + + + + + 45.01132 + -122.82498 + + 58.9 + 43945.79 + + + + + 45.01129 + -122.8252 + + 58.8 + 43963.423 + + + + + 45.00617 + -122.82508 + + 51.9 + 44533.453 + + + + + 45.00614 + -122.82508 + + 52.0 + 44536.793 + + + + + 45.00614 + -122.82507 + + 52.0 + 44537.58 + + + + + 45.00035 + -122.82493 + + 53.3 + 45182.21 + + + + + 44.99438 + -122.82487 + + 62.4 + 45846.801 + + + + + 44.99419 + -122.82489 + + 62.3 + 45868.01 + + + + + 44.99248 + -122.82486 + + 61.6 + 46058.38 + + + + + 44.9899 + -122.82484 + + 68.0 + 46345.587 + + + + + 44.98978 + -122.82482 + + 68.3 + 46359.037 + + + + + 44.98971 + -122.82473 + + 68.4 + 46369.569 + + + + + 44.9896699 + -122.82456 + + 68.4 + 46383.675 + + + + + 44.98968 + -122.8242 + + 68.4 + 46412.039 + + + + + 44.98965 + -122.82405 + + 68.5 + 46424.311 + + + + + 44.98962 + -122.82398 + + 68.6 + 46430.755 + + + + + 44.98953 + -122.82392 + + 68.8 + 46441.831 + + + + + 44.98516 + -122.82371 + + 86.6 + 46928.576 + + + + + 44.9843 + -122.82369 + + 87.6 + 47024.323 + + + + + 44.9843199 + -122.82655 + + 82.1 + 47249.518 + + + + + 44.9843199 + -122.83294 + + 57.4 + 47752.64 + + + + + 44.98434 + -122.8344 + + 49.8 + 47867.616 + + + + + 44.98431 + -122.83566 + + 53.2 + 47966.88 + + + + + 44.98428 + -122.8363 + + 55.8 + 48017.381 + + + + + 44.98428 + -122.83631 + + 55.9 + 48018.168 + + + + + 44.98412 + -122.83976 + + 62.9 + 48290.391 + + + + + 44.98407 + -122.84003 + + 63.2 + 48312.366 + + + + + 44.98137 + -122.84791 + + 65.7 + 49001.788 + + + + + 44.9812299 + -122.84835 + + 65.3 + 49039.777 + + + + + 44.98119 + -122.84853 + + 65.1 + 49054.634 + + + + + 44.98115 + -122.8488 + + 64.3 + 49076.355 + + + + + 44.98115 + -122.849 + + 63.9 + 49092.103 + + + + + 44.9812 + -122.85742 + + 67.3 + 49755.118 + + + + + 44.98116 + -122.85772 + + 67.2 + 49779.157 + + + + + 44.9811 + -122.858 + + 66.9 + 49802.193 + + + + + 44.98068 + -122.85942 + + 65.6 + 49923.386 + + + + + 44.98061 + -122.85969 + + 65.3 + 49946.029 + + + + + 44.97674 + -122.85969 + + 67.3 + 50376.833 + + + + + 44.97648 + -122.85971 + + 67.5 + 50405.819 + + + + + 44.97621 + -122.85978 + + 67.7 + 50436.376 + + + + + 44.9747 + -122.86042 + + 68.5 + 50611.861 + + + + + 44.97446 + -122.8605 + + 68.8 + 50639.31 + + + + + 44.9742 + -122.86056 + + 69.1 + 50668.636 + + + + + 44.97392 + -122.86059 + + 69.4 + 50699.895 + + + + + 44.97059 + -122.86079 + + 68.2 + 51070.921 + + + + + 44.97084 + -122.87621 + + 64.4 + 52285.635 + + + + + 44.97087 + -122.87738 + + 64.8 + 52377.838 + + + + + 44.97102 + -122.87738 + + 64.7 + 52394.536 + + + + + 44.9707999 + -122.88076 + + 66.7 + 52661.849 + + + + + 44.9707999 + -122.88077 + + 66.7 + 52662.637 + + + + + 44.97048 + -122.88574 + + 61.8 + 53055.665 + + + + + 44.97026 + -122.88964 + + 65.2 + 53363.784 + + + + + 44.97018 + -122.89091 + + 63.6 + 53464.199 + + + + + 44.9642 + -122.89223 + + 65.5 + 54137.955 + + + + + 44.95917 + -122.89331 + + 66.9 + 54704.314 + + + + + 44.9583299 + -122.89348 + + 66.8 + 54798.776 + + + + + 44.95834 + -122.89746 + + 67.6 + 55112.288 + + + + + 44.95834 + -122.89883 + + 67.9 + 55220.205 + + + + + 44.95835 + -122.90229 + + 68.2 + 55492.757 + + + + + 44.9583299 + -122.90287 + + 68.3 + 55538.499 + + + + + 44.95829 + -122.9037 + + 68.0 + 55604.031 + + + + + 44.95817 + -122.90482 + + 67.4 + 55693.261 + + + + + 44.95783 + -122.90705 + + 66.1 + 55872.953 + + + + + 44.95771 + -122.90828 + + 64.9 + 55970.76 + + + + + 44.95741 + -122.91337 + + 64.3 + 56373.101 + + + + + 44.9572999 + -122.91476 + + 63.8 + 56483.277 + + + + + 44.95704 + -122.91633 + + 61.9 + 56610.293 + + + + + 44.95688 + -122.91713 + + 61.5 + 56675.78 + + + + + 44.95641 + -122.91913 + + 57.8 + 56841.788 + + + + + 44.9563599 + -122.91939 + + 57.7 + 56863.012 + + + + + 44.95631 + -122.91981 + + 56.8 + 56896.562 + + + + + 44.95631 + -122.92003 + + 55.3 + 56913.893 + + + + + 44.95634 + -122.92051 + + 53.8 + 56951.852 + + + + + 44.95694 + -122.92322 + + 56.6 + 57175.533 + + + + + 44.95702 + -122.92371 + + 57.1 + 57215.146 + + + + + 44.95707 + -122.92422 + + 59.1 + 57255.705 + + + + + 44.95706 + -122.92482 + + 59.5 + 57302.982 + + + + + 44.95705 + -122.92509 + + 59.7 + 57324.28 + + + + + 44.9570099 + -122.92536 + + 59.9 + 57346.009 + + + + + 44.95691 + -122.92586 + + 60.4 + 57386.939 + + + + + 44.95524 + -122.93236 + + 60.1 + 57931.677 + + + + + 44.95511 + -122.93293 + + 57.0 + 57978.854 + + + + + 44.95505 + -122.93338 + + 56.1 + 58014.927 + + + + + 44.95503 + -122.93473 + + 56.5 + 58121.298 + + + + + 44.955 + -122.93528 + + 56.9 + 58164.753 + + + + + 44.9549499 + -122.9359 + + 57.9 + 58213.91 + + + + + 44.95483 + -122.93683 + + 59.0 + 58288.38 + + + + + 44.95478 + -122.93742 + + 59.3 + 58335.19 + + + + + 44.95477 + -122.93817 + + 59.0 + 58394.283 + + + + + 44.9548599 + -122.95337 + + 63.8 + 59591.726 + + + + + 44.95487 + -122.95461 + + 63.4 + 59689.415 + + + + + 44.9549499 + -122.96642 + + 61.7 + 60619.805 + + + + + 44.9549499 + -122.96721 + + 62.0 + 60682.038 + + + + + 44.95493 + -122.96782 + + 61.9 + 60730.143 + + + + + 44.95485 + -122.96842 + + 61.7 + 60778.24 + + + + + 44.9539199 + -122.97348 + + 63.6 + 61190.076 + + + + + 44.9539 + -122.98387 + + 63.0 + 62008.578 + + + + + 44.9539 + -122.99172 + + 61.7 + 62626.983 + + + + + 44.95395 + -123.00362 + + 57.5 + 63564.453 + + + + + 44.95398 + -123.00393 + + 57.2 + 63589.101 + + + + + 44.95403 + -123.00419 + + 56.8 + 63610.326 + + + + + 44.95415 + -123.00455 + + 56.5 + 63641.674 + + + + + 44.95543 + -123.00664 + + 54.7 + 63859.413 + + + + + 44.95568 + -123.00705 + + 54.3 + 63902.047 + + + + + 44.95584 + -123.00737 + + 54.1 + 63932.912 + + + + + 44.95595 + -123.00766 + + 53.9 + 63958.832 + + + + + 44.95606 + -123.00799 + + 53.7 + 63987.567 + + + + + 44.95615 + -123.00844 + + 53.5 + 64024.404 + + + + + 44.95621 + -123.00922 + + 53.3 + 64086.21 + + + + + 44.95624 + -123.01065 + + 52.9 + 64198.907 + + + + + 44.95625 + -123.0108 + + 52.8 + 64210.775 + + + + + 44.95526 + -123.01083 + + 53.3 + 64321.006 + + + + + 44.9540099 + -123.01089 + + 53.8 + 64460.235 + + + + + 44.95371 + -123.011 + + 53.9 + 64494.737 + + + + + 44.9515699 + -123.0121 + + 54.4 + 64748.231 + + + + + 44.94423 + -123.0158 + + 54.0 + 65615.755 + + + + + 44.94414 + -123.01585 + + 53.9 + 65626.52 + + + + + 44.9443 + -123.01649 + + 53.6 + 65679.998 + + + + + 44.94438 + -123.01677 + + 53.5 + 65703.789 + + + + + 44.9448099 + -123.01852 + + 52.7 + 65849.746 + + + + + 44.94503 + -123.01938 + + 52.2 + 65921.795 + + + + + 44.9450999 + -123.01965 + + 52.1 + 65944.451 + + + + + 44.94516 + -123.01992 + + 52.0 + 65966.748 + + + + + 44.94329 + -123.02086 + + 51.1 + 66187.697 + + + + + 44.9431299 + -123.02092 + + 51.1 + 66206.125 + + + + + 44.94248 + -123.02124 + + 50.6 + 66282.748 + + + + + 44.94248 + -123.02123 + + 50.6 + 66283.537 + + + + + 44.94248 + -123.02124 + + 50.6 + 66284.325 + + + + + 44.9418099 + -123.02156 + + 50.1 + 66363.054 + + + + + 44.94135 + -123.02182 + + 49.5 + 66418.208 + + + + + 44.94063 + -123.02218 + + 53.8 + 66503.229 + + + + + 44.94095 + -123.02349 + + 53.1 + 66612.425 + + + + + 44.94094 + -123.02361 + + 53.2 + 66621.946 + + + + + 44.941 + -123.02372 + + 53.2 + 66632.888 + + + + + 44.9415059 + -123.025691 + + 52.2 + 66798.092 + + + + + 44.94151 + -123.02569 + + 52.2 + 66798.545 + + + + + 44.9417199 + -123.02653 + + 51.6 + 66868.739 + + + + + 44.94178 + -123.02675 + + 51.6 + 66887.316 + + + + + 44.94183 + -123.02688 + + 51.6 + 66898.974 + + + + + 44.94193 + -123.02699 + + 51.5 + 66913.081 + + + + + 44.94194 + -123.02701 + + 51.5 + 66915.011 + + + + + 44.94197 + -123.02704 + + 51.5 + 66919.103 + + + + + 44.94198 + -123.02708 + + 51.4 + 66922.446 + + + + + 44.94202 + -123.02715 + + 51.4 + 66929.534 + + + + + 44.94202 + -123.02717 + + 51.4 + 66931.11 + + + + + 44.94205 + -123.02725 + + 51.3 + 66938.244 + + + + + 44.94213 + -123.02755 + + 51.1 + 66963.504 + + + + + 44.9423 + -123.02821 + + 50.8 + 67018.844 + + + + + 44.94236 + -123.02845 + + 50.7 + 67038.899 + + + + + 44.9421 + -123.02857 + + 50.7 + 67069.346 + + + + + 44.94165 + -123.02879 + + 50.9 + 67122.355 + + + + + 44.9412499 + -123.02899 + + 50.9 + 67169.589 + + + + + 44.94119 + -123.02877 + + 51.1 + 67188.166 + + + + + 44.94113 + -123.02854 + + 51.2 + 67207.481 + + + + + 44.94036 + -123.02892 + + 51.2 + 67298.276 + + + + + 44.94005 + -123.02907 + + 51.1 + 67334.752 + + + + + 44.93996 + -123.02912 + + 51.1 + 67345.518 + + + + + 44.93987 + -123.02916 + + 51.1 + 67356.021 + + + + + 44.93948 + -123.02936 + + 51.1 + 67402.207 + + + + + 44.93903 + -123.02959 + + 51.1 + 67455.478 + + + + Start of r + + + 44.93903 + -123.02959 + + Generic + Start of route + + + North Capi + + + 44.939047 + -123.029581 + + Left + North on Capitol Mall to start + + + Center St + + + 44.94113 + -123.02854 + + Right + Right on Center St NE + + + 17th St NE + + + 44.93934 + -123.01811 + + Left + Left on 17th St NE + + + Sunnyview + + + 44.95626 + -123.01072 + + Right + Right on Sunnyview Rd NE + + + Cordon Rd + + + 44.9549 + -122.95885 + + Left + Left on Cordon Rd NE + + + Hazelgreen + + + 45.0047 + -122.94851 + + Right + Right on Hazelgreen Rd NE + + + 62nd Ave N + + + 45.0097 + -122.93313 + + Left + Left on 62nd Ave NE + + + Perkins St + + + 45.02726 + -122.93276 + + Right + Continue onto Perkins St NE + + + 65th Ave N + + + 45.02762 + -122.92813 + + Left + Continue onto 65th Ave NE + + + Labish Cen + + + 45.03069 + -122.92782 + + Right + Right on Labish Center Rd NE + + + 72nd Ave N + + + 45.03117 + -122.91311 + + Left + Left on 72nd Ave NE + + + Brooklake + + + 45.0379099 + -122.9126 + + Right + Continue onto Brooklake Rd NE + + + 75th Ave N + + + 45.0384699 + -122.9082 + + Left + Continue onto 75th Ave NE + + + Rambler Dr + + + 45.04159 + -122.90756 + + Right + Right on Rambler Dr NE + + + Howell Pra + + + 45.04601 + -122.86717 + + Left + Left on Howell Prairie Rd NE + + + Saratoga D + + + 45.0505 + -122.86655 + + Right + Right on Saratoga Dr NE + + + 114th St N + + + 45.06242 + -122.82735 + + Left + Left on 114th St NE + + + W Church R + + + 45.06492 + -122.82704 + + Right + Right on W Church Rd NE + + + E College + + + 45.06757 + -122.79527 + + Straight + Continue onto E College St + + + Abbey Dr + + + 45.06387 + -122.78492 + + Right + Right on Abbey Dr + + + Return dow + + + 45.05792 + -122.77689 + + U turn + Return down Abbey Dr after visiting the Abbey + + + East Colle + + + 45.06385 + -122.78498 + + Left + Left on East College Rd NE + + + Humpert Ln + + + 45.06429 + -122.7865 + + Left + Left on Humpert Ln + + + Downs Rd N + + + 45.04219 + -122.78215 + + Right + Right on Downs Rd NE + + + Gallon Hou + + + 45.04209 + -122.80201 + + Left + Left on Gallon House Rd NE + + + Hobart Rd + + + 45.02603 + -122.79764 + + Right + Right on Hobart Rd NE + + + Mount Ange + + + 45.0275 + -122.81583 + + Left + Left on Mount Angel Hwy + + + Hazelgreen + + + 45.01206 + -122.81717 + + Right + Right on Hazelgreen Rd NE + + + Brush Cree + + + 45.01129 + -122.8252 + + Left + Left on Brush Creek Dr NE + + + Selah Spri + + + 44.9843 + -122.82369 + + Right + Right on Selah Springs Dr NE + + + Desart Rd + + + 44.98061 + -122.85969 + + Left + Left on Desart Rd NE + + + Kaufman Rd + + + 44.97059 + -122.86079 + + Right + Turn right onto Kaufman Rd NE + + + Cross Howe + + + 44.97087 + -122.87738 + + Straight + Cross Howell Prairie Rd onto Lardon Rd NE + + + 82nd Ave N + + + 44.97018 + -122.89091 + + Left + Left on 82nd Ave NE + + + Sunnyview + + + 44.9583299 + -122.89348 + + Right + Right on Sunnyview Rd NE + + + 17th St NE + + + 44.95625 + -123.0108 + + Left + Left on 17th St NE + + + D St NE + + + 44.94414 + -123.01585 + + Right + Right on D St NE + + + 14th St NE + + + 44.94516 + -123.01992 + + Left + Left on 14th St NE + + + Marion St + + + 44.94063 + -123.02218 + + Right + Right on Marion St NE + + + Summer St + + + 44.94236 + -123.02845 + + Left + Left on Summer St NE + + + Center St + + + 44.9412499 + -123.02899 + + Left + Left on Center St NE + + + Capitol Ma + + + 44.94113 + -123.02854 + + Right + Right on Capitol Mall to return to the starting point + + + End of rou + + + 44.93903 + -123.02959 + + Generic + End of route + + + + diff --git a/tests/test_loader.py b/tests/test_loader.py new file mode 100644 index 00000000..543ce1ea --- /dev/null +++ b/tests/test_loader.py @@ -0,0 +1,12 @@ +import unittest + +from modules.loaders.tcx import TcxLoader + + +class TestLoader(unittest.TestCase): + def test_tcx(self): + data_course, data_course_points = TcxLoader.load_file( + "tests/data/tcx/Mt_Angel_Abbey.tcx" + ) + self.assertEqual(len(data_course["latitude"]), 946) + self.assertEqual(len(data_course_points["latitude"]), 42)