From 592e451d29810a97a1d2def2052b2857271afef5 Mon Sep 17 00:00:00 2001 From: nilsnolde Date: Sun, 1 Feb 2026 00:33:40 +0100 Subject: [PATCH] add more costing options --- .../settings-panel/settings-options.ts | 61 ++++++++++++++++++- .../settings-panel/settings-panel.spec.tsx | 56 +++++++++++++++++ .../settings-panel/settings-panel.tsx | 4 +- src/components/types.ts | 25 ++------ src/utils/valhalla.spec.ts | 12 ++++ 5 files changed, 133 insertions(+), 25 deletions(-) diff --git a/src/components/settings-panel/settings-options.ts b/src/components/settings-panel/settings-options.ts index 117b468..455c7c5 100644 --- a/src/components/settings-panel/settings-options.ts +++ b/src/components/settings-panel/settings-options.ts @@ -1,4 +1,4 @@ -import type { BicycleType } from '@/components/types'; +import type { BicycleType, PedestrianType } from '@/components/types'; interface NumericSetting { name: string; @@ -194,6 +194,32 @@ const gatePenalty = { }, }; +const destinationOnlyPenalty = { + name: 'Destination-only Penalty', + param: 'destination_only_penalty', + description: + 'A penalty applied when entering an road which is only allowed to enter if necessary to reach the destination.', + unit: 'sec', + settings: { + min: 0, + max: 600, + step: 30, + }, +}; + +const elevatorPenalty = { + name: 'Elevator Penalty', + param: 'elevator_penalty', + description: + 'A penalty in seconds added to each transition via an elevator node or onto an elevator edge. Higher values apply larger cost penalties to avoid elevators.', + unit: 'sec', + settings: { + min: 0, + max: 600, + step: 30, + }, +}; + const tollBoothCost = { name: 'Toll Booth Cost', param: 'toll_booth_cost', @@ -551,6 +577,29 @@ const bicycleType = { ], }; +const pedestrianType = { + name: 'Pedestrian Type', + description: `One of "foot", "wheelchair", "blind". If set to blind, enables additional route instructions, especially useful for blind users: Announcing crossed streets, the stairs, bridges, tunnels, gates and bollards, which need to be passed on route; information about traffic signals on crosswalks; route numbers not announced for named routes. If set to wheelchair, changes the defaults for max_distance, walking_speed, and step_penalty to be better aligned to the needs of wheelchair users.`, + param: 'type', + enums: [ + { + key: 'Foot', + text: 'Foot', + value: 'Foot', + }, + { + key: 'Wheelchair', + text: 'Wheelchair', + value: 'Wheelchair', + }, + { + key: 'Blind', + text: 'Blind', + value: 'Blind', + }, + ], +}; + const cyclingSpeed = { name: 'Cycling Speed', param: 'cycling_speed', @@ -826,6 +875,8 @@ export const settingsInit = { use_living_streets: 0.5, use_tracks: 0, private_access_penalty: 450, + destination_only_penalty: 300, + elevator_penalty: 120, ignore_closures: false, ignore_restrictions: false, ignore_access: false, @@ -836,6 +887,7 @@ export const settingsInit = { shortest: false, exclude_cash_only_tolls: false, bicycle_type: 'Hybrid' as BicycleType, + type: 'Foot' as PedestrianType, cycling_speed: 20, use_roads: 0.5, use_hills: 0.5, @@ -891,6 +943,7 @@ const commonVehicleProfileNumeric = [ topSpeed, fixedSpeed, privateAccessPenalty, + destinationOnlyPenalty, closureFactor, ...serviceSettings, ...gateSettings, @@ -964,10 +1017,14 @@ export const profileSettings: Record = { sidewalkFactor, alleyFactor, drivewayFactor, + privateAccessPenalty, + destinationOnlyPenalty, + elevatorPenalty, stepPenalty, maxHikingDifficulty, ], - [shortest] + [shortest], + [pedestrianType] ), motor_scooter: createSettings( diff --git a/src/components/settings-panel/settings-panel.spec.tsx b/src/components/settings-panel/settings-panel.spec.tsx index 73d2175..2a6c1c8 100644 --- a/src/components/settings-panel/settings-panel.spec.tsx +++ b/src/components/settings-panel/settings-panel.spec.tsx @@ -51,6 +51,7 @@ vi.mock('@/stores/common-store', () => ({ use_living_streets: 0.5, alternates: 2, bicycle_type: 'Hybrid', + type: 'Foot', service_penalty: 15, service_factor: 1, maneuver_penalty: 5, @@ -61,6 +62,16 @@ vi.mock('@/stores/common-store', () => ({ country_crossing_cost: 600, country_crossing_penalty: 0, turn_penalty_factor: 0, + use_lit: 0.5, + walking_speed: 5.1, + walkway_factor: 1, + sidewalk_factor: 1, + alley_factor: 2, + driveway_factor: 5, + step_penalty: 0, + max_hiking_difficulty: 1, + destination_only_penalty: 300, + elevator_penalty: 120, }, settingsPanelOpen: true, updateSettings: mockUpdateSettings, @@ -501,6 +512,51 @@ describe('SettingsPanel', () => { }); }); + describe('Enum Settings', () => { + it('should render Bicycle Type select only once for bicycle profile', () => { + renderWithQueryClient(); + const bicycleTypeLabels = screen.getAllByText('Bicycle Type'); + expect(bicycleTypeLabels).toHaveLength(1); + }); + + it('should display current bicycle_type value from settings', () => { + renderWithQueryClient(); + // The mock has bicycle_type: 'Hybrid' + expect(screen.getByText('Hybrid')).toBeInTheDocument(); + }); + + it('should render Pedestrian Type select only once for pedestrian profile', () => { + mockUseSearch.mockReturnValue({ profile: 'pedestrian' }); + renderWithQueryClient(); + const pedestrianTypeLabels = screen.getAllByText('Pedestrian Type'); + expect(pedestrianTypeLabels).toHaveLength(1); + }); + + it('should display current pedestrian type value from settings', () => { + mockUseSearch.mockReturnValue({ profile: 'pedestrian' }); + renderWithQueryClient(); + // The mock has type: 'Foot' + expect(screen.getByText('Foot')).toBeInTheDocument(); + }); + + it('should render bicycle type combobox with correct id', () => { + renderWithQueryClient(); + const bicycleTypeSelect = screen.getByRole('combobox', { + name: /Bicycle Type/i, + }); + expect(bicycleTypeSelect).toBeInTheDocument(); + }); + + it('should render pedestrian type combobox with correct id', () => { + mockUseSearch.mockReturnValue({ profile: 'pedestrian' }); + renderWithQueryClient(); + const pedestrianTypeSelect = screen.getByRole('combobox', { + name: /Pedestrian Type/i, + }); + expect(pedestrianTypeSelect).toBeInTheDocument(); + }); + }); + describe('Language Picker', () => { it('should render Directions Language section when activeTab is directions', () => { renderWithQueryClient(); diff --git a/src/components/settings-panel/settings-panel.tsx b/src/components/settings-panel/settings-panel.tsx index 1b8d302..86aebb6 100644 --- a/src/components/settings-panel/settings-panel.tsx +++ b/src/components/settings-panel/settings-panel.tsx @@ -227,8 +227,8 @@ export const SettingsPanel = () => { id={option.param} label={option.name} description={option.description} - placeholder="Select Bicycle Type" - value={settings.bicycle_type as string} + placeholder={`Select ${option.name}`} + value={settings[option.param] as string} options={option.enums} onValueChange={(value) => { handleUpdateSettings({ diff --git a/src/components/types.ts b/src/components/types.ts index 9a6eb52..a835951 100644 --- a/src/components/types.ts +++ b/src/components/types.ts @@ -19,6 +19,7 @@ export type Settings = Record< >; export type BicycleType = 'Hybrid' | 'Road' | 'City' | 'Cross' | 'Mountain'; +export type PedestrianType = 'Foot' | 'Wheelchair' | 'Blind'; export interface PossibleSettings { maneuver_penalty: number; @@ -37,6 +38,8 @@ export interface PossibleSettings { use_living_streets: number; use_tracks: number; private_access_penalty: number; + destination_only_penalty: number; + elevator_penalty: number; ignore_closures: boolean; ignore_restrictions: boolean; ignore_access: boolean; @@ -47,6 +50,7 @@ export interface PossibleSettings { shortest: boolean; exclude_cash_only_tolls: boolean; bicycle_type: BicycleType; + type: PedestrianType; cycling_speed: number; use_roads: number; use_hills: number; @@ -180,27 +184,6 @@ export interface Center { addressindex: number; } -export interface Costing { - maneuver_penalty: number; - use_ferry: number; - use_living_streets: number; - service_penalty: number; - service_factor: number; - shortest: boolean; - bicycle_type: string; - cycling_speed: number; - use_roads: number; - use_hills: number; - avoid_bad_surfaces: number; - gate_penalty: number; - gate_cost: number; -} - -export interface Directions { - alternates: number; - exclude_polygons: GeoJSON.GeoJSON[]; -} - export interface NominationResponse { place_id: number; licence: string; diff --git a/src/utils/valhalla.spec.ts b/src/utils/valhalla.spec.ts index d560b45..ca88150 100644 --- a/src/utils/valhalla.spec.ts +++ b/src/utils/valhalla.spec.ts @@ -199,6 +199,8 @@ describe('valhalla.ts', () => { const mockSettings: Settings = { costing: { + destination_only_penalty: 300, + elevator_penalty: 300, maneuver_penalty: 5, country_crossing_penalty: 0, country_crossing_cost: 600, @@ -225,6 +227,7 @@ describe('valhalla.ts', () => { shortest: false, exclude_cash_only_tolls: false, bicycle_type: 'Hybrid', + type: 'Foot', cycling_speed: 20, use_roads: 0.5, use_hills: 0.5, @@ -260,6 +263,8 @@ describe('valhalla.ts', () => { speed_types: [], }, directions: { + destination_only_penalty: 300, + elevator_penalty: 300, maneuver_penalty: 5, country_crossing_penalty: 0, country_crossing_cost: 600, @@ -286,6 +291,7 @@ describe('valhalla.ts', () => { shortest: false, exclude_cash_only_tolls: false, bicycle_type: 'Hybrid', + type: 'Foot', cycling_speed: 20, use_roads: 0.5, use_hills: 0.5, @@ -719,6 +725,8 @@ describe('valhalla.ts', () => { const mockSettings: Settings = { costing: { + destination_only_penalty: 300, + elevator_penalty: 300, maneuver_penalty: 5, country_crossing_penalty: 0, country_crossing_cost: 600, @@ -745,6 +753,7 @@ describe('valhalla.ts', () => { shortest: false, exclude_cash_only_tolls: false, bicycle_type: 'Hybrid', + type: 'Foot', cycling_speed: 20, use_roads: 0.5, use_hills: 0.5, @@ -780,6 +789,8 @@ describe('valhalla.ts', () => { speed_types: [], }, directions: { + destination_only_penalty: 300, + elevator_penalty: 300, maneuver_penalty: 5, country_crossing_penalty: 0, country_crossing_cost: 600, @@ -806,6 +817,7 @@ describe('valhalla.ts', () => { shortest: false, exclude_cash_only_tolls: false, bicycle_type: 'Hybrid', + type: 'Foot', cycling_speed: 20, use_roads: 0.5, use_hills: 0.5,