diff --git a/src/lib/index.ts b/src/lib/index.ts index 856f2b6..b1cebb7 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -1 +1,6 @@ -// place files you want to import through the `$lib` alias in this folder. +export interface GameData { + center: [number, number]; + lines: [GeoJSON.Feature, string][]; + stopName: string; + stop: GeoJSON.Position; +} diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 8f66f1f..fcc9830 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -15,14 +15,11 @@ const hidden = !$page.url.searchParams.has("debug"); - const lignes = data.lines; - const point = L.latLng(45.75388713, 4.84715287); + const lignes = data.gameData.lines; + const point = L.latLng(data.gameData.stop[1], data.gameData.stop[0]); + const center = L.latLng(data.gameData.center); - const bbox = lignes.bbox || [0, 0, 0, 0]; - const center = L.latLng((bbox[1] + bbox[3]) / 2, (bbox[0] + bbox[2]) / 2); - - const linesJson = lignes.features.map((feature) => { - let color = "rgb(" + feature.properties?.couleur?.replaceAll(" ", ",") + ")"; + const linesJson = lignes.map(([feature, color]) => { return L.geoJSON(feature, { style: { color } }); }); @@ -32,8 +29,11 @@ let lines = $state(true); let labels = $state(false); + let submitted = $state(false); let map: L.Map | null = $state(null); + let playerMarker: L.Marker | null = $state(null); + let tileLayer = $derived( L.tileLayer( `https://basemaps.cartocdn.com/light_${labels ? "all" : "nolabels"}/{z}/{x}/{y}{r}.png`, @@ -50,19 +50,21 @@ tileLayer.addTo(map); if (lines) { - linesJson.forEach((line) => map && line.addTo(map)); + linesJson.forEach((line) => line.addTo(map!)); } else { - linesJson.forEach((line) => map && line.removeFrom(map)); + linesJson.forEach((line) => line.removeFrom(map!)); } }); function createMap(node: HTMLElement) { map = L.map(node).setView(center, 13); + playerMarker = L.marker([0, 0]).addTo(map); - const marker = L.marker([0, 0]); map.on("click", (e) => { - if (map) marker.setLatLng(e.latlng).addTo(map); - latlng = e.latlng; + if (map && playerMarker && !submitted) { + playerMarker.setLatLng(e.latlng); + latlng = e.latlng; + } }); } @@ -76,8 +78,23 @@ return score * multiplier; } + + function checkLocation() { + submitted = true; + if (map) { + L.marker(point).bindPopup(data.gameData.stopName).addTo(map).openPopup(); + } + } +

{data.gameData.stopName}

+ + + + + @@ -95,6 +112,11 @@ text-align: center; } + .results { + color: green; + font-size: 20px; + } + #map { height: 100%; width: 100%; diff --git a/src/routes/+page.ts b/src/routes/+page.ts index 32fbd51..e6fc043 100644 --- a/src/routes/+page.ts +++ b/src/routes/+page.ts @@ -1,13 +1,11 @@ import type { PageLoad } from "./$types"; - -const linesUrl = - "https://data.grandlyon.com/geoserver/sytral/ows?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=sytral:tcl_sytral.tcllignemf_2_0_0&outputFormat=application/json&SRSNAME=EPSG:4171&sortBy=gid"; +import type { GameData } from "$lib"; export const load: PageLoad = async ({ fetch }) => { - const res = await fetch(linesUrl); - const lines: GeoJSON.FeatureCollection = await res.json(); + const res = await fetch("/api/data"); + const gameData: GameData = await res.json(); - return { lines }; + return { gameData }; }; export const ssr = false; diff --git a/src/routes/api/data/+server.ts b/src/routes/api/data/+server.ts new file mode 100644 index 0000000..f65b330 --- /dev/null +++ b/src/routes/api/data/+server.ts @@ -0,0 +1,55 @@ +import type { RequestHandler } from "./$types"; +import type { GameData } from "$lib"; + +const linesUrl = + "https://data.grandlyon.com/geoserver/sytral/ows?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=sytral:tcl_sytral.tcllignemf_2_0_0&outputFormat=application/json&SRSNAME=EPSG:4171&sortBy=gid"; +const stopsUrl = + "https://data.grandlyon.com/geoserver/sytral/ows?SERVICE=WFS&VERSION=2.0.0&request=GetFeature&typename=sytral:tcl_sytral.tclarret&outputFormat=application/json&SRSNAME=EPSG:4171&sortBy=gid"; + +let lazyLines: GeoJSON.FeatureCollection | null = null; +let lazyStops: GeoJSON.FeatureCollection | null = null; + +export const GET: RequestHandler = async ({ fetch }) => { + const lines: GeoJSON.FeatureCollection = + lazyLines || (lazyLines = await fetch(linesUrl).then((r) => r.json())); + const stops: GeoJSON.FeatureCollection = + lazyStops || (lazyStops = await fetch(stopsUrl).then((r) => r.json())); + + const bbox = lines.bbox ?? [0, 0, 0, 0]; + const centerLat = (bbox[1] + bbox[3]) / 2; + const centerLon = (bbox[0] + bbox[2]) / 2; + + const lineColors: [GeoJSON.Feature, string][] = lines.features.map((f) => { + const components = f.properties!.couleur!.split(" "); + return [f, `rgb(${components.join(",")})`]; + }); + + const lineCodes = new Set(lines.features.map((f) => f.properties!.code_ligne)); + + const crossingStops = stops.features.filter((f) => { + if (f.properties?.desserte) { + return f.properties.desserte + .split(",") + .map((d: string) => d.split(":")[0]) + .some((d: string) => lineCodes.has(d)); + } else { + return false; + } + }); + + const uniqueStops = crossingStops.filter( + (f1, i, arr) => arr.findIndex((f2) => f2.properties?.nom === f1.properties?.nom) === i, + ); + + const randomStop = uniqueStops[Math.floor(Math.random() * uniqueStops.length)]; + const coords = randomStop.geometry.type === "Point" ? randomStop.geometry.coordinates : [0, 0]; + + const data: GameData = { + center: [centerLat, centerLon], + lines: lineColors, + stopName: randomStop.properties!.nom, + stop: coords, + }; + + return Response.json(data); +};