From 18de70af684b921f36c0774c1125a6053ab8d869 Mon Sep 17 00:00:00 2001 From: Kohei Otsuka Date: Wed, 28 Oct 2026 03:12:31 +0900 Subject: [PATCH 1/2] feat(demo): add a browser benchmark page comparing proj-wasm and proj4js - demo/benchmark.html with its own portal card: the same point sequence and the same CRS rows go forward and back through proj-wasm (single-threaded browser runtime) and proj4js. The page shows elapsed time, success and failure counts, round-trip error in metres, the largest difference between the two engines' forward output, and the message behind every failure. Work larger than the point set cycles back to the start. - CRS scope: EPSG:3857, JGD2000 and Tokyo plane rectangular I-XIX, JGD2000 UTM 51-55 and the historical city-plan grids, selectable as all, one group or one CRS. Rows are found through the Atlas search around the whole bundled dataset, so they do not depend on the points of the run; an id that cannot be found stays in the table as not found. - A one-click contrast (engine-coverage): plane rectangular IX, where both engines succeed, against historical grid zone B, which only proj-wasm can read. - Bundled data: summits from the GSI list of 1003 major mountains and the four extreme points of Japan, with source, licence (PDL1.0) and processing notes. - proj4 2.22.0 as an exact devDependency; it is not a runtime dependency and the demo is not in the published files. - Pages build verification covers the new page, its data and the second portal card. - Candidate CRS demo: a test that the page calls initDemo(), a test for going back to neutral after a manual change, one area-name mapping instead of two, jsdom ^30.0.1. Co-Authored-By: Claude Opus 5.5 --- README.ja.md | 1 + README.md | 1 + demo/benchmark-engine.d.ts | 129 ++ demo/benchmark-engine.js | 443 +++++++ demo/benchmark-presets.d.ts | 30 + demo/benchmark-presets.js | 61 + demo/benchmark.css | 48 + demo/benchmark.d.ts | 63 + demo/benchmark.html | 142 +++ demo/benchmark.js | 589 +++++++++ demo/data/benchmark-places.json | 1065 +++++++++++++++++ demo/data/benchmark-places.provenance.json | 58 + demo/main.js | 24 +- demo/map-geometry.ts | 6 +- demo/presets.d.ts | 1 + demo/presets.js | 12 +- index.html | 11 + .../changes/atlas-benchmark-demo/tasks.md | 22 +- package.json | 3 +- pnpm-lock.yaml | 28 +- scripts/verify-demo-pages-build.mjs | 57 + tests/benchmark-engine.test.ts | 228 ++++ tests/benchmark-wiring.test.ts | 291 +++++ tests/demo-bootstrap.test.ts | 48 + tests/demo-map-geometry.test.ts | 25 +- tests/demo-wiring.test.ts | 49 +- tests/proj-wasm-transform.test.ts | 32 +- tests/public-api.test.ts | 8 +- tests/publish-and-demo.test.ts | 24 +- vite.config.ts | 1 + 30 files changed, 3426 insertions(+), 74 deletions(-) create mode 100644 demo/benchmark-engine.d.ts create mode 100644 demo/benchmark-engine.js create mode 100644 demo/benchmark-presets.d.ts create mode 100644 demo/benchmark-presets.js create mode 100644 demo/benchmark.css create mode 100644 demo/benchmark.d.ts create mode 100644 demo/benchmark.html create mode 100644 demo/benchmark.js create mode 100644 demo/data/benchmark-places.json create mode 100644 demo/data/benchmark-places.provenance.json create mode 100644 tests/benchmark-engine.test.ts create mode 100644 tests/benchmark-wiring.test.ts create mode 100644 tests/demo-bootstrap.test.ts diff --git a/README.ja.md b/README.ja.md index 587f506..c1ee31b 100644 --- a/README.ja.md +++ b/README.ja.md @@ -223,6 +223,7 @@ proj-wasm 0.1.0-alpha8 は PROJ object を解放してもメモリを全部は - 入口ページ: `index.html` - 実 demo: `demo/crs-candidate-lookup.html` +- benchmark のデモ: `demo/benchmark.html`(同じ地点列・同じ CRS で proj-wasm と proj4js を往復させ、時間・成功/失敗・往復誤差・2 つのエンジンの順方向の出力の差・失敗の理由を並べます。地点データとその出典は `demo/data/benchmark-places.provenance.json`) - GitHub Pages: `https://code4history.github.io/Atlas/` デモ画面には 3 つの対比のプリセットがあり、それぞれ A と B の 2 ボタンを持ちます。 diff --git a/README.md b/README.md index 39d486c..126704f 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,7 @@ in [CHANGELOG.md](./CHANGELOG.md). - Root portal: `index.html` - Demo app: `demo/crs-candidate-lookup.html` +- Benchmark demo: `demo/benchmark.html` (proj-wasm against proj4js on the same points and CRSs: elapsed time, success and failure counts, round-trip error, the difference of the two engines' forward output, and the message behind every failure; bundled Japan place data with its sources in `demo/data/benchmark-places.provenance.json`) - GitHub Pages target: `https://code4history.github.io/Atlas/` The demo page has three preset contrasts. Each contrast has an A and a B button: diff --git a/demo/benchmark-engine.d.ts b/demo/benchmark-engine.d.ts new file mode 100644 index 0000000..bb44126 --- /dev/null +++ b/demo/benchmark-engine.d.ts @@ -0,0 +1,129 @@ +import type { ListProjectedCrsFromProjWasmOptions, ProjectedCrsInfo } from "../src/proj-wasm-crs.ts"; + +/** One point of the bundled dataset. */ +export interface BenchmarkPlace { + category: string; + name: string; + lon: number; + lat: number; + sourceId?: string; + sourceUrl?: string; +} + +/** The CRS to benchmark: everything, one group, or one CRS. */ +export interface CrsScope { + kind: "all" | "group" | "crs"; + groupId?: string; + id?: string; +} + +export interface CrsGroup { + id: string; + label: string; + ids: readonly string[]; +} + +/** One point that did not go through both directions. */ +export interface BenchmarkFailure { + index: number; + lon: number; + lat: number; + message: string; +} + +/** What one engine did on one CRS. */ +export interface BenchmarkEngineStats { + totalMs: number; + success: number; + failure: number; + meanErrorMeters: number | null; + maxErrorMeters: number | null; + failures: BenchmarkFailure[]; +} + +/** One row of the result table. */ +export interface BenchmarkRowResult { + id: string; + name: string; + found: boolean; + status: "ok" | "failed" | "not-found"; + workCount: number; + projWasm: BenchmarkEngineStats; + proj4js: BenchmarkEngineStats; + forwardDifferenceMaxMeters: number | null; +} + +export interface BenchmarkResult { + pointCount: number; + workCount: number; + laps: number; + targetIds: string[]; + searchCount: number; + forwardDifferenceMaxMeters: number | null; + rows: BenchmarkRowResult[]; + totals: { + projWasm: { totalMs: number; success: number; failure: number }; + proj4js: { totalMs: number; success: number; failure: number }; + }; +} + +/** The part of the Atlas runtime the benchmark needs. */ +export interface BenchmarkRuntime { + listProjectedCrsFromProjWasm(options: ListProjectedCrsFromProjWasmOptions): Promise; +} + +export interface BenchmarkRowLookup { + rows: Map; + searches: number; +} + +export const LONLAT_WGS84_DEFINITION: string; +export const DEFAULT_MAX_SEARCHES: number; +export const CRS_GROUPS: readonly CrsGroup[]; +export const CRS_TARGET_IDS: readonly string[]; + +export function crsScopeIds(scope?: CrsScope | null): string[]; +export function selectPoints( + places: readonly BenchmarkPlace[], + options?: { category?: string; pointLimit?: number | "all" }, +): BenchmarkPlace[]; +export function buildWorkSequence(places: readonly T[], workCount: number): T[]; +export function lapsFor(pointCount: number, workCount: number): number; +export function distanceMeters( + from: readonly [number, number], + to: readonly [number, number], +): number; +export function searchPlaces(places: readonly T[], maxSearches?: number): T[]; +export function findCrsRows(options: { + runtime: BenchmarkRuntime; + places: readonly BenchmarkPlace[]; + targetIds: readonly string[]; + maxSearches?: number; +}): Promise; + +export function runProjWasmEngine(options: { + projWasm: unknown; + row: Pick; + sequence: readonly BenchmarkPlace[]; + now?: () => number; +}): Promise<{ stats: BenchmarkEngineStats; forward: Array<[number, number] | null> }>; + +export function runProj4Engine(options: { + proj4js: unknown; + row: Pick; + sequence: readonly BenchmarkPlace[]; + now?: () => number; +}): { stats: BenchmarkEngineStats; forward: Array<[number, number] | null> }; + +export function runBenchmark(options: { + places: readonly BenchmarkPlace[]; + /** Where to search for the Atlas rows (defaults to `places`); the page passes the whole dataset. */ + catalogPlaces?: readonly BenchmarkPlace[]; + workCount: number; + targetIds: readonly string[]; + runtime: BenchmarkRuntime; + projWasm: unknown; + proj4js: unknown; + maxSearches?: number; + now?: () => number; +}): Promise; diff --git a/demo/benchmark-engine.js b/demo/benchmark-engine.js new file mode 100644 index 0000000..0e493da --- /dev/null +++ b/demo/benchmark-engine.js @@ -0,0 +1,443 @@ +/** + * Benchmark engine: the arithmetic of the benchmark page (no DOM, no globals that a test cannot + * inject). + * + * Both engines are driven through their own raw API, in the same shape for every CRS: + * + * - `proj-wasm` for a CRS row (`EPSG:...`): one `projCreateCrsToCrs` operation from WGS 84 + * lon/lat to the row's definition, used `PJ_FWD` and then `PJ_INV`. + * - `proj-wasm` for a pipeline row (`UTJCP:*`, its `proj4` text is a pipeline, not a CRS): one + * `projCreate` object, used `PJ_INV` (the pipeline runs lon/lat -> grid) and then `PJ_FWD`. + * - `proj4js` for both kinds: `proj4(source, row.proj4)`, used `.forward` and then `.inverse`. + * + * Atlas' own transform helpers are deliberately not used: the page measures the engines, not + * Atlas' wrapping of them. A definition one engine cannot read is an explicit failure with the + * message the engine threw; it is never dropped from the result. + * + * Every engine call is made once per point (forward and inverse), for both engines, so the + * elapsed times cover the same number of calls. + */ + +/** The source CRS of every transformation, the same text for both engines. */ +export const LONLAT_WGS84_DEFINITION = "+proj=longlat +datum=WGS84 +no_defs +type=crs"; + +/** Metres per degree of latitude (the scale the round-trip error is reported in). */ +const METERS_PER_DEGREE = 111320; + +/** Half-width of the box searched around a point (in degrees); about 55 m. */ +const SEARCH_BOX_DEGREES = 0.0005; + +/** How many Atlas searches one run may make to find the requested rows. */ +export const DEFAULT_MAX_SEARCHES = 600; + +function planeRectangularIds(firstCode, count) { + return Object.freeze(Array.from({ length: count }, (_, index) => `EPSG:${firstCode + index}`)); +} + +/** The CRS the page can benchmark, in the groups the CRS scope selector offers. */ +export const CRS_GROUPS = Object.freeze([ + Object.freeze({ id: "web-mercator", label: "Web メルカトル", ids: Object.freeze(["EPSG:3857"]) }), + Object.freeze({ + id: "jgd2000-plane", + label: "JGD2000 平面直角座標系 I〜XIX", + ids: planeRectangularIds(2443, 19), + }), + Object.freeze({ + id: "tokyo-plane", + label: "旧日本測地系 平面直角座標系 I〜XIX", + ids: planeRectangularIds(30161, 19), + }), + Object.freeze({ + id: "jgd2000-utm", + label: "JGD2000 UTM 51〜55", + ids: Object.freeze(["EPSG:3097", "EPSG:3098", "EPSG:3099", "EPSG:3100", "EPSG:3101"]), + }), + Object.freeze({ + id: "historical-grid", + label: "歴史グリッド(A・B・C 帯)", + ids: Object.freeze(["UTJCP:ZONE-A", "UTJCP:ZONE-B", "UTJCP:ZONE-C"]), + }), +]); + +/** Every CRS the page knows, in group order. */ +export const CRS_TARGET_IDS = Object.freeze(CRS_GROUPS.flatMap((group) => group.ids)); + +/** + * The CRS ids of a scope: `{ kind: "all" }`, `{ kind: "group", groupId }` or + * `{ kind: "crs", id }`. + */ +export function crsScopeIds(scope) { + const kind = scope?.kind ?? "all"; + if (kind === "all") return [...CRS_TARGET_IDS]; + if (kind === "group") { + const group = CRS_GROUPS.find((entry) => entry.id === scope.groupId); + return group ? [...group.ids] : []; + } + if (kind === "crs") return scope.id ? [scope.id] : []; + throw new Error(`Unknown CRS scope kind: ${String(kind)}`); +} + +/** + * The places of one category (`"all"` for every category), cut to `pointLimit` (`"all"` keeps + * every place). The order is the order of the dataset. + */ +export function selectPoints(places, { category = "all", pointLimit = "all" } = {}) { + const matching = (places ?? []).filter((place) => category === "all" || place.category === category); + if (pointLimit === "all" || pointLimit === undefined || pointLimit === null) { + return matching.map((place) => ({ ...place })); + } + const limit = Math.floor(Number(pointLimit)); + const kept = Number.isFinite(limit) && limit >= 0 ? matching.slice(0, limit) : matching; + return kept.map((place) => ({ ...place })); +} + +/** + * The points one run transforms: `workCount` of them, going back to the first point when the + * requested work is larger than the point set. + */ +export function buildWorkSequence(places, workCount) { + const points = places ?? []; + if (points.length === 0) return []; + const total = Math.floor(Number(workCount)); + if (!Number.isFinite(total) || total <= 0) return []; + return Array.from({ length: total }, (_, index) => points[index % points.length]); +} + +/** How many times the point set is walked for a given work count. */ +export function lapsFor(pointCount, workCount) { + if (pointCount <= 0) return 0; + return Math.ceil(Math.floor(Number(workCount)) / pointCount); +} + +function clock() { + const source = globalThis.performance; + if (source && typeof source.now === "function") return () => source.now(); + return () => Date.now(); +} + +function messageOf(error) { + if (error instanceof Error) return error.message; + return String(error); +} + +/** The distance in metres between two lon/lat points, the same way the page reports error. */ +export function distanceMeters(from, to) { + const latRad = (from[1] * Math.PI) / 180; + const dx = (to[0] - from[0]) * METERS_PER_DEGREE * Math.cos(latRad); + const dy = (to[1] - from[1]) * METERS_PER_DEGREE; + return Math.hypot(dx, dy); +} + +/** + * Atlas' search is bbox-based: a row comes back only when the requested extent fits inside the + * row's bbox, so one Japan-wide extent would never return a plane rectangular zone. The rows are + * therefore looked up with one search per probe point (a small box around it, custom projections + * on, `onshoreOnly: false`), and the searches stop early once every requested id is found. + * + * The probe points spread over the map first: one point per 1-degree cell, then one per + * remaining 0.5-degree cell, then one per remaining 0.25-degree cell. A row is found as soon as + * one probe falls inside its bbox, so a coarse sweep finds most rows in a few searches and the + * finer sweeps only run for rows that are still missing. + */ +const PROBE_CELL_DEGREES = [1, 0.5, 0.25]; + +export function searchPlaces(places, maxSearches = DEFAULT_MAX_SEARCHES) { + const points = places ?? []; + const limit = Math.max(1, Math.floor(Number(maxSearches))); + const picked = []; + const used = new Set(); + for (const size of PROBE_CELL_DEGREES) { + const cells = new Set(); + points.forEach((place, index) => { + if (used.has(index)) return; + const cell = `${Math.floor(place.lon / size)}:${Math.floor(place.lat / size)}`; + if (cells.has(cell)) return; + if (picked.some((other) => Math.floor(other.lon / size) === Math.floor(place.lon / size) + && Math.floor(other.lat / size) === Math.floor(place.lat / size))) return; + cells.add(cell); + used.add(index); + picked.push(place); + }); + } + return picked.slice(0, limit); +} + +/** Find the Atlas rows of `targetIds` by searching around the given places. */ +export async function findCrsRows({ runtime, places, targetIds, maxSearches = DEFAULT_MAX_SEARCHES }) { + const wanted = new Set(targetIds ?? []); + const found = new Map(); + let searches = 0; + for (const place of searchPlaces(places, maxSearches)) { + if (found.size >= wanted.size) break; + const extent = { + west: place.lon - SEARCH_BOX_DEGREES, + south: place.lat - SEARCH_BOX_DEGREES, + east: place.lon + SEARCH_BOX_DEGREES, + north: place.lat + SEARCH_BOX_DEGREES, + }; + const rows = await runtime.listProjectedCrsFromProjWasm({ + extent, + includeCustomProjections: true, + onshoreOnly: false, + }); + searches += 1; + for (const row of rows) { + if (wanted.has(row.id) && !found.has(row.id)) found.set(row.id, row); + } + } + return { rows: found, searches }; +} + +function emptyStats() { + return { totalMs: 0, success: 0, failure: 0, meanErrorMeters: null, maxErrorMeters: null, failures: [] }; +} + +function finishStats(stats) { + const errors = stats.errors ?? []; + stats.meanErrorMeters = errors.length + ? errors.reduce((sum, value) => sum + value, 0) / errors.length + : null; + stats.maxErrorMeters = errors.length ? Math.max(...errors) : null; + delete stats.errors; + return stats; +} + +function fail(stats, index, place, message) { + stats.failure += 1; + stats.failures.push({ index, lon: place.lon, lat: place.lat, message }); +} + +/** + * Round trip through `proj-wasm`'s raw API. Returns the per-engine statistics and the forward + * outputs (CRS units, metres) of the points that succeeded. + */ +export async function runProjWasmEngine({ projWasm, row, sequence, now = clock() }) { + const stats = emptyStats(); + stats.errors = []; + const forward = new Array(sequence.length).fill(null); + const definition = row.proj4; + const isPipeline = definition.includes("+proj=pipeline"); + + let operation; + let forwardDirection; + let inverseDirection; + try { + operation = isPipeline + ? await projWasm.projCreate({ definition }) + : await projWasm.projCreateCrsToCrs({ + source_crs: LONLAT_WGS84_DEFINITION, + target_crs: definition, + }); + if (operation === undefined || operation === null) { + throw new Error(`proj-wasm could not create an operation for ${row.id}`); + } + forwardDirection = isPipeline ? projWasm.PJ_INV : projWasm.PJ_FWD; + inverseDirection = isPipeline ? projWasm.PJ_FWD : projWasm.PJ_INV; + } catch (error) { + const message = messageOf(error); + sequence.forEach((place, index) => fail(stats, index, place, message)); + return finishStats(stats); + } + + const startedAt = now(); + try { + for (let index = 0; index < sequence.length; index += 1) { + const place = sequence[index]; + try { + const forwardCoords = await projWasm.coordArray(1); + await projWasm.setCoords(forwardCoords, [[place.lon, place.lat, 0, 0]]); + const forwardErrors = await projWasm.projTransArray({ + p: operation, + direction: forwardDirection, + n: 1, + coord: forwardCoords, + }); + const [x, y] = await projWasm.getCoords(forwardCoords, 0); + if (!Number.isFinite(x) || !Number.isFinite(y)) { + throw new Error( + `proj-wasm returned a non-finite coordinate (projTransArray reported ${String(forwardErrors)} error(s))`, + ); + } + + const inverseCoords = await projWasm.coordArray(1); + await projWasm.setCoords(inverseCoords, [[x, y, 0, 0]]); + await projWasm.projTransArray({ + p: operation, + direction: inverseDirection, + n: 1, + coord: inverseCoords, + }); + const back = await projWasm.getCoords(inverseCoords, 0); + if (!Number.isFinite(back[0]) || !Number.isFinite(back[1])) { + throw new Error("proj-wasm returned a non-finite coordinate from the inverse step"); + } + + stats.errors.push(distanceMeters([place.lon, place.lat], [back[0], back[1]])); + stats.success += 1; + forward[index] = [x, y]; + } catch (error) { + fail(stats, index, place, messageOf(error)); + } + } + } finally { + stats.totalMs = now() - startedAt; + if (operation !== undefined && operation !== null) { + try { + await projWasm.projDestroy({ pj: operation }); + } catch { + // The object is gone either way; keep the measured result. + } + } + } + + finishStats(stats); + return { stats, forward }; +} + +/** Round trip through `proj4js`. */ +export function runProj4Engine({ proj4js, row, sequence, now = clock() }) { + const stats = emptyStats(); + stats.errors = []; + const forward = new Array(sequence.length).fill(null); + + let converter; + try { + converter = proj4js(LONLAT_WGS84_DEFINITION, row.proj4); + } catch (error) { + const message = messageOf(error); + sequence.forEach((place, index) => fail(stats, index, place, message)); + return { stats: finishStats(stats), forward }; + } + + const startedAt = now(); + for (let index = 0; index < sequence.length; index += 1) { + const place = sequence[index]; + try { + const projected = converter.forward([place.lon, place.lat]); + const x = projected?.[0]; + const y = projected?.[1]; + if (!Number.isFinite(x) || !Number.isFinite(y)) { + throw new Error("proj4js returned a non-finite coordinate from the forward step"); + } + const back = converter.inverse([x, y]); + if (!Number.isFinite(back?.[0]) || !Number.isFinite(back?.[1])) { + throw new Error("proj4js returned a non-finite coordinate from the inverse step"); + } + stats.errors.push(distanceMeters([place.lon, place.lat], [back[0], back[1]])); + stats.success += 1; + forward[index] = [x, y]; + } catch (error) { + fail(stats, index, place, messageOf(error)); + } + } + stats.totalMs = now() - startedAt; + + finishStats(stats); + return { stats, forward }; +} + +function forwardDifferenceMax(wasmForward, proj4Forward) { + let max = null; + for (let index = 0; index < wasmForward.length; index += 1) { + const wasm = wasmForward[index]; + const js = proj4Forward[index]; + if (!wasm || !js) continue; + const distance = Math.hypot(wasm[0] - js[0], wasm[1] - js[1]); + if (max === null || distance > max) max = distance; + } + return max; +} + +function rowStatus(row) { + if (!row.found) return "not-found"; + return row.projWasm.failure === 0 && row.proj4js.failure === 0 ? "ok" : "failed"; +} + +/** + * Run one benchmark: the same point sequence and the same CRS rows for both engines. + * + * `places` are the points of the run (their order is the order of the sequence), `workCount` the + * requested work, `targetIds` the CRS to compare. `runtime` is the Atlas runtime + * (`listProjectedCrsFromProjWasm`), `projWasm` the loaded `proj-wasm` module and `proj4js` the + * `proj4js` function. Initialization is the caller's job and is not timed. + */ +export async function runBenchmark({ + places, + catalogPlaces, + workCount, + targetIds, + runtime, + projWasm, + proj4js, + maxSearches = DEFAULT_MAX_SEARCHES, + now = clock(), +}) { + const pointCount = (places ?? []).length; + const sequence = buildWorkSequence(places, workCount); + const ids = [...(targetIds ?? [])]; + // The rows are a property of the CRS, not of the run: search around the whole dataset when the + // caller gives it, so a run over a few northern points still finds the southern zones. + const lookup = await findCrsRows({ runtime, places: catalogPlaces ?? places ?? [], targetIds: ids, maxSearches }); + + const resultRows = []; + for (const id of ids) { + const row = lookup.rows.get(id); + if (row === undefined) { + resultRows.push({ + id, + // Either the id is not in the catalog, or no place of the dataset lies inside its area of + // use (the search can only find a row around a point). + name: "見つからない(カタログに無いか、データのどの地点もこの CRS の範囲に入らない)", + found: false, + status: "not-found", + workCount: sequence.length, + projWasm: emptyStats(), + proj4js: emptyStats(), + forwardDifferenceMaxMeters: null, + }); + continue; + } + + const wasm = await runProjWasmEngine({ projWasm, row, sequence, now }); + const js = runProj4Engine({ proj4js, row, sequence, now }); + const entry = { + id, + name: row.name, + found: true, + workCount: sequence.length, + projWasm: wasm.stats, + proj4js: js.stats, + forwardDifferenceMaxMeters: forwardDifferenceMax(wasm.forward, js.forward), + }; + entry.status = rowStatus(entry); + resultRows.push(entry); + } + + const totals = { + projWasm: { totalMs: 0, success: 0, failure: 0 }, + proj4js: { totalMs: 0, success: 0, failure: 0 }, + }; + let forwardDifferenceMaxMeters = null; + for (const entry of resultRows) { + for (const engine of ["projWasm", "proj4js"]) { + totals[engine].totalMs += entry[engine].totalMs; + totals[engine].success += entry[engine].success; + totals[engine].failure += entry[engine].failure; + } + const difference = entry.forwardDifferenceMaxMeters; + if (difference !== null && (forwardDifferenceMaxMeters === null || difference > forwardDifferenceMaxMeters)) { + forwardDifferenceMaxMeters = difference; + } + } + + return { + pointCount, + workCount: sequence.length, + laps: lapsFor(pointCount, sequence.length), + targetIds: ids, + searchCount: lookup.searches, + forwardDifferenceMaxMeters, + rows: resultRows, + totals, + }; +} diff --git a/demo/benchmark-presets.d.ts b/demo/benchmark-presets.d.ts new file mode 100644 index 0000000..e7b8279 --- /dev/null +++ b/demo/benchmark-presets.d.ts @@ -0,0 +1,30 @@ +import type { CrsScope } from "./benchmark-engine.js"; + +export interface BenchmarkPresetInput { + category: string; + pointLimit: number | "all"; + laps: number; + crsScope: CrsScope; +} + +export interface BenchmarkPresetOption { + id: string; + label: string; + expectation: string; + input: BenchmarkPresetInput; +} + +export interface BenchmarkPresetContrast { + id: string; + title: string; + options: BenchmarkPresetOption[]; +} + +export const ENGINE_COVERAGE_EXPECTATION: string; +export const BENCHMARK_PRESETS: readonly BenchmarkPresetContrast[]; +export const NEUTRAL_EXPECTATION: string; +export function findBenchmarkPreset( + presetId: string, + optionId: string, +): BenchmarkPresetOption | undefined; +export function presetWorkCount(input: BenchmarkPresetInput, pointCount: number): number; diff --git a/demo/benchmark-presets.js b/demo/benchmark-presets.js new file mode 100644 index 0000000..b08f0e7 --- /dev/null +++ b/demo/benchmark-presets.js @@ -0,0 +1,61 @@ +/** + * Preset values for the benchmark demo (data only — this module never touches the DOM). + * + * The page (`demo/benchmark.js`) and the tests (`tests/benchmark-engine.test.ts`, + * `tests/benchmark-wiring.test.ts`) import these values, so the numbers and the expectation line + * live here and nowhere else. + * + * An option's `input` is what the controls of the page show: a dataset category, a point limit + * (`"all"` or a number), the number of laps through that point set (`laps`) and a CRS scope. The + * work count of a run is `laps` times the number of points selected, which is why it is written + * as laps here instead of a raw number. + */ + +export const ENGINE_COVERAGE_EXPECTATION = + "エンジンの対応範囲で結果が分かれる。A は proj-wasm も proj4js も全点成功し、順方向の差は小さい(値は画面に出る)。B は proj-wasm だけが成功し、proj4js は全点が「pipeline を読めない」失敗になる(失敗が表に明示される)。"; + +function presetInput(crsScope) { + return Object.freeze({ + category: "mountain-summit", + pointLimit: "all", + laps: 1, + crsScope: Object.freeze(crsScope), + }); +} + +export const BENCHMARK_PRESETS = Object.freeze([ + { + id: "engine-coverage", + title: "エンジンの対応範囲", + options: [ + { + id: "a", + label: "A: 平面直角座標系 IX 系(EPSG:2451)・全地点", + expectation: ENGINE_COVERAGE_EXPECTATION, + input: presetInput({ kind: "crs", id: "EPSG:2451" }), + }, + { + id: "b", + label: "B: 歴史グリッド B 帯(UTJCP:ZONE-B)・同じ地点", + expectation: ENGINE_COVERAGE_EXPECTATION, + input: presetInput({ kind: "crs", id: "UTJCP:ZONE-B" }), + }, + ], + }, +]); + +/** The one-line description shown while no preset is pressed. */ +export const NEUTRAL_EXPECTATION = "手で変えた入力(プリセットではない)。プリセットのボタンを押すと戻る。"; + +/** Find one preset option by contrast id and option id. */ +export function findBenchmarkPreset(presetId, optionId) { + return BENCHMARK_PRESETS.find((preset) => preset.id === presetId)?.options.find( + (option) => option.id === optionId, + ); +} + +/** The work count of a run: one lap is one pass through the selected point set. */ +export function presetWorkCount(input, pointCount) { + const laps = Math.max(1, Math.floor(Number(input?.laps ?? 1))); + return Math.max(0, Math.floor(Number(pointCount)) * laps); +} diff --git a/demo/benchmark.css b/demo/benchmark.css new file mode 100644 index 0000000..1fbafc7 --- /dev/null +++ b/demo/benchmark.css @@ -0,0 +1,48 @@ +.atlas-benchmark-controls label { + white-space: nowrap; +} + +.atlas-benchmark-controls input[type="number"] { + width: 6em; +} + +.atlas-benchmark-summary { + margin: 16px 24px; +} + +.atlas-benchmark-line { + margin: 6px 0 0; + font-size: 13px; + color: #2c3e50; +} + +.atlas-benchmark-table { + font-variant-numeric: tabular-nums; +} + +.atlas-benchmark-table th, +.atlas-benchmark-table td { + white-space: nowrap; +} + +.atlas-benchmark-row-failed td { + background: rgba(192, 57, 43, 0.08); +} + +.atlas-benchmark-row-not-found td { + color: #7f8c8d; +} + +.atlas-benchmark-failures { + margin: 0; + padding-left: 18px; +} + +.atlas-benchmark-failures li { + margin-bottom: 10px; +} + +.atlas-benchmark-failure-point { + font-size: 12px; + color: #7f8c8d; +} diff --git a/demo/benchmark.d.ts b/demo/benchmark.d.ts new file mode 100644 index 0000000..b906644 --- /dev/null +++ b/demo/benchmark.d.ts @@ -0,0 +1,63 @@ +import type { + BenchmarkPlace, + BenchmarkResult, + BenchmarkRuntime, + CrsGroup, + CrsScope, +} from "./benchmark-engine.js"; + +/** The benchmark arithmetic the page calls (a test can replace it). */ +export interface BenchmarkEngineModule { + CRS_GROUPS: readonly CrsGroup[]; + CRS_TARGET_IDS: readonly string[]; + crsScopeIds(scope?: CrsScope | null): string[]; + selectPoints( + places: readonly BenchmarkPlace[], + options?: { category?: string; pointLimit?: number | "all" }, + ): BenchmarkPlace[]; + runBenchmark(options: { + places: readonly BenchmarkPlace[]; + workCount: number; + targetIds: readonly string[]; + runtime: BenchmarkRuntime; + projWasm: unknown; + proj4js: unknown; + }): Promise; +} + +export interface BenchmarkProvenance { + title?: string; + summary?: string; + attribution?: string; + processedNotice?: string; + retrievedAt?: string; + sources?: Array<{ + name?: string; + version?: string; + pageUrl?: string; + attribution?: string; + }>; +} + +export interface BenchmarkState { + ready: boolean; + running: boolean; + activePreset: string | null; + placeCount: number; + result: BenchmarkResult | null; + error: string | null; + status: string; +} + +export interface BenchmarkHandle { + whenSettled(): Promise; + getState(): BenchmarkState; +} + +export function initBenchmark(config?: { + runtime?: BenchmarkRuntime & { initializeProjWasmRuntime(options?: { projWasmAssetBaseUrl?: string | URL }): Promise | void }; + doc?: unknown; + engine?: BenchmarkEngineModule; + data?: { places: BenchmarkPlace[]; provenance: BenchmarkProvenance }; + modules?: { projWasm: unknown; proj4js: unknown }; +}): BenchmarkHandle; diff --git a/demo/benchmark.html b/demo/benchmark.html new file mode 100644 index 0000000..11043d4 --- /dev/null +++ b/demo/benchmark.html @@ -0,0 +1,142 @@ + + + + + + Atlas Benchmark Demo + + + + + +
+ ← デモ一覧 +

Atlas - Browser Benchmark Demo

+
+ + + +
+ + + +
+ + +
+ 初期化中... +
+ +
+
+

見え方:

+
+ +
+ 操作: 入力(分類・地点数の上限・作業量・CRS の範囲)を決めて「実行」を押します。同じ点列と同じ CRS で + proj-wasm と proj4js を往復(順方向 → 逆方向)させ、時間・成功/失敗・往復誤差・2 つのエンジンの順方向の差を表に出します。 + 作業量が地点数より多いときは先頭へ戻って繰り返します。地図は使いません。 +
+ +
+
+

結果の要約

+
+

まだ実行していません。

+

+ 順方向の差(両エンジンが成功した点だけの最大、m): - +

+
+ +
+
+

CRS ごとの結果

+

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
CRS名前作業量proj-wasmproj4js順方向の差 (m)状態
総時間 (ms)成功失敗往復誤差 平均 (m)往復誤差 最大 (m)総時間 (ms)成功失敗往復誤差 平均 (m)往復誤差 最大 (m)
まだ実行していません。
+
+
+ +
+
+

失敗の詳細

+
+
+

まだ失敗はありません。

+
+
+ +
+
+

出典

+
+
+
+ +
+ + + + diff --git a/demo/benchmark.js b/demo/benchmark.js new file mode 100644 index 0000000..87a6162 --- /dev/null +++ b/demo/benchmark.js @@ -0,0 +1,589 @@ +import { version as packageVersion } from "../package.json"; +import * as benchmarkEngineModule from "./benchmark-engine.js"; +import { + BENCHMARK_PRESETS, + NEUTRAL_EXPECTATION, + presetWorkCount, +} from "./benchmark-presets.js"; + +const DEFAULT_CONTROLS = Object.freeze({ + category: "mountain-summit", + pointLimit: "50", + workCount: "100", + scopeKind: "all", + groupId: benchmarkEngineModule.CRS_GROUPS[0].id, + crsId: benchmarkEngineModule.CRS_TARGET_IDS[0], +}); + +const state = { + ready: false, + running: false, + status: "初期化中...", + error: null, + places: [], + provenance: null, + projWasm: null, + proj4js: null, + result: null, + activePreset: null, +}; + +let dom = globalThis.document; +let injectedRuntime = null; +let injectedEngine = null; +let injectedData = null; +let injectedModules = null; +let atlasModulePromise; +let settled = Promise.resolve(); + +function defaultControls() { + return { ...DEFAULT_CONTROLS }; +} + +/** Queue async work so tests can await everything a click started. */ +function enqueue(task) { + const next = settled.then(task); + settled = next.then(() => undefined, () => undefined); + return next; +} + +/** The benchmark engine functions; a test can replace all of them. */ +function engine() { + return injectedEngine ?? benchmarkEngineModule; +} + +function loadAtlasRuntime() { + if (injectedRuntime) return Promise.resolve(injectedRuntime); + if (!atlasModulePromise) { + atlasModulePromise = import("@c4h/atlas"); + } + return atlasModulePromise; +} + +function projWasmAssetBaseUrl() { + const modulePathSegments = new URL(import.meta.url).pathname.split("/"); + if (modulePathSegments.includes("assets")) { + return new URL("./", import.meta.url); + } + + const baseURI = dom?.baseURI && dom.baseURI !== "about:blank" ? dom.baseURI : "http://localhost/"; + const pageRoot = new URL("../", baseURI); + return new URL("assets/", pageRoot); +} + +function atlasVersion() { + return typeof __ATLAS_VERSION__ === "string" && __ATLAS_VERSION__.length > 0 + ? __ATLAS_VERSION__ + : packageVersion; +} + +function escapeHtml(value) { + return String(value) + .replaceAll("&", "&") + .replaceAll("<", "<") + .replaceAll(">", ">") + .replaceAll('"', """) + .replaceAll("'", "'"); +} + +function messageOf(error) { + return error instanceof Error ? error.message : String(error); +} + +function element(id) { + return dom.getElementById(id); +} + +function text(id) { + return element(id)?.textContent ?? ""; +} + +function formatMeters(value) { + if (value === null || value === undefined || !Number.isFinite(value)) return "-"; + if (value === 0) return "0"; + return value.toExponential(2); +} + +function formatMilliseconds(value) { + if (!Number.isFinite(value)) return "-"; + return value.toFixed(1); +} + +function pointLimitOf(value) { + return value === "all" ? "all" : Number(value); +} + +function readControls() { + const kind = element("crs-scope")?.value ?? "all"; + const scope = kind === "group" + ? { kind, groupId: element("crs-group").value } + : kind === "crs" + ? { kind, id: element("crs-id").value } + : { kind: "all" }; + const workCount = Math.floor(Number(element("work-count")?.value)); + return { + category: element("category").value, + pointLimit: pointLimitOf(element("point-limit").value), + workCount: Number.isFinite(workCount) && workCount > 0 ? workCount : 1, + crsScope: scope, + }; +} + +function syncControls(controls) { + const category = element("category"); + if (category) category.value = controls.category; + const pointLimit = element("point-limit"); + if (pointLimit) pointLimit.value = String(controls.pointLimit); + const workCount = element("work-count"); + if (workCount) workCount.value = String(controls.workCount); + const scope = element("crs-scope"); + if (scope) scope.value = controls.scopeKind; + const group = element("crs-group"); + if (group) group.value = controls.groupId; + const crs = element("crs-id"); + if (crs) crs.value = controls.crsId; + renderScopeInputs(); +} + +/** Only the selector that the chosen scope kind uses stays usable. */ +function renderScopeInputs() { + const kind = element("crs-scope")?.value ?? "all"; + const group = element("crs-group"); + const crs = element("crs-id"); + if (group) group.disabled = kind !== "group" || !state.ready; + if (crs) crs.disabled = kind !== "crs" || !state.ready; +} + +function renderScopeOptions() { + const engineModule = engine(); + const group = element("crs-group"); + if (group) { + group.innerHTML = engineModule.CRS_GROUPS + .map((entry) => ``) + .join(""); + } + const crs = element("crs-id"); + if (crs) { + crs.innerHTML = engineModule.CRS_GROUPS + .flatMap((entry) => + entry.ids.map((id) => ``)) + .join(""); + } +} + +function renderInteractivity() { + const disabled = !state.ready || state.running; + const run = element("run-button"); + if (run) run.disabled = disabled; + for (const id of ["category", "point-limit", "work-count", "crs-scope", "reset-button"]) { + const control = element(id); + if (control) control.disabled = !state.ready; + } + renderScopeInputs(); + for (const button of dom.querySelectorAll("#preset-bar button")) { + button.disabled = disabled; + } +} + +function renderStatus() { + const status = element("init-status"); + if (!status) return; + status.textContent = state.error ?? state.status; +} + +function renderSummary() { + const summary = element("result-summary"); + const forward = element("forward-diff"); + const result = state.result; + if (!result) { + if (summary) summary.textContent = "まだ実行していません。"; + if (forward) forward.textContent = "-"; + return; + } + + const notFound = result.rows.filter((row) => row.status === "not-found").length; + const failed = result.rows.filter((row) => row.status === "failed").length; + const ok = result.rows.filter((row) => row.status === "ok").length; + if (summary) { + summary.textContent = + `CRS ${result.rows.length} 件(成功 ${ok}・失敗 ${failed}・見つからない ${notFound})・` + + `地点 ${result.pointCount} 件・作業量 ${result.workCount} 回(${result.laps} 周)・` + + `Atlas の検索 ${result.searchCount} 回。` + + `proj-wasm: 成功 ${result.totals.projWasm.success}・失敗 ${result.totals.projWasm.failure}・` + + `総時間 ${formatMilliseconds(result.totals.projWasm.totalMs)} ms。` + + `proj4js: 成功 ${result.totals.proj4js.success}・失敗 ${result.totals.proj4js.failure}・` + + `総時間 ${formatMilliseconds(result.totals.proj4js.totalMs)} ms。`; + } + if (forward) forward.textContent = formatMeters(result.forwardDifferenceMaxMeters); +} + +function renderTable() { + const body = element("results-body"); + const count = element("row-count"); + const result = state.result; + if (count) count.textContent = result ? `${result.rows.length} 件` : ""; + if (!body) return; + + if (!result) { + body.innerHTML = 'まだ実行していません。'; + return; + } + if (result.rows.length === 0) { + body.innerHTML = '選んだ範囲に CRS がありません。'; + return; + } + + body.innerHTML = result.rows.map((row) => { + const cells = [ + row.id, + row.name, + String(row.workCount), + formatMilliseconds(row.projWasm.totalMs), + String(row.projWasm.success), + String(row.projWasm.failure), + formatMeters(row.projWasm.meanErrorMeters), + formatMeters(row.projWasm.maxErrorMeters), + formatMilliseconds(row.proj4js.totalMs), + String(row.proj4js.success), + String(row.proj4js.failure), + formatMeters(row.proj4js.meanErrorMeters), + formatMeters(row.proj4js.maxErrorMeters), + formatMeters(row.forwardDifferenceMaxMeters), + row.status, + ]; + return `${ + cells.map((cell) => `${escapeHtml(cell)}`).join("") + }`; + }).join(""); +} + +/** One entry per (CRS, engine, message): the number of points and where the first few are. */ +function failureLines() { + const lines = []; + for (const row of state.result?.rows ?? []) { + for (const [engineName, engineResult] of [["proj-wasm", row.projWasm], ["proj4js", row.proj4js]]) { + const byMessage = new Map(); + for (const failure of engineResult.failures) { + const entry = byMessage.get(failure.message) ?? { message: failure.message, count: 0, first: failure }; + entry.count += 1; + byMessage.set(failure.message, entry); + } + for (const entry of byMessage.values()) { + lines.push({ + id: row.id, + engine: engineName, + count: entry.count, + message: entry.message, + first: entry.first, + workCount: row.workCount, + }); + } + } + } + return lines; +} + +function renderFailures() { + const target = element("failure-details"); + if (!target) return; + if (!state.result) { + target.innerHTML = '

まだ失敗はありません。

'; + return; + } + const lines = failureLines(); + if (lines.length === 0) { + target.innerHTML = '

失敗はありません。すべての点が両方向とも通りました。

'; + return; + } + target.innerHTML = `
    ${lines.map((line) => ` +
  • + ${escapeHtml(line.engine)} / ${escapeHtml(line.id)}: + ${line.count} / ${line.workCount} 点が失敗 +
    ${escapeHtml(line.message)}
    + 最初の点: ${escapeHtml( + `(${line.first.lon}, ${line.first.lat})`, + )} +
  • + `).join("")}
`; +} + +/** The URL of the provenance JSON, as the bundler resolves it for this build. */ +function provenanceUrl() { + return new URL("./data/benchmark-places.provenance.json", import.meta.url).href; +} + +function renderProvenance() { + const target = element("provenance"); + if (!target) return; + const provenance = state.provenance; + if (!provenance) { + target.innerHTML = '

出典を読み込んでいます。

'; + return; + } + const sources = (provenance.sources ?? []).map((source) => ` +
  • + ${escapeHtml(source.name)}(${escapeHtml(source.version ?? "")}) + ${escapeHtml(source.pageUrl ?? "")} +
    ${escapeHtml(source.attribution ?? "")} +
  • + `).join(""); + target.innerHTML = ` +

    ${escapeHtml(provenance.summary ?? "")}

    +

    出典の記載: ${escapeHtml(provenance.processedNotice ?? provenance.attribution ?? "")}

    +

    利用条件: 国土地理院コンテンツ利用規約(公共データ利用規約(第1.0版。PDL1.0。CC BY 4.0 互換)) + https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html

    +

    取得日: ${escapeHtml(provenance.retrievedAt ?? "")}

    +
      ${sources}
    +

    加工の記録(provenance JSON)

    + `; +} + +function renderVersion() { + const target = element("version"); + if (!target) return; + target.textContent = `使っている版: @c4h/atlas ${atlasVersion()}`; +} + +function render() { + renderInteractivity(); + renderStatus(); + renderSummary(); + renderTable(); + renderFailures(); + renderProvenance(); +} + +function markPressed(activeId) { + for (const button of dom.querySelectorAll("#preset-bar button")) { + const pressed = button.dataset.preset === activeId; + button.setAttribute("aria-pressed", String(pressed)); + button.classList.toggle("active", pressed); + } +} + +function renderPresetBar() { + const bar = element("preset-bar"); + if (!bar) return; + while (bar.firstChild) bar.removeChild(bar.firstChild); + for (const preset of BENCHMARK_PRESETS) { + const group = dom.createElement("div"); + group.className = "atlas-preset-group"; + const title = dom.createElement("span"); + title.className = "atlas-preset-title"; + title.textContent = preset.title; + group.appendChild(title); + for (const option of preset.options) { + const button = dom.createElement("button"); + button.type = "button"; + button.className = "atlas-preset-btn"; + button.dataset.preset = `${preset.id}:${option.id}`; + button.textContent = option.label; + button.setAttribute("aria-pressed", "false"); + button.addEventListener("click", () => enqueue(() => applyPreset(preset, option))); + group.appendChild(button); + } + bar.appendChild(group); + } +} + +function setExpectation(line) { + const expectation = element("preset-expectation"); + if (expectation) expectation.textContent = line; +} + +/** + * Once a control changes the input by hand, the pressed preset no longer describes what is on + * screen. Nothing is re-run: the run is explicit on this page. + */ +function leavePreset() { + if (!state.activePreset) return; + state.activePreset = null; + markPressed(null); + setExpectation(NEUTRAL_EXPECTATION); +} + +async function applyPreset(preset, option) { + const input = option.input; + const engineModule = engine(); + const points = engineModule.selectPoints(state.places, input); + const workCount = presetWorkCount(input, points.length); + state.activePreset = `${preset.id}:${option.id}`; + syncControls({ + category: input.category, + pointLimit: input.pointLimit, + workCount, + scopeKind: input.crsScope.kind, + groupId: element("crs-group")?.value ?? DEFAULT_CONTROLS.groupId, + crsId: input.crsScope.id ?? DEFAULT_CONTROLS.crsId, + }); + markPressed(state.activePreset); + setExpectation(option.expectation); + render(); + await runOnce(); +} + +async function runOnce() { + if (!state.ready || state.running) return; + const controls = readControls(); + const engineModule = engine(); + const places = engineModule.selectPoints(state.places, controls); + const targetIds = engineModule.crsScopeIds(controls.crsScope); + + state.running = true; + state.error = null; + state.status = `実行中... (${places.length} 地点・作業量 ${controls.workCount} 回・CRS ${targetIds.length} 件)`; + render(); + + try { + const runtime = await loadAtlasRuntime(); + state.result = await engineModule.runBenchmark({ + places, + catalogPlaces: state.places, + workCount: controls.workCount, + targetIds, + runtime, + projWasm: state.projWasm, + proj4js: state.proj4js, + }); + state.status = "実行が終わりました。"; + } catch (error) { + state.error = messageOf(error); + } finally { + state.running = false; + render(); + } +} + +function reset() { + state.result = null; + state.error = null; + state.activePreset = null; + state.status = state.ready ? "準備完了。" : state.status; + syncControls(defaultControls()); + markPressed(null); + setExpectation(NEUTRAL_EXPECTATION); + render(); +} + +function bindControls() { + for (const id of ["category", "point-limit", "work-count", "crs-scope", "crs-group", "crs-id"]) { + const control = element(id); + if (!control) continue; + control.addEventListener("change", () => { + if (id === "crs-scope") renderScopeInputs(); + leavePreset(); + }); + } + const run = element("run-button"); + if (run) run.addEventListener("click", () => enqueue(runOnce)); + const resetButton = element("reset-button"); + if (resetButton) resetButton.addEventListener("click", () => reset()); +} + +async function loadJson(url) { + const response = await fetch(url); + if (!response.ok) { + throw new Error(`データを読めません: ${url.href} (${response.status} ${response.statusText})`); + } + return response.json(); +} + +async function loadData() { + if (injectedData) return injectedData; + const [places, provenance] = await Promise.all([ + loadJson(new URL("./data/benchmark-places.json", import.meta.url)), + loadJson(new URL("./data/benchmark-places.provenance.json", import.meta.url)), + ]); + return { places, provenance }; +} + +async function loadModules() { + if (injectedModules) return injectedModules; + const [projWasm, proj4js] = await Promise.all([ + import("proj-wasm"), + import("proj4"), + ]); + return { projWasm, proj4js: proj4js.default ?? proj4js }; +} + +async function initialize() { + try { + state.status = "初期化中: proj-wasm の実行時..."; + render(); + const runtime = await loadAtlasRuntime(); + await runtime.initializeProjWasmRuntime({ projWasmAssetBaseUrl: projWasmAssetBaseUrl() }); + + state.status = "初期化中: エンジン..."; + render(); + const modules = await loadModules(); + state.projWasm = modules.projWasm; + state.proj4js = modules.proj4js; + + state.status = "初期化中: 地点データと出典..." + render(); + const data = await loadData(); + state.places = data.places ?? []; + state.provenance = data.provenance ?? null; + + state.ready = true; + state.status = `準備完了。地点 ${state.places.length} 件を読み込みました。`; + } catch (error) { + state.ready = false; + state.error = `初期化に失敗しました: ${messageOf(error)}`; + } + render(); +} + +/** + * Wire the benchmark page and return a small handle for tests. + * + * `runtime` is the same object shape as `@c4h/atlas` (`initializeProjWasmRuntime` and + * `listProjectedCrsFromProjWasm`); by default it is the dynamic import of the package. + * `engine` replaces the functions of `demo/benchmark-engine.js` (the benchmark arithmetic), and + * `data`/`modules` replace the fetched dataset and the loaded engines. The page calls this once; + * importing this module does nothing by itself. + */ +export function initBenchmark({ runtime, doc, engine: injected, data, modules } = {}) { + dom = doc ?? globalThis.document; + injectedRuntime = runtime ?? null; + injectedEngine = injected ?? null; + injectedData = data ?? null; + injectedModules = modules ?? null; + atlasModulePromise = undefined; + settled = Promise.resolve(); + Object.assign(state, { + ready: false, + running: false, + status: "初期化中...", + error: null, + places: [], + provenance: null, + projWasm: null, + proj4js: null, + result: null, + activePreset: null, + }); + + renderScopeOptions(); + renderPresetBar(); + renderVersion(); + bindControls(); + syncControls(defaultControls()); + markPressed(null); + setExpectation(NEUTRAL_EXPECTATION); + render(); + enqueue(initialize); + + return { + whenSettled: () => settled, + getState: () => ({ + ready: state.ready, + running: state.running, + activePreset: state.activePreset, + placeCount: state.places.length, + result: state.result, + error: state.error, + status: text("init-status"), + }), + }; +} diff --git a/demo/data/benchmark-places.json b/demo/data/benchmark-places.json new file mode 100644 index 0000000..72f15b6 --- /dev/null +++ b/demo/data/benchmark-places.json @@ -0,0 +1,1065 @@ +[ + {"category":"mountain-summit","name":"知床岳","lon":145.27394,"lat":44.235726,"sourceId":"1"}, + {"category":"mountain-summit","name":"硫黄山","lon":145.16129,"lat":44.133258,"sourceId":"2"}, + {"category":"mountain-summit","name":"知円別岳","lon":145.168839,"lat":44.124615,"sourceId":"3"}, + {"category":"mountain-summit","name":"サシルイ岳","lon":145.143678,"lat":44.095319,"sourceId":"4"}, + {"category":"mountain-summit","name":"羅臼岳","lon":145.122246,"lat":44.075917,"sourceId":"5"}, + {"category":"mountain-summit","name":"遠音別岳","lon":145.013274,"lat":43.993399,"sourceId":"6"}, + {"category":"mountain-summit","name":"海別岳","lon":144.87654,"lat":43.876845,"sourceId":"7"}, + {"category":"mountain-summit","name":"斜里岳","lon":144.717659,"lat":43.765743,"sourceId":"8"}, + {"category":"mountain-summit","name":"標津岳","lon":144.704818,"lat":43.668171,"sourceId":"9"}, + {"category":"mountain-summit","name":"藻琴山","lon":144.330987,"lat":43.704313,"sourceId":"10"}, + {"category":"mountain-summit","name":"アトサヌプリ(硫黄山)","lon":144.438652,"lat":43.610395,"sourceId":"11"}, + {"category":"mountain-summit","name":"カムイヌプリ(摩周岳)","lon":144.560902,"lat":43.572273,"sourceId":"12"}, + {"category":"mountain-summit","name":"雄阿寒岳","lon":144.164617,"lat":43.454056,"sourceId":"13"}, + {"category":"mountain-summit","name":"雌阿寒岳","lon":144.008813,"lat":43.386318,"sourceId":"14"}, + {"category":"mountain-summit","name":"雌阿寒岳<阿寒富士>","lon":144.006504,"lat":43.374063,"sourceId":"15"}, + {"category":"mountain-summit","name":"ウコタキヌプリ","lon":143.797571,"lat":43.208527,"sourceId":"16"}, + {"category":"mountain-summit","name":"礼文岳","lon":141.015803,"lat":45.37252,"sourceId":"17"}, + {"category":"mountain-summit","name":"利尻山(利尻富士)","lon":141.242036,"lat":45.178536,"sourceId":"18"}, + {"category":"mountain-summit","name":"幌尻山","lon":142.008524,"lat":45.165622,"sourceId":"19"}, + {"category":"mountain-summit","name":"パンケ山","lon":142.149839,"lat":44.857042,"sourceId":"20"}, + {"category":"mountain-summit","name":"ポロヌプリ山","lon":142.420377,"lat":44.961847,"sourceId":"21"}, + {"category":"mountain-summit","name":"敏音知岳","lon":142.234535,"lat":44.89306,"sourceId":"22"}, + {"category":"mountain-summit","name":"函岳","lon":142.412131,"lat":44.665888,"sourceId":"23"}, + {"category":"mountain-summit","name":"ピヤシリ山","lon":142.584098,"lat":44.433695,"sourceId":"24"}, + {"category":"mountain-summit","name":"欝岳","lon":143.08253,"lat":44.291743,"sourceId":"25"}, + {"category":"mountain-summit","name":"ウエンシリ岳","lon":142.876338,"lat":44.228801,"sourceId":"26"}, + {"category":"mountain-summit","name":"渚滑岳","lon":142.917386,"lat":44.040358,"sourceId":"27"}, + {"category":"mountain-summit","name":"天塩岳","lon":142.887986,"lat":43.964372,"sourceId":"28"}, + {"category":"mountain-summit","name":"チトカニウシ山","lon":143.048547,"lat":43.906794,"sourceId":"29"}, + {"category":"mountain-summit","name":"仁頃山","lon":143.718209,"lat":43.878268,"sourceId":"30"}, + {"category":"mountain-summit","name":"ニセイカウシュッペ山","lon":142.985346,"lat":43.779967,"sourceId":"31"}, + {"category":"mountain-summit","name":"武利岳","lon":143.176511,"lat":43.732934,"sourceId":"32"}, + {"category":"mountain-summit","name":"武華山","lon":143.160519,"lat":43.705705,"sourceId":"33"}, + {"category":"mountain-summit","name":"北見富士","lon":143.295921,"lat":43.693735,"sourceId":"34"}, + {"category":"mountain-summit","name":"大雪山(ヌタプカウシペ)<旭岳>","lon":142.854138,"lat":43.663582,"sourceId":"35"}, + {"category":"mountain-summit","name":"大雪山(ヌタプカウシペ)<黒岳>","lon":142.920277,"lat":43.6975,"sourceId":"36"}, + {"category":"mountain-summit","name":"大雪山(ヌタプカウシペ)<北鎮岳>","lon":142.8799,"lat":43.692768,"sourceId":"37"}, + {"category":"mountain-summit","name":"大雪山(ヌタプカウシペ)<愛別岳>","lon":142.857119,"lat":43.707911,"sourceId":"38"}, + {"category":"mountain-summit","name":"大雪山(ヌタプカウシペ)<白雲岳>","lon":142.905915,"lat":43.661188,"sourceId":"39"}, + {"category":"mountain-summit","name":"忠別岳","lon":142.894507,"lat":43.59108,"sourceId":"40"}, + {"category":"mountain-summit","name":"トムラウシ山","lon":142.84879,"lat":43.527111,"sourceId":"41"}, + {"category":"mountain-summit","name":"オプタテシケ山","lon":142.751538,"lat":43.470244,"sourceId":"42"}, + {"category":"mountain-summit","name":"美瑛岳","lon":142.706563,"lat":43.439895,"sourceId":"43"}, + {"category":"mountain-summit","name":"十勝岳","lon":142.686283,"lat":43.417859,"sourceId":"44"}, + {"category":"mountain-summit","name":"上ホロカメットク山","lon":142.671772,"lat":43.404887,"sourceId":"45"}, + {"category":"mountain-summit","name":"富良野岳","lon":142.634976,"lat":43.393723,"sourceId":"46"}, + {"category":"mountain-summit","name":"三国山","lon":143.146416,"lat":43.595395,"sourceId":"47"}, + {"category":"mountain-summit","name":"石狩岳","lon":143.022311,"lat":43.546573,"sourceId":"48"}, + {"category":"mountain-summit","name":"音更山","lon":143.03531,"lat":43.561775,"sourceId":"49"}, + {"category":"mountain-summit","name":"ニペソツ山","lon":143.032245,"lat":43.455985,"sourceId":"50"}, + {"category":"mountain-summit","name":"ウペペサンケ山","lon":143.082096,"lat":43.389519,"sourceId":"51"}, + {"category":"mountain-summit","name":"東ヌプカウシヌプリ","lon":143.102143,"lat":43.245784,"sourceId":"52"}, + {"category":"mountain-summit","name":"佐幌岳","lon":142.780995,"lat":43.173381,"sourceId":"53"}, + {"category":"mountain-summit","name":"西クマネシリ岳","lon":143.233282,"lat":43.519124,"sourceId":"54"}, + {"category":"mountain-summit","name":"東三国山","lon":143.463732,"lat":43.508208,"sourceId":"55"}, + {"category":"mountain-summit","name":"喜登牛山","lon":143.456324,"lat":43.447692,"sourceId":"56"}, + {"category":"mountain-summit","name":"ピッシリ山","lon":142.032063,"lat":44.355624,"sourceId":"57"}, + {"category":"mountain-summit","name":"三頭山","lon":142.085817,"lat":44.091549,"sourceId":"58"}, + {"category":"mountain-summit","name":"ポロシリ山","lon":141.836233,"lat":43.962587,"sourceId":"59"}, + {"category":"mountain-summit","name":"暑寒別岳","lon":141.522949,"lat":43.715865,"sourceId":"60"}, + {"category":"mountain-summit","name":"群別岳","lon":141.487671,"lat":43.68474,"sourceId":"61"}, + {"category":"mountain-summit","name":"ピンネシリ","lon":141.70674,"lat":43.491807,"sourceId":"62"}, + {"category":"mountain-summit","name":"イルムケップ山","lon":142.110281,"lat":43.629214,"sourceId":"63"}, + {"category":"mountain-summit","name":"富良野西岳","lon":142.311645,"lat":43.309197,"sourceId":"64"}, + {"category":"mountain-summit","name":"芦別岳","lon":142.283553,"lat":43.23573,"sourceId":"65"}, + {"category":"mountain-summit","name":"幾春別岳","lon":142.159415,"lat":43.227072,"sourceId":"66"}, + {"category":"mountain-summit","name":"夕張岳","lon":142.251011,"lat":43.099691,"sourceId":"67"}, + {"category":"mountain-summit","name":"ハッタオマナイ岳","lon":142.313123,"lat":42.846969,"sourceId":"68"}, + {"category":"mountain-summit","name":"トマム山","lon":142.596096,"lat":43.078634,"sourceId":"69"}, + {"category":"mountain-summit","name":"芽室岳","lon":142.785395,"lat":42.868967,"sourceId":"70"}, + {"category":"mountain-summit","name":"剣山","lon":142.885059,"lat":42.848703,"sourceId":"71"}, + {"category":"mountain-summit","name":"チロロ岳","lon":142.675406,"lat":42.825585,"sourceId":"72"}, + {"category":"mountain-summit","name":"ピパイロ岳","lon":142.72761,"lat":42.773184,"sourceId":"73"}, + {"category":"mountain-summit","name":"戸蔦別岳","lon":142.695107,"lat":42.738665,"sourceId":"74"}, + {"category":"mountain-summit","name":"幌尻岳","lon":142.682894,"lat":42.719435,"sourceId":"75"}, + {"category":"mountain-summit","name":"エサオマントッタベツ岳","lon":142.757826,"lat":42.689411,"sourceId":"76"}, + {"category":"mountain-summit","name":"札内岳","lon":142.79675,"lat":42.693848,"sourceId":"77"}, + {"category":"mountain-summit","name":"十勝幌尻岳","lon":142.859408,"lat":42.695467,"sourceId":"78"}, + {"category":"mountain-summit","name":"イドンナップ岳","lon":142.648841,"lat":42.630386,"sourceId":"79"}, + {"category":"mountain-summit","name":"カムイエクウチカウシ山","lon":142.766406,"lat":42.625112,"sourceId":"80"}, + {"category":"mountain-summit","name":"1839峰","lon":142.808116,"lat":42.539346,"sourceId":"81"}, + {"category":"mountain-summit","name":"ペテガリ岳","lon":142.871019,"lat":42.499337,"sourceId":"82"}, + {"category":"mountain-summit","name":"中ノ岳","lon":142.884608,"lat":42.468032,"sourceId":"83"}, + {"category":"mountain-summit","name":"神威岳","lon":142.906866,"lat":42.428465,"sourceId":"84"}, + {"category":"mountain-summit","name":"ピリカヌプリ","lon":142.966802,"lat":42.402225,"sourceId":"85"}, + {"category":"mountain-summit","name":"楽古岳","lon":143.111252,"lat":42.272513,"sourceId":"86"}, + {"category":"mountain-summit","name":"豊似岳","lon":143.232975,"lat":42.076973,"sourceId":"87"}, + {"category":"mountain-summit","name":"アポイ岳","lon":143.025658,"lat":42.107849,"sourceId":"88"}, + {"category":"mountain-summit","name":"藻岩山","lon":141.322328,"lat":43.02238,"sourceId":"89"}, + {"category":"mountain-summit","name":"手稲山","lon":141.192632,"lat":43.076737,"sourceId":"90"}, + {"category":"mountain-summit","name":"余市岳","lon":141.019787,"lat":43.032728,"sourceId":"91"}, + {"category":"mountain-summit","name":"本倶登山","lon":140.912621,"lat":42.955178,"sourceId":"92"}, + {"category":"mountain-summit","name":"無意根山","lon":141.040671,"lat":42.930775,"sourceId":"93"}, + {"category":"mountain-summit","name":"札幌岳","lon":141.200521,"lat":42.900189,"sourceId":"94"}, + {"category":"mountain-summit","name":"空沼岳","lon":141.253694,"lat":42.865007,"sourceId":"95"}, + {"category":"mountain-summit","name":"恵庭岳","lon":141.285351,"lat":42.793311,"sourceId":"96"}, + {"category":"mountain-summit","name":"風不死岳","lon":141.358874,"lat":42.716842,"sourceId":"97"}, + {"category":"mountain-summit","name":"樽前山","lon":141.37665,"lat":42.690539,"sourceId":"98"}, + {"category":"mountain-summit","name":"ホロホロ山","lon":141.142532,"lat":42.633513,"sourceId":"99"}, + {"category":"mountain-summit","name":"余別岳","lon":140.459136,"lat":43.26006,"sourceId":"100"}, + {"category":"mountain-summit","name":"天狗岳","lon":140.660816,"lat":43.184805,"sourceId":"101"}, + {"category":"mountain-summit","name":"雷電山","lon":140.469623,"lat":42.903633,"sourceId":"102"}, + {"category":"mountain-summit","name":"目国内岳","lon":140.515293,"lat":42.899746,"sourceId":"103"}, + {"category":"mountain-summit","name":"ニセコアンヌプリ","lon":140.658839,"lat":42.875018,"sourceId":"104"}, + {"category":"mountain-summit","name":"羊蹄山(蝦夷富士)","lon":140.811488,"lat":42.826659,"sourceId":"105"}, + {"category":"mountain-summit","name":"尻別岳","lon":140.910348,"lat":42.772572,"sourceId":"106"}, + {"category":"mountain-summit","name":"貫気別山","lon":140.92273,"lat":42.710081,"sourceId":"107"}, + {"category":"mountain-summit","name":"昆布岳","lon":140.655469,"lat":42.71037,"sourceId":"108"}, + {"category":"mountain-summit","name":"昭和新山","lon":140.86435,"lat":42.542555,"sourceId":"109"}, + {"category":"mountain-summit","name":"有珠山<大有珠>","lon":140.839251,"lat":42.543998,"sourceId":"110"}, + {"category":"mountain-summit","name":"母衣月山","lon":140.214933,"lat":42.770126,"sourceId":"111"}, + {"category":"mountain-summit","name":"大平山","lon":140.135584,"lat":42.635959,"sourceId":"112"}, + {"category":"mountain-summit","name":"狩場山","lon":139.940602,"lat":42.613353,"sourceId":"113"}, + {"category":"mountain-summit","name":"毛無山","lon":139.810731,"lat":42.295409,"sourceId":"114"}, + {"category":"mountain-summit","name":"遊楽部岳(見市岳)","lon":140.01034,"lat":42.219023,"sourceId":"115"}, + {"category":"mountain-summit","name":"乙部岳","lon":140.274586,"lat":42.03937,"sourceId":"116"}, + {"category":"mountain-summit","name":"駒ヶ岳<剣ヶ峯>","lon":140.677287,"lat":42.063344,"sourceId":"117"}, + {"category":"mountain-summit","name":"横津岳","lon":140.771135,"lat":41.937884,"sourceId":"118"}, + {"category":"mountain-summit","name":"恵山","lon":141.166031,"lat":41.804707,"sourceId":"119"}, + {"category":"mountain-summit","name":"函館山","lon":140.704302,"lat":41.759027,"sourceId":"120"}, + {"category":"mountain-summit","name":"八幡岳","lon":140.229991,"lat":41.851881,"sourceId":"121"}, + {"category":"mountain-summit","name":"桂岳","lon":140.4644,"lat":41.789257,"sourceId":"122"}, + {"category":"mountain-summit","name":"七ッ岳","lon":140.21407,"lat":41.624218,"sourceId":"123"}, + {"category":"mountain-summit","name":"大千軒岳","lon":140.160818,"lat":41.579319,"sourceId":"124"}, + {"category":"mountain-summit","name":"岩部岳","lon":140.310867,"lat":41.527175,"sourceId":"125"}, + {"category":"mountain-summit","name":"神威山","lon":139.442662,"lat":42.160273,"sourceId":"126"}, + {"category":"mountain-summit","name":"江良岳","lon":139.367158,"lat":41.509904,"sourceId":"127"}, + {"category":"mountain-summit","name":"燧岳","lon":141.052919,"lat":41.439132,"sourceId":"128"}, + {"category":"mountain-summit","name":"釜臥山","lon":141.120015,"lat":41.278517,"sourceId":"129"}, + {"category":"mountain-summit","name":"桑畑山","lon":141.440735,"lat":41.387582,"sourceId":"130"}, + {"category":"mountain-summit","name":"吹越烏帽子","lon":141.317141,"lat":41.037443,"sourceId":"131"}, + {"category":"mountain-summit","name":"丸屋形岳","lon":140.586625,"lat":41.152825,"sourceId":"132"}, + {"category":"mountain-summit","name":"四ッ滝山","lon":140.400483,"lat":41.12169,"sourceId":"133"}, + {"category":"mountain-summit","name":"梵珠山","lon":140.575489,"lat":40.799693,"sourceId":"134"}, + {"category":"mountain-summit","name":"階上岳(種市岳)","lon":141.584364,"lat":40.400693,"sourceId":"135"}, + {"category":"mountain-summit","name":"名久井岳","lon":141.308363,"lat":40.387975,"sourceId":"136"}, + {"category":"mountain-summit","name":"折爪岳","lon":141.374481,"lat":40.26876,"sourceId":"137"}, + {"category":"mountain-summit","name":"安家森","lon":141.540989,"lat":40.038037,"sourceId":"138"}, + {"category":"mountain-summit","name":"遠島山","lon":141.644249,"lat":40.020655,"sourceId":"139"}, + {"category":"mountain-summit","name":"三巣子岳","lon":141.485421,"lat":39.900078,"sourceId":"140"}, + {"category":"mountain-summit","name":"姫神山","lon":141.246896,"lat":39.844024,"sourceId":"141"}, + {"category":"mountain-summit","name":"堺ノ神岳","lon":141.626389,"lat":39.750263,"sourceId":"142"}, + {"category":"mountain-summit","name":"害鷹森","lon":141.604551,"lat":39.711729,"sourceId":"143"}, + {"category":"mountain-summit","name":"青松葉山","lon":141.483563,"lat":39.697498,"sourceId":"144"}, + {"category":"mountain-summit","name":"峠ノ神山","lon":141.781082,"lat":39.726881,"sourceId":"145"}, + {"category":"mountain-summit","name":"早池峰山","lon":141.488876,"lat":39.558445,"sourceId":"146"}, + {"category":"mountain-summit","name":"薬師岳","lon":141.495734,"lat":39.529927,"sourceId":"147"}, + {"category":"mountain-summit","name":"十二神山","lon":141.974887,"lat":39.538579,"sourceId":"148"}, + {"category":"mountain-summit","name":"六角牛山","lon":141.650179,"lat":39.318339,"sourceId":"149"}, + {"category":"mountain-summit","name":"五葉山","lon":141.732123,"lat":39.205453,"sourceId":"150"}, + {"category":"mountain-summit","name":"物見山(種山)","lon":141.401724,"lat":39.201143,"sourceId":"151"}, + {"category":"mountain-summit","name":"氷上山","lon":141.660547,"lat":39.058929,"sourceId":"152"}, + {"category":"mountain-summit","name":"室根山","lon":141.447249,"lat":38.975194,"sourceId":"153"}, + {"category":"mountain-summit","name":"徳仙丈山","lon":141.469945,"lat":38.848421,"sourceId":"154"}, + {"category":"mountain-summit","name":"金華山","lon":141.566804,"lat":38.295305,"sourceId":"155"}, + {"category":"mountain-summit","name":"八甲田山<大岳>","lon":140.877249,"lat":40.658907,"sourceId":"156"}, + {"category":"mountain-summit","name":"八甲田山<高田大岳>","lon":140.907131,"lat":40.65305,"sourceId":"157"}, + {"category":"mountain-summit","name":"櫛ヶ峯(上岳)","lon":140.842575,"lat":40.603174,"sourceId":"158"}, + {"category":"mountain-summit","name":"戸来岳<三ッ岳>","lon":141.001503,"lat":40.452926,"sourceId":"159"}, + {"category":"mountain-summit","name":"白地山","lon":140.788164,"lat":40.447215,"sourceId":"160"}, + {"category":"mountain-summit","name":"中岳","lon":140.944368,"lat":40.246022,"sourceId":"161"}, + {"category":"mountain-summit","name":"稲庭岳","lon":141.04621,"lat":40.198619,"sourceId":"162"}, + {"category":"mountain-summit","name":"五ノ宮嶽","lon":140.845939,"lat":40.145836,"sourceId":"163"}, + {"category":"mountain-summit","name":"七時雨山","lon":141.105652,"lat":40.069278,"sourceId":"164"}, + {"category":"mountain-summit","name":"森吉山","lon":140.544174,"lat":39.976753,"sourceId":"165"}, + {"category":"mountain-summit","name":"焼山","lon":140.757004,"lat":39.963818,"sourceId":"166"}, + {"category":"mountain-summit","name":"八幡平","lon":140.854094,"lat":39.957808,"sourceId":"167"}, + {"category":"mountain-summit","name":"八幡平<茶臼岳>","lon":140.898932,"lat":39.950761,"sourceId":"168"}, + {"category":"mountain-summit","name":"八幡平<畚岳>","lon":140.850603,"lat":39.935921,"sourceId":"169"}, + {"category":"mountain-summit","name":"大深岳","lon":140.879683,"lat":39.881092,"sourceId":"170"}, + {"category":"mountain-summit","name":"岩手山","lon":141.001018,"lat":39.852607,"sourceId":"171"}, + {"category":"mountain-summit","name":"烏帽子岳(乳頭山)","lon":140.838626,"lat":39.804889,"sourceId":"172"}, + {"category":"mountain-summit","name":"駒ヶ岳<男女岳>","lon":140.799381,"lat":39.761097,"sourceId":"173"}, + {"category":"mountain-summit","name":"岩木山","lon":140.303075,"lat":40.655916,"sourceId":"174"}, + {"category":"mountain-summit","name":"向白神岳","lon":140.045433,"lat":40.523919,"sourceId":"175"}, + {"category":"mountain-summit","name":"白神岳","lon":140.01823,"lat":40.507426,"sourceId":"176"}, + {"category":"mountain-summit","name":"二ッ森","lon":140.117906,"lat":40.435045,"sourceId":"177"}, + {"category":"mountain-summit","name":"駒ヶ岳","lon":140.251138,"lat":40.412911,"sourceId":"178"}, + {"category":"mountain-summit","name":"田代岳","lon":140.408843,"lat":40.428486,"sourceId":"179"}, + {"category":"mountain-summit","name":"房住山","lon":140.216095,"lat":40.060516,"sourceId":"180"}, + {"category":"mountain-summit","name":"太平山","lon":140.310685,"lat":39.796992,"sourceId":"181"}, + {"category":"mountain-summit","name":"大仏岳","lon":140.515642,"lat":39.813647,"sourceId":"182"}, + {"category":"mountain-summit","name":"東光山","lon":140.161576,"lat":39.40658,"sourceId":"183"}, + {"category":"mountain-summit","name":"八塩山","lon":140.227682,"lat":39.236573,"sourceId":"184"}, + {"category":"mountain-summit","name":"鳥海山<新山>","lon":140.048885,"lat":39.099369,"sourceId":"185"}, + {"category":"mountain-summit","name":"丁岳","lon":140.219333,"lat":39.032022,"sourceId":"186"}, + {"category":"mountain-summit","name":"甑山<男甑山>","lon":140.310571,"lat":39.012053,"sourceId":"187"}, + {"category":"mountain-summit","name":"男加無山","lon":140.260287,"lat":39.009413,"sourceId":"188"}, + {"category":"mountain-summit","name":"寒風山","lon":139.875349,"lat":39.933704,"sourceId":"189"}, + {"category":"mountain-summit","name":"本山","lon":139.753772,"lat":39.906841,"sourceId":"190"}, + {"category":"mountain-summit","name":"東根山","lon":141.059766,"lat":39.58735,"sourceId":"191"}, + {"category":"mountain-summit","name":"和賀岳","lon":140.754255,"lat":39.570396,"sourceId":"192"}, + {"category":"mountain-summit","name":"真昼岳","lon":140.676545,"lat":39.447854,"sourceId":"193"}, + {"category":"mountain-summit","name":"黒森","lon":140.86838,"lat":39.348275,"sourceId":"194"}, + {"category":"mountain-summit","name":"焼石岳","lon":140.828835,"lat":39.163672,"sourceId":"195"}, + {"category":"mountain-summit","name":"栗駒山(須川岳)","lon":140.78843,"lat":38.960912,"sourceId":"196"}, + {"category":"mountain-summit","name":"高松岳","lon":140.606336,"lat":38.967703,"sourceId":"197"}, + {"category":"mountain-summit","name":"虎毛山","lon":140.618816,"lat":38.90458,"sourceId":"198"}, + {"category":"mountain-summit","name":"荒雄岳","lon":140.691545,"lat":38.833153,"sourceId":"199"}, + {"category":"mountain-summit","name":"神室山","lon":140.4923,"lat":38.902253,"sourceId":"200"}, + {"category":"mountain-summit","name":"火打岳","lon":140.448473,"lat":38.84021,"sourceId":"201"}, + {"category":"mountain-summit","name":"杢蔵山","lon":140.40138,"lat":38.789521,"sourceId":"202"}, + {"category":"mountain-summit","name":"禿岳(小鏑山)","lon":140.595092,"lat":38.810947,"sourceId":"203"}, + {"category":"mountain-summit","name":"箟岳山","lon":141.178755,"lat":38.565746,"sourceId":"204"}, + {"category":"mountain-summit","name":"大高森","lon":141.152511,"lat":38.340614,"sourceId":"205"}, + {"category":"mountain-summit","name":"翁山","lon":140.544798,"lat":38.650866,"sourceId":"206"}, + {"category":"mountain-summit","name":"薬?山","lon":140.705262,"lat":38.575542,"sourceId":"207"}, + {"category":"mountain-summit","name":"船形山(御所山)","lon":140.619866,"lat":38.455447,"sourceId":"208"}, + {"category":"mountain-summit","name":"泉ヶ岳","lon":140.708761,"lat":38.411987,"sourceId":"209"}, + {"category":"mountain-summit","name":"太白山","lon":140.802178,"lat":38.234935,"sourceId":"210"}, + {"category":"mountain-summit","name":"大東岳","lon":140.523563,"lat":38.302146,"sourceId":"211"}, + {"category":"mountain-summit","name":"蔵王山<熊野岳>","lon":140.439741,"lat":38.143878,"sourceId":"212"}, + {"category":"mountain-summit","name":"蔵王山<刈田岳>","lon":140.448187,"lat":38.127754,"sourceId":"213"}, + {"category":"mountain-summit","name":"蔵王山<?風岳>","lon":140.476135,"lat":38.095707,"sourceId":"214"}, + {"category":"mountain-summit","name":"蔵王山<不忘山(御前岳)>","lon":140.478859,"lat":38.075661,"sourceId":"215"}, + {"category":"mountain-summit","name":"青麻山","lon":140.607578,"lat":38.084769,"sourceId":"216"}, + {"category":"mountain-summit","name":"栗子山","lon":140.269967,"lat":37.874343,"sourceId":"217"}, + {"category":"mountain-summit","name":"信夫山","lon":140.46159,"lat":37.770714,"sourceId":"218"}, + {"category":"mountain-summit","name":"西吾妻山","lon":140.1408,"lat":37.73814,"sourceId":"219"}, + {"category":"mountain-summit","name":"西吾妻山<西大巓>","lon":140.12769,"lat":37.733973,"sourceId":"220"}, + {"category":"mountain-summit","name":"西吾妻山<東大巓>","lon":140.188764,"lat":37.754466,"sourceId":"221"}, + {"category":"mountain-summit","name":"東吾妻山","lon":140.230302,"lat":37.711673,"sourceId":"222"}, + {"category":"mountain-summit","name":"東吾妻山<一切経山>","lon":140.244343,"lat":37.735364,"sourceId":"223"}, + {"category":"mountain-summit","name":"東吾妻山<吾妻小富士>","lon":140.263596,"lat":37.722134,"sourceId":"224"}, + {"category":"mountain-summit","name":"箕輪山","lon":140.280908,"lat":37.647091,"sourceId":"225"}, + {"category":"mountain-summit","name":"安達太良山<鉄山>","lon":140.283175,"lat":37.633051,"sourceId":"226"}, + {"category":"mountain-summit","name":"安達太良山","lon":140.287891,"lat":37.621123,"sourceId":"227"}, + {"category":"mountain-summit","name":"磐梯山","lon":140.072248,"lat":37.600975,"sourceId":"228"}, + {"category":"mountain-summit","name":"高館山","lon":139.747502,"lat":38.762963,"sourceId":"229"}, + {"category":"mountain-summit","name":"金峯山","lon":139.798184,"lat":38.682144,"sourceId":"230"}, + {"category":"mountain-summit","name":"羽黒山","lon":139.982693,"lat":38.702594,"sourceId":"231"}, + {"category":"mountain-summit","name":"月山","lon":140.026971,"lat":38.549136,"sourceId":"232"}, + {"category":"mountain-summit","name":"葉山","lon":140.210522,"lat":38.529262,"sourceId":"233"}, + {"category":"mountain-summit","name":"温海岳","lon":139.630738,"lat":38.62013,"sourceId":"234"}, + {"category":"mountain-summit","name":"摩耶山","lon":139.727851,"lat":38.520194,"sourceId":"235"}, + {"category":"mountain-summit","name":"以東岳","lon":139.849062,"lat":38.342954,"sourceId":"236"}, + {"category":"mountain-summit","name":"朝日岳<大朝日岳>","lon":139.922321,"lat":38.260533,"sourceId":"237"}, + {"category":"mountain-summit","name":"祝瓶山","lon":139.879609,"lat":38.19761,"sourceId":"238"}, + {"category":"mountain-summit","name":"鷲ヶ巣山","lon":139.668993,"lat":38.221927,"sourceId":"239"}, + {"category":"mountain-summit","name":"新保岳","lon":139.510815,"lat":38.361136,"sourceId":"240"}, + {"category":"mountain-summit","name":"白鷹山","lon":140.173121,"lat":38.222663,"sourceId":"241"}, + {"category":"mountain-summit","name":"?差岳","lon":139.608833,"lat":37.942611,"sourceId":"242"}, + {"category":"mountain-summit","name":"北股岳","lon":139.638576,"lat":37.879955,"sourceId":"243"}, + {"category":"mountain-summit","name":"飯豊山","lon":139.70711,"lat":37.854831,"sourceId":"244"}, + {"category":"mountain-summit","name":"大日岳","lon":139.660672,"lat":37.833203,"sourceId":"245"}, + {"category":"mountain-summit","name":"二王子岳","lon":139.499151,"lat":37.899648,"sourceId":"246"}, + {"category":"mountain-summit","name":"五頭山","lon":139.345673,"lat":37.800974,"sourceId":"247"}, + {"category":"mountain-summit","name":"高陽山","lon":139.649601,"lat":37.729076,"sourceId":"248"}, + {"category":"mountain-summit","name":"飯森山","lon":139.9256,"lat":37.813858,"sourceId":"249"}, + {"category":"mountain-summit","name":"霊山","lon":140.687197,"lat":37.768747,"sourceId":"250"}, + {"category":"mountain-summit","name":"日山(天王山)","lon":140.683566,"lat":37.542852,"sourceId":"251"}, + {"category":"mountain-summit","name":"鎌倉岳","lon":140.68891,"lat":37.467024,"sourceId":"252"}, + {"category":"mountain-summit","name":"大滝根山","lon":140.701664,"lat":37.354899,"sourceId":"253"}, + {"category":"mountain-summit","name":"屹兎屋山","lon":140.867466,"lat":37.210624,"sourceId":"254"}, + {"category":"mountain-summit","name":"蓬田岳","lon":140.534758,"lat":37.265799,"sourceId":"255"}, + {"category":"mountain-summit","name":"三大明神山","lon":140.762644,"lat":37.052097,"sourceId":"256"}, + {"category":"mountain-summit","name":"竪破山","lon":140.562278,"lat":36.710793,"sourceId":"257"}, + {"category":"mountain-summit","name":"男体山","lon":140.419684,"lat":36.724207,"sourceId":"258"}, + {"category":"mountain-summit","name":"高鈴山","lon":140.58784,"lat":36.62098,"sourceId":"259"}, + {"category":"mountain-summit","name":"八溝山","lon":140.272988,"lat":36.930151,"sourceId":"260"}, + {"category":"mountain-summit","name":"吾国山","lon":140.201101,"lat":36.321865,"sourceId":"261"}, + {"category":"mountain-summit","name":"加波山","lon":140.143659,"lat":36.298811,"sourceId":"262"}, + {"category":"mountain-summit","name":"筑波山","lon":140.106942,"lat":36.225359,"sourceId":"263"}, + {"category":"mountain-summit","name":"三本槍岳","lon":139.961414,"lat":37.150142,"sourceId":"264"}, + {"category":"mountain-summit","name":"那須岳<茶臼岳>","lon":139.963135,"lat":37.124861,"sourceId":"265"}, + {"category":"mountain-summit","name":"男鹿岳","lon":139.822082,"lat":37.085312,"sourceId":"266"}, + {"category":"mountain-summit","name":"大佐飛山","lon":139.844451,"lat":37.063484,"sourceId":"267"}, + {"category":"mountain-summit","name":"高原山<釈迦ヶ岳>","lon":139.776761,"lat":36.899965,"sourceId":"268"}, + {"category":"mountain-summit","name":"女峰山","lon":139.536492,"lat":36.811457,"sourceId":"269"}, + {"category":"mountain-summit","name":"女峰山<大真名子山>","lon":139.507101,"lat":36.795257,"sourceId":"270"}, + {"category":"mountain-summit","name":"女峰山<小真名子山>","lon":139.510865,"lat":36.807263,"sourceId":"271"}, + {"category":"mountain-summit","name":"太郎山","lon":139.48278,"lat":36.817874,"sourceId":"272"}, + {"category":"mountain-summit","name":"男体山","lon":139.490684,"lat":36.764951,"sourceId":"273"}, + {"category":"mountain-summit","name":"白根山","lon":139.375921,"lat":36.798602,"sourceId":"274"}, + {"category":"mountain-summit","name":"皇海山","lon":139.336965,"lat":36.689863,"sourceId":"275"}, + {"category":"mountain-summit","name":"庚申山","lon":139.36128,"lat":36.673091,"sourceId":"276"}, + {"category":"mountain-summit","name":"袈裟丸山","lon":139.327503,"lat":36.649389,"sourceId":"277"}, + {"category":"mountain-summit","name":"横根山","lon":139.522277,"lat":36.619807,"sourceId":"278"}, + {"category":"mountain-summit","name":"赤城山<黒檜山>","lon":139.19327,"lat":36.560367,"sourceId":"279"}, + {"category":"mountain-summit","name":"赤城山<地蔵岳>","lon":139.177351,"lat":36.540999,"sourceId":"280"}, + {"category":"mountain-summit","name":"博士山","lon":139.714751,"lat":37.363165,"sourceId":"281"}, + {"category":"mountain-summit","name":"小野岳","lon":139.886589,"lat":37.325919,"sourceId":"282"}, + {"category":"mountain-summit","name":"七ヶ岳","lon":139.657482,"lat":37.124279,"sourceId":"283"}, + {"category":"mountain-summit","name":"荒海山(太郎岳)","lon":139.643769,"lat":37.036071,"sourceId":"284"}, + {"category":"mountain-summit","name":"田代山","lon":139.47855,"lat":36.974394,"sourceId":"285"}, + {"category":"mountain-summit","name":"帝釈山","lon":139.459994,"lat":36.969811,"sourceId":"286"}, + {"category":"mountain-summit","name":"黒岩山","lon":139.393636,"lat":36.908751,"sourceId":"287"}, + {"category":"mountain-summit","name":"鬼怒沼山","lon":139.371703,"lat":36.882676,"sourceId":"288"}, + {"category":"mountain-summit","name":"会津朝日岳","lon":139.339661,"lat":37.221148,"sourceId":"289"}, + {"category":"mountain-summit","name":"丸山岳","lon":139.334858,"lat":37.172743,"sourceId":"290"}, + {"category":"mountain-summit","name":"駒ヶ岳","lon":139.353543,"lat":37.047574,"sourceId":"291"}, + {"category":"mountain-summit","name":"平ヶ岳","lon":139.170919,"lat":37.002089,"sourceId":"292"}, + {"category":"mountain-summit","name":"景鶴山","lon":139.210582,"lat":36.956884,"sourceId":"293"}, + {"category":"mountain-summit","name":"燧ヶ岳<柴安嵓>","lon":139.285334,"lat":36.955102,"sourceId":"294"}, + {"category":"mountain-summit","name":"至仏山","lon":139.173248,"lat":36.903474,"sourceId":"295"}, + {"category":"mountain-summit","name":"武尊山","lon":139.132601,"lat":36.805189,"sourceId":"296"}, + {"category":"mountain-summit","name":"御神楽岳","lon":139.425864,"lat":37.521877,"sourceId":"297"}, + {"category":"mountain-summit","name":"粟ヶ岳","lon":139.188723,"lat":37.555199,"sourceId":"298"}, + {"category":"mountain-summit","name":"矢筈岳","lon":139.266065,"lat":37.512363,"sourceId":"299"}, + {"category":"mountain-summit","name":"守門岳","lon":139.136567,"lat":37.397687,"sourceId":"300"}, + {"category":"mountain-summit","name":"浅草岳","lon":139.233574,"lat":37.343588,"sourceId":"301"}, + {"category":"mountain-summit","name":"毛猛山","lon":139.175144,"lat":37.256704,"sourceId":"302"}, + {"category":"mountain-summit","name":"未丈ヶ岳","lon":139.189485,"lat":37.183319,"sourceId":"303"}, + {"category":"mountain-summit","name":"駒ヶ岳","lon":139.075249,"lat":37.12362,"sourceId":"304"}, + {"category":"mountain-summit","name":"八海山<入道岳>","lon":139.023999,"lat":37.103952,"sourceId":"305"}, + {"category":"mountain-summit","name":"中ノ岳","lon":139.077584,"lat":37.085261,"sourceId":"306"}, + {"category":"mountain-summit","name":"荒沢岳","lon":139.148187,"lat":37.100451,"sourceId":"307"}, + {"category":"mountain-summit","name":"下津川山","lon":139.050047,"lat":36.994781,"sourceId":"308"}, + {"category":"mountain-summit","name":"巻機山","lon":138.964448,"lat":36.978551,"sourceId":"309"}, + {"category":"mountain-summit","name":"大源太山","lon":138.927132,"lat":36.902626,"sourceId":"310"}, + {"category":"mountain-summit","name":"朝日岳","lon":138.972624,"lat":36.880662,"sourceId":"311"}, + {"category":"mountain-summit","name":"朝日岳<白毛門>","lon":138.967356,"lat":36.859586,"sourceId":"312"}, + {"category":"mountain-summit","name":"谷川岳<茂倉岳>","lon":138.916666,"lat":36.849166,"sourceId":"313"}, + {"category":"mountain-summit","name":"谷川岳<一ノ倉岳>","lon":138.924363,"lat":36.847111,"sourceId":"314"}, + {"category":"mountain-summit","name":"谷川岳<オキノ耳>","lon":138.930157,"lat":36.837115,"sourceId":"315"}, + {"category":"mountain-summit","name":"万太郎山","lon":138.879218,"lat":36.824152,"sourceId":"316"}, + {"category":"mountain-summit","name":"仙ノ倉山","lon":138.839518,"lat":36.817468,"sourceId":"317"}, + {"category":"mountain-summit","name":"吾妻耶山","lon":138.927349,"lat":36.746448,"sourceId":"318"}, + {"category":"mountain-summit","name":"苗場山","lon":138.69032,"lat":36.845937,"sourceId":"319"}, + {"category":"mountain-summit","name":"佐武流山","lon":138.670573,"lat":36.772288,"sourceId":"320"}, + {"category":"mountain-summit","name":"白砂山","lon":138.693474,"lat":36.738071,"sourceId":"321"}, + {"category":"mountain-summit","name":"鳥甲山","lon":138.583986,"lat":36.839173,"sourceId":"322"}, + {"category":"mountain-summit","name":"高社山(高井富士)","lon":138.403813,"lat":36.799269,"sourceId":"323"}, + {"category":"mountain-summit","name":"岩菅山<裏岩菅山>","lon":138.567803,"lat":36.752898,"sourceId":"324"}, + {"category":"mountain-summit","name":"岩菅山","lon":138.559524,"lat":36.742041,"sourceId":"325"}, + {"category":"mountain-summit","name":"志賀山","lon":138.515575,"lat":36.700227,"sourceId":"326"}, + {"category":"mountain-summit","name":"笠ヶ岳","lon":138.481478,"lat":36.676633,"sourceId":"327"}, + {"category":"mountain-summit","name":"横手山","lon":138.525913,"lat":36.668919,"sourceId":"328"}, + {"category":"mountain-summit","name":"白根山","lon":138.527727,"lat":36.643948,"sourceId":"329"}, + {"category":"mountain-summit","name":"本白根山","lon":138.531722,"lat":36.622923,"sourceId":"330"}, + {"category":"mountain-summit","name":"四阿山","lon":138.412969,"lat":36.541764,"sourceId":"331"}, + {"category":"mountain-summit","name":"四阿山<根子岳>","lon":138.395272,"lat":36.549117,"sourceId":"332"}, + {"category":"mountain-summit","name":"湯ノ丸山","lon":138.400961,"lat":36.434743,"sourceId":"333"}, + {"category":"mountain-summit","name":"東篭ノ登山","lon":138.447168,"lat":36.419305,"sourceId":"334"}, + {"category":"mountain-summit","name":"浅間山","lon":138.522963,"lat":36.406526,"sourceId":"335"}, + {"category":"mountain-summit","name":"浅間隠山","lon":138.652493,"lat":36.453704,"sourceId":"336"}, + {"category":"mountain-summit","name":"鼻曲山","lon":138.645322,"lat":36.410874,"sourceId":"337"}, + {"category":"mountain-summit","name":"榛名山<掃部ヶ岳>","lon":138.850889,"lat":36.477402,"sourceId":"338"}, + {"category":"mountain-summit","name":"子持山","lon":138.997692,"lat":36.591957,"sourceId":"339"}, + {"category":"mountain-summit","name":"妙義山<相馬岳>","lon":138.748852,"lat":36.298645,"sourceId":"340"}, + {"category":"mountain-summit","name":"荒船山","lon":138.637127,"lat":36.203993,"sourceId":"341"}, + {"category":"mountain-summit","name":"赤久縄山","lon":138.843288,"lat":36.143656,"sourceId":"342"}, + {"category":"mountain-summit","name":"西御荷鉾山","lon":138.918307,"lat":36.152254,"sourceId":"343"}, + {"category":"mountain-summit","name":"二子山","lon":138.863701,"lat":36.069643,"sourceId":"344"}, + {"category":"mountain-summit","name":"両神山","lon":138.841317,"lat":36.0234,"sourceId":"345"}, + {"category":"mountain-summit","name":"御座山","lon":138.606775,"lat":36.034105,"sourceId":"346"}, + {"category":"mountain-summit","name":"三宝山","lon":138.727832,"lat":35.917623,"sourceId":"347"}, + {"category":"mountain-summit","name":"甲武信ヶ岳","lon":138.728908,"lat":35.909115,"sourceId":"348"}, + {"category":"mountain-summit","name":"横尾山","lon":138.521142,"lat":35.918768,"sourceId":"349"}, + {"category":"mountain-summit","name":"瑞牆山","lon":138.592088,"lat":35.893455,"sourceId":"350"}, + {"category":"mountain-summit","name":"金峰山","lon":138.62553,"lat":35.871661,"sourceId":"351"}, + {"category":"mountain-summit","name":"朝日岳","lon":138.642398,"lat":35.874632,"sourceId":"352"}, + {"category":"mountain-summit","name":"国師ヶ岳","lon":138.672966,"lat":35.870835,"sourceId":"353"}, + {"category":"mountain-summit","name":"北奥千丈岳","lon":138.670754,"lat":35.868604,"sourceId":"354"}, + {"category":"mountain-summit","name":"乾徳山","lon":138.714898,"lat":35.822776,"sourceId":"355"}, + {"category":"mountain-summit","name":"小楢山","lon":138.664198,"lat":35.781533,"sourceId":"356"}, + {"category":"mountain-summit","name":"茅ヶ岳","lon":138.513772,"lat":35.795014,"sourceId":"357"}, + {"category":"mountain-summit","name":"堂平山","lon":139.190071,"lat":36.005752,"sourceId":"358"}, + {"category":"mountain-summit","name":"武甲山","lon":139.097796,"lat":35.951624,"sourceId":"359"}, + {"category":"mountain-summit","name":"伊豆ヶ岳","lon":139.160641,"lat":35.926873,"sourceId":"360"}, + {"category":"mountain-summit","name":"川乗山","lon":139.106883,"lat":35.850801,"sourceId":"361"}, + {"category":"mountain-summit","name":"酉谷山(天目山)","lon":139.010309,"lat":35.897096,"sourceId":"362"}, + {"category":"mountain-summit","name":"雲取山","lon":138.943913,"lat":35.855508,"sourceId":"363"}, + {"category":"mountain-summit","name":"白石山","lon":138.878015,"lat":35.899407,"sourceId":"364"}, + {"category":"mountain-summit","name":"唐松尾山","lon":138.845407,"lat":35.867685,"sourceId":"365"}, + {"category":"mountain-summit","name":"大洞山(飛龍山)","lon":138.892214,"lat":35.839787,"sourceId":"366"}, + {"category":"mountain-summit","name":"鶏冠山(黒川山)","lon":138.835644,"lat":35.788483,"sourceId":"367"}, + {"category":"mountain-summit","name":"大菩薩嶺","lon":138.845506,"lat":35.748762,"sourceId":"368"}, + {"category":"mountain-summit","name":"小金沢山","lon":138.856623,"lat":35.716946,"sourceId":"369"}, + {"category":"mountain-summit","name":"雁ケ腹摺山","lon":138.884831,"lat":35.686789,"sourceId":"370"}, + {"category":"mountain-summit","name":"権現山","lon":139.021435,"lat":35.667872,"sourceId":"371"}, + {"category":"mountain-summit","name":"権現山<扇山>","lon":139.013417,"lat":35.63708,"sourceId":"372"}, + {"category":"mountain-summit","name":"三頭山","lon":139.013762,"lat":35.738944,"sourceId":"373"}, + {"category":"mountain-summit","name":"大岳山","lon":139.130419,"lat":35.76522,"sourceId":"374"}, + {"category":"mountain-summit","name":"陣馬山(陣場山)","lon":139.166546,"lat":35.652177,"sourceId":"375"}, + {"category":"mountain-summit","name":"高尾山","lon":139.243611,"lat":35.625166,"sourceId":"376"}, + {"category":"mountain-summit","name":"清澄山<妙見山>","lon":140.152313,"lat":35.162267,"sourceId":"377"}, + {"category":"mountain-summit","name":"鹿野山","lon":139.971855,"lat":35.2556,"sourceId":"378"}, + {"category":"mountain-summit","name":"鋸山","lon":139.840864,"lat":35.160369,"sourceId":"379"}, + {"category":"mountain-summit","name":"富山","lon":139.881524,"lat":35.09885,"sourceId":"380"}, + {"category":"mountain-summit","name":"伊予ヶ岳","lon":139.913976,"lat":35.107302,"sourceId":"381"}, + {"category":"mountain-summit","name":"愛宕山","lon":139.986821,"lat":35.11488,"sourceId":"382"}, + {"category":"mountain-summit","name":"大山","lon":139.777722,"lat":34.964189,"sourceId":"383"}, + {"category":"mountain-summit","name":"大楠山","lon":139.628076,"lat":35.250068,"sourceId":"384"}, + {"category":"mountain-summit","name":"大山","lon":139.231158,"lat":35.440835,"sourceId":"385"}, + {"category":"mountain-summit","name":"丹沢山<蛭ヶ岳>","lon":139.138865,"lat":35.486279,"sourceId":"386"}, + {"category":"mountain-summit","name":"丹沢山","lon":139.16268,"lat":35.474293,"sourceId":"387"}, + {"category":"mountain-summit","name":"丹沢山<塔ノ岳>","lon":139.163313,"lat":35.454059,"sourceId":"388"}, + {"category":"mountain-summit","name":"大室山","lon":139.06858,"lat":35.51096,"sourceId":"389"}, + {"category":"mountain-summit","name":"御正体山","lon":138.931607,"lat":35.486924,"sourceId":"390"}, + {"category":"mountain-summit","name":"菰釣山","lon":138.978497,"lat":35.463695,"sourceId":"391"}, + {"category":"mountain-summit","name":"三ツ峠山","lon":138.809204,"lat":35.549272,"sourceId":"392"}, + {"category":"mountain-summit","name":"黒岳","lon":138.749436,"lat":35.552071,"sourceId":"393"}, + {"category":"mountain-summit","name":"黒岳<釈迦ヶ岳>","lon":138.72094,"lat":35.562904,"sourceId":"394"}, + {"category":"mountain-summit","name":"節刀ヶ岳","lon":138.683427,"lat":35.52849,"sourceId":"395"}, + {"category":"mountain-summit","name":"富士山<剣ヶ峯>","lon":138.727373,"lat":35.360738,"sourceId":"396"}, + {"category":"mountain-summit","name":"愛鷹山<越前岳>","lon":138.794016,"lat":35.238057,"sourceId":"397"}, + {"category":"mountain-summit","name":"毛無山","lon":138.543888,"lat":35.415833,"sourceId":"398"}, + {"category":"mountain-summit","name":"天子ヶ岳","lon":138.536708,"lat":35.329078,"sourceId":"399"}, + {"category":"mountain-summit","name":"箱根山<神山>","lon":139.020872,"lat":35.233404,"sourceId":"400"}, + {"category":"mountain-summit","name":"箱根山<金時山>","lon":139.004866,"lat":35.289673,"sourceId":"401"}, + {"category":"mountain-summit","name":"玄岳","lon":139.028108,"lat":35.08167,"sourceId":"402"}, + {"category":"mountain-summit","name":"大室山","lon":139.094653,"lat":34.903099,"sourceId":"403"}, + {"category":"mountain-summit","name":"天城山<万三郎岳>","lon":139.001803,"lat":34.862844,"sourceId":"404"}, + {"category":"mountain-summit","name":"達磨山","lon":138.839287,"lat":34.954989,"sourceId":"405"}, + {"category":"mountain-summit","name":"長九郎山","lon":138.867996,"lat":34.788372,"sourceId":"406"}, + {"category":"mountain-summit","name":"三原山<三原新山>","lon":139.394473,"lat":34.724493,"sourceId":"407"}, + {"category":"mountain-summit","name":"宮塚山","lon":139.279288,"lat":34.52041,"sourceId":"408"}, + {"category":"mountain-summit","name":"天上山","lon":139.153171,"lat":34.21954,"sourceId":"409"}, + {"category":"mountain-summit","name":"雄山","lon":139.526183,"lat":34.093581,"sourceId":"410"}, + {"category":"mountain-summit","name":"御山","lon":139.601835,"lat":33.874456,"sourceId":"411"}, + {"category":"mountain-summit","name":"西山(八丈富士)","lon":139.766064,"lat":33.13704,"sourceId":"412"}, + {"category":"mountain-summit","name":"硫黄山","lon":140.302979,"lat":30.48386,"sourceId":"413"}, + {"category":"mountain-summit","name":"中央山","lon":142.218704,"lat":27.073342,"sourceId":"414"}, + {"category":"mountain-summit","name":"乳房山","lon":142.161478,"lat":26.659541,"sourceId":"415"}, + {"category":"mountain-summit","name":"榊ヶ峰","lon":141.281192,"lat":25.427729,"sourceId":"416"}, + {"category":"mountain-summit","name":"摺鉢山(パイプ山)","lon":141.28911,"lat":24.750385,"sourceId":"417"}, + {"category":"mountain-summit","name":"[南硫黄島]","lon":141.463556,"lat":24.233895,"sourceId":"418"}, + {"category":"mountain-summit","name":"金北山","lon":138.349816,"lat":38.103882,"sourceId":"419"}, + {"category":"mountain-summit","name":"大地山","lon":138.47971,"lat":37.958575,"sourceId":"420"}, + {"category":"mountain-summit","name":"弥彦山","lon":138.809053,"lat":37.704721,"sourceId":"421"}, + {"category":"mountain-summit","name":"米山","lon":138.483947,"lat":37.28954,"sourceId":"422"}, + {"category":"mountain-summit","name":"黒姫山","lon":138.592154,"lat":37.22608,"sourceId":"423"}, + {"category":"mountain-summit","name":"菱ヶ岳","lon":138.493284,"lat":37.032165,"sourceId":"424"}, + {"category":"mountain-summit","name":"斑尾山","lon":138.274312,"lat":36.837396,"sourceId":"425"}, + {"category":"mountain-summit","name":"鉾ヶ岳","lon":138.026453,"lat":37.028665,"sourceId":"426"}, + {"category":"mountain-summit","name":"駒ヶ岳","lon":137.948408,"lat":36.942751,"sourceId":"427"}, + {"category":"mountain-summit","name":"鋸岳","lon":137.968928,"lat":36.931868,"sourceId":"428"}, + {"category":"mountain-summit","name":"焼山","lon":138.03595,"lat":36.920958,"sourceId":"429"}, + {"category":"mountain-summit","name":"雨飾山","lon":137.96261,"lat":36.902021,"sourceId":"430"}, + {"category":"mountain-summit","name":"火打山","lon":138.068122,"lat":36.922702,"sourceId":"431"}, + {"category":"mountain-summit","name":"妙高山","lon":138.113457,"lat":36.89126,"sourceId":"432"}, + {"category":"mountain-summit","name":"黒姫山","lon":138.127183,"lat":36.813474,"sourceId":"433"}, + {"category":"mountain-summit","name":"高妻山","lon":138.051934,"lat":36.800046,"sourceId":"434"}, + {"category":"mountain-summit","name":"戸隠山","lon":138.05511,"lat":36.770389,"sourceId":"435"}, + {"category":"mountain-summit","name":"飯縄山(飯綱山)","lon":138.133694,"lat":36.739437,"sourceId":"436"}, + {"category":"mountain-summit","name":"虫倉山","lon":138.018948,"lat":36.649806,"sourceId":"437"}, + {"category":"mountain-summit","name":"聖山","lon":138.021415,"lat":36.484201,"sourceId":"438"}, + {"category":"mountain-summit","name":"冠着山(姨捨山)","lon":138.10666,"lat":36.468797,"sourceId":"439"}, + {"category":"mountain-summit","name":"美ヶ原<王ヶ頭>","lon":138.107445,"lat":36.225796,"sourceId":"440"}, + {"category":"mountain-summit","name":"鉢伏山","lon":138.059154,"lat":36.163046,"sourceId":"441"}, + {"category":"mountain-summit","name":"霧ヶ峰<車山>","lon":138.196669,"lat":36.102894,"sourceId":"442"}, + {"category":"mountain-summit","name":"蓼科山","lon":138.295071,"lat":36.103729,"sourceId":"443"}, + {"category":"mountain-summit","name":"横岳","lon":138.320056,"lat":36.08744,"sourceId":"444"}, + {"category":"mountain-summit","name":"縞枯山","lon":138.331549,"lat":36.075319,"sourceId":"445"}, + {"category":"mountain-summit","name":"天狗岳","lon":138.355453,"lat":36.019171,"sourceId":"446"}, + {"category":"mountain-summit","name":"峰の松目","lon":138.350526,"lat":35.99686,"sourceId":"447"}, + {"category":"mountain-summit","name":"硫黄岳","lon":138.369968,"lat":35.998776,"sourceId":"448"}, + {"category":"mountain-summit","name":"横岳","lon":138.372841,"lat":35.985965,"sourceId":"449"}, + {"category":"mountain-summit","name":"赤岳","lon":138.370067,"lat":35.970797,"sourceId":"450"}, + {"category":"mountain-summit","name":"阿弥陀岳","lon":138.358797,"lat":35.972453,"sourceId":"451"}, + {"category":"mountain-summit","name":"権現岳","lon":138.359742,"lat":35.949802,"sourceId":"452"}, + {"category":"mountain-summit","name":"権現岳<西岳>","lon":138.335031,"lat":35.950099,"sourceId":"453"}, + {"category":"mountain-summit","name":"権現岳<三ッ頭>","lon":138.363961,"lat":35.942263,"sourceId":"454"}, + {"category":"mountain-summit","name":"編笠山","lon":138.345057,"lat":35.941696,"sourceId":"455"}, + {"category":"mountain-summit","name":"黒姫山","lon":137.790069,"lat":36.976628,"sourceId":"456"}, + {"category":"mountain-summit","name":"明星山","lon":137.821852,"lat":36.940895,"sourceId":"457"}, + {"category":"mountain-summit","name":"犬ヶ岳","lon":137.715599,"lat":36.906887,"sourceId":"458"}, + {"category":"mountain-summit","name":"朝日岳","lon":137.729884,"lat":36.826738,"sourceId":"459"}, + {"category":"mountain-summit","name":"雪倉岳","lon":137.754036,"lat":36.794644,"sourceId":"460"}, + {"category":"mountain-summit","name":"鉢ヶ岳","lon":137.749502,"lat":36.780197,"sourceId":"461"}, + {"category":"mountain-summit","name":"小蓮華山","lon":137.77597,"lat":36.773521,"sourceId":"462"}, + {"category":"mountain-summit","name":"乗鞍岳","lon":137.799157,"lat":36.788274,"sourceId":"463"}, + {"category":"mountain-summit","name":"風吹岳","lon":137.839021,"lat":36.814216,"sourceId":"464"}, + {"category":"mountain-summit","name":"白馬岳","lon":137.758555,"lat":36.75851,"sourceId":"465"}, + {"category":"mountain-summit","name":"旭岳","lon":137.745854,"lat":36.757603,"sourceId":"466"}, + {"category":"mountain-summit","name":"清水岳","lon":137.718944,"lat":36.764724,"sourceId":"467"}, + {"category":"mountain-summit","name":"杓子岳","lon":137.759288,"lat":36.740596,"sourceId":"468"}, + {"category":"mountain-summit","name":"鑓ヶ岳","lon":137.755357,"lat":36.731353,"sourceId":"469"}, + {"category":"mountain-summit","name":"唐松岳","lon":137.754685,"lat":36.687234,"sourceId":"470"}, + {"category":"mountain-summit","name":"五龍岳","lon":137.752691,"lat":36.658407,"sourceId":"471"}, + {"category":"mountain-summit","name":"鹿島槍ヶ岳","lon":137.747058,"lat":36.624553,"sourceId":"472"}, + {"category":"mountain-summit","name":"爺ヶ岳","lon":137.750922,"lat":36.588301,"sourceId":"473"}, + {"category":"mountain-summit","name":"岩小屋沢岳","lon":137.713385,"lat":36.577009,"sourceId":"474"}, + {"category":"mountain-summit","name":"鳴沢岳","lon":137.695448,"lat":36.568794,"sourceId":"475"}, + {"category":"mountain-summit","name":"赤沢岳","lon":137.687554,"lat":36.561954,"sourceId":"476"}, + {"category":"mountain-summit","name":"スバリ岳","lon":137.684855,"lat":36.544364,"sourceId":"477"}, + {"category":"mountain-summit","name":"針ノ木岳","lon":137.684427,"lat":36.538077,"sourceId":"478"}, + {"category":"mountain-summit","name":"蓮華岳","lon":137.710543,"lat":36.535747,"sourceId":"479"}, + {"category":"mountain-summit","name":"北葛岳","lon":137.705546,"lat":36.51766,"sourceId":"480"}, + {"category":"mountain-summit","name":"不動岳","lon":137.667455,"lat":36.495459,"sourceId":"481"}, + {"category":"mountain-summit","name":"南沢岳","lon":137.653133,"lat":36.489886,"sourceId":"482"}, + {"category":"mountain-summit","name":"烏帽子岳","lon":137.650918,"lat":36.47951,"sourceId":"483"}, + {"category":"mountain-summit","name":"三ッ岳","lon":137.649436,"lat":36.454773,"sourceId":"484"}, + {"category":"mountain-summit","name":"野口五郎岳","lon":137.637903,"lat":36.432683,"sourceId":"485"}, + {"category":"mountain-summit","name":"南真砂岳","lon":137.641746,"lat":36.413473,"sourceId":"486"}, + {"category":"mountain-summit","name":"赤牛岳","lon":137.603289,"lat":36.46159,"sourceId":"487"}, + {"category":"mountain-summit","name":"水晶岳(黒岳)","lon":137.602779,"lat":36.426363,"sourceId":"488"}, + {"category":"mountain-summit","name":"祖父岳","lon":137.591183,"lat":36.411172,"sourceId":"489"}, + {"category":"mountain-summit","name":"鷲羽岳","lon":137.60525,"lat":36.402996,"sourceId":"490"}, + {"category":"mountain-summit","name":"三俣蓮華岳","lon":137.587734,"lat":36.389989,"sourceId":"491"}, + {"category":"mountain-summit","name":"僧ヶ岳","lon":137.563713,"lat":36.76172,"sourceId":"492"}, + {"category":"mountain-summit","name":"毛勝山","lon":137.590844,"lat":36.701111,"sourceId":"493"}, + {"category":"mountain-summit","name":"猫又山","lon":137.590383,"lat":36.681223,"sourceId":"494"}, + {"category":"mountain-summit","name":"池平山","lon":137.625404,"lat":36.641447,"sourceId":"495"}, + {"category":"mountain-summit","name":"剱岳","lon":137.616878,"lat":36.623248,"sourceId":"496"}, + {"category":"mountain-summit","name":"別山","lon":137.620403,"lat":36.597537,"sourceId":"497"}, + {"category":"mountain-summit","name":"剱御前","lon":137.608842,"lat":36.602429,"sourceId":"498"}, + {"category":"mountain-summit","name":"奥大日岳","lon":137.580978,"lat":36.598373,"sourceId":"499"}, + {"category":"mountain-summit","name":"大日岳","lon":137.549885,"lat":36.599456,"sourceId":"500"}, + {"category":"mountain-summit","name":"真砂岳","lon":137.619969,"lat":36.586688,"sourceId":"501"}, + {"category":"mountain-summit","name":"立山<大汝山>","lon":137.619808,"lat":36.575959,"sourceId":"502"}, + {"category":"mountain-summit","name":"国見岳","lon":137.589759,"lat":36.571238,"sourceId":"503"}, + {"category":"mountain-summit","name":"龍王岳","lon":137.60719,"lat":36.564724,"sourceId":"504"}, + {"category":"mountain-summit","name":"龍王岳<浄土山>","lon":137.603709,"lat":36.568474,"sourceId":"505"}, + {"category":"mountain-summit","name":"鷲岳","lon":137.587149,"lat":36.54185,"sourceId":"506"}, + {"category":"mountain-summit","name":"鳶山","lon":137.58602,"lat":36.534249,"sourceId":"507"}, + {"category":"mountain-summit","name":"越中沢岳","lon":137.583009,"lat":36.515037,"sourceId":"508"}, + {"category":"mountain-summit","name":"鍬崎山","lon":137.478028,"lat":36.539915,"sourceId":"509"}, + {"category":"mountain-summit","name":"薬師岳","lon":137.5448,"lat":36.468841,"sourceId":"510"}, + {"category":"mountain-summit","name":"北ノ俣岳(上ノ岳)","lon":137.512379,"lat":36.421042,"sourceId":"511"}, + {"category":"mountain-summit","name":"黒部五郎岳(中ノ俣岳)","lon":137.539948,"lat":36.39255,"sourceId":"512"}, + {"category":"mountain-summit","name":"双六岳","lon":137.587134,"lat":36.371857,"sourceId":"513"}, + {"category":"mountain-summit","name":"樅沢岳","lon":137.607869,"lat":36.366573,"sourceId":"514"}, + {"category":"mountain-summit","name":"弓折岳","lon":137.596538,"lat":36.349536,"sourceId":"515"}, + {"category":"mountain-summit","name":"抜戸岳","lon":137.575135,"lat":36.327665,"sourceId":"516"}, + {"category":"mountain-summit","name":"笠ヶ岳","lon":137.550401,"lat":36.315477,"sourceId":"517"}, + {"category":"mountain-summit","name":"錫杖岳","lon":137.547222,"lat":36.281944,"sourceId":"518"}, + {"category":"mountain-summit","name":"硫黄岳","lon":137.650609,"lat":36.380614,"sourceId":"519"}, + {"category":"mountain-summit","name":"唐沢岳","lon":137.716737,"lat":36.461169,"sourceId":"520"}, + {"category":"mountain-summit","name":"餓鬼岳","lon":137.736029,"lat":36.447226,"sourceId":"521"}, + {"category":"mountain-summit","name":"燕岳","lon":137.712756,"lat":36.406817,"sourceId":"522"}, + {"category":"mountain-summit","name":"有明山","lon":137.770678,"lat":36.391087,"sourceId":"523"}, + {"category":"mountain-summit","name":"大天井岳","lon":137.701124,"lat":36.364989,"sourceId":"524"}, + {"category":"mountain-summit","name":"東天井岳","lon":137.715563,"lat":36.353409,"sourceId":"525"}, + {"category":"mountain-summit","name":"横通岳","lon":137.726983,"lat":36.34261,"sourceId":"526"}, + {"category":"mountain-summit","name":"常念岳","lon":137.727592,"lat":36.325524,"sourceId":"527"}, + {"category":"mountain-summit","name":"蝶ヶ岳","lon":137.726084,"lat":36.287421,"sourceId":"528"}, + {"category":"mountain-summit","name":"大滝山","lon":137.743253,"lat":36.274092,"sourceId":"529"}, + {"category":"mountain-summit","name":"赤岩岳","lon":137.68405,"lat":36.343339,"sourceId":"530"}, + {"category":"mountain-summit","name":"西岳","lon":137.679658,"lat":36.337249,"sourceId":"531"}, + {"category":"mountain-summit","name":"赤沢山","lon":137.679755,"lat":36.329192,"sourceId":"532"}, + {"category":"mountain-summit","name":"槍ヶ岳","lon":137.647643,"lat":36.342024,"sourceId":"533"}, + {"category":"mountain-summit","name":"大喰岳","lon":137.646054,"lat":36.335859,"sourceId":"534"}, + {"category":"mountain-summit","name":"中岳","lon":137.646699,"lat":36.329819,"sourceId":"535"}, + {"category":"mountain-summit","name":"南岳","lon":137.650732,"lat":36.319012,"sourceId":"536"}, + {"category":"mountain-summit","name":"北穂高岳","lon":137.652029,"lat":36.30254,"sourceId":"537"}, + {"category":"mountain-summit","name":"涸沢岳","lon":137.64713,"lat":36.295845,"sourceId":"538"}, + {"category":"mountain-summit","name":"奥穂高岳","lon":137.647988,"lat":36.289226,"sourceId":"539"}, + {"category":"mountain-summit","name":"前穂高岳","lon":137.660574,"lat":36.281979,"sourceId":"540"}, + {"category":"mountain-summit","name":"西穂高岳","lon":137.629062,"lat":36.278994,"sourceId":"541"}, + {"category":"mountain-summit","name":"焼岳","lon":137.587067,"lat":36.226864,"sourceId":"542"}, + {"category":"mountain-summit","name":"霞沢岳","lon":137.640601,"lat":36.221205,"sourceId":"543"}, + {"category":"mountain-summit","name":"十石山","lon":137.59053,"lat":36.163593,"sourceId":"544"}, + {"category":"mountain-summit","name":"輝山","lon":137.522112,"lat":36.194134,"sourceId":"545"}, + {"category":"mountain-summit","name":"乗鞍岳<剣ヶ峰>","lon":137.553634,"lat":36.106487,"sourceId":"546"}, + {"category":"mountain-summit","name":"鎌ヶ峰","lon":137.599091,"lat":36.026598,"sourceId":"547"}, + {"category":"mountain-summit","name":"鉢盛山","lon":137.754849,"lat":36.086649,"sourceId":"548"}, + {"category":"mountain-summit","name":"御嶽山<剣ヶ峰>","lon":137.480439,"lat":35.893014,"sourceId":"549"}, + {"category":"mountain-summit","name":"小秀山","lon":137.397006,"lat":35.785438,"sourceId":"550"}, + {"category":"mountain-summit","name":"奥三界岳","lon":137.507219,"lat":35.681606,"sourceId":"551"}, + {"category":"mountain-summit","name":"尾城山","lon":137.34377,"lat":35.695967,"sourceId":"552"}, + {"category":"mountain-summit","name":"二ッ森山","lon":137.400381,"lat":35.571896,"sourceId":"553"}, + {"category":"mountain-summit","name":"経ヶ岳","lon":137.862633,"lat":35.912816,"sourceId":"554"}, + {"category":"mountain-summit","name":"大棚入山","lon":137.810099,"lat":35.848077,"sourceId":"555"}, + {"category":"mountain-summit","name":"将棊頭山","lon":137.827058,"lat":35.803393,"sourceId":"556"}, + {"category":"mountain-summit","name":"将棊頭山<茶臼山>","lon":137.821546,"lat":35.81492,"sourceId":"557"}, + {"category":"mountain-summit","name":"駒ヶ岳","lon":137.804574,"lat":35.78955,"sourceId":"558"}, + {"category":"mountain-summit","name":"駒ヶ岳<宝剣岳>","lon":137.809181,"lat":35.781411,"sourceId":"559"}, + {"category":"mountain-summit","name":"駒ヶ岳<麦草岳>","lon":137.786056,"lat":35.796716,"sourceId":"560"}, + {"category":"mountain-summit","name":"三沢岳","lon":137.794028,"lat":35.766737,"sourceId":"561"}, + {"category":"mountain-summit","name":"檜尾岳","lon":137.813288,"lat":35.751982,"sourceId":"562"}, + {"category":"mountain-summit","name":"熊沢岳","lon":137.803313,"lat":35.73933,"sourceId":"563"}, + {"category":"mountain-summit","name":"東川岳","lon":137.805523,"lat":35.725542,"sourceId":"564"}, + {"category":"mountain-summit","name":"空木岳","lon":137.817092,"lat":35.718979,"sourceId":"565"}, + {"category":"mountain-summit","name":"南駒ヶ岳","lon":137.810742,"lat":35.701424,"sourceId":"566"}, + {"category":"mountain-summit","name":"南駒ヶ岳<赤梛岳>","lon":137.815423,"lat":35.706333,"sourceId":"567"}, + {"category":"mountain-summit","name":"仙涯嶺","lon":137.813596,"lat":35.692319,"sourceId":"568"}, + {"category":"mountain-summit","name":"越百山","lon":137.803255,"lat":35.679414,"sourceId":"569"}, + {"category":"mountain-summit","name":"安平路山","lon":137.774372,"lat":35.630424,"sourceId":"570"}, + {"category":"mountain-summit","name":"摺古木山","lon":137.737595,"lat":35.612898,"sourceId":"571"}, + {"category":"mountain-summit","name":"南木曽岳","lon":137.644409,"lat":35.592555,"sourceId":"572"}, + {"category":"mountain-summit","name":"風越山(権現山)","lon":137.78381,"lat":35.549474,"sourceId":"573"}, + {"category":"mountain-summit","name":"恵那山","lon":137.597891,"lat":35.443044,"sourceId":"574"}, + {"category":"mountain-summit","name":"守屋山","lon":138.093406,"lat":35.967516,"sourceId":"575"}, + {"category":"mountain-summit","name":"入笠山","lon":138.171603,"lat":35.896323,"sourceId":"576"}, + {"category":"mountain-summit","name":"鋸岳","lon":138.210073,"lat":35.779,"sourceId":"577"}, + {"category":"mountain-summit","name":"駒ヶ岳","lon":138.236688,"lat":35.758009,"sourceId":"578"}, + {"category":"mountain-summit","name":"駒津峰","lon":138.230836,"lat":35.75468,"sourceId":"579"}, + {"category":"mountain-summit","name":"双児山","lon":138.221499,"lat":35.750668,"sourceId":"580"}, + {"category":"mountain-summit","name":"アサヨ峰","lon":138.241327,"lat":35.731697,"sourceId":"581"}, + {"category":"mountain-summit","name":"アサヨ峰<栗沢山>","lon":138.23423,"lat":35.737895,"sourceId":"582"}, + {"category":"mountain-summit","name":"高嶺","lon":138.287708,"lat":35.709766,"sourceId":"583"}, + {"category":"mountain-summit","name":"地蔵ヶ岳","lon":138.298738,"lat":35.71202,"sourceId":"584"}, + {"category":"mountain-summit","name":"観音ヶ岳","lon":138.304594,"lat":35.70173,"sourceId":"585"}, + {"category":"mountain-summit","name":"薬師ヶ岳","lon":138.311643,"lat":35.695922,"sourceId":"586"}, + {"category":"mountain-summit","name":"辻山","lon":138.321986,"lat":35.676284,"sourceId":"587"}, + {"category":"mountain-summit","name":"櫛形山","lon":138.369208,"lat":35.586693,"sourceId":"588"}, + {"category":"mountain-summit","name":"仙丈ヶ岳","lon":138.183581,"lat":35.720069,"sourceId":"589"}, + {"category":"mountain-summit","name":"伊那荒倉岳","lon":138.193859,"lat":35.689141,"sourceId":"590"}, + {"category":"mountain-summit","name":"小太郎山","lon":138.239996,"lat":35.700818,"sourceId":"591"}, + {"category":"mountain-summit","name":"北岳","lon":138.238828,"lat":35.674314,"sourceId":"592"}, + {"category":"mountain-summit","name":"間ノ岳","lon":138.228324,"lat":35.646012,"sourceId":"593"}, + {"category":"mountain-summit","name":"農鳥岳<西農鳥岳>","lon":138.230322,"lat":35.624763,"sourceId":"594"}, + {"category":"mountain-summit","name":"農鳥岳","lon":138.236857,"lat":35.621151,"sourceId":"595"}, + {"category":"mountain-summit","name":"大唐松山","lon":138.269444,"lat":35.621582,"sourceId":"596"}, + {"category":"mountain-summit","name":"広河内岳","lon":138.235839,"lat":35.606013,"sourceId":"597"}, + {"category":"mountain-summit","name":"大籠岳","lon":138.243406,"lat":35.593077,"sourceId":"598"}, + {"category":"mountain-summit","name":"笹山","lon":138.243995,"lat":35.571069,"sourceId":"599"}, + {"category":"mountain-summit","name":"黒檜山","lon":138.186112,"lat":35.644563,"sourceId":"600"}, + {"category":"mountain-summit","name":"安倍荒倉岳","lon":138.202969,"lat":35.623059,"sourceId":"601"}, + {"category":"mountain-summit","name":"新蛇抜山","lon":138.205353,"lat":35.607158,"sourceId":"602"}, + {"category":"mountain-summit","name":"北荒川岳","lon":138.194535,"lat":35.594694,"sourceId":"603"}, + {"category":"mountain-summit","name":"塩見岳","lon":138.183636,"lat":35.573139,"sourceId":"604"}, + {"category":"mountain-summit","name":"蝙蝠岳","lon":138.207315,"lat":35.556928,"sourceId":"605"}, + {"category":"mountain-summit","name":"徳右衛門岳","lon":138.226457,"lat":35.539425,"sourceId":"606"}, + {"category":"mountain-summit","name":"本谷山","lon":138.151673,"lat":35.570922,"sourceId":"607"}, + {"category":"mountain-summit","name":"戸倉山(伊那富士)","lon":138.051972,"lat":35.745036,"sourceId":"608"}, + {"category":"mountain-summit","name":"烏帽子岳","lon":138.153424,"lat":35.553537,"sourceId":"609"}, + {"category":"mountain-summit","name":"小河内岳","lon":138.153049,"lat":35.537741,"sourceId":"610"}, + {"category":"mountain-summit","name":"小日影山","lon":138.126357,"lat":35.525151,"sourceId":"611"}, + {"category":"mountain-summit","name":"大日影山","lon":138.138316,"lat":35.522162,"sourceId":"612"}, + {"category":"mountain-summit","name":"板屋岳","lon":138.146078,"lat":35.517883,"sourceId":"613"}, + {"category":"mountain-summit","name":"東岳(悪沢岳)","lon":138.182477,"lat":35.500735,"sourceId":"614"}, + {"category":"mountain-summit","name":"荒川岳<中岳>","lon":138.167067,"lat":35.496573,"sourceId":"615"}, + {"category":"mountain-summit","name":"荒川岳<前岳>","lon":138.164102,"lat":35.494059,"sourceId":"616"}, + {"category":"mountain-summit","name":"赤石岳","lon":138.15736,"lat":35.461206,"sourceId":"617"}, + {"category":"mountain-summit","name":"奥茶臼山","lon":138.069075,"lat":35.484968,"sourceId":"618"}, + {"category":"mountain-summit","name":"鬼面山","lon":137.990161,"lat":35.492011,"sourceId":"619"}, + {"category":"mountain-summit","name":"大沢岳","lon":138.120176,"lat":35.448905,"sourceId":"620"}, + {"category":"mountain-summit","name":"中盛丸山","lon":138.12202,"lat":35.442813,"sourceId":"621"}, + {"category":"mountain-summit","name":"兎岳","lon":138.121216,"lat":35.428589,"sourceId":"622"}, + {"category":"mountain-summit","name":"聖岳<前聖岳>","lon":138.139629,"lat":35.42254,"sourceId":"623"}, + {"category":"mountain-summit","name":"上河内岳","lon":138.152812,"lat":35.389619,"sourceId":"624"}, + {"category":"mountain-summit","name":"茶臼岳","lon":138.140555,"lat":35.369166,"sourceId":"625"}, + {"category":"mountain-summit","name":"仁田岳","lon":138.127095,"lat":35.36073,"sourceId":"626"}, + {"category":"mountain-summit","name":"光岳","lon":138.083779,"lat":35.338176,"sourceId":"627"}, + {"category":"mountain-summit","name":"池口岳","lon":138.038327,"lat":35.330208,"sourceId":"628"}, + {"category":"mountain-summit","name":"白倉山","lon":137.9921,"lat":35.301704,"sourceId":"629"}, + {"category":"mountain-summit","name":"熊伏山","lon":137.894893,"lat":35.263241,"sourceId":"630"}, + {"category":"mountain-summit","name":"大無間山","lon":138.161548,"lat":35.255979,"sourceId":"631"}, + {"category":"mountain-summit","name":"不動岳","lon":138.041108,"lat":35.235462,"sourceId":"632"}, + {"category":"mountain-summit","name":"黒法師岳","lon":138.029391,"lat":35.196168,"sourceId":"633"}, + {"category":"mountain-summit","name":"蕎麦粒山","lon":138.035381,"lat":35.126094,"sourceId":"634"}, + {"category":"mountain-summit","name":"高塚山","lon":138.003663,"lat":35.115464,"sourceId":"635"}, + {"category":"mountain-summit","name":"京丸山","lon":137.965929,"lat":35.096002,"sourceId":"636"}, + {"category":"mountain-summit","name":"秋葉山","lon":137.866006,"lat":34.981773,"sourceId":"637"}, + {"category":"mountain-summit","name":"八高山","lon":138.056682,"lat":34.907436,"sourceId":"638"}, + {"category":"mountain-summit","name":"笊ヶ岳","lon":138.25949,"lat":35.424158,"sourceId":"639"}, + {"category":"mountain-summit","name":"布引山","lon":138.25823,"lat":35.408473,"sourceId":"640"}, + {"category":"mountain-summit","name":"青薙山","lon":138.228743,"lat":35.369769,"sourceId":"641"}, + {"category":"mountain-summit","name":"身延山","lon":138.416295,"lat":35.397436,"sourceId":"642"}, + {"category":"mountain-summit","name":"七面山","lon":138.348906,"lat":35.369212,"sourceId":"643"}, + {"category":"mountain-summit","name":"山伏","lon":138.285269,"lat":35.304335,"sourceId":"644"}, + {"category":"mountain-summit","name":"十枚山","lon":138.377944,"lat":35.253666,"sourceId":"645"}, + {"category":"mountain-summit","name":"篠井山","lon":138.426477,"lat":35.247721,"sourceId":"646"}, + {"category":"mountain-summit","name":"高ドッキョウ","lon":138.438008,"lat":35.178497,"sourceId":"647"}, + {"category":"mountain-summit","name":"竜爪山","lon":138.40359,"lat":35.088344,"sourceId":"648"}, + {"category":"mountain-summit","name":"久能山","lon":138.468055,"lat":34.966111,"sourceId":"649"}, + {"category":"mountain-summit","name":"茶臼山","lon":137.655539,"lat":35.227555,"sourceId":"650"}, + {"category":"mountain-summit","name":"鷹ノ巣山","lon":137.494962,"lat":35.152597,"sourceId":"651"}, + {"category":"mountain-summit","name":"鳳来寺山","lon":137.5828,"lat":34.98402,"sourceId":"652"}, + {"category":"mountain-summit","name":"本宮山","lon":137.420576,"lat":34.909795,"sourceId":"653"}, + {"category":"mountain-summit","name":"猿投山","lon":137.166823,"lat":35.205909,"sourceId":"654"}, + {"category":"mountain-summit","name":"金華山","lon":136.781498,"lat":35.43344,"sourceId":"655"}, + {"category":"mountain-summit","name":"宝立山","lon":137.162548,"lat":37.428571,"sourceId":"656"}, + {"category":"mountain-summit","name":"鉢伏山","lon":136.97092,"lat":37.377754,"sourceId":"657"}, + {"category":"mountain-summit","name":"石動山","lon":136.971012,"lat":36.965562,"sourceId":"658"}, + {"category":"mountain-summit","name":"宝達山","lon":136.813035,"lat":36.78192,"sourceId":"659"}, + {"category":"mountain-summit","name":"二上山","lon":137.015378,"lat":36.790002,"sourceId":"660"}, + {"category":"mountain-summit","name":"牛岳","lon":137.031773,"lat":36.541225,"sourceId":"661"}, + {"category":"mountain-summit","name":"白木峰","lon":137.112128,"lat":36.415461,"sourceId":"662"}, + {"category":"mountain-summit","name":"金剛堂山","lon":137.048888,"lat":36.378888,"sourceId":"663"}, + {"category":"mountain-summit","name":"人形山","lon":136.939729,"lat":36.349016,"sourceId":"664"}, + {"category":"mountain-summit","name":"三ヶ辻山","lon":136.949927,"lat":36.337995,"sourceId":"665"}, + {"category":"mountain-summit","name":"猿ヶ馬場山","lon":136.942873,"lat":36.225951,"sourceId":"666"}, + {"category":"mountain-summit","name":"御前岳","lon":136.96019,"lat":36.198158,"sourceId":"667"}, + {"category":"mountain-summit","name":"大雨見山","lon":137.298483,"lat":36.253396,"sourceId":"668"}, + {"category":"mountain-summit","name":"位山","lon":137.196696,"lat":36.03823,"sourceId":"669"}, + {"category":"mountain-summit","name":"川上岳","lon":137.14958,"lat":36.008214,"sourceId":"670"}, + {"category":"mountain-summit","name":"鷲ヶ岳","lon":136.971464,"lat":35.940342,"sourceId":"671"}, + {"category":"mountain-summit","name":"八乙女山","lon":136.983162,"lat":36.540242,"sourceId":"672"}, + {"category":"mountain-summit","name":"高清水山","lon":136.942978,"lat":36.477349,"sourceId":"673"}, + {"category":"mountain-summit","name":"医王山<奥医王山>","lon":136.796013,"lat":36.512764,"sourceId":"674"}, + {"category":"mountain-summit","name":"口三方岳","lon":136.715502,"lat":36.373199,"sourceId":"675"}, + {"category":"mountain-summit","name":"高三郎山","lon":136.773611,"lat":36.371111,"sourceId":"676"}, + {"category":"mountain-summit","name":"大門山","lon":136.80369,"lat":36.365221,"sourceId":"677"}, + {"category":"mountain-summit","name":"大笠山","lon":136.789588,"lat":36.32045,"sourceId":"678"}, + {"category":"mountain-summit","name":"笈ヶ岳","lon":136.792193,"lat":36.298515,"sourceId":"679"}, + {"category":"mountain-summit","name":"三方岩岳","lon":136.844556,"lat":36.258193,"sourceId":"680"}, + {"category":"mountain-summit","name":"三方崩山","lon":136.86015,"lat":36.179001,"sourceId":"681"}, + {"category":"mountain-summit","name":"七倉山","lon":136.758842,"lat":36.171286,"sourceId":"682"}, + {"category":"mountain-summit","name":"白山<御前峰>","lon":136.771449,"lat":36.155063,"sourceId":"683"}, + {"category":"mountain-summit","name":"白山釈迦岳","lon":136.735687,"lat":36.152212,"sourceId":"684"}, + {"category":"mountain-summit","name":"別山","lon":136.765729,"lat":36.105439,"sourceId":"685"}, + {"category":"mountain-summit","name":"願教寺山","lon":136.739761,"lat":36.056929,"sourceId":"686"}, + {"category":"mountain-summit","name":"野伏ヶ岳","lon":136.732391,"lat":36.012843,"sourceId":"687"}, + {"category":"mountain-summit","name":"大日ヶ岳","lon":136.837818,"lat":36.001366,"sourceId":"688"}, + {"category":"mountain-summit","name":"赤兎山","lon":136.66191,"lat":36.06758,"sourceId":"689"}, + {"category":"mountain-summit","name":"経ヶ岳","lon":136.621893,"lat":36.046438,"sourceId":"690"}, + {"category":"mountain-summit","name":"鷲走ヶ岳","lon":136.615529,"lat":36.265868,"sourceId":"691"}, + {"category":"mountain-summit","name":"大日山","lon":136.493321,"lat":36.162094,"sourceId":"692"}, + {"category":"mountain-summit","name":"富士写ヶ岳","lon":136.363965,"lat":36.188553,"sourceId":"693"}, + {"category":"mountain-summit","name":"浄法寺山","lon":136.392678,"lat":36.124959,"sourceId":"694"}, + {"category":"mountain-summit","name":"国見岳","lon":136.083957,"lat":36.079266,"sourceId":"695"}, + {"category":"mountain-summit","name":"日野山","lon":136.207491,"lat":35.859184,"sourceId":"696"}, + {"category":"mountain-summit","name":"部子山","lon":136.439201,"lat":35.886133,"sourceId":"697"}, + {"category":"mountain-summit","name":"荒島岳","lon":136.601273,"lat":35.934289,"sourceId":"698"}, + {"category":"mountain-summit","name":"平家岳","lon":136.718893,"lat":35.809887,"sourceId":"699"}, + {"category":"mountain-summit","name":"高賀山","lon":136.859303,"lat":35.671819,"sourceId":"700"}, + {"category":"mountain-summit","name":"?風山","lon":136.6266,"lat":35.796862,"sourceId":"701"}, + {"category":"mountain-summit","name":"姥ヶ岳","lon":136.505847,"lat":35.809457,"sourceId":"702"}, + {"category":"mountain-summit","name":"能郷白山(権現山)","lon":136.51403,"lat":35.762394,"sourceId":"703"}, + {"category":"mountain-summit","name":"冠山","lon":136.409397,"lat":35.779773,"sourceId":"704"}, + {"category":"mountain-summit","name":"三周ヶ岳","lon":136.300103,"lat":35.683635,"sourceId":"705"}, + {"category":"mountain-summit","name":"金糞岳","lon":136.337233,"lat":35.55015,"sourceId":"706"}, + {"category":"mountain-summit","name":"伊吹山","lon":136.406346,"lat":35.417836,"sourceId":"707"}, + {"category":"mountain-summit","name":"霊仙山","lon":136.381231,"lat":35.27914,"sourceId":"708"}, + {"category":"mountain-summit","name":"笙ヶ岳","lon":136.511207,"lat":35.28364,"sourceId":"709"}, + {"category":"mountain-summit","name":"養老山","lon":136.523322,"lat":35.263152,"sourceId":"710"}, + {"category":"mountain-summit","name":"御池岳","lon":136.414738,"lat":35.178513,"sourceId":"711"}, + {"category":"mountain-summit","name":"竜ヶ岳","lon":136.445173,"lat":35.119324,"sourceId":"712"}, + {"category":"mountain-summit","name":"御在所山","lon":136.418015,"lat":35.020424,"sourceId":"713"}, + {"category":"mountain-summit","name":"雨乞岳","lon":136.384118,"lat":35.021075,"sourceId":"714"}, + {"category":"mountain-summit","name":"高畑山","lon":136.320803,"lat":34.889774,"sourceId":"715"}, + {"category":"mountain-summit","name":"霊山","lon":136.260474,"lat":34.817154,"sourceId":"716"}, + {"category":"mountain-summit","name":"経が峰","lon":136.37444,"lat":34.766521,"sourceId":"717"}, + {"category":"mountain-summit","name":"笠取山","lon":136.296371,"lat":34.733977,"sourceId":"718"}, + {"category":"mountain-summit","name":"武奈ヶ岳","lon":135.896999,"lat":35.264616,"sourceId":"719"}, + {"category":"mountain-summit","name":"皆子山","lon":135.835142,"lat":35.202273,"sourceId":"720"}, + {"category":"mountain-summit","name":"蓬莱山","lon":135.885735,"lat":35.209539,"sourceId":"721"}, + {"category":"mountain-summit","name":"比叡山<大比叡>","lon":135.834449,"lat":35.06583,"sourceId":"722"}, + {"category":"mountain-summit","name":"音羽山","lon":135.853004,"lat":34.976851,"sourceId":"723"}, + {"category":"mountain-summit","name":"三上山(近江富士)","lon":136.037563,"lat":35.050543,"sourceId":"724"}, + {"category":"mountain-summit","name":"鷲峰山","lon":135.908502,"lat":34.83049,"sourceId":"725"}, + {"category":"mountain-summit","name":"笠置山","lon":135.950672,"lat":34.751814,"sourceId":"726"}, + {"category":"mountain-summit","name":"若草山(三笠山)","lon":135.854317,"lat":34.691159,"sourceId":"727"}, + {"category":"mountain-summit","name":"三輪山","lon":135.866812,"lat":34.535033,"sourceId":"728"}, + {"category":"mountain-summit","name":"耳成山","lon":135.80536,"lat":34.514731,"sourceId":"729"}, + {"category":"mountain-summit","name":"天香久山","lon":135.818319,"lat":34.495631,"sourceId":"730"}, + {"category":"mountain-summit","name":"畝傍山","lon":135.784711,"lat":34.492586,"sourceId":"731"}, + {"category":"mountain-summit","name":"生駒山","lon":135.678994,"lat":34.678459,"sourceId":"732"}, + {"category":"mountain-summit","name":"信貴山","lon":135.668339,"lat":34.612698,"sourceId":"733"}, + {"category":"mountain-summit","name":"二上山<雄岳>","lon":135.677668,"lat":34.525625,"sourceId":"734"}, + {"category":"mountain-summit","name":"葛城山","lon":135.682317,"lat":34.456157,"sourceId":"735"}, + {"category":"mountain-summit","name":"金剛山","lon":135.672945,"lat":34.419446,"sourceId":"736"}, + {"category":"mountain-summit","name":"岩湧山","lon":135.551011,"lat":34.374078,"sourceId":"737"}, + {"category":"mountain-summit","name":"葛城山","lon":135.434375,"lat":34.348009,"sourceId":"738"}, + {"category":"mountain-summit","name":"俎石山","lon":135.207857,"lat":34.308398,"sourceId":"739"}, + {"category":"mountain-summit","name":"堀坂山","lon":136.432892,"lat":34.546535,"sourceId":"740"}, + {"category":"mountain-summit","name":"尼ケ岳","lon":136.219675,"lat":34.557587,"sourceId":"741"}, + {"category":"mountain-summit","name":"倶留尊山","lon":136.17031,"lat":34.530913,"sourceId":"742"}, + {"category":"mountain-summit","name":"局ヶ岳","lon":136.327036,"lat":34.460466,"sourceId":"743"}, + {"category":"mountain-summit","name":"三峰山","lon":136.206263,"lat":34.448712,"sourceId":"744"}, + {"category":"mountain-summit","name":"高見山","lon":136.088468,"lat":34.428659,"sourceId":"745"}, + {"category":"mountain-summit","name":"竜門岳","lon":135.897684,"lat":34.440632,"sourceId":"746"}, + {"category":"mountain-summit","name":"朝熊ヶ岳","lon":136.781584,"lat":34.461151,"sourceId":"747"}, + {"category":"mountain-summit","name":"七洞岳","lon":136.515752,"lat":34.373058,"sourceId":"748"}, + {"category":"mountain-summit","name":"迷岳","lon":136.207531,"lat":34.35013,"sourceId":"749"}, + {"category":"mountain-summit","name":"国見山","lon":136.086639,"lat":34.375773,"sourceId":"750"}, + {"category":"mountain-summit","name":"池木屋山","lon":136.12945,"lat":34.316079,"sourceId":"751"}, + {"category":"mountain-summit","name":"白鬚岳","lon":136.046943,"lat":34.292932,"sourceId":"752"}, + {"category":"mountain-summit","name":"大台ヶ原山<日出ヶ岳>","lon":136.109199,"lat":34.185227,"sourceId":"753"}, + {"category":"mountain-summit","name":"山上ヶ岳","lon":135.941058,"lat":34.25239,"sourceId":"754"}, + {"category":"mountain-summit","name":"大普賢岳","lon":135.9628,"lat":34.227853,"sourceId":"755"}, + {"category":"mountain-summit","name":"弥山","lon":135.909335,"lat":34.180121,"sourceId":"756"}, + {"category":"mountain-summit","name":"八経ヶ岳","lon":135.90755,"lat":34.173586,"sourceId":"757"}, + {"category":"mountain-summit","name":"仏生嶽","lon":135.91314,"lat":34.134501,"sourceId":"758"}, + {"category":"mountain-summit","name":"釈迦ヶ岳","lon":135.903126,"lat":34.114308,"sourceId":"759"}, + {"category":"mountain-summit","name":"涅槃岳","lon":135.896903,"lat":34.058235,"sourceId":"760"}, + {"category":"mountain-summit","name":"笠捨山","lon":135.898319,"lat":33.982804,"sourceId":"761"}, + {"category":"mountain-summit","name":"玉置山","lon":135.831617,"lat":33.926584,"sourceId":"762"}, + {"category":"mountain-summit","name":"高峰山","lon":136.147211,"lat":34.032003,"sourceId":"763"}, + {"category":"mountain-summit","name":"子ノ泊山","lon":135.93046,"lat":33.794579,"sourceId":"764"}, + {"category":"mountain-summit","name":"龍門山","lon":135.411366,"lat":34.238007,"sourceId":"765"}, + {"category":"mountain-summit","name":"生石ヶ峰","lon":135.334158,"lat":34.105074,"sourceId":"766"}, + {"category":"mountain-summit","name":"白馬山","lon":135.372249,"lat":34.015793,"sourceId":"767"}, + {"category":"mountain-summit","name":"伯母子岳","lon":135.650735,"lat":34.077416,"sourceId":"768"}, + {"category":"mountain-summit","name":"護摩壇山","lon":135.567128,"lat":34.057402,"sourceId":"769"}, + {"category":"mountain-summit","name":"牛廻山","lon":135.631278,"lat":33.948995,"sourceId":"770"}, + {"category":"mountain-summit","name":"冷水山","lon":135.652818,"lat":33.899102,"sourceId":"771"}, + {"category":"mountain-summit","name":"大塔山","lon":135.713839,"lat":33.726858,"sourceId":"772"}, + {"category":"mountain-summit","name":"法師山","lon":135.665343,"lat":33.725942,"sourceId":"773"}, + {"category":"mountain-summit","name":"那智山<烏帽子山>","lon":135.900842,"lat":33.696115,"sourceId":"774"}, + {"category":"mountain-summit","name":"善司ノ森山","lon":135.546946,"lat":33.593693,"sourceId":"775"}, + {"category":"mountain-summit","name":"野坂岳","lon":136.024585,"lat":35.589639,"sourceId":"776"}, + {"category":"mountain-summit","name":"雲谷山","lon":135.945117,"lat":35.557672,"sourceId":"777"}, + {"category":"mountain-summit","name":"久須夜ヶ岳","lon":135.733748,"lat":35.557446,"sourceId":"778"}, + {"category":"mountain-summit","name":"百里ヶ岳","lon":135.810096,"lat":35.391788,"sourceId":"779"}, + {"category":"mountain-summit","name":"飯盛山","lon":135.660108,"lat":35.451902,"sourceId":"780"}, + {"category":"mountain-summit","name":"青葉山","lon":135.483784,"lat":35.50525,"sourceId":"781"}, + {"category":"mountain-summit","name":"頭巾山","lon":135.53445,"lat":35.377385,"sourceId":"782"}, + {"category":"mountain-summit","name":"長老ヶ岳","lon":135.473986,"lat":35.303243,"sourceId":"783"}, + {"category":"mountain-summit","name":"桟敷ヶ岳","lon":135.717236,"lat":35.159212,"sourceId":"784"}, + {"category":"mountain-summit","name":"愛宕山","lon":135.634252,"lat":35.060005,"sourceId":"785"}, + {"category":"mountain-summit","name":"ポンポン山","lon":135.623809,"lat":34.935163,"sourceId":"786"}, + {"category":"mountain-summit","name":"妙見山","lon":135.467347,"lat":34.928916,"sourceId":"787"}, + {"category":"mountain-summit","name":"歌垣山","lon":135.476756,"lat":34.974033,"sourceId":"788"}, + {"category":"mountain-summit","name":"剣尾山","lon":135.403888,"lat":35.00336,"sourceId":"789"}, + {"category":"mountain-summit","name":"三嶽","lon":135.2449,"lat":35.127264,"sourceId":"790"}, + {"category":"mountain-summit","name":"白髪岳","lon":135.134825,"lat":35.046008,"sourceId":"791"}, + {"category":"mountain-summit","name":"太鼓山","lon":135.204236,"lat":35.695268,"sourceId":"792"}, + {"category":"mountain-summit","name":"磯砂山","lon":135.038108,"lat":35.548529,"sourceId":"793"}, + {"category":"mountain-summit","name":"大江山(千丈ヶ嶽)","lon":135.106653,"lat":35.453453,"sourceId":"794"}, + {"category":"mountain-summit","name":"東床尾山","lon":134.915863,"lat":35.421887,"sourceId":"795"}, + {"category":"mountain-summit","name":"来日岳","lon":134.785454,"lat":35.61195,"sourceId":"796"}, + {"category":"mountain-summit","name":"六甲山","lon":135.263741,"lat":34.778004,"sourceId":"797"}, + {"category":"mountain-summit","name":"摩耶山","lon":135.204328,"lat":34.732933,"sourceId":"798"}, + {"category":"mountain-summit","name":"妙見山","lon":134.943495,"lat":34.497243,"sourceId":"799"}, + {"category":"mountain-summit","name":"諭鶴羽山","lon":134.814188,"lat":34.234865,"sourceId":"800"}, + {"category":"mountain-summit","name":"粟鹿山","lon":134.915427,"lat":35.272785,"sourceId":"801"}, + {"category":"mountain-summit","name":"千ヶ峰","lon":134.883927,"lat":35.144714,"sourceId":"802"}, + {"category":"mountain-summit","name":"笠形山","lon":134.834717,"lat":35.064159,"sourceId":"803"}, + {"category":"mountain-summit","name":"久斗山","lon":134.562701,"lat":35.621151,"sourceId":"804"}, + {"category":"mountain-summit","name":"蘇武岳","lon":134.637965,"lat":35.470792,"sourceId":"805"}, + {"category":"mountain-summit","name":"妙見山","lon":134.64385,"lat":35.41058,"sourceId":"806"}, + {"category":"mountain-summit","name":"藤無山","lon":134.600501,"lat":35.266677,"sourceId":"807"}, + {"category":"mountain-summit","name":"段ヶ峰","lon":134.727417,"lat":35.191576,"sourceId":"808"}, + {"category":"mountain-summit","name":"雪彦山","lon":134.651212,"lat":35.072813,"sourceId":"809"}, + {"category":"mountain-summit","name":"扇ノ山","lon":134.440891,"lat":35.439806,"sourceId":"810"}, + {"category":"mountain-summit","name":"鉢伏山","lon":134.53637,"lat":35.395384,"sourceId":"811"}, + {"category":"mountain-summit","name":"氷ノ山(須賀ノ山)","lon":134.513842,"lat":35.353959,"sourceId":"812"}, + {"category":"mountain-summit","name":"三室山","lon":134.462814,"lat":35.236695,"sourceId":"813"}, + {"category":"mountain-summit","name":"東山","lon":134.383007,"lat":35.292274,"sourceId":"814"}, + {"category":"mountain-summit","name":"沖ノ山","lon":134.356769,"lat":35.248033,"sourceId":"815"}, + {"category":"mountain-summit","name":"後山","lon":134.411242,"lat":35.18682,"sourceId":"816"}, + {"category":"mountain-summit","name":"那岐山","lon":134.180277,"lat":35.171666,"sourceId":"817"}, + {"category":"mountain-summit","name":"書写山","lon":134.656452,"lat":34.892477,"sourceId":"818"}, + {"category":"mountain-summit","name":"白旗山","lon":134.380778,"lat":34.908203,"sourceId":"819"}, + {"category":"mountain-summit","name":"八塔寺山","lon":134.252183,"lat":34.923179,"sourceId":"820"}, + {"category":"mountain-summit","name":"金山","lon":133.940744,"lat":34.744738,"sourceId":"821"}, + {"category":"mountain-summit","name":"大平山","lon":133.706828,"lat":34.899025,"sourceId":"822"}, + {"category":"mountain-summit","name":"大満寺山","lon":133.330531,"lat":36.256773,"sourceId":"823"}, + {"category":"mountain-summit","name":"焼火山","lon":133.031865,"lat":36.074766,"sourceId":"824"}, + {"category":"mountain-summit","name":"朝日山","lon":132.976412,"lat":35.507875,"sourceId":"825"}, + {"category":"mountain-summit","name":"鼻高山","lon":132.75264,"lat":35.412608,"sourceId":"826"}, + {"category":"mountain-summit","name":"高鉢山","lon":134.051146,"lat":35.360327,"sourceId":"827"}, + {"category":"mountain-summit","name":"花知ヶ仙","lon":133.974965,"lat":35.259528,"sourceId":"828"}, + {"category":"mountain-summit","name":"津黒山","lon":133.814994,"lat":35.251808,"sourceId":"829"}, + {"category":"mountain-summit","name":"蒜山<上蒜山>","lon":133.663636,"lat":35.324978,"sourceId":"830"}, + {"category":"mountain-summit","name":"矢筈ヶ山","lon":133.580902,"lat":35.386725,"sourceId":"831"}, + {"category":"mountain-summit","name":"大山<剣ヶ峰>","lon":133.546356,"lat":35.371204,"sourceId":"832"}, + {"category":"mountain-summit","name":"烏ヶ山","lon":133.570446,"lat":35.355788,"sourceId":"833"}, + {"category":"mountain-summit","name":"毛無山","lon":133.514697,"lat":35.235685,"sourceId":"834"}, + {"category":"mountain-summit","name":"宝仏山","lon":133.470074,"lat":35.229824,"sourceId":"835"}, + {"category":"mountain-summit","name":"星山","lon":133.677359,"lat":35.142068,"sourceId":"836"}, + {"category":"mountain-summit","name":"花見山","lon":133.40122,"lat":35.152385,"sourceId":"837"}, + {"category":"mountain-summit","name":"大倉山","lon":133.355216,"lat":35.133731,"sourceId":"838"}, + {"category":"mountain-summit","name":"船通山","lon":133.178625,"lat":35.155909,"sourceId":"839"}, + {"category":"mountain-summit","name":"道後山","lon":133.232894,"lat":35.069504,"sourceId":"840"}, + {"category":"mountain-summit","name":"比婆山<立烏帽子山>","lon":133.06552,"lat":35.051964,"sourceId":"841"}, + {"category":"mountain-summit","name":"比婆山<烏帽子山>","lon":133.052312,"lat":35.070007,"sourceId":"842"}, + {"category":"mountain-summit","name":"猿政山","lon":132.967776,"lat":35.081313,"sourceId":"843"}, + {"category":"mountain-summit","name":"大万木山","lon":132.854546,"lat":35.087547,"sourceId":"844"}, + {"category":"mountain-summit","name":"琴引山(弥山)","lon":132.78249,"lat":35.048183,"sourceId":"845"}, + {"category":"mountain-summit","name":"三瓶山<男三瓶山>","lon":132.621536,"lat":35.140558,"sourceId":"846"}, + {"category":"mountain-summit","name":"大江高山","lon":132.428679,"lat":35.063907,"sourceId":"847"}, + {"category":"mountain-summit","name":"星居山","lon":133.221715,"lat":34.748152,"sourceId":"848"}, + {"category":"mountain-summit","name":"岳山","lon":133.156782,"lat":34.648976,"sourceId":"849"}, + {"category":"mountain-summit","name":"龍王山","lon":133.083416,"lat":34.45576,"sourceId":"850"}, + {"category":"mountain-summit","name":"鷹ノ巣山","lon":132.746014,"lat":34.565994,"sourceId":"851"}, + {"category":"mountain-summit","name":"白木山","lon":132.575438,"lat":34.521261,"sourceId":"852"}, + {"category":"mountain-summit","name":"呉娑々宇山","lon":132.54785,"lat":34.422894,"sourceId":"853"}, + {"category":"mountain-summit","name":"野呂山<膳棚山>","lon":132.666197,"lat":34.262293,"sourceId":"854"}, + {"category":"mountain-summit","name":"冠山","lon":132.501852,"lat":34.917493,"sourceId":"855"}, + {"category":"mountain-summit","name":"阿佐山","lon":132.381411,"lat":34.785932,"sourceId":"856"}, + {"category":"mountain-summit","name":"天狗石山","lon":132.342657,"lat":34.792234,"sourceId":"857"}, + {"category":"mountain-summit","name":"雲月山","lon":132.23876,"lat":34.802109,"sourceId":"858"}, + {"category":"mountain-summit","name":"大佐山","lon":132.201303,"lat":34.747718,"sourceId":"859"}, + {"category":"mountain-summit","name":"臥龍山(刈尾山)","lon":132.196763,"lat":34.690121,"sourceId":"860"}, + {"category":"mountain-summit","name":"深入山","lon":132.206797,"lat":34.649929,"sourceId":"861"}, + {"category":"mountain-summit","name":"恐羅漢山","lon":132.129674,"lat":34.595463,"sourceId":"862"}, + {"category":"mountain-summit","name":"十方山","lon":132.142161,"lat":34.565042,"sourceId":"863"}, + {"category":"mountain-summit","name":"大峯山","lon":132.216933,"lat":34.415768,"sourceId":"864"}, + {"category":"mountain-summit","name":"冠山","lon":132.076098,"lat":34.468731,"sourceId":"865"}, + {"category":"mountain-summit","name":"寂地山","lon":132.054434,"lat":34.467264,"sourceId":"866"}, + {"category":"mountain-summit","name":"安蔵寺山","lon":131.967229,"lat":34.477126,"sourceId":"867"}, + {"category":"mountain-summit","name":"小五郎山","lon":132.00858,"lat":34.416817,"sourceId":"868"}, + {"category":"mountain-summit","name":"羅漢山","lon":132.067231,"lat":34.355584,"sourceId":"869"}, + {"category":"mountain-summit","name":"鈴ノ大谷山","lon":131.839182,"lat":34.376647,"sourceId":"870"}, + {"category":"mountain-summit","name":"平家ヶ岳","lon":131.899699,"lat":34.318392,"sourceId":"871"}, + {"category":"mountain-summit","name":"馬糞ヶ岳","lon":131.896392,"lat":34.250621,"sourceId":"872"}, + {"category":"mountain-summit","name":"莇ヶ岳","lon":131.777774,"lat":34.332474,"sourceId":"873"}, + {"category":"mountain-summit","name":"青野山","lon":131.798081,"lat":34.462208,"sourceId":"874"}, + {"category":"mountain-summit","name":"十種ヶ峰","lon":131.695282,"lat":34.438334,"sourceId":"875"}, + {"category":"mountain-summit","name":"高山","lon":131.613406,"lat":34.652091,"sourceId":"876"}, + {"category":"mountain-summit","name":"西鳳翩山","lon":131.409425,"lat":34.2108,"sourceId":"877"}, + {"category":"mountain-summit","name":"大平山","lon":131.630339,"lat":34.073422,"sourceId":"878"}, + {"category":"mountain-summit","name":"桂木山","lon":131.302829,"lat":34.305399,"sourceId":"879"}, + {"category":"mountain-summit","name":"花尾山","lon":131.221028,"lat":34.283882,"sourceId":"880"}, + {"category":"mountain-summit","name":"天井ヶ岳","lon":131.042886,"lat":34.311004,"sourceId":"881"}, + {"category":"mountain-summit","name":"狗留孫山(御岳)","lon":130.976289,"lat":34.214638,"sourceId":"882"}, + {"category":"mountain-summit","name":"竜王山","lon":130.935859,"lat":34.068408,"sourceId":"883"}, + {"category":"mountain-summit","name":"嶮岨山<星ヶ城山>","lon":134.31748,"lat":34.51535,"sourceId":"884"}, + {"category":"mountain-summit","name":"熊ヶ峰","lon":133.368204,"lat":34.433416,"sourceId":"885"}, + {"category":"mountain-summit","name":"鷲ケ頭山","lon":133.021401,"lat":34.23914,"sourceId":"886"}, + {"category":"mountain-summit","name":"弥山","lon":132.319531,"lat":34.279431,"sourceId":"887"}, + {"category":"mountain-summit","name":"皇座山","lon":132.142361,"lat":33.853123,"sourceId":"888"}, + {"category":"mountain-summit","name":"五剣山(八栗山)","lon":134.141445,"lat":34.360989,"sourceId":"889"}, + {"category":"mountain-summit","name":"大平山","lon":133.952363,"lat":34.329854,"sourceId":"890"}, + {"category":"mountain-summit","name":"飯野山(讃岐富士)","lon":133.845875,"lat":34.274387,"sourceId":"891"}, + {"category":"mountain-summit","name":"象頭山<大麻山>","lon":133.788494,"lat":34.196627,"sourceId":"892"}, + {"category":"mountain-summit","name":"矢筈山","lon":134.196816,"lat":34.194245,"sourceId":"893"}, + {"category":"mountain-summit","name":"大滝山","lon":134.127065,"lat":34.123149,"sourceId":"894"}, + {"category":"mountain-summit","name":"竜王山","lon":134.048205,"lat":34.115455,"sourceId":"895"}, + {"category":"mountain-summit","name":"大川山","lon":133.939649,"lat":34.115143,"sourceId":"896"}, + {"category":"mountain-summit","name":"雲辺寺山","lon":133.722791,"lat":34.034983,"sourceId":"897"}, + {"category":"mountain-summit","name":"東三方ヶ森","lon":132.959708,"lat":33.902786,"sourceId":"898"}, + {"category":"mountain-summit","name":"高縄山","lon":132.850039,"lat":33.94572,"sourceId":"899"}, + {"category":"mountain-summit","name":"眉山","lon":134.516423,"lat":34.060846,"sourceId":"900"}, + {"category":"mountain-summit","name":"中津峰山","lon":134.509465,"lat":33.962425,"sourceId":"901"}, + {"category":"mountain-summit","name":"太竜寺山","lon":134.519785,"lat":33.874092,"sourceId":"902"}, + {"category":"mountain-summit","name":"高越山","lon":134.19596,"lat":34.017291,"sourceId":"903"}, + {"category":"mountain-summit","name":"雲早山","lon":134.294771,"lat":33.905736,"sourceId":"904"}, + {"category":"mountain-summit","name":"八面山","lon":134.09984,"lat":33.923431,"sourceId":"905"}, + {"category":"mountain-summit","name":"剣山","lon":134.094281,"lat":33.853576,"sourceId":"906"}, + {"category":"mountain-summit","name":"剣山<丸笹山>","lon":134.089867,"lat":33.874221,"sourceId":"907"}, + {"category":"mountain-summit","name":"塔丸","lon":134.039254,"lat":33.866938,"sourceId":"908"}, + {"category":"mountain-summit","name":"矢筈山","lon":133.974905,"lat":33.925053,"sourceId":"909"}, + {"category":"mountain-summit","name":"中津山","lon":133.844098,"lat":33.924625,"sourceId":"910"}, + {"category":"mountain-summit","name":"国見山","lon":133.784674,"lat":33.907541,"sourceId":"911"}, + {"category":"mountain-summit","name":"天狗塚","lon":133.943338,"lat":33.826902,"sourceId":"912"}, + {"category":"mountain-summit","name":"三嶺","lon":133.987666,"lat":33.839401,"sourceId":"913"}, + {"category":"mountain-summit","name":"白髪山","lon":133.992926,"lat":33.810133,"sourceId":"914"}, + {"category":"mountain-summit","name":"石立山","lon":134.054874,"lat":33.784833,"sourceId":"915"}, + {"category":"mountain-summit","name":"梶ヶ森","lon":133.75152,"lat":33.759191,"sourceId":"916"}, + {"category":"mountain-summit","name":"甚吉森","lon":134.110066,"lat":33.694814,"sourceId":"917"}, + {"category":"mountain-summit","name":"天狗森","lon":134.067874,"lat":33.6188,"sourceId":"918"}, + {"category":"mountain-summit","name":"鐘ヶ龍森","lon":134.082042,"lat":33.547023,"sourceId":"919"}, + {"category":"mountain-summit","name":"工石山","lon":133.579965,"lat":33.855616,"sourceId":"920"}, + {"category":"mountain-summit","name":"白髪山","lon":133.590702,"lat":33.81599,"sourceId":"921"}, + {"category":"mountain-summit","name":"稲叢山","lon":133.359302,"lat":33.747025,"sourceId":"922"}, + {"category":"mountain-summit","name":"工石山","lon":133.505893,"lat":33.669911,"sourceId":"923"}, + {"category":"mountain-summit","name":"赤星山","lon":133.457936,"lat":33.919376,"sourceId":"924"}, + {"category":"mountain-summit","name":"東赤石山","lon":133.373874,"lat":33.87526,"sourceId":"925"}, + {"category":"mountain-summit","name":"笹ヶ峰","lon":133.274591,"lat":33.828111,"sourceId":"926"}, + {"category":"mountain-summit","name":"伊予富士","lon":133.248041,"lat":33.788003,"sourceId":"927"}, + {"category":"mountain-summit","name":"瓶ヶ森","lon":133.193258,"lat":33.794607,"sourceId":"928"}, + {"category":"mountain-summit","name":"石鎚山<天狗岳>","lon":133.115086,"lat":33.76777,"sourceId":"929"}, + {"category":"mountain-summit","name":"二ノ森","lon":133.093777,"lat":33.758541,"sourceId":"930"}, + {"category":"mountain-summit","name":"筒上山","lon":133.161135,"lat":33.731951,"sourceId":"931"}, + {"category":"mountain-summit","name":"石墨山","lon":132.984648,"lat":33.733786,"sourceId":"932"}, + {"category":"mountain-summit","name":"障子山","lon":132.765228,"lat":33.705328,"sourceId":"933"}, + {"category":"mountain-summit","name":"壺神山","lon":132.554612,"lat":33.605567,"sourceId":"934"}, + {"category":"mountain-summit","name":"出石山","lon":132.465243,"lat":33.534926,"sourceId":"935"}, + {"category":"mountain-summit","name":"神南山","lon":132.632889,"lat":33.517433,"sourceId":"936"}, + {"category":"mountain-summit","name":"御在所山","lon":132.755822,"lat":33.343979,"sourceId":"937"}, + {"category":"mountain-summit","name":"笠取山","lon":132.922506,"lat":33.555494,"sourceId":"938"}, + {"category":"mountain-summit","name":"明神山(中津山)","lon":133.046646,"lat":33.575552,"sourceId":"939"}, + {"category":"mountain-summit","name":"不入山","lon":133.063174,"lat":33.440779,"sourceId":"940"}, + {"category":"mountain-summit","name":"蟠蛇森","lon":133.259548,"lat":33.441107,"sourceId":"941"}, + {"category":"mountain-summit","name":"鈴ヶ森","lon":133.051017,"lat":33.356285,"sourceId":"942"}, + {"category":"mountain-summit","name":"五在所ノ峯","lon":133.150743,"lat":33.177287,"sourceId":"943"}, + {"category":"mountain-summit","name":"堂が森","lon":132.876657,"lat":33.159873,"sourceId":"944"}, + {"category":"mountain-summit","name":"高月山","lon":132.634642,"lat":33.208681,"sourceId":"945"}, + {"category":"mountain-summit","name":"三本杭","lon":132.635416,"lat":33.188324,"sourceId":"946"}, + {"category":"mountain-summit","name":"篠山","lon":132.659067,"lat":33.055727,"sourceId":"947"}, + {"category":"mountain-summit","name":"今ノ山","lon":132.850118,"lat":32.857521,"sourceId":"948"}, + {"category":"mountain-summit","name":"足立山(霧ヶ岳)","lon":130.91722,"lat":33.859516,"sourceId":"949"}, + {"category":"mountain-summit","name":"貫山","lon":130.909361,"lat":33.780687,"sourceId":"950"}, + {"category":"mountain-summit","name":"福智山","lon":130.803889,"lat":33.742247,"sourceId":"951"}, + {"category":"mountain-summit","name":"犬ヶ岳","lon":131.001717,"lat":33.512212,"sourceId":"952"}, + {"category":"mountain-summit","name":"英彦山","lon":130.925973,"lat":33.476247,"sourceId":"953"}, + {"category":"mountain-summit","name":"馬見山","lon":130.767889,"lat":33.486462,"sourceId":"954"}, + {"category":"mountain-summit","name":"犬鳴山(熊ヶ城)","lon":130.54924,"lat":33.687241,"sourceId":"955"}, + {"category":"mountain-summit","name":"三郡山","lon":130.583767,"lat":33.555536,"sourceId":"956"}, + {"category":"mountain-summit","name":"基山","lon":130.510173,"lat":33.44271,"sourceId":"957"}, + {"category":"mountain-summit","name":"脊振山","lon":130.368623,"lat":33.43644,"sourceId":"958"}, + {"category":"mountain-summit","name":"浮嶽","lon":130.099226,"lat":33.469965,"sourceId":"959"}, + {"category":"mountain-summit","name":"天山","lon":130.142897,"lat":33.338886,"sourceId":"960"}, + {"category":"mountain-summit","name":"両子山","lon":131.601541,"lat":33.58312,"sourceId":"961"}, + {"category":"mountain-summit","name":"鹿嵐山","lon":131.252756,"lat":33.434588,"sourceId":"962"}, + {"category":"mountain-summit","name":"鶴見岳","lon":131.429751,"lat":33.286621,"sourceId":"963"}, + {"category":"mountain-summit","name":"由布岳(豊後富士)","lon":131.390233,"lat":33.282267,"sourceId":"964"}, + {"category":"mountain-summit","name":"万年山","lon":131.130589,"lat":33.23135,"sourceId":"965"}, + {"category":"mountain-summit","name":"涌蓋山","lon":131.164344,"lat":33.139941,"sourceId":"966"}, + {"category":"mountain-summit","name":"くじゅう連山<中岳>","lon":131.249001,"lat":33.085955,"sourceId":"967"}, + {"category":"mountain-summit","name":"くじゅう連山<黒岳>","lon":131.292913,"lat":33.105963,"sourceId":"968"}, + {"category":"mountain-summit","name":"くじゅう連山<大船山>","lon":131.28069,"lat":33.095024,"sourceId":"969"}, + {"category":"mountain-summit","name":"くじゅう連山<三俣山>","lon":131.246516,"lat":33.104003,"sourceId":"970"}, + {"category":"mountain-summit","name":"くじゅう連山<星生山>","lon":131.232687,"lat":33.090768,"sourceId":"971"}, + {"category":"mountain-summit","name":"くじゅう連山<久住山>","lon":131.240871,"lat":33.082187,"sourceId":"972"}, + {"category":"mountain-summit","name":"阿蘇山<高岳>","lon":131.103918,"lat":32.884318,"sourceId":"973"}, + {"category":"mountain-summit","name":"阿蘇山<根子岳>","lon":131.144371,"lat":32.883527,"sourceId":"974"}, + {"category":"mountain-summit","name":"阿蘇山<中岳>","lon":131.097001,"lat":32.883528,"sourceId":"975"}, + {"category":"mountain-summit","name":"阿蘇山<杵島岳>","lon":131.057898,"lat":32.892701,"sourceId":"976"}, + {"category":"mountain-summit","name":"阿蘇山<烏帽子岳>","lon":131.058014,"lat":32.873679,"sourceId":"977"}, + {"category":"mountain-summit","name":"鷹取山","lon":130.722822,"lat":33.303028,"sourceId":"978"}, + {"category":"mountain-summit","name":"釈迦岳","lon":130.889012,"lat":33.187593,"sourceId":"979"}, + {"category":"mountain-summit","name":"酒呑童子山","lon":130.908413,"lat":33.097007,"sourceId":"980"}, + {"category":"mountain-summit","name":"国見山","lon":130.817547,"lat":33.105431,"sourceId":"981"}, + {"category":"mountain-summit","name":"筒ヶ岳","lon":130.527365,"lat":32.984633,"sourceId":"982"}, + {"category":"mountain-summit","name":"金峰山","lon":130.639004,"lat":32.814034,"sourceId":"983"}, + {"category":"mountain-summit","name":"黒髪山","lon":129.901689,"lat":33.214177,"sourceId":"984"}, + {"category":"mountain-summit","name":"国見山","lon":129.812839,"lat":33.236889,"sourceId":"985"}, + {"category":"mountain-summit","name":"虚空蔵山(虚空蔵岳)","lon":129.921333,"lat":33.090457,"sourceId":"986"}, + {"category":"mountain-summit","name":"多良岳<経ヶ岳>","lon":130.076281,"lat":32.987528,"sourceId":"987"}, + {"category":"mountain-summit","name":"多良岳","lon":130.092539,"lat":32.975491,"sourceId":"988"}, + {"category":"mountain-summit","name":"多良岳<五家原岳>","lon":130.076548,"lat":32.958044,"sourceId":"989"}, + {"category":"mountain-summit","name":"雲仙岳<普賢岳>","lon":130.292147,"lat":32.759909,"sourceId":"990"}, + {"category":"mountain-summit","name":"雲仙岳<平成新山>","lon":130.298849,"lat":32.761257,"sourceId":"991"}, + {"category":"mountain-summit","name":"長浦岳","lon":129.744841,"lat":32.913531,"sourceId":"992"}, + {"category":"mountain-summit","name":"八郎岳","lon":129.856522,"lat":32.670216,"sourceId":"993"}, + {"category":"mountain-summit","name":"御岳<雄岳>","lon":129.381747,"lat":34.572366,"sourceId":"994"}, + {"category":"mountain-summit","name":"白嶽","lon":129.249595,"lat":34.26391,"sourceId":"995"}, + {"category":"mountain-summit","name":"有明山","lon":129.265008,"lat":34.204406,"sourceId":"996"}, + {"category":"mountain-summit","name":"矢立山","lon":129.226116,"lat":34.177379,"sourceId":"997"}, + {"category":"mountain-summit","name":"山王山(雄嶽)","lon":129.052939,"lat":32.933462,"sourceId":"998"}, + {"category":"mountain-summit","name":"父ヶ岳","lon":128.681048,"lat":32.698195,"sourceId":"999"}, + {"category":"mountain-summit","name":"佩楯山","lon":131.645749,"lat":32.953095,"sourceId":"1000"}, + {"category":"mountain-summit","name":"傾山","lon":131.475911,"lat":32.839,"sourceId":"1001"}, + {"category":"mountain-summit","name":"大崩山","lon":131.513389,"lat":32.737774,"sourceId":"1002"}, + {"category":"mountain-summit","name":"行縢山","lon":131.572272,"lat":32.625935,"sourceId":"1003"}, + {"category":"mountain-summit","name":"祖母山","lon":131.347045,"lat":32.828131,"sourceId":"1004"}, + {"category":"mountain-summit","name":"古祖母山","lon":131.364321,"lat":32.802729,"sourceId":"1005"}, + {"category":"mountain-summit","name":"諸塚山","lon":131.287423,"lat":32.634592,"sourceId":"1006"}, + {"category":"mountain-summit","name":"向坂山","lon":131.104868,"lat":32.579622,"sourceId":"1007"}, + {"category":"mountain-summit","name":"国見岳","lon":131.018363,"lat":32.547131,"sourceId":"1008"}, + {"category":"mountain-summit","name":"上福根山","lon":130.946812,"lat":32.473426,"sourceId":"1009"}, + {"category":"mountain-summit","name":"江代山(津野岳)","lon":131.076072,"lat":32.370442,"sourceId":"1010"}, + {"category":"mountain-summit","name":"市房山","lon":131.101036,"lat":32.311682,"sourceId":"1011"}, + {"category":"mountain-summit","name":"石堂山","lon":131.167673,"lat":32.300192,"sourceId":"1012"}, + {"category":"mountain-summit","name":"尾鈴山","lon":131.426587,"lat":32.299301,"sourceId":"1013"}, + {"category":"mountain-summit","name":"仰烏帽子山","lon":130.777717,"lat":32.371428,"sourceId":"1014"}, + {"category":"mountain-summit","name":"白髪岳","lon":130.943371,"lat":32.150399,"sourceId":"1015"}, + {"category":"mountain-summit","name":"国見山","lon":130.614181,"lat":32.187845,"sourceId":"1016"}, + {"category":"mountain-summit","name":"矢筈岳","lon":130.400678,"lat":32.122915,"sourceId":"1017"}, + {"category":"mountain-summit","name":"紫尾山(上宮山)","lon":130.367641,"lat":31.980835,"sourceId":"1018"}, + {"category":"mountain-summit","name":"鰐塚山","lon":131.269503,"lat":31.768866,"sourceId":"1019"}, + {"category":"mountain-summit","name":"霧島山<韓国岳>","lon":130.861577,"lat":31.93417,"sourceId":"1020"}, + {"category":"mountain-summit","name":"霧島山<新燃岳>","lon":130.886395,"lat":31.909582,"sourceId":"1021"}, + {"category":"mountain-summit","name":"霧島山<高千穂峰>","lon":130.918939,"lat":31.886257,"sourceId":"1022"}, + {"category":"mountain-summit","name":"高隈山<大箆柄岳>","lon":130.818538,"lat":31.486212,"sourceId":"1023"}, + {"category":"mountain-summit","name":"高隈山<御岳>","lon":130.820618,"lat":31.460302,"sourceId":"1024"}, + {"category":"mountain-summit","name":"甫与志岳","lon":130.990121,"lat":31.264171,"sourceId":"1025"}, + {"category":"mountain-summit","name":"稲尾岳","lon":130.884683,"lat":31.123479,"sourceId":"1026"}, + {"category":"mountain-summit","name":"御岳(北岳)","lon":130.656638,"lat":31.592626,"sourceId":"1027"}, + {"category":"mountain-summit","name":"八重山","lon":130.448447,"lat":31.734508,"sourceId":"1028"}, + {"category":"mountain-summit","name":"冠岳(西岳)","lon":130.331238,"lat":31.748703,"sourceId":"1029"}, + {"category":"mountain-summit","name":"熊ヶ岳","lon":130.463887,"lat":31.455293,"sourceId":"1030"}, + {"category":"mountain-summit","name":"金峯山","lon":130.382944,"lat":31.467668,"sourceId":"1031"}, + {"category":"mountain-summit","name":"野間岳","lon":130.158222,"lat":31.40452,"sourceId":"1032"}, + {"category":"mountain-summit","name":"開聞岳","lon":130.528406,"lat":31.179926,"sourceId":"1033"}, + {"category":"mountain-summit","name":"倉岳","lon":130.326985,"lat":32.427784,"sourceId":"1034"}, + {"category":"mountain-summit","name":"角山","lon":130.093892,"lat":32.399331,"sourceId":"1035"}, + {"category":"mountain-summit","name":"尾岳","lon":129.739338,"lat":31.723184,"sourceId":"1036"}, + {"category":"mountain-summit","name":"天女ヶ倉","lon":131.053398,"lat":30.743587,"sourceId":"1037"}, + {"category":"mountain-summit","name":"硫黄岳","lon":130.305254,"lat":30.793004,"sourceId":"1038"}, + {"category":"mountain-summit","name":"櫓岳","lon":129.937494,"lat":30.828329,"sourceId":"1039"}, + {"category":"mountain-summit","name":"古岳","lon":130.217344,"lat":30.443215,"sourceId":"1040"}, + {"category":"mountain-summit","name":"宮之浦岳","lon":130.504287,"lat":30.335951,"sourceId":"1041"}, + {"category":"mountain-summit","name":"永田岳","lon":130.49244,"lat":30.34287,"sourceId":"1042"}, + {"category":"mountain-summit","name":"モッチョム岳","lon":130.564221,"lat":30.256697,"sourceId":"1043"}, + {"category":"mountain-summit","name":"前岳","lon":129.925441,"lat":29.968026,"sourceId":"1044"}, + {"category":"mountain-summit","name":"御岳","lon":129.856983,"lat":29.859296,"sourceId":"1045"}, + {"category":"mountain-summit","name":"御岳","lon":129.541639,"lat":29.903088,"sourceId":"1046"}, + {"category":"mountain-summit","name":"御岳","lon":129.71389,"lat":29.638415,"sourceId":"1047"}, + {"category":"mountain-summit","name":"御岳","lon":129.594734,"lat":29.464976,"sourceId":"1048"}, + {"category":"mountain-summit","name":"イマキラ岳","lon":129.208053,"lat":29.144466,"sourceId":"1049"}, + {"category":"mountain-summit","name":"[横当島]","lon":128.99477,"lat":28.800775,"sourceId":"1050"}, + {"category":"mountain-summit","name":"湯湾岳","lon":129.32116,"lat":28.296088,"sourceId":"1051"}, + {"category":"mountain-summit","name":"井之川岳","lon":128.983155,"lat":27.778702,"sourceId":"1052"}, + {"category":"mountain-summit","name":"大山","lon":128.5667,"lat":27.369144,"sourceId":"1053"}, + {"category":"mountain-summit","name":"与那覇岳","lon":128.218696,"lat":26.717044,"sourceId":"1054"}, + {"category":"mountain-summit","name":"八重岳","lon":127.928346,"lat":26.635498,"sourceId":"1055"}, + {"category":"mountain-summit","name":"恩納岳","lon":127.874742,"lat":26.479362,"sourceId":"1056"}, + {"category":"mountain-summit","name":"於茂登岳","lon":124.183264,"lat":24.427171,"sourceId":"1057"}, + {"category":"mountain-summit","name":"古見岳","lon":123.891176,"lat":24.359226,"sourceId":"1058"}, + {"category":"mountain-summit","name":"宇良部岳","lon":123.005502,"lat":24.452912,"sourceId":"1059"}, + {"category":"extreme-point","name":"最東端(東京都 南鳥島)","lon":153.986667,"lat":24.283056,"sourceId":"east","sourceUrl":"https://www.gsi.go.jp/KOKUJYOHO/center.htm"}, + {"category":"extreme-point","name":"最西端(沖縄県 与那国島)","lon":122.9325,"lat":24.451389,"sourceId":"west","sourceUrl":"https://www.gsi.go.jp/KOKUJYOHO/center.htm"}, + {"category":"extreme-point","name":"最南端(東京都 沖ノ鳥島)","lon":136.069722,"lat":20.425278,"sourceId":"south","sourceUrl":"https://www.gsi.go.jp/KOKUJYOHO/center.htm"}, + {"category":"extreme-point","name":"最北端(北海道 択捉島)","lon":148.752222,"lat":45.557222,"sourceId":"north","sourceUrl":"https://www.gsi.go.jp/KOKUJYOHO/center.htm"} +] diff --git a/demo/data/benchmark-places.provenance.json b/demo/data/benchmark-places.provenance.json new file mode 100644 index 0000000..debdb77 --- /dev/null +++ b/demo/data/benchmark-places.provenance.json @@ -0,0 +1,58 @@ +{ + "title": "benchmark-places.json の出典と加工の記録", + "summary": "このファイルは国土地理院が作成したものではない。国土地理院ウェブサイトで公開されている2つのデータから地点を選び、加工して Atlas のデモ用に Atlas プロジェクトが作成したものである。", + "retrievedAt": "2026-10-27", + "attribution": "出典:国土地理院ウェブサイト(https://www.gsi.go.jp/kihonjohochousa/kihonjohochousa41139.html、https://www.gsi.go.jp/KOKUJYOHO/center.htm)", + "processedNotice": "「日本の主な山岳標高一覧(1003山)」および「日本の東西南北端点の経度緯度」(国土地理院ウェブサイト)を加工して Atlas プロジェクトが作成", + "sources": [ + { + "category": "mountain-summit", + "name": "日本の主な山岳標高一覧(1003山)", + "version": "2026年3月31日版", + "publisher": "国土地理院", + "pageUrl": "https://www.gsi.go.jp/kihonjohochousa/kihonjohochousa41139.html", + "fileUrl": "https://www.gsi.go.jp/KOKUJYOHO/MOUNTAIN/1003zan20260331.csv", + "fileFormat": "CSV(文字コードは Shift_JIS(CP932))", + "retrievedAt": "2026-10-27", + "license": { + "name": "国土地理院コンテンツ利用規約(令和7年11月20日改正)", + "terms": "公共データ利用規約(第1.0版。PDL1.0。CC BY 4.0 互換)", + "url": "https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html" + }, + "attribution": "出典:国土地理院ウェブサイト(https://www.gsi.go.jp/kihonjohochousa/kihonjohochousa41139.html)", + "processing": [ + "CSV の全行を読み、列「連番」「山名<山頂名>」「緯度」「経度」だけを選んで JSON の配列にした(標高・種別・都道府県・よみは取り込んでいない)。", + "「連番」を sourceId として残した。", + "緯度・経度は CSV の値(世界測地系の 10 進の度)をそのまま数値として使った。丸め直しはしていない。", + "category は元データに無い項目で、このファイルで新しく付けた値 \"mountain-summit\" である。", + "元データは 1,003 山だが、複数の山頂を持つ山は山頂ごとに行があるため、このファイルの地点は 1,059 件になる。" + ] + }, + { + "category": "extreme-point", + "name": "日本の東西南北端点の経度緯度", + "version": "令和元年6月1日に最東端・最西端の数値を更新した版", + "publisher": "国土地理院", + "pageUrl": "https://www.gsi.go.jp/KOKUJYOHO/center.htm", + "retrievedAt": "2026-10-27", + "license": { + "name": "国土地理院コンテンツ利用規約(令和7年11月20日改正)", + "terms": "公共データ利用規約(第1.0版。PDL1.0。CC BY 4.0 互換)", + "url": "https://www.gsi.go.jp/kikakuchousei/kikakuchousei40182.html" + }, + "attribution": "出典:国土地理院ウェブサイト(https://www.gsi.go.jp/KOKUJYOHO/center.htm)", + "processing": [ + "ページの一覧から4点(最東端・最西端・最南端・最北端)を選んだ。", + "経度・緯度は度分秒(秒単位)で示されているため、10 進の度に直した(度 + 分 ÷ 60 + 秒 ÷ 3600)。例: 153°59′12″ → 153.986667。", + "10 進に直した値は小数第 6 位に丸めた。", + "name は区分と場所(例: 最東端(東京都 南鳥島))、sourceId は区分(east・west・south・north)、sourceUrl はページの URL とした。", + "category はこのファイルで新しく付けた値 \"extreme-point\" である。", + "ページは、値が2万5千分1地形図をもとに秒単位で求められたもので、1秒程度の誤差があり得るとしており、現地の厳密な位置を示すものではない。" + ] + } + ], + "verification": [ + "地点の緯度・経度がすべて日本の範囲(東経 122〜154 度、北緯 20〜46 度)にあることを読み込み時に確かめている。", + "地点の総数は 1,063 件(山頂 1,059 件、端点 4 件)。" + ] +} diff --git a/demo/main.js b/demo/main.js index 6b2da70..31b94ec 100644 --- a/demo/main.js +++ b/demo/main.js @@ -9,7 +9,7 @@ import { worldToLonLat, } from "./map-geometry.ts"; import { wktText } from "./detail-text.ts"; -import { PRESETS, buildLookupOptions } from "./presets.js"; +import { AREA_BY_MODE, NEUTRAL_EXPECTATION, PRESETS, buildLookupOptions } from "./presets.js"; const MAP_WIDTH = 720; const MAP_HEIGHT = 460; @@ -17,12 +17,13 @@ const MAP_HEIGHT = 460; // これを bleed として渡すことで、コピー境界でマーカーが早すぎる消え方をしない。 const MARKER_BLEED_PX = 48; +// The area name of each mode lives in `AREA_BY_MODE` (`demo/presets.js`) and is not repeated here. const COUNTRIES = { - auto: { label: "Auto", area: undefined, bounds: null }, - japan: { label: "Japan", area: "Japan", bounds: { west: 122, south: 20, east: 154, north: 46 }, center: [137.5, 36.5], zoom: 5 }, - united_states: { label: "United States", area: "United States", bounds: { west: -125, south: 24, east: -66, north: 49 }, center: [-96, 38], zoom: 4 }, - korea: { label: "Korea", area: "Korea", bounds: { west: 124, south: 33, east: 132, north: 39.5 }, center: [128, 36.2], zoom: 6 }, - china: { label: "China", area: "China", bounds: { west: 73, south: 18, east: 135, north: 54 }, center: [104, 35], zoom: 4 }, + auto: { label: "Auto", bounds: null }, + japan: { label: "Japan", bounds: { west: 122, south: 20, east: 154, north: 46 }, center: [137.5, 36.5], zoom: 5 }, + united_states: { label: "United States", bounds: { west: -125, south: 24, east: -66, north: 49 }, center: [-96, 38], zoom: 4 }, + korea: { label: "Korea", bounds: { west: 124, south: 33, east: 132, north: 39.5 }, center: [128, 36.2], zoom: 6 }, + china: { label: "China", bounds: { west: 73, south: 18, east: 135, north: 54 }, center: [104, 35], zoom: 4 }, }; const DEFAULT_STATE = Object.freeze({ @@ -135,10 +136,10 @@ function deriveAutoArea() { if (state.areaMode !== "auto") return undefined; const firstMarker = state.markers[0]; if (!firstMarker) return undefined; - return Object.entries(COUNTRIES) + const match = Object.entries(COUNTRIES) .filter(([key]) => key !== "auto") - .map(([, country]) => country) - .find((country) => pointInBounds(firstMarker, country.bounds))?.area; + .find(([, country]) => pointInBounds(firstMarker, country.bounds)); + return match ? AREA_BY_MODE[match[0]] : undefined; } function currentLookupInput() { @@ -612,10 +613,7 @@ function leavePreset() { state.activePreset = null; markPressed(null); const expectation = dom.getElementById("preset-expectation"); - if (expectation) { - expectation.textContent = - "手で変えた入力(プリセットではない)。プリセットのボタンを押すと戻る。"; - } + if (expectation) expectation.textContent = NEUTRAL_EXPECTATION; } function renderVersion() { diff --git a/demo/map-geometry.ts b/demo/map-geometry.ts index 104e267..4160306 100644 --- a/demo/map-geometry.ts +++ b/demo/map-geometry.ts @@ -1,11 +1,10 @@ /** * デモ地図の座標変換と、日付変更線を跨ぐ bbox の描画幾何。 * - * 設計正本: `docs/superpowers/specs/2026-08-29-m3-t3-maplat-v4-crs-libs-task-design.md` §6.1。 * 本モジュールは **DOM を参照しない / `@c4h/atlas` を参照しない純関数だけ**を置く * (`demo/main.js` から結線し、`tests/demo-map-geometry.test.ts` から直接呼んで検査するため)。 * - * 本モジュールが解く欠陥(HR-m3-011): 経度写像 `x = ((lon + 180) / 360) * scale` は + * 本モジュールが解く欠陥: 経度写像 `x = ((lon + 180) / 360) * scale` は * **巡回性を持たない** ∴ west / east を独立に写して差を取ると、`west > east`(跨ぎ)の bbox で * **補集合**が描かれる(EPSG:3832 では正しい幅 193.31° に対し 166.69°。和は 360.00°)。 */ @@ -85,7 +84,8 @@ export function lonSpanDegrees(west: number, east: number): number { * * `bleedPx` は図形の実体が左端 x・幅 width の矩形からはみ出す分(マーカーの円やラベル)。 * - * 反復回数の上限は設計 §6.3 で導出しており(デモの拘束下で最大 2)、m3-t3/AC-5 (c) が検査する。 + * 反復回数は、デモの拘束下(zoom 2〜12・幅 720 px・図形の幅が世界の幅以下)で最大 2 になる。 + * `tests/demo-map-geometry.test.ts` がその上限を検査する。 */ export function worldCopyXs(x: number, width: number, view: MapView, bleedPx = 0): number[] { const worldWidth = worldSizePx(view.zoom); diff --git a/demo/presets.d.ts b/demo/presets.d.ts index faf4b40..df0486a 100644 --- a/demo/presets.d.ts +++ b/demo/presets.d.ts @@ -36,6 +36,7 @@ export interface LookupOptions { } export const AREA_BY_MODE: Readonly>; +export const NEUTRAL_EXPECTATION: string; export const PRESETS: readonly PresetContrast[]; export function findPreset(presetId: string, optionId: string): PresetOption | undefined; export function buildLookupOptions(input: PresetInput, options?: { autoArea?: string }): LookupOptions; diff --git a/demo/presets.js b/demo/presets.js index 3071771..188a403 100644 --- a/demo/presets.js +++ b/demo/presets.js @@ -12,7 +12,13 @@ const KYOTO = Object.freeze({ west: 135.6, south: 34.9, east: 135.9, north: 35.1 }); const TOKYO = Object.freeze({ west: 139.6, south: 35.6, east: 139.85, north: 35.75 }); -/** Area names accepted by the proj.db `area` filter, keyed by the demo's select values. */ +/** + * Area names accepted by the proj.db `area` filter, keyed by the demo's select values. + * + * This is the single place the mapping is written down: `demo/main.js` reads it for the `auto` + * area it derives from a marker as well, so the select values, the derived area and the preset + * lookup can never disagree. + */ export const AREA_BY_MODE = Object.freeze({ japan: "Japan", united_states: "United States", @@ -20,6 +26,10 @@ export const AREA_BY_MODE = Object.freeze({ china: "China", }); +/** The line shown when the input was changed by hand and no preset is pressed. */ +export const NEUTRAL_EXPECTATION = + "手で変えた入力(プリセットではない)。プリセットのボタンを押すと戻る。"; + function presetInput(extent, { areaFilterEnabled, includeCustomProjections }) { return Object.freeze({ extent, diff --git a/index.html b/index.html index 2467965..1ad2191 100644 --- a/index.html +++ b/index.html @@ -25,6 +25,17 @@

    Atlas CRS Demo

    + +
    +
    Demo - Browser Benchmark
    +

    Atlas Benchmark Demo

    +
    +
    + 同じ地点列と CRS で proj-wasm と proj4js を往復させ、時間・成功/失敗・往復誤差・ + 2 つのエンジンの出力の差を並べて比べます。エンジンの対応範囲で結果が分かれる対比をボタンで切り替えられます。 +
    + +
    diff --git a/openspec/changes/atlas-benchmark-demo/tasks.md b/openspec/changes/atlas-benchmark-demo/tasks.md index 05b7a9c..468b14d 100644 --- a/openspec/changes/atlas-benchmark-demo/tasks.md +++ b/openspec/changes/atlas-benchmark-demo/tasks.md @@ -1,19 +1,19 @@ ## 1. Benchmark Page -- [ ] 1.1 Add a second portal card for the benchmark demo. -- [ ] 1.2 Add `demo/benchmark.html` within the existing demo shell. -- [ ] 1.3 Add controls for dataset category, point limit, iteration count, CRS scope, run, and reset. +- [x] 1.1 Add a second portal card for the benchmark demo. — `index.html`; `tests/publish-and-demo.test.ts` › "uses the C4H demo boilerplate shell" +- [x] 1.2 Add `demo/benchmark.html` within the existing demo shell. — `demo/benchmark.html`, `demo/benchmark.js`, `demo/benchmark.css`, `demo/benchmark.d.ts`; `tests/benchmark-wiring.test.ts` › "keeps the run button disabled until every initialization has finished"; `tests/demo-bootstrap.test.ts` › "demo/benchmark.html imports initBenchmark from ./benchmark.js and calls it" +- [x] 1.3 Add controls for dataset category, point limit, iteration count, CRS scope, run, and reset. — `demo/benchmark.html`, `demo/benchmark.js` (`readControls`, `syncControls`), `demo/benchmark-engine.js` (`CRS_GROUPS`, `crsScopeIds`); `tests/benchmark-wiring.test.ts` › "button engine-coverage:a fills the controls with the preset input and runs once" ## 2. Benchmark Engine -- [ ] 2.1 Bundle a Japan-focused coordinate dataset with provenance metadata. -- [ ] 2.2 Initialize Atlas runtime, `proj-wasm`, `proj4js`, dataset, and CRS definitions before enabling execution. -- [ ] 2.3 Compare forward and inverse round trips for `proj-wasm` and `proj4js` using the same point sequence. -- [ ] 2.4 Report total elapsed time, success count, failure count, mean error, and max error. -- [ ] 2.5 Treat unsupported CRS behavior as explicit benchmark failures. +- [x] 2.1 Bundle a Japan-focused coordinate dataset with provenance metadata. — `demo/data/benchmark-places.json`, `demo/data/benchmark-places.provenance.json`, `demo/benchmark-engine.js` (`selectPoints`); `tests/benchmark-engine.test.ts` › "A fails nowhere on proj4js" (reads the bundled dataset); `scripts/verify-demo-pages-build.mjs` (both data assets) +- [x] 2.2 Initialize Atlas runtime, `proj-wasm`, `proj4js`, dataset, and CRS definitions before enabling execution. — `demo/benchmark.js` (`initialize`); `tests/benchmark-wiring.test.ts` › "keeps the run button disabled until every initialization has finished" +- [x] 2.3 Compare forward and inverse round trips for `proj-wasm` and `proj4js` using the same point sequence. — `demo/benchmark-engine.js` (`buildWorkSequence`, `runProjWasmEngine`, `runProj4Engine`, `runBenchmark`); `tests/benchmark-engine.test.ts` › "cycles back to the first point until the requested work is done" +- [x] 2.4 Report total elapsed time, success count, failure count, mean error, and max error. — `demo/benchmark-engine.js` (per-engine statistics), `demo/benchmark.js` (`renderSummary`, `renderTable`); `tests/benchmark-engine.test.ts` › "finishes every point on both engines with a round-trip error below 1e-6 m"; `tests/benchmark-wiring.test.ts` › "shows the summary, the row of every CRS and the forward difference" +- [x] 2.5 Treat unsupported CRS behavior as explicit benchmark failures. — `demo/benchmark-engine.js` (failure records with the engine's message), `demo/benchmark.js` (`renderFailures`); `tests/benchmark-engine.test.ts` › "keeps proj4js failures explicit with the message the engine throws"; `tests/benchmark-wiring.test.ts` › "lists the failure message of an engine that could not read a definition" ## 3. Verification -- [ ] 3.1 Include the benchmark page in demo build verification. -- [ ] 3.2 Verify the benchmark page resolves under the `/Atlas/` base path. -- [ ] 3.3 Verify benchmark runtime initialization and asset loading on a Pages-compatible static build. +- [x] 3.1 Include the benchmark page in demo build verification. — `vite.config.ts` (build input `benchmark`), `scripts/verify-demo-pages-build.mjs`; `tests/publish-and-demo.test.ts` › "has artifact-level Pages verification" +- [x] 3.2 Verify the benchmark page resolves under the `/Atlas/` base path. — `scripts/verify-demo-pages-build.mjs` (built page under `/Atlas/assets/`, portal cards); `tests/publish-and-demo.test.ts` › "builds the portal, the CRS subpage and the benchmark subpage" +- [x] 3.3 Verify benchmark runtime initialization and asset loading on a Pages-compatible static build. — `scripts/verify-demo-pages-build.mjs` (both data assets are referenced by the built scripts and exist); `tests/benchmark-wiring.test.ts` › "keeps the run button disabled until every initialization has finished" diff --git a/package.json b/package.json index 3b98e73..5b8d9e5 100644 --- a/package.json +++ b/package.json @@ -61,8 +61,9 @@ }, "devDependencies": { "@types/node": "^26.6.5", - "jsdom": "^30.0.0", + "jsdom": "^30.0.1", "jszip": "^3.10.2", + "proj4": "2.22.0", "typescript": "^6.0.3", "vite": "^7.3.5", "vite-plugin-dts": "^5.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f77f8b2..64c5941 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,11 +16,14 @@ importers: specifier: ^26.6.5 version: 26.6.5 jsdom: - specifier: ^30.0.0 + specifier: ^30.0.1 version: 30.1.3 jszip: specifier: ^3.10.2 version: 3.10.2 + proj4: + specifier: 2.22.0 + version: 2.22.0 typescript: specifier: ^6.0.3 version: 6.0.3 @@ -660,6 +663,9 @@ packages: mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + mgrs@1.0.0: + resolution: {integrity: sha512-awNbTOqCxK1DBGjalK3xqWIstBZgN6fxsMSiXLs9/spqWkF2pAhb2rrYCFSsr1/tT7PhcDGjZndG8SWYn0byYA==} + mlly@1.8.2: resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} @@ -718,6 +724,14 @@ packages: proj-wasm@0.1.0-alpha8: resolution: {integrity: sha512-vMWnEz4I6Lx5ruwFr2c2XpRdDFZTNI/rnvSNS8SUroWhdi5VyHruXXzb04XoZ8vvl6jJbl4YUAPz0YMLX1di0Q==} + proj4@2.22.0: + resolution: {integrity: sha512-MVvH09QrYm3A235rcX39WDiCKanrBiblu28l0KYIn/B2DofmYhvP/LLS43kE66jNDfx/P1l/nah3JmsAq8UgbQ==} + peerDependencies: + geotiff: '*' + peerDependenciesMeta: + geotiff: + optional: true + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -994,6 +1008,9 @@ packages: engines: {node: '>=20.11'} hasBin: true + wkt-parser@1.6.0: + resolution: {integrity: sha512-O2WMUagkdOKkgcfqZBUauysHVeifCwcs+zUd2QG7UIzVljLwK9g5xHUfL/TkQFwmQ3eF7oHFKGnMcb/kZdvbaw==} + xhr2@0.2.1: resolution: {integrity: sha512-sID0rrVCqkVNUn8t6xuv9+6FViXjUVXq8H5rWOH2rz9fDNQEd4g0EA2XlcEdJXRz5BMEn4O1pJFdT+z4YHhoWw==} engines: {node: '>= 6'} @@ -1496,6 +1513,8 @@ snapshots: mdn-data@2.27.1: {} + mgrs@1.0.0: {} + mlly@1.8.2: dependencies: acorn: 8.19.0 @@ -1553,6 +1572,11 @@ snapshots: resource-tracker: 0.0.1-alpha1 xhr2: 0.2.1 + proj4@2.22.0: + dependencies: + mgrs: 1.0.0 + wkt-parser: 1.6.0 + punycode@2.3.1: {} quansync@0.2.11: {} @@ -1780,6 +1804,8 @@ snapshots: why-is-node-running@3.2.1: {} + wkt-parser@1.6.0: {} + xhr2@0.2.1: {} xml-name-validator@5.0.0: {} diff --git a/scripts/verify-demo-pages-build.mjs b/scripts/verify-demo-pages-build.mjs index ebf507d..d5d46cb 100644 --- a/scripts/verify-demo-pages-build.mjs +++ b/scripts/verify-demo-pages-build.mjs @@ -5,6 +5,7 @@ const root = new URL("../dist-demo/", import.meta.url); const requiredFiles = [ "index.html", "demo/crs-candidate-lookup.html", + "demo/benchmark.html", "assets/proj.db", "assets/proj.ini", "assets/proj-worker.mjs", @@ -50,12 +51,68 @@ if (projDb.size < 1_000_000) { const portalHtml = await readText("index.html"); const demoHtml = await readText("demo/crs-candidate-lookup.html"); +const benchmarkHtml = await readText("demo/benchmark.html"); if (/http-equiv=["']refresh/i.test(portalHtml) || /http-equiv=["']refresh/i.test(demoHtml)) { throw new Error("demo pages must not be redirect stubs"); } if (!portalHtml.includes("/Atlas/assets/") || !demoHtml.includes("/Atlas/assets/")) { throw new Error("built HTML does not resolve bundled modules under /Atlas/assets/"); } +if (!/\/Atlas\/assets\/[^"']+\.js/.test(benchmarkHtml)) { + throw new Error("built benchmark.html does not resolve its bundle under /Atlas/assets/"); +} + +// The portal links to both demo pages, each on its own card. +const portalCards = portalHtml.match(/class="c4h-demo-card"/g)?.length ?? 0; +if (portalCards < 2) { + throw new Error(`the portal has ${portalCards} demo card(s); the benchmark demo needs its own card`); +} +for (const page of ["./demo/crs-candidate-lookup.html", "./demo/benchmark.html"]) { + if (!portalHtml.includes(`href="${page}"`)) { + throw new Error(`the portal does not link to ${page}`); + } +} + +// The benchmark data is bundled as two assets; the built page must point at both of them. +const builtFiles = await listFiles(); +const benchmarkAssets = builtFiles.filter((file) => /^assets\/benchmark-places[^/]*\.json$/.test(file)); +if (benchmarkAssets.length !== 2) { + throw new Error(`expected the two benchmark data assets, found ${benchmarkAssets.join(", ") || "none"}`); +} +const placesAsset = benchmarkAssets.find((file) => !file.includes("provenance")); +const provenanceAsset = benchmarkAssets.find((file) => file.includes("provenance")); +if (!placesAsset || !provenanceAsset) { + throw new Error(`benchmark data assets are not the expected pair: ${benchmarkAssets.join(", ")}`); +} +const places = JSON.parse(await readText(placesAsset)); +if (!Array.isArray(places) || places.length === 0) { + throw new Error(`${placesAsset} does not hold a non-empty array of places`); +} +const provenance = JSON.parse(await readText(provenanceAsset)); +if (!Array.isArray(provenance.sources) || provenance.sources.length === 0) { + throw new Error(`${provenanceAsset} does not hold the source records`); +} +for (const place of places) { + if (!Number.isFinite(place.lon) || !Number.isFinite(place.lat)) { + throw new Error(`${placesAsset} has a place without numeric lon/lat: ${JSON.stringify(place)}`); + } +} +const benchmarkScripts = builtFiles.filter((file) => /^assets\/.*\.js$/.test(file)); +const referencedAssets = new Set(); +for (const file of benchmarkScripts) { + const text = await readText(file); + for (const match of text.matchAll(/\/Atlas\/assets\/([^"'\s)]+)/g)) { + referencedAssets.add(`assets/${match[1]}`); + } +} +for (const asset of [placesAsset, provenanceAsset]) { + if (!referencedAssets.has(asset)) { + throw new Error(`${asset} is not referenced by any built script`); + } +} +for (const asset of referencedAssets) { + if (/\.(?:js|mjs|json|css|wasm|db|ini)$/.test(asset)) await assertFile(asset); +} const textFiles = (await listFiles()).filter((file) => /\.(?:html|js|mjs|css)$/.test(file)); for (const file of textFiles) { diff --git a/tests/benchmark-engine.test.ts b/tests/benchmark-engine.test.ts new file mode 100644 index 0000000..d35f224 --- /dev/null +++ b/tests/benchmark-engine.test.ts @@ -0,0 +1,228 @@ +/** + * Benchmark engine: the real `proj-wasm` and `proj4js` engines over real Atlas rows. + * + * The points are real summits from `demo/data/benchmark-places.json` (written out here so this + * file does not depend on the dataset changing). They sit in the Kanto plain, where both of the + * CRS used below are found by the Atlas search: `EPSG:2451` (plane rectangular IX) and + * `UTJCP:ZONE-B` (historical grid zone B). + * + * The Atlas rows come from `listProjectedCrsFromProjWasm` through the same `runBenchmark` the + * page calls, so a definition taken from somewhere else would show up here as a different row. + */ +import { readFileSync } from "node:fs"; + +import { describe, expect, it } from "vitest"; +import proj4 from "proj4"; + +import { initializeProjWasmRuntime, listProjectedCrsFromProjWasm } from "../src/proj-wasm-crs.ts"; +import { + crsScopeIds, + runBenchmark, + selectPoints, + type BenchmarkPlace, + type BenchmarkResult, +} from "../demo/benchmark-engine.js"; +import { findBenchmarkPreset, presetWorkCount } from "../demo/benchmark-presets.js"; + +/** Real summits (the same values as in the bundled dataset). */ +const KANTO_SUMMITS: BenchmarkPlace[] = [ + { category: "mountain-summit", name: "小楢山", lon: 138.664198, lat: 35.781533 }, + { category: "mountain-summit", name: "茅ヶ岳", lon: 138.513772, lat: 35.795014 }, + { category: "mountain-summit", name: "鶏冠山", lon: 138.835644, lat: 35.788483 }, + { category: "mountain-summit", name: "大菩薩嶺", lon: 138.845506, lat: 35.748762 }, + { category: "mountain-summit", name: "小金沢山", lon: 138.856623, lat: 35.716946 }, + { category: "mountain-summit", name: "雁ケ腹摺山", lon: 138.884831, lat: 35.686789 }, + { category: "mountain-summit", name: "御正体山", lon: 138.931607, lat: 35.486924 }, + { category: "mountain-summit", name: "三ツ峠山", lon: 138.809204, lat: 35.549272 }, + { category: "mountain-summit", name: "節刀ヶ岳", lon: 138.683427, lat: 35.52849 }, + { category: "mountain-summit", name: "富士山<剣ヶ峯>", lon: 138.727373, lat: 35.360738 }, +]; + +/** + * These tests go through the real engines; the first one in the file also pays for the start-up + * of `proj-wasm` and for expanding the Atlas rows they ask for. + */ +const ENGINE_TEST_TIMEOUT_MS = 120_000; + +let runtimePromise: Promise<{ listProjectedCrsFromProjWasm: typeof listProjectedCrsFromProjWasm }> | undefined; +let projWasmPromise: Promise | undefined; + +function atlasRuntime() { + if (!runtimePromise) { + runtimePromise = (async () => { + await initializeProjWasmRuntime(); + return { listProjectedCrsFromProjWasm }; + })(); + } + return runtimePromise; +} + +function projWasmModule() { + if (!projWasmPromise) projWasmPromise = import("proj-wasm"); + return projWasmPromise; +} + +async function run( + places: BenchmarkPlace[], + workCount: number, + targetIds: string[], +): Promise { + return runBenchmark({ + places, + workCount, + targetIds, + runtime: await atlasRuntime(), + projWasm: await projWasmModule(), + proj4js: proj4, + }); +} + +function rowOf(result: BenchmarkResult, id: string) { + const row = result.rows.find((entry) => entry.id === id); + if (row === undefined) throw new Error(`${id} is missing from the result (${result.rows.map((entry) => entry.id).join(", ")})`); + return row; +} + +describe("benchmark engine: plane rectangular IX (EPSG:2451)", { timeout: ENGINE_TEST_TIMEOUT_MS }, () => { + it("finishes every point on both engines with a round-trip error below 1e-6 m", async () => { + const result = await run(KANTO_SUMMITS, 10, ["EPSG:2451"]); + const row = rowOf(result, "EPSG:2451"); + + console.info( + `[benchmark-engine] EPSG:2451 proj-wasm round trip mean ${row.projWasm.meanErrorMeters} m, ` + + `max ${row.projWasm.maxErrorMeters} m; proj4js mean ${row.proj4js.meanErrorMeters} m, ` + + `max ${row.proj4js.maxErrorMeters} m; forward difference max ${row.forwardDifferenceMaxMeters} m`, + ); + + expect(row.found).toBe(true); + expect(row.projWasm.success).toBe(10); + expect(row.projWasm.failure).toBe(0); + expect(row.proj4js.success).toBe(10); + expect(row.proj4js.failure).toBe(0); + expect(row.projWasm.maxErrorMeters).toBeLessThan(1e-6); + expect(row.proj4js.maxErrorMeters).toBeLessThan(1e-6); + expect(Number.isFinite(row.forwardDifferenceMaxMeters)).toBe(true); + expect(row.status).toBe("ok"); + }); +}); + +describe("benchmark engine: historical grid zone B (UTJCP:ZONE-B)", { timeout: ENGINE_TEST_TIMEOUT_MS }, () => { + it("keeps proj4js failures explicit with the message the engine throws", async () => { + const result = await run(KANTO_SUMMITS, 10, ["UTJCP:ZONE-B"]); + const row = rowOf(result, "UTJCP:ZONE-B"); + + console.info( + `[benchmark-engine] UTJCP:ZONE-B proj-wasm round trip max ${row.projWasm.maxErrorMeters} m; ` + + `proj4js failures ${row.proj4js.failure}, message ${row.proj4js.failures[0]?.message}`, + ); + + expect(row.projWasm.success).toBe(10); + expect(row.projWasm.failure).toBe(0); + expect(row.proj4js.success).toBe(0); + expect(row.proj4js.failure).toBe(10); + expect(row.proj4js.failures).toHaveLength(10); + expect(row.proj4js.failures[0].message).toContain("pipeline"); + expect(row.status).toBe("failed"); + }); +}); + +describe("benchmark engine: work larger than the point set", { timeout: ENGINE_TEST_TIMEOUT_MS }, () => { + it("cycles back to the first point until the requested work is done", async () => { + const result = await run(KANTO_SUMMITS, 25, ["EPSG:2451"]); + const row = rowOf(result, "EPSG:2451"); + + expect(result.pointCount).toBe(10); + expect(result.workCount).toBe(25); + expect(result.laps).toBe(3); + expect(row.workCount).toBe(25); + expect(row.projWasm.success).toBe(25); + expect(row.proj4js.success).toBe(25); + }); +}); + +describe("benchmark engine: ids that are not in the catalog", { timeout: ENGINE_TEST_TIMEOUT_MS }, () => { + it("keeps an unknown id in the result as not found", async () => { + const result = await run(KANTO_SUMMITS, 3, ["EPSG:2451", "EPSG:9999"]); + const missing = rowOf(result, "EPSG:9999"); + + expect(missing.found).toBe(false); + expect(missing.status).toBe("not-found"); + expect(missing.projWasm.success).toBe(0); + expect(missing.proj4js.success).toBe(0); + expect(rowOf(result, "EPSG:2451").found).toBe(true); + }); +}); + +describe("benchmark engine: the engine-coverage contrast", { timeout: ENGINE_TEST_TIMEOUT_MS }, () => { + const places = JSON.parse( + readFileSync(new URL("../demo/data/benchmark-places.json", import.meta.url), "utf8"), + ) as BenchmarkPlace[]; + + async function runPreset(optionId: "a" | "b"): Promise { + const option = findBenchmarkPreset("engine-coverage", optionId); + if (option === undefined) throw new Error(`engine-coverage:${optionId} is missing`); + const selected = selectPoints(places, option.input); + return run(selected, presetWorkCount(option.input, selected.length), crsScopeIds(option.input.crsScope)); + } + + it("A fails nowhere on proj4js", async () => { + const result = await runPreset("a"); + const row = rowOf(result, "EPSG:2451"); + + console.info( + `[benchmark-engine] engine-coverage A: ${result.workCount} points, ` + + `proj-wasm failures ${row.projWasm.failure}, proj4js failures ${row.proj4js.failure}, ` + + `forward difference max ${row.forwardDifferenceMaxMeters} m`, + ); + + expect(row.proj4js.failure).toBe(0); + expect(row.projWasm.failure).toBe(0); + }); + + it("B fails on every proj4js point and nowhere on proj-wasm", async () => { + const result = await runPreset("b"); + const row = rowOf(result, "UTJCP:ZONE-B"); + + console.info( + `[benchmark-engine] engine-coverage B: ${result.workCount} points, ` + + `proj-wasm failures ${row.projWasm.failure}, proj4js successes ${row.proj4js.success}, ` + + `proj4js message ${row.proj4js.failures[0]?.message}`, + ); + + expect(row.proj4js.success).toBe(0); + expect(row.projWasm.failure).toBe(0); + expect(row.projWasm.success).toBe(result.workCount); + }); +}); + +describe("benchmark engine: the CRS rows do not depend on the points of the run", { timeout: 300_000 }, () => { + it("finds every target CRS from the whole dataset even when the run uses only northern points", async () => { + const dataset = JSON.parse( + readFileSync(new URL("../demo/data/benchmark-places.json", import.meta.url), "utf8"), + ) as BenchmarkPlace[]; + const northern = selectPoints(dataset, { category: "mountain-summit", pointLimit: 10 }); + expect(Math.min(...northern.map((place) => place.lat))).toBeGreaterThan(43); + + const result = await runBenchmark({ + places: northern, + catalogPlaces: dataset, + workCount: 10, + targetIds: crsScopeIds({ kind: "all" }), + runtime: await atlasRuntime(), + projWasm: await projWasmModule(), + proj4js: proj4, + }); + + // Zones far from the run's points are found through the rest of the dataset. + const southern = ["EPSG:2443", "EPSG:2451", "EPSG:2457", "EPSG:30161", "EPSG:3097", "UTJCP:ZONE-B", "UTJCP:ZONE-C"]; + for (const id of southern) { + expect(result.rows.find((row) => row.id === id)?.found, id).toBe(true); + } + // Plane rectangular XVII (the Daito Islands, both datums) is the only zone without a place of + // the dataset inside it, so it is the only one left "not found", with the reason in its name. + const missing = result.rows.filter((row) => !row.found); + expect(missing.map((row) => row.id)).toEqual(["EPSG:2459", "EPSG:30177"]); + for (const row of missing) expect(row.name).toContain("データのどの地点も"); + expect(result.workCount).toBe(10); + }); +}); diff --git a/tests/benchmark-wiring.test.ts b/tests/benchmark-wiring.test.ts new file mode 100644 index 0000000..9e81dc2 --- /dev/null +++ b/tests/benchmark-wiring.test.ts @@ -0,0 +1,291 @@ +// @vitest-environment jsdom +/// +/** + * Benchmark page wiring (jsdom, injected runtime, injected engines, injected dataset). + * + * The DOM is built by reading `demo/benchmark.html` itself, so renamed ids in the page break + * this test. `runBenchmark` is a stub, so no proj-wasm work happens here; the real engines are + * covered by `tests/benchmark-engine.test.ts`. + */ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { beforeEach, describe, expect, it, vi } from "vitest"; + +import { initBenchmark } from "../demo/benchmark.js"; +import * as benchmarkEngine from "../demo/benchmark-engine.js"; +import type { BenchmarkResult, BenchmarkPlace } from "../demo/benchmark-engine.js"; +import { ENGINE_COVERAGE_EXPECTATION, NEUTRAL_EXPECTATION } from "../demo/benchmark-presets.js"; +import { version as packageVersion } from "../package.json"; + +const testsDir = dirname(fileURLToPath(import.meta.url)); +const pageHtml = readFileSync(join(testsDir, "..", "demo", "benchmark.html"), "utf8"); +const bodyInner = /]*>([\s\S]*)<\/body>/i.exec(pageHtml)?.[1]; + +const PLACES: BenchmarkPlace[] = [ + { category: "mountain-summit", name: "小楢山", lon: 138.664198, lat: 35.781533 }, + { category: "mountain-summit", name: "茅ヶ岳", lon: 138.513772, lat: 35.795014 }, + { category: "mountain-summit", name: "鶏冠山", lon: 138.835644, lat: 35.788483 }, + { category: "mountain-summit", name: "大菩薩嶺", lon: 138.845506, lat: 35.748762 }, + { category: "mountain-summit", name: "小金沢山", lon: 138.856623, lat: 35.716946 }, + { category: "mountain-summit", name: "富士山<剣ヶ峯>", lon: 138.727373, lat: 35.360738 }, + { category: "extreme-point", name: "最東端(東京都 南鳥島)", lon: 153.986667, lat: 24.283056 }, + { category: "extreme-point", name: "最西端(沖縄県 与那国島)", lon: 122.9325, lat: 24.451389 }, +]; + +const PROVENANCE = { title: "テスト用の出典", attribution: "出典:テスト" }; + +function mountPage(): void { + if (bodyInner === undefined) throw new Error("cannot read of benchmark.html"); + document.body.innerHTML = bodyInner.replace(//gi, ""); +} + +function fakeResult(overrides: Partial = {}): BenchmarkResult { + const stats = { totalMs: 1.5, success: 6, failure: 0, meanErrorMeters: 1e-9, maxErrorMeters: 2e-9, failures: [] }; + return { + pointCount: 6, + workCount: 6, + laps: 1, + targetIds: ["EPSG:2451"], + searchCount: 1, + forwardDifferenceMaxMeters: 1e-7, + rows: [ + { + id: "EPSG:2451", + name: "JGD2000 / Japan Plane Rectangular CS IX", + found: true, + status: "ok", + workCount: 6, + projWasm: { ...stats }, + proj4js: { ...stats }, + forwardDifferenceMaxMeters: 1e-7, + }, + ], + totals: { + projWasm: { totalMs: 1.5, success: 6, failure: 0 }, + proj4js: { totalMs: 1.5, success: 6, failure: 0 }, + }, + ...overrides, + }; +} + +function makeRuntime() { + return { + initializeProjWasmRuntime: vi.fn<() => Promise>(async () => undefined), + listProjectedCrsFromProjWasm: vi.fn(async () => []), + }; +} + +/** The arguments the page hands to the benchmark arithmetic. */ +interface RunOptions { + places: readonly BenchmarkPlace[]; + workCount: number; + targetIds: readonly string[]; + runtime?: unknown; + projWasm?: unknown; + proj4js?: unknown; +} + +function makeEngine() { + const runBenchmark = vi.fn(async (_options: RunOptions): Promise => fakeResult()); + return { ...benchmarkEngine, runBenchmark }; +} + +type Engine = ReturnType; + +function value(id: string): string { + return (document.getElementById(id) as HTMLInputElement | HTMLSelectElement | null)?.value ?? ""; +} + +function text(id: string): string { + return document.getElementById(id)?.textContent ?? ""; +} + +function pressedButtons(): string[] { + return [...document.querySelectorAll("#preset-bar button[aria-pressed='true']")].map( + (button) => button.dataset.preset ?? "", + ); +} + +function clickPreset(preset: string, option: string): void { + const button = document.querySelector(`[data-preset="${preset}:${option}"]`); + if (!button) throw new Error(`preset button ${preset}:${option} is missing`); + button.click(); +} + +async function start(options: { + runtime?: ReturnType; + engine?: Engine; +} = {}) { + const runtime = options.runtime ?? makeRuntime(); + const engine = options.engine ?? makeEngine(); + const benchmark = initBenchmark({ + runtime, + doc: document, + engine, + data: { places: PLACES, provenance: PROVENANCE }, + modules: { projWasm: {}, proj4js: () => undefined }, + }); + await benchmark.whenSettled(); + return { benchmark, engine, runtime }; +} + +describe("benchmark wiring: initialization", () => { + beforeEach(() => { + mountPage(); + }); + + it("keeps the run button disabled until every initialization has finished", async () => { + let finishInit: (() => void) | undefined; + const runtime = makeRuntime(); + runtime.initializeProjWasmRuntime.mockImplementation( + () => new Promise((resolve) => { finishInit = resolve; }), + ); + const engine = makeEngine(); + const benchmark = initBenchmark({ + runtime, + doc: document, + engine, + data: { places: PLACES, provenance: PROVENANCE }, + modules: { projWasm: {}, proj4js: () => undefined }, + }); + + const runButton = document.getElementById("run-button") as HTMLButtonElement; + expect(runButton.disabled).toBe(true); + expect(text("init-status")).not.toBe(""); + + // Let the page reach its first `await` before answering the pending initialization. + await new Promise((resolve) => setTimeout(resolve, 0)); + finishInit?.(); + await benchmark.whenSettled(); + + expect(runButton.disabled).toBe(false); + expect(engine.runBenchmark).not.toHaveBeenCalled(); + }); + + it("shows the package version in the footer", async () => { + await start(); + expect(text("version")).toContain(`@c4h/atlas ${packageVersion}`); + }); +}); + +describe("benchmark wiring: preset buttons", () => { + beforeEach(() => { + mountPage(); + }); + + it("button engine-coverage:a fills the controls with the preset input and runs once", async () => { + const { benchmark, engine } = await start(); + + clickPreset("engine-coverage", "a"); + await benchmark.whenSettled(); + + expect(value("category")).toBe("mountain-summit"); + expect(value("point-limit")).toBe("all"); + expect(value("crs-scope")).toBe("crs"); + expect(value("crs-id")).toBe("EPSG:2451"); + expect(value("work-count")).toBe("6"); + + expect(engine.runBenchmark).toHaveBeenCalledTimes(1); + const call = engine.runBenchmark.mock.calls[0][0]; + expect(call.workCount).toBe(6); + expect(call.targetIds).toEqual(["EPSG:2451"]); + expect(call.places).toHaveLength(6); + expect(call.places.every((place: BenchmarkPlace) => place.category === "mountain-summit")).toBe(true); + + expect(pressedButtons()).toEqual(["engine-coverage:a"]); + expect(text("preset-expectation")).toBe(ENGINE_COVERAGE_EXPECTATION); + }); + + it("button engine-coverage:b uses the same points and the historical grid", async () => { + const { benchmark, engine } = await start(); + + clickPreset("engine-coverage", "b"); + await benchmark.whenSettled(); + + expect(value("category")).toBe("mountain-summit"); + expect(value("point-limit")).toBe("all"); + expect(value("crs-scope")).toBe("crs"); + expect(value("crs-id")).toBe("UTJCP:ZONE-B"); + expect(value("work-count")).toBe("6"); + + expect(engine.runBenchmark).toHaveBeenCalledTimes(1); + const call = engine.runBenchmark.mock.calls[0][0]; + expect(call.targetIds).toEqual(["UTJCP:ZONE-B"]); + expect(call.places).toHaveLength(6); + expect(pressedButtons()).toEqual(["engine-coverage:b"]); + }); + + it("goes back to neutral when a control is changed by hand", async () => { + const { benchmark, engine } = await start(); + + clickPreset("engine-coverage", "a"); + await benchmark.whenSettled(); + expect(pressedButtons()).toEqual(["engine-coverage:a"]); + + const category = document.getElementById("category") as HTMLSelectElement; + category.value = "extreme-point"; + category.dispatchEvent(new Event("change", { bubbles: true })); + + expect(pressedButtons()).toEqual([]); + expect(text("preset-expectation")).toBe(NEUTRAL_EXPECTATION); + expect(engine.runBenchmark).toHaveBeenCalledTimes(1); + }); +}); + +describe("benchmark wiring: results", () => { + beforeEach(() => { + mountPage(); + }); + + it("shows the summary, the row of every CRS and the forward difference", async () => { + const { benchmark } = await start(); + + clickPreset("engine-coverage", "a"); + await benchmark.whenSettled(); + + expect(text("results-body")).toContain("EPSG:2451"); + expect(text("results-body")).toContain("JGD2000 / Japan Plane Rectangular CS IX"); + expect(text("result-summary")).toContain("6"); + expect(text("forward-diff")).not.toBe("-"); + }); + + it("lists the failure message of an engine that could not read a definition", async () => { + const engine = makeEngine(); + engine.runBenchmark.mockResolvedValue(fakeResult({ + rows: [ + { + id: "UTJCP:ZONE-B", + name: "Japan City Plans Polyconic Grid Zone B", + found: true, + status: "failed", + workCount: 2, + projWasm: { totalMs: 1, success: 2, failure: 0, meanErrorMeters: 0.002, maxErrorMeters: 0.003, failures: [] }, + proj4js: { + totalMs: 0, + success: 0, + failure: 2, + meanErrorMeters: null, + maxErrorMeters: null, + failures: [ + { index: 0, lon: 138.7, lat: 35.3, message: "Could not get projection name from: +proj=pipeline ..." }, + { index: 1, lon: 138.8, lat: 35.4, message: "Could not get projection name from: +proj=pipeline ..." }, + ], + }, + forwardDifferenceMaxMeters: null, + }, + ], + workCount: 2, + targetIds: ["UTJCP:ZONE-B"], + })); + const { benchmark } = await start({ engine }); + + clickPreset("engine-coverage", "b"); + await benchmark.whenSettled(); + + expect(text("failure-details")).toContain("proj4js"); + expect(text("failure-details")).toContain("Could not get projection name from"); + expect(text("results-body")).toContain("failed"); + }); +}); diff --git a/tests/demo-bootstrap.test.ts b/tests/demo-bootstrap.test.ts new file mode 100644 index 0000000..4895165 --- /dev/null +++ b/tests/demo-bootstrap.test.ts @@ -0,0 +1,48 @@ +/** + * Every demo page must actually start itself. + * + * The jsdom tests import the page modules and call their `init...` function by hand, so they stay + * green even when the page stops calling it. This file reads the pages themselves: each page has + * an inline module script that imports its entry function and calls it once. + */ +import { readFileSync } from "node:fs"; +import { dirname, join } from "node:path"; +import { fileURLToPath } from "node:url"; + +import { describe, expect, it } from "vitest"; + +const root = join(dirname(fileURLToPath(import.meta.url)), ".."); + +const PAGES = [ + { page: "demo/crs-candidate-lookup.html", module: "./main.js", entry: "initDemo" }, + { page: "demo/benchmark.html", module: "./benchmark.js", entry: "initBenchmark" }, +]; + +/** The bodies of the page's inline `