/** * Computes the statistics of `data/example/example.gpx` with the engine, and writes them to * `data/example/example-data.ts`, where the pages that show an example (the home page, the * documentation) take them from. Run it when the example, or the way the engine computes the * statistics, changes: * * npm run generate:example * * The engine has to be built (`wasm-pack build --target bundler`, in * `wasm`). */ import fs from 'fs'; import path from 'path'; import { fileURLToPath, pathToFileURL } from 'url'; import prettier from 'prettier'; const here = path.dirname(fileURLToPath(import.meta.url)); const example = path.resolve(here, '../data/example'); const packageDirectory = path.resolve(here, '../../../../wasm/pkg'); export const exampleGPX = path.join(example, 'example.gpx'); export const exampleData = path.join(example, 'example-data.ts'); /** Whether the engine is built. */ export function engineIsBuilt() { return fs.existsSync(path.join(packageDirectory, 'gpx_wasm_bg.wasm')); } /** Loads the engine in Node: the package is made for bundlers, which import the wasm file. */ async function loadEngine() { const glue = await import(pathToFileURL(path.join(packageDirectory, 'gpx_wasm_bg.js')).href); const { instance } = await WebAssembly.instantiate( fs.readFileSync(path.join(packageDirectory, 'gpx_wasm_bg.wasm')), { './gpx_wasm_bg.js': glue } ); glue.__wbg_set_wasm(instance.exports); (instance.exports.__wbindgen_start as () => void)(); return glue as typeof import('gpx-wasm'); } type Values = Float64Array | BigInt64Array | undefined; /** The arrays that the engine has for the example, which are all the pages need. */ export async function computeExampleData() { const engine = await loadEngine(); engine.start(); const data = fs.readFileSync(exampleGPX); engine.load_files(new Uint8Array(data), Uint32Array.from([data.length]), ['example']); // numbers are enough for the times, which are milliseconds const numbers = (values: Values | Uint32Array) => values && Array.from(values as ArrayLike, (value) => Number(value)); const length = engine.total_distance().length; const points = Array.from({ length }, (_, index) => engine.selection_point(index)!); const slopeSegmentStarts = Array.from(engine.slope_segment_starts()); return { global: engine.selection_statistics(), totalDistance: numbers(engine.total_distance()), movingDistance: numbers(engine.moving_distance()), totalTime: numbers(engine.total_time()), movingTime: numbers(engine.moving_time()), elevationGain: numbers(engine.elevation_gain()), elevationLoss: numbers(engine.elevation_loss()), slope: points.map((point) => point.slope!), slopeSegmentStarts, slopeSegmentSlopes: numbers(engine.slope_segment_slopes()), // the length of each segment, which is the one of its trackpoints slopeSegmentDistances: slopeSegmentStarts.map( (start) => points[start].slopeSegmentDistance! ), lng: numbers(engine.lng()), lat: numbers(engine.lat()), ele: numbers(engine.ele()), timestamps: points.map((point) => point.time!), speed: numbers(engine.speed()), atemp: numbers(engine.atemp()), }; } async function generate() { const data = await computeExampleData(); const source = `// Generated by src/lib/scripts/example-statistics.ts from example.gpx: do not edit. export const exampleData = ${JSON.stringify(data)}; `; const options = (await prettier.resolveConfig(exampleData)) ?? {}; fs.writeFileSync( exampleData, await prettier.format(source, { ...options, parser: 'typescript' }) ); console.log(`Wrote ${path.relative(process.cwd(), exampleData)}`); } if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) { generate(); }