diff --git a/.github/workflows/test_and_build.yml b/.github/workflows/test_and_build.yml index b24ce0de..a13f13e4 100644 --- a/.github/workflows/test_and_build.yml +++ b/.github/workflows/test_and_build.yml @@ -3,13 +3,13 @@ on: push jobs: test_and_build: - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: registry-url: https://registry.npmjs.org/ node-version: v20.14.0 diff --git a/src/NavBar.ts b/src/NavBar.ts index a55b6e73..ababfca7 100644 --- a/src/NavBar.ts +++ b/src/NavBar.ts @@ -1,6 +1,6 @@ import { coordinateToText } from '@/Converters' import Dispatcher from '@/stores/Dispatcher' -import { ClearPoints, SelectMapLayer, SetBBox, SetQueryPoints, SetVehicleProfile } from '@/actions/Actions' +import { ClearPoints, SelectMapLayer, SetBBox, SetPOIs, SetQueryPoints, SetVehicleProfile } from '@/actions/Actions' // import the window like this so that it can be mocked during testing import { window } from '@/Window' import QueryStore, { getBBoxFromCoord, QueryPoint, QueryPointType, QueryStoreState } from '@/stores/QueryStore' @@ -57,6 +57,7 @@ export default class NavBar { isInitialized: false, id: idx, queryText: parameter, + streetName: '' /**/, color: '', type: QueryPointType.Via, } @@ -132,6 +133,7 @@ export default class NavBar { return { ...p, queryText: res.hits[0].name, + streetName: res.hits[0].street, coordinate: { lat: res.hits[0].point.lat, lng: res.hits[0].point.lng }, isInitialized: true, } diff --git a/src/api/Api.ts b/src/api/Api.ts index 9f4959b5..db7d75d2 100644 --- a/src/api/Api.ts +++ b/src/api/Api.ts @@ -277,6 +277,7 @@ export class ApiImpl implements Api { const request: RoutingRequest = { points: args.points, + point_hints: args.pointHints, profile: args.profile, elevation: true, instructions: true, diff --git a/src/api/graphhopper.d.ts b/src/api/graphhopper.d.ts index d75670bb..52b66d19 100644 --- a/src/api/graphhopper.d.ts +++ b/src/api/graphhopper.d.ts @@ -6,6 +6,7 @@ export type Bbox = [number, number, number, number] export interface RoutingArgs { readonly points: [number, number][] + readonly pointHints: string[] readonly profile: string readonly maxAlternativeRoutes: number readonly customModel: CustomModel | null @@ -13,6 +14,7 @@ export interface RoutingArgs { export interface RoutingRequest { readonly points: ReadonlyArray<[number, number]> + readonly point_hints: string[] profile: string locale: string points_encoded: boolean diff --git a/src/map/MapComponent.tsx b/src/map/MapComponent.tsx index 757185bd..c1a9713e 100644 --- a/src/map/MapComponent.tsx +++ b/src/map/MapComponent.tsx @@ -22,22 +22,22 @@ export default function ({ map }: MapComponentProps) { } export function onCurrentLocationSelected( - onSelect: (queryText: string, coordinate: Coordinate | undefined, bbox: Bbox | undefined) => void + onSelect: (queryText: string, street: string, coordinate: Coordinate | undefined, bbox: Bbox | undefined) => void ) { if (!navigator.geolocation) { Dispatcher.dispatch(new ErrorAction('Geolocation is not supported in this browser')) return } - onSelect(tr('searching_location') + ' ...', undefined, undefined) + onSelect(tr('searching_location') + ' ...', '', undefined, undefined) navigator.geolocation.getCurrentPosition( position => { const coordinate = { lat: position.coords.latitude, lng: position.coords.longitude } - onSelect(tr('current_location'), coordinate, getBBoxFromCoord(coordinate)) + onSelect(tr('current_location'), '', coordinate, getBBoxFromCoord(coordinate)) }, error => { Dispatcher.dispatch(new ErrorAction(tr('searching_location_failed') + ': ' + error.message)) - onSelect('', undefined, undefined) + onSelect('', '', undefined, undefined) }, // DO NOT use e.g. maximumAge: 5_000 -> getCurrentPosition will then never return on mobile firefox!? { timeout: 300_000, enableHighAccuracy: true } diff --git a/src/pois/AddressParseResult.ts b/src/pois/AddressParseResult.ts index d5d71115..556412b9 100644 --- a/src/pois/AddressParseResult.ts +++ b/src/pois/AddressParseResult.ts @@ -10,15 +10,15 @@ export class AddressParseResult { location: string query: POIQuery icon: string - poi: string + poiType: string static TRIGGER_VALUES: PoiTriggerPhrases[] static REMOVE_VALUES: string[] - constructor(location: string, query: POIQuery, icon: string, poi: string) { + constructor(location: string, query: POIQuery, icon: string, poiType: string) { this.location = location this.query = query this.icon = icon - this.poi = poi + this.poiType = poiType } hasPOIs(): boolean { diff --git a/src/sidebar/search/AddressInput.tsx b/src/sidebar/search/AddressInput.tsx index 5c83fe41..9710d1cc 100644 --- a/src/sidebar/search/AddressInput.tsx +++ b/src/sidebar/search/AddressInput.tsx @@ -23,7 +23,7 @@ export interface AddressInputProps { point: QueryPoint points: QueryPoint[] onCancel: () => void - onAddressSelected: (queryText: string, coord: Coordinate | undefined) => void + onAddressSelected: (queryText: string, streetHint: string, coord: Coordinate | undefined) => void onChange: (value: string) => void clearDragDrop: () => void moveStartIndex: number @@ -59,6 +59,7 @@ export default function AddressInput(props: AddressInputProps) { new GeocodingItem( obj.mainText, obj.secondText, + hit.street, hit.point, hit.extent ? hit.extent : getBBoxFromCoord(hit.point) ) @@ -120,13 +121,13 @@ export default function AddressInput(props: AddressInputProps) { // try to parse input as coordinate. Otherwise query nominatim const coordinate = textToCoordinate(text) if (coordinate) { - props.onAddressSelected(text, coordinate) + props.onAddressSelected(text, '', coordinate) } else if (autocompleteItems.length > 0) { const index = highlightedResult >= 0 ? highlightedResult : 0 const item = autocompleteItems[index] if (item instanceof POIQueryItem) { handlePoiSearch(poiSearch, item.result, props.map) - props.onAddressSelected(item.result.text(item.result.poi), undefined) + props.onAddressSelected(item.result.text(item.result.poiType), '', undefined) } else if (highlightedResult < 0 && !props.point.isInitialized) { // by default use the first result, otherwise the highlighted one getApi() @@ -135,13 +136,17 @@ export default function AddressInput(props: AddressInputProps) { if (result && result.hits.length > 0) { const hit: GeocodingHit = result.hits[0] const res = nominatimHitToItem(hit) - props.onAddressSelected(res.mainText + ', ' + res.secondText, hit.point) + props.onAddressSelected( + res.mainText + ', ' + res.secondText, + hit.street, + hit.point + ) } else if (item instanceof GeocodingItem) { - props.onAddressSelected(item.toText(), item.point) + props.onAddressSelected(item.toText(), item.street, item.point) } }) } else if (item instanceof GeocodingItem) { - props.onAddressSelected(item.toText(), item.point) + props.onAddressSelected(item.toText(), item.street, item.point) } } // do not disturb 'tab' cycle @@ -262,10 +267,10 @@ export default function AddressInput(props: AddressInputProps) { highlightedItem={autocompleteItems[highlightedResult]} onSelect={item => { if (item instanceof GeocodingItem) { - props.onAddressSelected(item.toText(), item.point) + props.onAddressSelected(item.toText(), item.street, item.point) } else if (item instanceof POIQueryItem) { handlePoiSearch(poiSearch, item.result, props.map) - setText(item.result.text(item.result.poi)) + setText(item.result.text(item.result.poiType)) } searchInput.current!.blur() // see also AutocompleteEntry->onMouseDown }} diff --git a/src/sidebar/search/AddressInputAutocomplete.tsx b/src/sidebar/search/AddressInputAutocomplete.tsx index 292a8cf7..e2568676 100644 --- a/src/sidebar/search/AddressInputAutocomplete.tsx +++ b/src/sidebar/search/AddressInputAutocomplete.tsx @@ -7,12 +7,14 @@ export interface AutocompleteItem {} export class GeocodingItem implements AutocompleteItem { mainText: string secondText: string + street: string point: { lat: number; lng: number } bbox: Bbox - constructor(mainText: string, secondText: string, point: { lat: number; lng: number }, bbox: Bbox) { + constructor(mainText: string, secondText: string, street: string, point: { lat: number; lng: number }, bbox: Bbox) { this.mainText = mainText this.secondText = secondText + this.street = street this.point = point this.bbox = bbox } @@ -65,7 +67,7 @@ export function POIQueryEntry({ isHighlighted: boolean onSelect: (item: POIQueryItem) => void }) { - const poi = item.result.poi ? item.result.poi : '' + const poi = item.result.poiType ? item.result.poiType : '' return ( onSelect(item)}>
diff --git a/src/sidebar/search/Search.tsx b/src/sidebar/search/Search.tsx index 9b3b7037..5fdc7eb5 100644 --- a/src/sidebar/search/Search.tsx +++ b/src/sidebar/search/Search.tsx @@ -164,7 +164,7 @@ const SearchBox = ({ point={point} points={points} onCancel={() => console.log('cancel')} - onAddressSelected={(queryText, coordinate) => { + onAddressSelected={(queryText, street, coordinate) => { const initCount = points.filter(p => p.isInitialized).length if (coordinate && initCount != points.length) Dispatcher.dispatch(new SetBBox(getBBoxFromCoord(coordinate))) @@ -175,6 +175,7 @@ const SearchBox = ({ ...point, isInitialized: !!coordinate, queryText: queryText, + streetName: street, coordinate: coordinate ? coordinate : point.coordinate, }, initCount > 0 diff --git a/src/stores/QueryStore.ts b/src/stores/QueryStore.ts index 40080b1a..9b824372 100644 --- a/src/stores/QueryStore.ts +++ b/src/stores/QueryStore.ts @@ -46,6 +46,7 @@ export interface QueryStoreState { export interface QueryPoint { readonly coordinate: Coordinate readonly queryText: string + readonly streetName: string readonly isInitialized: boolean readonly color: string readonly id: number @@ -176,6 +177,7 @@ export default class QueryStore extends Store { coordinate: action.coordinate, id: state.nextQueryPointId, queryText: queryText, + streetName: '', color: '', isInitialized: action.isInitialized, type: QueryPointType.Via, @@ -215,6 +217,7 @@ export default class QueryStore extends Store { id: queryPoints.length, type: type, color: QueryStore.getMarkerColor(type), + streetName: '', queryText: '', isInitialized: false, coordinate: { lat: 0, lng: 0 }, @@ -450,6 +453,7 @@ export default class QueryStore extends Store { return { points: coordinates, + pointHints: state.queryPoints.map(point => point.streetName), profile: state.routingProfile.name, maxAlternativeRoutes: state.maxAlternativeRoutes, customModel: customModel, @@ -460,6 +464,7 @@ export default class QueryStore extends Store { return { isInitialized: false, queryText: '', + streetName: '', coordinate: { lat: 0, lng: 0 }, id: id, color: QueryStore.getMarkerColor(type), diff --git a/test/NavBar.test.ts b/test/NavBar.test.ts index b9fb1991..115f2417 100644 --- a/test/NavBar.test.ts +++ b/test/NavBar.test.ts @@ -120,6 +120,7 @@ describe('NavBar', function () { type: QueryPointType.To, isInitialized: false, queryText: 'some1address-with!/ { res = AddressParseResult.parse('dresden super market', false) expect(res.location).toEqual('dresden') - expect(res.poi).toEqual('super markets') + expect(res.poiType).toEqual('super markets') res = AddressParseResult.parse('dresden park', false) expect(res.location).toEqual('dresden') - expect(res.poi).toEqual('parks') + expect(res.poiType).toEqual('parks') res = AddressParseResult.parse('dresden parking', false) expect(res.location).toEqual('dresden') - expect(res.poi).toEqual('parking') + expect(res.poiType).toEqual('parking') res = AddressParseResult.parse('restaurants in this area', false) expect(res.location).toEqual('') - expect(res.poi).toEqual('restaurants') + expect(res.poiType).toEqual('restaurants') }) it('should parse generic', async () => { diff --git a/test/routing/Api.test.ts b/test/routing/Api.test.ts index a0ccb6b5..cfc34b76 100644 --- a/test/routing/Api.test.ts +++ b/test/routing/Api.test.ts @@ -71,6 +71,7 @@ describe('route', () => { it('should use correct metadata', async () => { const args: RoutingArgs = { points: [], + pointHints: [], maxAlternativeRoutes: 1, profile: 'profile', customModel: null, @@ -99,6 +100,7 @@ describe('route', () => { it('transforms routingArgs into routing request with default algorithm for maxAlternativeRoutes: 1', async () => { const args: RoutingArgs = { points: [], + pointHints: [], maxAlternativeRoutes: 1, profile: 'car', customModel: null, @@ -106,6 +108,7 @@ describe('route', () => { const expectedBody: RoutingRequest = { points: args.points, + point_hints: [], profile: args.profile, elevation: true, instructions: true, @@ -141,6 +144,7 @@ describe('route', () => { it('transforms routingArgs into routing request with alternative_route algorithm for maxAlternativeRoutes > 1', async () => { const args: RoutingArgs = { points: [], + pointHints: [], maxAlternativeRoutes: 2, profile: 'car', customModel: null, @@ -148,6 +152,7 @@ describe('route', () => { const expectedBody: RoutingRequest = { points: args.points, + point_hints: [], profile: args.profile, elevation: true, instructions: true, @@ -185,6 +190,7 @@ describe('route', () => { it('transforms routingArgs into routing request with custom model', async () => { const args: RoutingArgs = { points: [], + pointHints: [], maxAlternativeRoutes: 1, profile: 'car', customModel: { @@ -199,6 +205,7 @@ describe('route', () => { const expectedBody: RoutingRequest = { points: args.points, + point_hints: [], profile: args.profile, elevation: true, instructions: true, @@ -240,6 +247,7 @@ describe('route', () => { [0, 0], [1, 1], ], + pointHints: [], maxAlternativeRoutes: 1, profile: 'bla', customModel: null, @@ -261,6 +269,7 @@ describe('route', () => { [0, 0], [1, 1], ], + pointHints: [], maxAlternativeRoutes: 1, profile: 'bla', customModel: null, @@ -285,6 +294,7 @@ describe('route', () => { const args: RoutingArgs = { profile: 'car', points: [], + pointHints: [], maxAlternativeRoutes: 3, customModel: null, } @@ -299,6 +309,7 @@ describe('route', () => { setTranslation('de', true) const args: RoutingArgs = { points: [], + pointHints: [], maxAlternativeRoutes: 1, profile: 'car', customModel: null, @@ -306,6 +317,7 @@ describe('route', () => { const expectedBody: RoutingRequest = { points: args.points, + point_hints: [], profile: args.profile, elevation: true, instructions: true, diff --git a/test/stores/QueryStore.test.ts b/test/stores/QueryStore.test.ts index 5410c08f..d9db2d34 100644 --- a/test/stores/QueryStore.test.ts +++ b/test/stores/QueryStore.test.ts @@ -344,6 +344,7 @@ describe('QueryStore', () => { const routingArgs: RoutingArgs = { maxAlternativeRoutes: 1, points: [], + pointHints: [], profile: 'some-profile', customModel: null, } @@ -375,6 +376,7 @@ describe('QueryStore', () => { const routingArgs: RoutingArgs = { maxAlternativeRoutes: 1, points: [], + pointHints: [], profile: 'some-profile', customModel: null, } @@ -401,6 +403,7 @@ function getQueryPoint(id: number): QueryPoint { type: QueryPointType.From, isInitialized: true, queryText: '', + streetName: '', color: '', coordinate: { lat: 0, lng: 0 }, id: id, diff --git a/test/stores/RouteStore.test.ts b/test/stores/RouteStore.test.ts index 04989c29..5e677929 100644 --- a/test/stores/RouteStore.test.ts +++ b/test/stores/RouteStore.test.ts @@ -66,6 +66,7 @@ function createEmptyQueryPoint(): QueryPoint { return { isInitialized: false, queryText: '', + streetName: '', coordinate: { lat: 0, lng: 0 }, id: 0, color: '',