PageSourceSearch

https://auroramap.org/js/forecastRouting.js?v=efdc4d2b97

js auroramap.org collected 2026-09-28 06:34:36 UTC 3,427 bytes, 95 lines download raw bytes

1const FORECAST_PATH_PATTERN = /^\/forecast\/([^/]+?)(?:\.html)?\/?$/i;
2const FORECAST_CITIES_PATH = '/data/forecast-cities.json';
3
4export function getForecastSlugFromPathname(pathname) {
5    if (typeof pathname !== 'string') return null;
6
7    const match = pathname.match(FORECAST_PATH_PATTERN);
8    if (!match) return null;
9
10    let slug = match[1];
11    try {
12        slug = decodeURIComponent(slug);
13    } catch (err) {
14        // A malformed escape (e.g. a lone %) makes decodeURIComponent throw.
15        // This runs before the caller's try block, so an unhandled throw here
16        // would break page init for anyone arriving on a broken link.
17        console.warn(`Malformed forecast slug in path: ${pathname}`);
18        return null;
19    }
20
21    return slug.trim().toLowerCase();
22}
23
24function setMetaAttribute(selector, attribute, value) {
25    const el = document.querySelector(selector);
26    if (el) {
27        el.setAttribute(attribute, value);
28    }
29}
30
31export async function resolveForecastCityTarget() {
32    const slug = getForecastSlugFromPathname(window.location.pathname);
33    if (!slug) return null;
34
35    try {
36        const res = await fetch(FORECAST_CITIES_PATH, { cache: 'no-store' });
37        if (!res.ok) {
38            console.warn(`Could not load forecast cities from ${FORECAST_CITIES_PATH}`);
39            return null;
40        }
41
42        const cities = await res.json();
43        const city = cities[slug];
44        if (!city) return null;
45
46        return {
47            slug,
48            name: city.name,
49            lat: Number(city.lat),
50            lon: Number(city.lon)
51        };
52    } catch (err) {
53        console.warn('Failed to resolve forecast city target from URL path.', err);
54        return null;
55    }
56}
57
58export function applyForecastCitySeo(cityTarget) {
59    if (!cityTarget) return;
60
61    const { slug, name } = cityTarget;
62    const title = `Aurora Forecast for ${name} - Real-Time Visibility`;
63    const description = `Current aurora borealis forecast for ${name}. Real-time Kp index and visibility probability map for ${name}.`;
64    const pageUrl = `https://auroramap.org/forecast/${slug}/`;
65
66    document.title = title;
67    setMetaAttribute('meta[name="description"]', 'content', description);
68    setMetaAttribute('meta[property="og:url"]', 'content', pageUrl);
69    setMetaAttribute('meta[property="og:title"]', 'content', title);
70    setMetaAttribute('meta[property="og:description"]', 'content', description);
71    setMetaAttribute('meta[property="twitter:url"]', 'content', pageUrl);
72    setMetaAttribute('meta[property="twitter:title"]', 'content', title);
73    setMetaAttribute('meta[property="twitter:description"]', 'content', description);
74    setMetaAttribute('link[rel="canonical"]', 'href', pageUrl);
75
76    const header = document.querySelector('header h1');
77    if (header) {
78        header.textContent = `Aurora Forecast: ${name}`;
79    }
80
81    const { lat, lon } = cityTarget;
82    const existingSchema = document.getElementById('ld-place');
83    if (existingSchema) existingSchema.remove();
84    const script = document.createElement('script');
85    script.type = 'application/ld+json';
86    script.id = 'ld-place';
87    script.textContent = JSON.stringify({
88        '@context': 'https://schema.org',
89        '@type': 'Place',
90        name,
91        geo: { '@type': 'GeoCoordinates', latitude: lat, longitude: lon },
92        url: pageUrl
93    });
94    document.head.appendChild(script);
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.