import { createServer } from 'node:http';
import { pathToFileURL } from 'node:url';
import { parseAPI } from '@parseapi/sdk';
// Your application supplies its own route. These are sample waypoint sketches.
// example:routes
export const ROUTES = {
denver: { name: 'Denver sample', path: [[39.7392, -104.9903], [39.7508, -104.9766], [39.77, -104.9]] },
foothills: { name: 'Foothills sample', path: [[39.7328, -105.2385], [39.739, -105.224], [39.747, -105.214]] }
};
// /example:routes
const SAMPLES = 64;
export function routeDistance(path) {
let distance = 0;
for (let index = 1; index < path.length; index += 1) {
const [a, b] = [path[index - 1], path[index]].map(point => point.map(value => value * Math.PI / 180));
const h = Math.sin((b[0] - a[0]) / 2) ** 2 + Math.cos(a[0]) * Math.cos(b[0]) * Math.sin((b[1] - a[1]) / 2) ** 2;
distance += 6371.0088 * 2 * Math.asin(Math.sqrt(Math.min(1, h)));
}
return distance;
}
export function chartFor(points) {
const known = points.map(point => point.elevation).filter(value => value !== null);
if (!known.length) return { path: '', dots: [], minimum: null, maximum: null, available: 0 };
const minimum = Math.min(...known), maximum = Math.max(...known);
const padding = Math.max(5, (maximum - minimum) * .1);
const low = minimum - padding, high = maximum + padding;
const dots = [];
let path = '', connected = false;
// example:chart
for (const [index, point] of points.entries()) {
if (point.elevation === null) { connected = false; continue; }
const x = 20 + index / (points.length - 1) * 760;
const y = 220 - (point.elevation - low) / (high - low) * 200;
path += `${connected ? 'L' : 'M'}${x.toFixed(2)},${y.toFixed(2)} `;
dots.push({ x, y });
connected = true;
}
// /example:chart
return { path: path.trim(), dots, minimum, maximum, available: known.length };
}
export async function routeProfile(id, parse, signal) {
if (!Object.hasOwn(ROUTES, id)) throw new Error('unknown_route');
const route = ROUTES[id];
// example:request
const profile = await parse.elevation.path(route.path, 64, { signal });
// /example:request
if (!profile || !Array.isArray(profile.points) || profile.points.length !== SAMPLES ||
profile.points.some(point => !point || !Number.isFinite(point.latitude) || Math.abs(point.latitude) > 90 ||
!Number.isFinite(point.longitude) || Math.abs(point.longitude) > 180 ||
(point.elevation !== null && !Number.isFinite(point.elevation)))) throw new Error('unexpected_response');
return { name: route.name, distance: routeDistance(route.path), points: profile.points, chart: chartFor(profile.points) };
}
export const PAGE = `
Route elevation profileSee the shape of your route.
Compare ground elevation along a set of waypoints.
Choose a sample to load its profile.
Waypoint distance
Lowest known sample
Highest known sample
0 km
Sample waypoints are sketches, not directions. Heights describe ground elevation, not bridge or building decks. Missing samples remain gaps.
`;
export function createAppServer(parse) {
return createServer(async (request, response) => {
response.setHeader('Cache-Control', 'no-store');
if (request.method !== 'GET') { response.writeHead(405, { Allow: 'GET' }); response.end(); return; }
const url = new URL(request.url, 'http://127.0.0.1');
if (url.pathname === '/') { response.setHeader('Content-Type', 'text/html; charset=utf-8'); response.end(PAGE); return; }
if (url.pathname !== '/profile') { response.writeHead(404); response.end(); return; }
response.setHeader('Content-Type', 'application/json');
const id = url.searchParams.get('route') || '';
if (!Object.hasOwn(ROUTES, id)) { response.writeHead(400); response.end(JSON.stringify({ error: 'Choose a listed route.' })); return; }
const controller = new AbortController();
response.once('close', () => { if (!response.writableEnded) controller.abort(); });
try { response.end(JSON.stringify(await routeProfile(id, parse, controller.signal))); }
catch { if (!response.destroyed) { response.writeHead(502); response.end(JSON.stringify({ error: 'The elevation lookup could not complete.' })); } }
});
}
function main() {
const port = Number(process.env.PORT || 8000);
if (!Number.isInteger(port) || port < 1 || port > 65535) throw new Error('PORT must be between 1 and 65535.');
const key = process.env.PARSEAPI_KEY;
if (!key) throw new Error('Set PARSEAPI_KEY to your secret server key.');
const parse = parseAPI(key, { timeoutMs: 8000, retries: 0 });
createAppServer(parse).listen(port, '127.0.0.1', () => console.log('Open http://127.0.0.1:' + port));
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
try { main(); } catch (error) { console.error(error.message); process.exitCode = 1; }
}