diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2e90919..e7bdb90 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -111,9 +111,6 @@ jobs: - name: Move Profiles if: matrix.config.mode == 'Release' run: | - cd deps\tiles - git reset --hard HEAD - cd ..\.. mkdir dist Copy-Item .\deps\tiles\profile dist\tiles-profiles -Recurse mv .\build\motis.exe dist diff --git a/exe/server.cc b/exe/server.cc index 48f2848..172b54c 100644 --- a/exe/server.cc +++ b/exe/server.cc @@ -17,6 +17,7 @@ #include "motis/endpoints/elevators.h" #include "motis/endpoints/footpaths.h" #include "motis/endpoints/graph.h" +#include "motis/endpoints/initial.h" #include "motis/endpoints/levels.h" #include "motis/endpoints/matches.h" #include "motis/endpoints/osr_routing.h" @@ -61,7 +62,8 @@ int server(data d, config const& c) { POST(qr, "/api/graph", d); POST(qr, "/api/update_elevator", d); GET(qr, "/api/debug/footpaths", d); - GET(qr, "/api/v1/levels", d); + GET(qr, "/api/v1/map/levels", d); + GET(qr, "/api/v1/map/initial", d); GET(qr, "/api/v1/reverse-geocode", d); GET(qr, "/api/v1/geocode", d); GET(qr, "/api/v1/plan", d); diff --git a/include/motis/endpoints/initial.h b/include/motis/endpoints/initial.h new file mode 100644 index 0000000..1949a2b --- /dev/null +++ b/include/motis/endpoints/initial.h @@ -0,0 +1,16 @@ +#pragma once + +#include "boost/url/url.hpp" + +#include "motis-api/motis-api.h" +#include "motis/fwd.h" + +namespace motis::ep { + +struct initial { + api::initial_response operator()(boost::urls::url_view const&) const; + + nigiri::timetable const& tt_; +}; + +} // namespace motis::ep \ No newline at end of file diff --git a/include/motis/timetable/initial_permalink.h b/include/motis/timetable/initial_permalink.h new file mode 100644 index 0000000..d19918a --- /dev/null +++ b/include/motis/timetable/initial_permalink.h @@ -0,0 +1,11 @@ +#pragma once + +#include + +#include "motis/fwd.h" + +namespace motis { + +std::string get_initial_permalink(nigiri::timetable const&); + +} // namespace motis \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml index a03ae26..01ef7a6 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -460,7 +460,35 @@ paths: The next page is a set of itineraries departing AFTER the last itinerary in this result. type: string - /api/v1/levels: + /api/v1/map/initial: + get: + tags: + - map + operationId: initial + summary: initial location to view the map at after loading based on where public transport should be visible + responses: + '200': + description: latitude, longitude and zoom level to set the map to + content: + application/json: + schema: + type: object + required: + - lat + - lon + - zoom + properties: + lat: + description: latitude + type: number + lon: + description: longitude + type: number + zoom: + description: zoom level + type: number + + /api/v1/map/levels: get: tags: - map diff --git a/src/endpoints/initial.cc b/src/endpoints/initial.cc new file mode 100644 index 0000000..0ecb92d --- /dev/null +++ b/src/endpoints/initial.cc @@ -0,0 +1,59 @@ +#include "motis/endpoints/initial.h" + +#include "utl/erase_if.h" +#include "utl/to_vec.h" + +#include "tiles/fixed/convert.h" +#include "tiles/fixed/fixed_geometry.h" + +#include "nigiri/timetable.h" + +namespace n = nigiri; + +namespace motis::ep { + +api::initial_response initial::operator()(boost::urls::url_view const&) const { + auto const get_quantiles = [](std::vector&& coords) { + utl::erase_if(coords, [](auto const c) { return c == 0.; }); + if (coords.empty()) { + return std::make_pair(0., 0.); + } + if (coords.size() < 10) { + return std::make_pair(coords.front(), coords.back()); + } + + std::sort(begin(coords), end(coords)); + constexpr auto const kQuantile = .8; + return std::make_pair( + coords.at(static_cast(coords.size()) * (1 - kQuantile)), + coords.at(static_cast(coords.size()) * (kQuantile))); + }; + + auto const [lat_min, lat_max] = get_quantiles(utl::to_vec( + tt_.locations_.coordinates_, [](auto const& s) { return s.lat_; })); + auto const [lng_min, lng_max] = get_quantiles(utl::to_vec( + tt_.locations_.coordinates_, [](auto const& s) { return s.lng_; })); + + auto const fixed0 = tiles::latlng_to_fixed({lat_min, lng_min}); + auto const fixed1 = tiles::latlng_to_fixed({lat_max, lng_max}); + + auto const center = tiles::fixed_to_latlng( + {(fixed0.x() + fixed1.x()) / 2, (fixed0.y() + fixed1.y()) / 2}); + + auto const d = static_cast(std::max( + std::abs(fixed0.x() - fixed1.x()), std::abs(fixed0.y() - fixed1.y()))); + + auto zoom = 0U; + for (; zoom < (tiles::kMaxZoomLevel - 1); ++zoom) { + if (((tiles::kTileSize * 2ULL) * + (1ULL << (tiles::kMaxZoomLevel - (zoom + 1)))) < d) { + break; + } + } + + return {.lat_ = center.lat_, + .lon_ = center.lng_, + .zoom_ = static_cast(zoom)}; +} + +} // namespace motis::ep \ No newline at end of file diff --git a/src/timetable/initial_permalink.cc b/src/timetable/initial_permalink.cc new file mode 100644 index 0000000..d3f34e7 --- /dev/null +++ b/src/timetable/initial_permalink.cc @@ -0,0 +1,3 @@ +#include "motis/timetable/initial_permalink.h" + +namespace motis {} // namespace motis \ No newline at end of file diff --git a/ui/README.md b/ui/README.md index 5ce6766..f46ba72 100644 --- a/ui/README.md +++ b/ui/README.md @@ -1,38 +1,5 @@ -# create-svelte - -Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/main/packages/create-svelte). - -## Creating a project - -If you're seeing this, you've probably already done this step. Congrats! +Generate OpenAPI client: ```bash -# create a new project in the current directory -npm create svelte@latest - -# create a new project in my-app -npm create svelte@latest my-app +npx @hey-api/openapi-ts -i ../openapi.yaml -o src/lib/openapi -c @hey-api/client-fetch ``` - -## Developing - -Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server: - -```bash -npm run dev - -# or start the server and open the app in a new browser tab -npm run dev -- --open -``` - -## Building - -To create a production version of your app: - -```bash -npm run build -``` - -You can preview the production build with `npm run preview`. - -> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment. diff --git a/ui/src/lib/map/Map.svelte b/ui/src/lib/map/Map.svelte index 9ea05f0..d579597 100644 --- a/ui/src/lib/map/Map.svelte +++ b/ui/src/lib/map/Map.svelte @@ -29,13 +29,6 @@ let ctx = $state<{ map: maplibregl.Map | undefined }>({ map: undefined }); setContext('map', ctx); - $effect(() => { - if (style != currStyle && ctx.map) { - ctx.map.setStyle(style); - } - }); - - let currentCenter = center; const createMap = (container: HTMLElement) => { map = new maplibregl.Map({ container, zoom, center, style, transformRequest }); @@ -75,8 +68,23 @@ }; $effect(() => { - if (center != currentCenter) { - map?.setCenter(center); + if (style != currStyle && ctx.map) { + ctx.map.setStyle(style); + } + }); + + let currentZoom = zoom; + $effect(() => { + if (map && zoom != currentZoom) { + map.setZoom(zoom); + currentZoom = zoom; + } + }); + + let currentCenter = center; + $effect(() => { + if (map && center != currentCenter) { + map.setCenter(center); currentCenter = center; } }); diff --git a/ui/src/lib/openapi/services.gen.ts b/ui/src/lib/openapi/services.gen.ts index 8df6e91..c3026eb 100644 --- a/ui/src/lib/openapi/services.gen.ts +++ b/ui/src/lib/openapi/services.gen.ts @@ -1,7 +1,7 @@ // This file is auto-generated by @hey-api/openapi-ts import { createClient, createConfig, type Options } from '@hey-api/client-fetch'; -import type { ReverseGeocodeData, ReverseGeocodeError, ReverseGeocodeResponse, GeocodeData, GeocodeError, GeocodeResponse, TripData, TripError, TripResponse, StoptimesData, StoptimesError, StoptimesResponse, PlanData, PlanError, PlanResponse, LevelsData, LevelsError, LevelsResponse, FootpathsData, FootpathsError, FootpathsResponse } from './types.gen'; +import type { ReverseGeocodeData, ReverseGeocodeError, ReverseGeocodeResponse, GeocodeData, GeocodeError, GeocodeResponse, TripData, TripError, TripResponse, StoptimesData, StoptimesError, StoptimesResponse, PlanData, PlanError, PlanResponse, InitialError, InitialResponse, LevelsData, LevelsError, LevelsResponse, FootpathsData, FootpathsError, FootpathsResponse } from './types.gen'; export const client = createClient(createConfig()); @@ -45,12 +45,20 @@ export const plan = (options: Options(options?: Options) => { return (options?.client ?? client).get({ + ...options, + url: '/api/v1/map/initial' +}); }; + /** * Get all available levels for a map section */ export const levels = (options: Options) => { return (options?.client ?? client).get({ ...options, - url: '/api/v1/levels' + url: '/api/v1/map/levels' }); }; /** diff --git a/ui/src/lib/openapi/types.gen.ts b/ui/src/lib/openapi/types.gen.ts index d7623ff..dd799a4 100644 --- a/ui/src/lib/openapi/types.gen.ts +++ b/ui/src/lib/openapi/types.gen.ts @@ -766,6 +766,23 @@ export type PlanResponse = ({ export type PlanError = unknown; +export type InitialResponse = ({ + /** + * latitude + */ + lat: number; + /** + * longitude + */ + lon: number; + /** + * zoom level + */ + zoom: number; +}); + +export type InitialError = unknown; + export type LevelsData = { query: { /** diff --git a/ui/src/routes/+page.svelte b/ui/src/routes/+page.svelte index b11b97a..43279aa 100644 --- a/ui/src/routes/+page.svelte +++ b/ui/src/routes/+page.svelte @@ -6,7 +6,7 @@ import SearchMask from './SearchMask.svelte'; import { posToLocation, type Location } from '$lib/Location'; import { Card } from '$lib/components/ui/card'; - import { type Itinerary, plan, type PlanResponse, trip } from '$lib/openapi'; + import { initial, type Itinerary, plan, type PlanResponse, trip } from '$lib/openapi'; import ItineraryList from './ItineraryList.svelte'; import ConnectionDetail from './ConnectionDetail.svelte'; import { Button } from '$lib/components/ui/button'; @@ -22,6 +22,7 @@ import { client } from '$lib/openapi'; import StopTimes from './StopTimes.svelte'; import { toDateTime } from '$lib/toDateTime'; + import { onMount } from 'svelte'; const urlParams = browser && new URLSearchParams(window.location.search); const hasDebug = urlParams && urlParams.has('debug'); @@ -36,11 +37,22 @@ document.documentElement.classList.add('dark'); } + let center = $state.raw<[number, number]>([8.652235, 49.876908]); let level = $state(0); let zoom = $state(15); let bounds = $state(); let map = $state(); + onMount(() => { + initial().then((d) => { + const r = d.data; + if (r) { + center = [r.lon, r.lat]; + zoom = r.zoom; + } + }); + }); + let fromMarker = $state(); let toMarker = $state(); let from = $state({ label: '', value: {} }); @@ -147,7 +159,7 @@ return { url: `${client.getConfig().baseUrl}/tiles${url}` }; } }} - center={[8.652235, 49.876908]} + {center} class={cn('h-screen overflow-clip', theme)} style={getStyle(theme, level)} >