"""Approximate same-start, same-direction full-course matching, without raw samples.""" import math from itertools import pairwise from .analytics import numeric def distance(a, b): lat1, lat2 = math.radians(a[0]), math.radians(b[0]) dlat, dlon = lat2 - lat1, math.radians(b[1] - a[1]) h = math.sin(dlat / 2) ** 2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon / 2) ** 2 return 12742000 * math.asin(math.sqrt(min(1, h))) def resample(segments): # A recording break must not invent a connecting route. if not isinstance(segments, list) or len(segments) != 1: return None points = segments[0] if len(points) < 2 or any(not isinstance(p, list) or len(p) != 2 or not all(numeric(v) for v in p) or not -90 <= p[0] <= 90 or not -180 <= p[1] <= 180 for p in points): return None cumulative = [0.0] for a, b in pairwise(points): cumulative.append(cumulative[-1] + distance(a, b)) total = cumulative[-1] if total < 500: return None result, cursor = [], 0 for i in range(33): target = total * i / 32 while cursor < len(points) - 2 and cumulative[cursor + 1] < target: cursor += 1 span = cumulative[cursor + 1] - cumulative[cursor] fraction = (target - cumulative[cursor]) / span if span else 0 result.append([a + (b - a) * fraction for a, b in zip(points[cursor], points[cursor + 1], strict=True)]) return total, result def similar(reference, candidate): a, b = resample(reference), resample(candidate) if not a or not b or abs(b[0] / a[0] - 1) > .1: return False differences = sorted(distance(x, y) for x, y in zip(a[1], b[1], strict=True)) return (distance(a[1][0], b[1][0]) <= 100 and distance(a[1][-1], b[1][-1]) <= 100 and sum(differences) / len(differences) <= 100 and differences[29] <= 200)