From b0dc464796f1b37f020703a297ab483bfb19bb53 Mon Sep 17 00:00:00 2001 From: Philip Sargent Date: Sat, 9 May 2026 21:55:53 +0100 Subject: [PATCH] more area location tests --- core/position_tests.py | 76 +++++++++++++++++++++++++++++++++++-- core/position_utils.py | 34 ++++++++--------- core/random_test_points.gpx | 2 + 3 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 core/random_test_points.gpx diff --git a/core/position_tests.py b/core/position_tests.py index bdd8ca01..2a8cd62f 100644 --- a/core/position_tests.py +++ b/core/position_tests.py @@ -75,6 +75,76 @@ def run_12_point_test(which_area_func): print(f"\nSummary: {passed}/12 points passed.") -# Uncomment to run: -run_limit_tests(which_area) -run_12_point_test(which_area) \ No newline at end of file + +import random +import time +import xml.etree.ElementTree as ET +from position_utils import which_area + +def generate_performance_test(num_points=200): + # Border limits derived from GPX data + west_lon = 13.72476763 # Point 1626-22 + east_lon = 13.86031535 # Point 1626-256 + + # Calculate the span to create a square N/S range + span = east_lon - west_lon + + # Midpoint of the border (approximate center for the test square) + mid_lat = 47.69 + min_lat = mid_lat - (span / 2) + max_lat = mid_lat + (span / 2) + + test_points = [] + for _ in range(num_points): + lat = random.uniform(min_lat, max_lat) + lon = random.uniform(west_lon, east_lon) + test_points.append((lat, lon)) + + return test_points + +def export_random_points_gpx(points_with_results, filename="random_test_points.gpx"): + root = ET.Element("gpx", version="1.1", creator="PerformanceTester", + xmlns="http://www.topografix.com/GPX/1/1") + + for lat, lon, area, valid in points_with_results: + wpt = ET.SubElement(root, "wpt", lat=f"{lat:.8f}", lon=f"{lon:.8f}") + name = ET.SubElement(wpt, "name") + name.text = f"{area}" + desc = ET.SubElement(wpt, "desc") + desc.text = f"Valid: {valid}" + + tree = ET.ElementTree(root) + tree.write(filename, encoding="utf-8", xml_declaration=True) + print(f"Random points exported to {filename}") + +def run_performance_test(): + # 1. Generate coordinates + coords = generate_performance_test(200) + results = [] + + # 2. Start Timing + start_time = time.perf_counter() + + for lat, lon in coords: + valid, area = which_area(lat, lon) + results.append((lat, lon, area, valid)) + + end_time = time.perf_counter() + + # 3. Output results + total_time_ms = (end_time - start_time) * 1000 + avg_time_us = (total_time_ms * 1000) / len(coords) + + print(f"--- Performance Results ---") + print(f"Total time for {len(coords)} points: {total_time_ms:.4f} ms") + print(f"Average time per lookup: {avg_time_us:.2f} microseconds") + + # 4. Export for visualization + export_random_points_gpx(results) + +if __name__ == "__main__": + run_limit_tests(which_area) + run_12_point_test(which_area) + run_performance_test() + + diff --git a/core/position_utils.py b/core/position_utils.py index c0168a63..56cb9cfb 100644 --- a/core/position_utils.py +++ b/core/position_utils.py @@ -6,7 +6,7 @@ def load_and_clean_gpx(filename): if not Path(filename).exists: print("No file") else: - print(f"Loading {filename}") + print(f"Loading '{filename}'") # Parse GPX tree = ET.parse(filename) root = tree.getroot() @@ -64,7 +64,7 @@ def save_cleaned_gpx(points, output_filename="cleaned_border.gpx"): ET.indent(tree, space="\t", level=0) tree.write(output_filename, encoding="windows-1252", xml_declaration=True) - print(f"Cleaned track exported to {output_filename}") + print(f"Cleaned track exported to '{output_filename}'") def split_into_monotonic_segments(points): @@ -94,18 +94,11 @@ def split_into_monotonic_segments(points): current_segment.append(p_curr) segments.append(current_segment) - print(len(segments)) + print(f"{len(segments)} segments in border.") return segments - - -PREPARED_SEGMENTS = [] -MIN_LON = 0 -MAX_LON = 0 - def generate_boundary_segments(): - # Example Workflow: points = load_and_clean_gpx('1623-6_border.gpx') # save_cleaned_gpx(points, "cleaned_border_output.gpx") # done once, not needed mono_segments = split_into_monotonic_segments(points) @@ -116,6 +109,7 @@ def generate_boundary_segments(): # Prepare segments for binary search # We store each segment as (sorted_lons, corresponding_lats) + PREPARED_SEGMENTS = [] for seg in mono_segments: lons = [p[0] for p in seg] lats = [p[1] for p in seg] @@ -132,23 +126,27 @@ def generate_boundary_segments(): 'min_lon': min(lons), 'max_lon': max(lons) }) - return MIN_LON, MAX_LON + return PREPARED_SEGMENTS, MIN_LON, MAX_LON def which_area(lat, lon): - global PREPARED_SEGMENTS, MIN_LON, MAX_LON + # Initialize state on the function object itself if it doesn't exist + # Yes, this is an attrubte *on a function*, an unusual python capbility + if not hasattr(which_area, "data"): + prepared_segments, min_l, max_l = generate_boundary_segments() + which_area.data = { + "prepared_segments": prepared_segments, + "min_lon": min_l, + "max_lon": max_l + } - if not PREPARED_SEGMENTS: - MIN_LON, MAX_LON = generate_boundary_segments() - - # Fast boundary check (East/West limits) - if lon < MIN_LON or lon > MAX_LON: + if lon < which_area.data["min_lon"] or lon > which_area.data["max_lon"]: return False, "None" # Area 1626 is North, Area 1623 is South # We find the boundary latitude at this longitude boundary_lat = None - for seg in PREPARED_SEGMENTS: + for seg in which_area.data["prepared_segments"]: # Check if lon is within this monotonic segment if seg['min_lon'] <= lon <= seg['max_lon']: lons = seg['lons'] diff --git a/core/random_test_points.gpx b/core/random_test_points.gpx new file mode 100644 index 00000000..d4a1a7ce --- /dev/null +++ b/core/random_test_points.gpx @@ -0,0 +1,2 @@ + +1626Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1623Valid: True1623Valid: True1623Valid: True1626Valid: True1626Valid: True1626Valid: True \ No newline at end of file