PageSourceSearch

https://emap-next.vercel.app/js/offlinePlan.js

js emap-next.vercel.app collected 2026-10-03 06:53:01 UTC 2,847 bytes, 95 lines download raw bytes

1/* Plan offline web — helpers puros (paridad con apps/mobile offlineStore). */
2window.EmapOfflinePlan = (function () {
3  'use strict';
4
5  const PLAN_MAX_AGE_MS = 36 * 3600 * 1000;
6
7  function samePlace(a, b, meters) {
8    if (meters == null) meters = 80;
9    if (!a || !b) return false;
10    const dlat = (Number(a.lat) - Number(b.lat)) * 111320;
11    const mlon = 111320 * Math.cos(((Number(a.lat) + Number(b.lat)) / 2) * (Math.PI / 180));
12    const dlon = (Number(a.lon) - Number(b.lon)) * mlon;
13    if (![dlat, dlon].every(Number.isFinite)) return false;
14    return Math.hypot(dlat, dlon) <= meters;
15  }
16
17  function usableRoutes(routes) {
18    return Object.values(routes || {}).some(
19      (r) => r && !r.unavailable && r.geometry && r.geometry.coordinates
20        && r.geometry.coordinates.length > 1,
21    );
22  }
23
24  function bannerKey(opts) {
25    opts = opts || {};
26    if (opts.navigating) return 'offlineBannerNav';
27    if (opts.routeFromCache || opts.hasRoute) return 'offlineBannerRoute';
28    return 'offlineBanner';
29  }
30
31  function slimPlace(p) {
32    if (!p) return null;
33    return {
34      lat: p.lat,
35      lon: p.lon,
36      label: p.label || p.name || '',
37      name: p.name || p.label || '',
38    };
39  }
40
41  function slimRoute(r) {
42    if (!r || r.unavailable) return r;
43    return {
44      duration: r.duration,
45      distance: r.distance,
46      geometry: r.geometry,
47      legs: r.legs,
48      start_time: r.start_time,
49      end_time: r.end_time,
50      fare: r.fare,
51      recommendation: r.recommendation,
52      alternatives: Array.isArray(r.alternatives)
53        ? r.alternatives.map((a) => ({
54            duration: a.duration,
55            distance: a.distance,
56            geometry: a.geometry,
57            legs: a.legs,
58          }))
59        : undefined,
60      selected: r.selected,
61    };
62  }
63
64  function planMatches(plan, origin, dest) {
65    if (!plan || !plan.origin || !plan.destination || !origin || !dest) return false;
66    return samePlace(plan.origin, origin) && samePlace(plan.destination, dest);
67  }
68
69  function slimPlan(plan) {
70    if (!plan || !plan.origin || !plan.destination || !plan.routes) return null;
71    if (!usableRoutes(plan.routes)) return null;
72    return {
73      ts: Date.now(),
74      origin: slimPlace(plan.origin),
75      destination: slimPlace(plan.destination),
76      via: plan.via ? slimPlace(plan.via) : null,
77      mode: plan.mode,
78      when: plan.when || null,
79      arriveBy: !!plan.arriveBy,
80      routes: Object.fromEntries(
81        Object.entries(plan.routes).map(([k, v]) => [k, slimRoute(v)]),
82      ),
83    };
84  }
85
86  function isPlanFresh(plan, now) {
87    if (now == null) now = Date.now();
88    return !!(plan && plan.ts && now - plan.ts <= PLAN_MAX_AGE_MS);
89  }
90
91  return {
92    samePlace, usableRoutes, bannerKey, planMatches, slimPlan, isPlanFresh,
93    PLAN_KEY: 'emap.lastPlan',
94  };
95})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.