feat: add advanced Overpass search composer

This commit is contained in:
zv
2026-06-23 19:15:01 +02:00
parent b1e1df7e1d
commit c578d41a1e
7 changed files with 820 additions and 70 deletions

View File

@@ -43,6 +43,38 @@ describe('buildOverpassQuery', () => {
expect(query).toContain('node["amenity"="library"](52.100000,20.900000,52.200000,21.100000);');
});
it('adds refinement filters to the target object', () => {
const query = buildOverpassQuery({
choice: 'restaurants',
filters: [
{ key: 'outdoor_seating', value: 'yes' },
{ key: 'opening_hours' },
],
area: { mode: 'bbox', bbox },
});
expect(query).toContain(
'node["amenity"="restaurant"]["outdoor_seating"="yes"]["opening_hours"](52.100000,20.900000,52.200000,21.100000);',
);
});
it('builds a nearby query for benches near shops', () => {
const query = buildOverpassQuery({
choice: 'benches',
nearby: {
presetId: 'shops',
radiusMeters: 100,
},
area: { mode: 'bbox', bbox },
});
expect(query).toContain('node["shop"](52.100000,20.900000,52.200000,21.100000);');
expect(query).toContain(')->.nearby;');
expect(query).toContain(
'node["amenity"="bench"](52.100000,20.900000,52.200000,21.100000)(around.nearby:100);',
);
});
it('builds a named area query', () => {
const query = buildOverpassQuery({
choice: 'hotels',
@@ -74,4 +106,30 @@ describe('buildOverpassQuery', () => {
expect(query).toContain('area["name"="Paris \\"Centre\\""]->.searchArea;');
expect(query).toContain('nwr["name"="Joe \\"Coffee\\""](area.searchArea);');
});
it('builds a named area query with a custom nearby condition', () => {
const query = buildOverpassQuery({
choice: 'cafes',
nearby: {
customKey: 'amenity',
customValue: 'toilets',
radiusMeters: 250,
},
area: { mode: 'namedArea', name: 'Berlin' },
});
expect(query).toBe(
[
'[out:json][timeout:25];',
'area["name"="Berlin"]->.searchArea;',
'(',
' nwr["amenity"="toilets"](area.searchArea);',
')->.nearby;',
'(',
' nwr["amenity"="cafe"](area.searchArea)(around.nearby:250);',
');',
'out center 100;',
].join('\n'),
);
});
});

View File

@@ -11,8 +11,9 @@ export function isBBoxTooLarge(bbox: BBox): boolean {
}
export function buildOverpassQuery(state: SearchState): string {
const tag = resolveTagFilter(state);
const filter = buildTagFilter(tag);
const targetFilter = buildTagFilters([resolveTagFilter(state), ...(state.filters ?? [])]);
const nearby = state.nearby ? resolveNearbyTagFilter(state.nearby) : null;
const nearbyFilter = nearby ? buildTagFilters([nearby]) : null;
if (state.area.mode === 'namedArea') {
const areaName = state.area.name.trim();
@@ -21,11 +22,25 @@ export function buildOverpassQuery(state: SearchState): string {
throw new Error('Named area is required.');
}
if (nearbyFilter) {
return [
'[out:json][timeout:25];',
`area["name"="${escapeOverpassString(areaName)}"]->.searchArea;`,
'(',
` nwr${nearbyFilter}(area.searchArea);`,
')->.nearby;',
'(',
` nwr${targetFilter}(area.searchArea)(around.nearby:${formatRadius(state.nearby?.radiusMeters)});`,
');',
'out center 100;',
].join('\n');
}
return [
'[out:json][timeout:25];',
`area["name"="${escapeOverpassString(areaName)}"]->.searchArea;`,
'(',
` nwr${filter}(area.searchArea);`,
` nwr${targetFilter}(area.searchArea);`,
');',
'out center 100;',
].join('\n');
@@ -33,12 +48,29 @@ export function buildOverpassQuery(state: SearchState): string {
const bbox = formatBBox(state.area.bbox);
if (nearbyFilter) {
return [
'[out:json][timeout:25];',
'(',
` node${nearbyFilter}(${bbox});`,
` way${nearbyFilter}(${bbox});`,
` relation${nearbyFilter}(${bbox});`,
')->.nearby;',
'(',
` node${targetFilter}(${bbox})(around.nearby:${formatRadius(state.nearby?.radiusMeters)});`,
` way${targetFilter}(${bbox})(around.nearby:${formatRadius(state.nearby?.radiusMeters)});`,
` relation${targetFilter}(${bbox})(around.nearby:${formatRadius(state.nearby?.radiusMeters)});`,
');',
'out center 100;',
].join('\n');
}
return [
'[out:json][timeout:25];',
'(',
` node${filter}(${bbox});`,
` way${filter}(${bbox});`,
` relation${filter}(${bbox});`,
` node${targetFilter}(${bbox});`,
` way${targetFilter}(${bbox});`,
` relation${targetFilter}(${bbox});`,
');',
'out center 100;',
].join('\n');
@@ -59,14 +91,47 @@ function resolveTagFilter(state: SearchState): OsmTagFilter {
return value ? { key, value } : { key };
}
function resolveNearbyTagFilter(nearby: NonNullable<SearchState['nearby']>): OsmTagFilter {
if (nearby.presetId) {
return getPresetById(nearby.presetId).tag;
}
const key = nearby.customKey?.trim();
const value = nearby.customValue?.trim();
if (!key) {
throw new Error('Nearby custom tag key is required.');
}
return value ? { key, value } : { key };
}
function buildTagFilters(tags: OsmTagFilter[]): string {
return tags.map(buildTagFilter).join('');
}
function buildTagFilter(tag: OsmTagFilter): string {
const key = escapeOverpassString(tag.key);
const key = escapeOverpassString(tag.key.trim());
if (!key) {
throw new Error('Tag key is required.');
}
if (!tag.value) {
return `["${key}"]`;
}
return `["${key}"="${escapeOverpassString(tag.value)}"]`;
return `["${key}"="${escapeOverpassString(tag.value.trim())}"]`;
}
function formatRadius(radiusMeters: number | undefined): number {
const radius = Math.round(radiusMeters ?? 100);
if (!Number.isFinite(radius) || radius <= 0) {
throw new Error('Nearby radius must be greater than zero.');
}
return radius;
}
function escapeOverpassString(value: string): string {

View File

@@ -0,0 +1,26 @@
import { describe, expect, it } from 'vitest';
import { getPresetById, searchPresetCategories, searchPresets } from './presets';
describe('search presets', () => {
it('provides a broad categorized preset catalog', () => {
expect(searchPresets.length).toBeGreaterThanOrEqual(120);
expect(searchPresetCategories).toEqual(
expect.arrayContaining([
'Food & Drink',
'Shopping',
'Transport',
'Amenities',
'Accessibility',
]),
);
});
it('keeps key MVP preset ids available', () => {
expect(getPresetById('benches').tag).toEqual({ key: 'amenity', value: 'bench' });
expect(getPresetById('shops').tag).toEqual({ key: 'shop' });
expect(getPresetById('drinking_water').tag).toEqual({
key: 'amenity',
value: 'drinking_water',
});
});
});

View File

@@ -1,59 +1,263 @@
import type { OsmTagFilter, SearchPresetId } from './types';
export type PresetFilter = OsmTagFilter & {
id: string;
label: string;
};
export type SearchPreset = {
id: SearchPresetId;
label: string;
category: string;
description: string;
tag: OsmTagFilter;
commonFilters?: PresetFilter[];
usefulNearbyPresetIds?: SearchPresetId[];
};
const filters = {
hasName: { id: 'has-name', label: 'Has a name', key: 'name' },
openingHours: { id: 'opening-hours', label: 'Has opening hours', key: 'opening_hours' },
wheelchair: { id: 'wheelchair-yes', label: 'Wheelchair accessible', key: 'wheelchair', value: 'yes' },
outdoorSeating: { id: 'outdoor-seating', label: 'Outdoor seating', key: 'outdoor_seating', value: 'yes' },
internet: { id: 'wifi', label: 'Wi-Fi available', key: 'internet_access', value: 'wlan' },
takeaway: { id: 'takeaway', label: 'Takeaway', key: 'takeaway', value: 'yes' },
delivery: { id: 'delivery', label: 'Delivery', key: 'delivery', value: 'yes' },
feeNo: { id: 'fee-no', label: 'Free access', key: 'fee', value: 'no' },
backrest: { id: 'backrest', label: 'Backrest', key: 'backrest', value: 'yes' },
covered: { id: 'covered', label: 'Covered', key: 'covered', value: 'yes' },
lit: { id: 'lit', label: 'Lit', key: 'lit', value: 'yes' },
accessPublic: { id: 'access-public', label: 'Public access', key: 'access', value: 'public' },
drinkingWater: { id: 'drinking-water', label: 'Drinking water', key: 'drinking_water', value: 'yes' },
changingTable: { id: 'changing-table', label: 'Changing table', key: 'changing_table', value: 'yes' },
supervised: { id: 'supervised', label: 'Supervised', key: 'supervised', value: 'yes' },
organic: { id: 'organic', label: 'Organic goods', key: 'organic' },
} satisfies Record<string, PresetFilter>;
export const searchPresets: SearchPreset[] = [
{
id: 'drinking_water',
label: 'Drinking water',
category: 'Food & Drink',
description: 'Public drinking water points.',
tag: { key: 'amenity', value: 'drinking_water' },
},
{
id: 'toilets',
label: 'Public toilets',
tag: { key: 'amenity', value: 'toilets' },
},
{
id: 'bicycle_parking',
label: 'Bicycle parking',
tag: { key: 'amenity', value: 'bicycle_parking' },
},
{
id: 'hotels',
label: 'Hotels',
tag: { key: 'tourism', value: 'hotel' },
commonFilters: [filters.feeNo, filters.accessPublic],
usefulNearbyPresetIds: ['benches', 'playgrounds', 'parks', 'bicycle_parking'],
},
{
id: 'restaurants',
label: 'Restaurants',
category: 'Food & Drink',
description: 'Restaurants and dining places.',
tag: { key: 'amenity', value: 'restaurant' },
commonFilters: [filters.openingHours, filters.wheelchair, filters.outdoorSeating, filters.takeaway, filters.delivery],
usefulNearbyPresetIds: ['parking', 'toilets', 'bicycle_parking', 'bus_stops'],
},
{
id: 'cafes',
label: 'Cafes',
category: 'Food & Drink',
description: 'Cafes and coffee places.',
tag: { key: 'amenity', value: 'cafe' },
commonFilters: [filters.openingHours, filters.wheelchair, filters.outdoorSeating, filters.internet, filters.takeaway],
usefulNearbyPresetIds: ['bicycle_parking', 'toilets', 'parks', 'bus_stops'],
},
{ id: 'fast_food', label: 'Fast food', category: 'Food & Drink', description: 'Fast food venues.', tag: { key: 'amenity', value: 'fast_food' }, commonFilters: [filters.openingHours, filters.takeaway, filters.delivery], usefulNearbyPresetIds: ['bus_stops', 'parking'] },
{ id: 'bars', label: 'Bars', category: 'Food & Drink', description: 'Bars and casual drinking venues.', tag: { key: 'amenity', value: 'bar' }, commonFilters: [filters.openingHours, filters.outdoorSeating, filters.wheelchair], usefulNearbyPresetIds: ['taxi_stands', 'bus_stops'] },
{ id: 'pubs', label: 'Pubs', category: 'Food & Drink', description: 'Pubs.', tag: { key: 'amenity', value: 'pub' }, commonFilters: [filters.openingHours, filters.outdoorSeating, filters.wheelchair], usefulNearbyPresetIds: ['taxi_stands', 'bus_stops'] },
{ id: 'biergartens', label: 'Biergartens', category: 'Food & Drink', description: 'Beer gardens.', tag: { key: 'amenity', value: 'biergarten' }, commonFilters: [filters.openingHours, filters.outdoorSeating], usefulNearbyPresetIds: ['parks', 'toilets'] },
{ id: 'food_courts', label: 'Food courts', category: 'Food & Drink', description: 'Food courts.', tag: { key: 'amenity', value: 'food_court' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['shopping_malls', 'toilets'] },
{ id: 'ice_cream', label: 'Ice cream', category: 'Food & Drink', description: 'Ice cream shops and kiosks.', tag: { key: 'amenity', value: 'ice_cream' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parks', 'playgrounds'] },
{ id: 'bakeries', label: 'Bakeries', category: 'Food & Drink', description: 'Bakery shops.', tag: { key: 'shop', value: 'bakery' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['bus_stops', 'bicycle_parking'] },
{ id: 'confectionery', label: 'Confectionery', category: 'Food & Drink', description: 'Sweets and confectionery shops.', tag: { key: 'shop', value: 'confectionery' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['cafes'] },
{ id: 'butchers', label: 'Butchers', category: 'Food & Drink', description: 'Butcher shops.', tag: { key: 'shop', value: 'butcher' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'greengrocers', label: 'Greengrocers', category: 'Food & Drink', description: 'Fruit and vegetable shops.', tag: { key: 'shop', value: 'greengrocer' }, commonFilters: [filters.openingHours, filters.organic], usefulNearbyPresetIds: ['marketplaces'] },
{ id: 'deli', label: 'Delis', category: 'Food & Drink', description: 'Delicatessen shops.', tag: { key: 'shop', value: 'deli' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bicycle_parking'] },
{ id: 'beverages', label: 'Beverage shops', category: 'Food & Drink', description: 'Beverage shops.', tag: { key: 'shop', value: 'beverages' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'wine_shops', label: 'Wine shops', category: 'Food & Drink', description: 'Wine shops.', tag: { key: 'shop', value: 'wine' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{
id: 'shops',
label: 'Shops',
label: 'All shops',
category: 'Shopping',
description: 'Any OSM object tagged as a shop.',
tag: { key: 'shop' },
commonFilters: [filters.hasName, filters.openingHours, filters.wheelchair],
usefulNearbyPresetIds: ['benches', 'parking', 'bicycle_parking', 'bus_stops'],
},
{ id: 'supermarkets', label: 'Supermarkets', category: 'Shopping', description: 'Supermarkets.', tag: { key: 'shop', value: 'supermarket' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking', 'bicycle_parking', 'atms'] },
{ id: 'convenience', label: 'Convenience stores', category: 'Shopping', description: 'Convenience stores.', tag: { key: 'shop', value: 'convenience' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops', 'benches'] },
{ id: 'shopping_malls', label: 'Shopping malls', category: 'Shopping', description: 'Shopping malls.', tag: { key: 'shop', value: 'mall' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking', 'bus_stops', 'toilets'] },
{ id: 'department_stores', label: 'Department stores', category: 'Shopping', description: 'Department stores.', tag: { key: 'shop', value: 'department_store' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'clothes', label: 'Clothes shops', category: 'Shopping', description: 'Clothes shops.', tag: { key: 'shop', value: 'clothes' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'shoes', label: 'Shoe shops', category: 'Shopping', description: 'Shoe shops.', tag: { key: 'shop', value: 'shoes' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'jewelry', label: 'Jewelry shops', category: 'Shopping', description: 'Jewelry shops.', tag: { key: 'shop', value: 'jewelry' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'electronics', label: 'Electronics shops', category: 'Shopping', description: 'Electronics shops.', tag: { key: 'shop', value: 'electronics' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'mobile_phone', label: 'Mobile phone shops', category: 'Shopping', description: 'Mobile phone shops.', tag: { key: 'shop', value: 'mobile_phone' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'computer', label: 'Computer shops', category: 'Shopping', description: 'Computer shops.', tag: { key: 'shop', value: 'computer' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'books', label: 'Bookshops', category: 'Shopping', description: 'Bookshops.', tag: { key: 'shop', value: 'books' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['cafes', 'bus_stops'] },
{ id: 'stationery', label: 'Stationery shops', category: 'Shopping', description: 'Stationery shops.', tag: { key: 'shop', value: 'stationery' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['schools', 'universities'] },
{ id: 'florist', label: 'Florists', category: 'Shopping', description: 'Florists.', tag: { key: 'shop', value: 'florist' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'gift_shops', label: 'Gift shops', category: 'Shopping', description: 'Gift shops.', tag: { key: 'shop', value: 'gift' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['tourist_attractions'] },
{ id: 'hairdressers', label: 'Hairdressers', category: 'Shopping', description: 'Hairdressers.', tag: { key: 'shop', value: 'hairdresser' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'beauty', label: 'Beauty shops', category: 'Shopping', description: 'Beauty and cosmetics services.', tag: { key: 'shop', value: 'beauty' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'laundry', label: 'Laundries', category: 'Shopping', description: 'Laundry services.', tag: { key: 'shop', value: 'laundry' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'dry_cleaning', label: 'Dry cleaners', category: 'Shopping', description: 'Dry cleaning services.', tag: { key: 'shop', value: 'dry_cleaning' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'hardware', label: 'Hardware shops', category: 'Shopping', description: 'Hardware shops.', tag: { key: 'shop', value: 'hardware' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'doityourself', label: 'DIY stores', category: 'Shopping', description: 'Do-it-yourself stores.', tag: { key: 'shop', value: 'doityourself' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'furniture', label: 'Furniture shops', category: 'Shopping', description: 'Furniture shops.', tag: { key: 'shop', value: 'furniture' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'garden_centre', label: 'Garden centres', category: 'Shopping', description: 'Garden centres.', tag: { key: 'shop', value: 'garden_centre' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'pet_shops', label: 'Pet shops', category: 'Shopping', description: 'Pet shops.', tag: { key: 'shop', value: 'pet' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'car_shops', label: 'Car shops', category: 'Shopping', description: 'Car sales shops.', tag: { key: 'shop', value: 'car' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['fuel'] },
{ id: 'car_repair', label: 'Car repair', category: 'Shopping', description: 'Car repair workshops.', tag: { key: 'shop', value: 'car_repair' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['fuel'] },
{ id: 'bicycle_shops', label: 'Bicycle shops', category: 'Shopping', description: 'Bicycle shops and repair.', tag: { key: 'shop', value: 'bicycle' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bicycle_parking'] },
{
id: 'benches',
label: 'Benches',
category: 'Amenities',
description: 'Public benches.',
tag: { key: 'amenity', value: 'bench' },
commonFilters: [filters.backrest, filters.covered, filters.lit],
usefulNearbyPresetIds: ['shops', 'parks', 'playgrounds', 'bus_stops', 'drinking_water'],
},
{
id: 'atms',
label: 'ATMs',
tag: { key: 'amenity', value: 'atm' },
id: 'toilets',
label: 'Public toilets',
category: 'Amenities',
description: 'Public toilets.',
tag: { key: 'amenity', value: 'toilets' },
commonFilters: [filters.wheelchair, filters.changingTable, filters.feeNo],
usefulNearbyPresetIds: ['parks', 'playgrounds', 'restaurants', 'tourist_attractions'],
},
{ id: 'atms', label: 'ATMs', category: 'Amenities', description: 'Cash machines.', tag: { key: 'amenity', value: 'atm' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['shops', 'banks'] },
{ id: 'banks', label: 'Banks', category: 'Amenities', description: 'Bank branches.', tag: { key: 'amenity', value: 'bank' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking', 'bus_stops'] },
{ id: 'post_boxes', label: 'Post boxes', category: 'Amenities', description: 'Mail post boxes.', tag: { key: 'amenity', value: 'post_box' }, usefulNearbyPresetIds: ['post_offices'] },
{ id: 'post_offices', label: 'Post offices', category: 'Amenities', description: 'Post offices.', tag: { key: 'amenity', value: 'post_office' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'libraries', label: 'Libraries', category: 'Amenities', description: 'Libraries.', tag: { key: 'amenity', value: 'library' }, commonFilters: [filters.openingHours, filters.wheelchair, filters.internet], usefulNearbyPresetIds: ['cafes', 'bus_stops', 'bicycle_parking'] },
{ id: 'community_centres', label: 'Community centres', category: 'Amenities', description: 'Community centres.', tag: { key: 'amenity', value: 'community_centre' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'shelters', label: 'Shelters', category: 'Amenities', description: 'Shelters and covered waiting areas.', tag: { key: 'amenity', value: 'shelter' }, commonFilters: [filters.covered, filters.lit], usefulNearbyPresetIds: ['bus_stops', 'parks'] },
{ id: 'waste_baskets', label: 'Waste baskets', category: 'Amenities', description: 'Public waste baskets.', tag: { key: 'amenity', value: 'waste_basket' }, usefulNearbyPresetIds: ['benches', 'parks'] },
{ id: 'recycling', label: 'Recycling points', category: 'Amenities', description: 'Recycling collection points.', tag: { key: 'amenity', value: 'recycling' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'marketplaces', label: 'Marketplaces', category: 'Amenities', description: 'Marketplaces.', tag: { key: 'amenity', value: 'marketplace' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking', 'bus_stops'] },
{ id: 'vending_machines', label: 'Vending machines', category: 'Amenities', description: 'Vending machines.', tag: { key: 'amenity', value: 'vending_machine' }, usefulNearbyPresetIds: ['stations', 'schools'] },
{ id: 'parcel_lockers', label: 'Parcel lockers', category: 'Amenities', description: 'Parcel pickup lockers.', tag: { key: 'amenity', value: 'parcel_locker' }, usefulNearbyPresetIds: ['parking', 'shops'] },
{ id: 'charging_stations_small_devices', label: 'Device charging points', category: 'Amenities', description: 'Public places for charging small devices.', tag: { key: 'amenity', value: 'charging_station' }, usefulNearbyPresetIds: ['cafes', 'libraries'] },
{ id: 'public_bookcases', label: 'Public bookcases', category: 'Amenities', description: 'Public book exchange shelves.', tag: { key: 'amenity', value: 'public_bookcase' }, usefulNearbyPresetIds: ['parks', 'benches'] },
{ id: 'fountains', label: 'Fountains', category: 'Amenities', description: 'Fountains.', tag: { key: 'amenity', value: 'fountain' }, usefulNearbyPresetIds: ['benches', 'parks'] },
{ id: 'clock', label: 'Public clocks', category: 'Amenities', description: 'Public clocks.', tag: { key: 'amenity', value: 'clock' }, usefulNearbyPresetIds: ['squares', 'stations'] },
{ id: 'telephone', label: 'Public telephones', category: 'Amenities', description: 'Public telephones.', tag: { key: 'amenity', value: 'telephone' }, usefulNearbyPresetIds: ['stations'] },
{
id: 'bicycle_parking',
label: 'Bicycle parking',
category: 'Transport',
description: 'Places to park bicycles.',
tag: { key: 'amenity', value: 'bicycle_parking' },
commonFilters: [filters.covered, filters.accessPublic],
usefulNearbyPresetIds: ['cafes', 'shops', 'stations', 'schools'],
},
{ id: 'bus_stops', label: 'Bus stops', category: 'Transport', description: 'Bus stops.', tag: { key: 'highway', value: 'bus_stop' }, commonFilters: [filters.covered, filters.lit], usefulNearbyPresetIds: ['benches', 'shelters', 'shops'] },
{ id: 'tram_stops', label: 'Tram stops', category: 'Transport', description: 'Tram stops.', tag: { key: 'railway', value: 'tram_stop' }, commonFilters: [filters.lit], usefulNearbyPresetIds: ['benches', 'shelters'] },
{ id: 'subway_entrances', label: 'Subway entrances', category: 'Transport', description: 'Subway station entrances.', tag: { key: 'railway', value: 'subway_entrance' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['bicycle_parking', 'atms'] },
{ id: 'stations', label: 'Railway stations', category: 'Transport', description: 'Railway stations.', tag: { key: 'railway', value: 'station' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['toilets', 'atms', 'bicycle_parking'] },
{ id: 'halts', label: 'Railway halts', category: 'Transport', description: 'Small railway stops.', tag: { key: 'railway', value: 'halt' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['bicycle_parking'] },
{ id: 'taxi_stands', label: 'Taxi stands', category: 'Transport', description: 'Taxi stands.', tag: { key: 'amenity', value: 'taxi' }, usefulNearbyPresetIds: ['stations', 'hotels'] },
{ id: 'parking', label: 'Car parking', category: 'Transport', description: 'Car parking lots.', tag: { key: 'amenity', value: 'parking' }, commonFilters: [filters.feeNo, filters.accessPublic, filters.lit], usefulNearbyPresetIds: ['shops', 'restaurants', 'parks'] },
{ id: 'parking_entrances', label: 'Parking entrances', category: 'Transport', description: 'Entrances to parking areas.', tag: { key: 'amenity', value: 'parking_entrance' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'motorcycle_parking', label: 'Motorcycle parking', category: 'Transport', description: 'Motorcycle parking.', tag: { key: 'amenity', value: 'motorcycle_parking' }, usefulNearbyPresetIds: ['shops'] },
{ id: 'fuel', label: 'Fuel stations', category: 'Transport', description: 'Fuel stations.', tag: { key: 'amenity', value: 'fuel' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['car_wash', 'atms'] },
{ id: 'ev_charging', label: 'EV charging', category: 'Transport', description: 'Electric vehicle charging stations.', tag: { key: 'amenity', value: 'charging_station' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking', 'restaurants'] },
{ id: 'car_wash', label: 'Car wash', category: 'Transport', description: 'Car wash places.', tag: { key: 'amenity', value: 'car_wash' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['fuel'] },
{ id: 'car_rental', label: 'Car rental', category: 'Transport', description: 'Car rental points.', tag: { key: 'amenity', value: 'car_rental' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['stations', 'airports'] },
{ id: 'car_sharing', label: 'Car sharing', category: 'Transport', description: 'Car sharing stations.', tag: { key: 'amenity', value: 'car_sharing' }, usefulNearbyPresetIds: ['stations'] },
{ id: 'bicycle_rental', label: 'Bicycle rental', category: 'Transport', description: 'Bicycle rental stations.', tag: { key: 'amenity', value: 'bicycle_rental' }, usefulNearbyPresetIds: ['parks', 'stations'] },
{ id: 'bicycle_repair_station', label: 'Bicycle repair stations', category: 'Transport', description: 'Public bicycle repair stations.', tag: { key: 'amenity', value: 'bicycle_repair_station' }, usefulNearbyPresetIds: ['bicycle_parking', 'bicycle_shops'] },
{ id: 'ferry_terminals', label: 'Ferry terminals', category: 'Transport', description: 'Ferry terminals.', tag: { key: 'amenity', value: 'ferry_terminal' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'airports', label: 'Airports', category: 'Transport', description: 'Airports.', tag: { key: 'aeroway', value: 'aerodrome' }, commonFilters: [filters.hasName], usefulNearbyPresetIds: ['car_rental', 'parking'] },
{
id: 'hotels',
label: 'Hotels',
category: 'Tourism & Stay',
description: 'Hotels.',
tag: { key: 'tourism', value: 'hotel' },
commonFilters: [filters.wheelchair, filters.internet],
usefulNearbyPresetIds: ['restaurants', 'parking', 'stations', 'atms'],
},
{ id: 'hostels', label: 'Hostels', category: 'Tourism & Stay', description: 'Hostels.', tag: { key: 'tourism', value: 'hostel' }, commonFilters: [filters.internet], usefulNearbyPresetIds: ['restaurants', 'stations'] },
{ id: 'guest_houses', label: 'Guest houses', category: 'Tourism & Stay', description: 'Guest houses.', tag: { key: 'tourism', value: 'guest_house' }, commonFilters: [filters.internet], usefulNearbyPresetIds: ['restaurants', 'parking'] },
{ id: 'apartments', label: 'Tourist apartments', category: 'Tourism & Stay', description: 'Tourist apartments.', tag: { key: 'tourism', value: 'apartment' }, commonFilters: [filters.internet], usefulNearbyPresetIds: ['shops'] },
{ id: 'camp_sites', label: 'Camp sites', category: 'Tourism & Stay', description: 'Camp sites.', tag: { key: 'tourism', value: 'camp_site' }, commonFilters: [filters.drinkingWater, filters.wheelchair], usefulNearbyPresetIds: ['toilets', 'drinking_water'] },
{ id: 'caravan_sites', label: 'Caravan sites', category: 'Tourism & Stay', description: 'Caravan sites.', tag: { key: 'tourism', value: 'caravan_site' }, commonFilters: [filters.drinkingWater], usefulNearbyPresetIds: ['toilets', 'fuel'] },
{ id: 'museums', label: 'Museums', category: 'Tourism & Stay', description: 'Museums.', tag: { key: 'tourism', value: 'museum' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['cafes', 'bus_stops'] },
{ id: 'artwork', label: 'Public artwork', category: 'Tourism & Stay', description: 'Public artworks.', tag: { key: 'tourism', value: 'artwork' }, usefulNearbyPresetIds: ['benches'] },
{ id: 'tourist_attractions', label: 'Tourist attractions', category: 'Tourism & Stay', description: 'Tourist attractions.', tag: { key: 'tourism', value: 'attraction' }, commonFilters: [filters.hasName], usefulNearbyPresetIds: ['toilets', 'cafes', 'bus_stops'] },
{ id: 'viewpoints', label: 'Viewpoints', category: 'Tourism & Stay', description: 'Viewpoints.', tag: { key: 'tourism', value: 'viewpoint' }, usefulNearbyPresetIds: ['benches', 'parking'] },
{ id: 'information', label: 'Tourist information', category: 'Tourism & Stay', description: 'Tourist information boards and offices.', tag: { key: 'tourism', value: 'information' }, usefulNearbyPresetIds: ['tourist_attractions'] },
{ id: 'picnic_sites_tourism', label: 'Picnic sites', category: 'Tourism & Stay', description: 'Picnic sites.', tag: { key: 'tourism', value: 'picnic_site' }, usefulNearbyPresetIds: ['toilets', 'drinking_water'] },
{ id: 'zoos', label: 'Zoos', category: 'Tourism & Stay', description: 'Zoos.', tag: { key: 'tourism', value: 'zoo' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking', 'toilets'] },
{ id: 'theme_parks', label: 'Theme parks', category: 'Tourism & Stay', description: 'Theme parks.', tag: { key: 'tourism', value: 'theme_park' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking', 'toilets'] },
{ id: 'parks', label: 'Parks', category: 'Outdoors', description: 'Public parks.', tag: { key: 'leisure', value: 'park' }, commonFilters: [filters.accessPublic], usefulNearbyPresetIds: ['benches', 'toilets', 'drinking_water'] },
{ id: 'playgrounds', label: 'Playgrounds', category: 'Outdoors', description: 'Playgrounds.', tag: { key: 'leisure', value: 'playground' }, commonFilters: [filters.supervised, filters.accessPublic], usefulNearbyPresetIds: ['benches', 'toilets', 'drinking_water'] },
{ id: 'gardens', label: 'Gardens', category: 'Outdoors', description: 'Gardens.', tag: { key: 'leisure', value: 'garden' }, commonFilters: [filters.accessPublic], usefulNearbyPresetIds: ['benches'] },
{ id: 'nature_reserves', label: 'Nature reserves', category: 'Outdoors', description: 'Nature reserves.', tag: { key: 'leisure', value: 'nature_reserve' }, usefulNearbyPresetIds: ['parking', 'viewpoints'] },
{ id: 'sports_centres', label: 'Sports centres', category: 'Outdoors', description: 'Sports centres.', tag: { key: 'leisure', value: 'sports_centre' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'sports_pitches', label: 'Sports pitches', category: 'Outdoors', description: 'Sports pitches.', tag: { key: 'leisure', value: 'pitch' }, commonFilters: [filters.lit], usefulNearbyPresetIds: ['toilets'] },
{ id: 'stadiums', label: 'Stadiums', category: 'Outdoors', description: 'Stadiums.', tag: { key: 'leisure', value: 'stadium' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['parking', 'bus_stops'] },
{ id: 'swimming_pools', label: 'Swimming pools', category: 'Outdoors', description: 'Swimming pools.', tag: { key: 'leisure', value: 'swimming_pool' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'fitness_centres', label: 'Fitness centres', category: 'Outdoors', description: 'Fitness centres and gyms.', tag: { key: 'leisure', value: 'fitness_centre' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'dog_parks', label: 'Dog parks', category: 'Outdoors', description: 'Dog parks.', tag: { key: 'leisure', value: 'dog_park' }, commonFilters: [filters.accessPublic], usefulNearbyPresetIds: ['benches', 'waste_baskets'] },
{ id: 'marinas', label: 'Marinas', category: 'Outdoors', description: 'Marinas.', tag: { key: 'leisure', value: 'marina' }, usefulNearbyPresetIds: ['restaurants', 'parking'] },
{ id: 'slipways', label: 'Slipways', category: 'Outdoors', description: 'Boat slipways.', tag: { key: 'leisure', value: 'slipway' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'beaches', label: 'Beaches', category: 'Outdoors', description: 'Beaches.', tag: { key: 'natural', value: 'beach' }, usefulNearbyPresetIds: ['toilets', 'drinking_water', 'parking'] },
{ id: 'squares', label: 'Squares', category: 'Outdoors', description: 'Named squares and plazas.', tag: { key: 'place', value: 'square' }, usefulNearbyPresetIds: ['benches', 'cafes'] },
{ id: 'pharmacies', label: 'Pharmacies', category: 'Health', description: 'Pharmacies.', tag: { key: 'amenity', value: 'pharmacy' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'hospitals', label: 'Hospitals', category: 'Health', description: 'Hospitals.', tag: { key: 'amenity', value: 'hospital' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['parking', 'bus_stops'] },
{ id: 'clinics', label: 'Clinics', category: 'Health', description: 'Clinics.', tag: { key: 'amenity', value: 'clinic' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'doctors', label: 'Doctors', category: 'Health', description: 'Doctors and medical practices.', tag: { key: 'amenity', value: 'doctors' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'dentists', label: 'Dentists', category: 'Health', description: 'Dentists.', tag: { key: 'amenity', value: 'dentist' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'veterinary', label: 'Veterinary clinics', category: 'Health', description: 'Veterinary clinics.', tag: { key: 'amenity', value: 'veterinary' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'defibrillators', label: 'Defibrillators', category: 'Health', description: 'Emergency defibrillators.', tag: { key: 'emergency', value: 'defibrillator' }, usefulNearbyPresetIds: ['schools', 'sports_centres', 'stations'] },
{ id: 'social_facilities', label: 'Social facilities', category: 'Health', description: 'Social support facilities.', tag: { key: 'amenity', value: 'social_facility' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'schools', label: 'Schools', category: 'Education', description: 'Schools.', tag: { key: 'amenity', value: 'school' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['bus_stops', 'bicycle_parking'] },
{ id: 'kindergartens', label: 'Kindergartens', category: 'Education', description: 'Kindergartens.', tag: { key: 'amenity', value: 'kindergarten' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['playgrounds'] },
{ id: 'universities', label: 'Universities', category: 'Education', description: 'Universities.', tag: { key: 'amenity', value: 'university' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['libraries', 'cafes', 'bicycle_parking'] },
{ id: 'colleges', label: 'Colleges', category: 'Education', description: 'Colleges.', tag: { key: 'amenity', value: 'college' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'music_schools', label: 'Music schools', category: 'Education', description: 'Music schools.', tag: { key: 'amenity', value: 'music_school' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'language_schools', label: 'Language schools', category: 'Education', description: 'Language schools.', tag: { key: 'amenity', value: 'language_school' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'driving_schools', label: 'Driving schools', category: 'Education', description: 'Driving schools.', tag: { key: 'amenity', value: 'driving_school' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['parking'] },
{ id: 'cinemas', label: 'Cinemas', category: 'Culture & Leisure', description: 'Cinemas.', tag: { key: 'amenity', value: 'cinema' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['restaurants', 'parking'] },
{ id: 'theatres', label: 'Theatres', category: 'Culture & Leisure', description: 'Theatres.', tag: { key: 'amenity', value: 'theatre' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['restaurants', 'parking'] },
{ id: 'arts_centres', label: 'Arts centres', category: 'Culture & Leisure', description: 'Arts centres.', tag: { key: 'amenity', value: 'arts_centre' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['cafes'] },
{ id: 'nightclubs', label: 'Nightclubs', category: 'Culture & Leisure', description: 'Nightclubs.', tag: { key: 'amenity', value: 'nightclub' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['taxi_stands'] },
{ id: 'events_venues', label: 'Events venues', category: 'Culture & Leisure', description: 'Events venues.', tag: { key: 'amenity', value: 'events_venue' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'casinos', label: 'Casinos', category: 'Culture & Leisure', description: 'Casinos.', tag: { key: 'amenity', value: 'casino' }, commonFilters: [filters.openingHours], usefulNearbyPresetIds: ['hotels'] },
{ id: 'police', label: 'Police', category: 'Public Services', description: 'Police stations.', tag: { key: 'amenity', value: 'police' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'fire_stations', label: 'Fire stations', category: 'Public Services', description: 'Fire stations.', tag: { key: 'amenity', value: 'fire_station' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'townhalls', label: 'Town halls', category: 'Public Services', description: 'Town halls.', tag: { key: 'amenity', value: 'townhall' }, commonFilters: [filters.openingHours, filters.wheelchair], usefulNearbyPresetIds: ['bus_stops'] },
{ id: 'courthouses', label: 'Courthouses', category: 'Public Services', description: 'Courthouses.', tag: { key: 'amenity', value: 'courthouse' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'embassies', label: 'Embassies', category: 'Public Services', description: 'Embassies.', tag: { key: 'amenity', value: 'embassy' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'places_of_worship', label: 'Places of worship', category: 'Public Services', description: 'Places of worship.', tag: { key: 'amenity', value: 'place_of_worship' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['parking'] },
{ id: 'cemeteries', label: 'Cemeteries', category: 'Public Services', description: 'Cemeteries.', tag: { key: 'amenity', value: 'grave_yard' }, usefulNearbyPresetIds: ['parking'] },
{ id: 'wheelchair_places', label: 'Wheelchair accessible places', category: 'Accessibility', description: 'Objects explicitly tagged wheelchair=yes.', tag: { key: 'wheelchair', value: 'yes' }, usefulNearbyPresetIds: ['bus_stops', 'parking', 'toilets'] },
{ id: 'accessible_toilets', label: 'Accessible toilets', category: 'Accessibility', description: 'Toilets tagged wheelchair=yes.', tag: { key: 'amenity', value: 'toilets' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['parks', 'stations'] },
{ id: 'tactile_paving', label: 'Tactile paving', category: 'Accessibility', description: 'Objects tagged with tactile paving.', tag: { key: 'tactile_paving', value: 'yes' }, usefulNearbyPresetIds: ['bus_stops', 'subway_entrances'] },
{ id: 'elevators', label: 'Elevators', category: 'Accessibility', description: 'Elevators.', tag: { key: 'highway', value: 'elevator' }, commonFilters: [filters.wheelchair], usefulNearbyPresetIds: ['subway_entrances', 'stations'] },
{ id: 'ramps', label: 'Ramps', category: 'Accessibility', description: 'Ramps and ramp-equipped ways.', tag: { key: 'ramp', value: 'yes' }, usefulNearbyPresetIds: ['wheelchair_places'] },
{ id: 'audible_signals', label: 'Audible crossing signals', category: 'Accessibility', description: 'Crossings with audible signals.', tag: { key: 'traffic_signals:sound', value: 'yes' }, usefulNearbyPresetIds: ['bus_stops'] },
];
export const searchPresetCategories = Array.from(
new Set(searchPresets.map((preset) => preset.category)),
);
export function getPresetById(id: SearchPresetId): SearchPreset {
const preset = searchPresets.find((item) => item.id === id);
@@ -63,3 +267,7 @@ export function getPresetById(id: SearchPresetId): SearchPreset {
return preset;
}
export function getPresetsByCategory(category: string): SearchPreset[] {
return searchPresets.filter((preset) => preset.category === category);
}

View File

@@ -1,15 +1,6 @@
export type OsmElementType = 'node' | 'way' | 'relation';
export type SearchPresetId =
| 'drinking_water'
| 'toilets'
| 'bicycle_parking'
| 'hotels'
| 'restaurants'
| 'cafes'
| 'shops'
| 'benches'
| 'atms';
export type SearchPresetId = string;
export type SearchChoice = SearchPresetId | 'custom';
@@ -34,6 +25,8 @@ export type SearchState = {
choice: SearchChoice;
customKey?: string;
customValue?: string;
filters?: OsmTagFilter[];
nearby?: NearbySearch | null;
area: SearchArea;
};
@@ -42,6 +35,13 @@ export type OsmTagFilter = {
value?: string;
};
export type NearbySearch = {
presetId?: SearchPresetId;
customKey?: string;
customValue?: string;
radiusMeters: number;
};
export type OverpassElement = {
type: OsmElementType;
id: number;