Initial overpass api connections

This commit is contained in:
Broks Randolfs Gailītis 2025-03-05 21:48:15 +02:00
parent 246debe642
commit 3dd43deec0
3 changed files with 135 additions and 0 deletions

View File

@ -0,0 +1,36 @@
import { FastifyInstance } from 'fastify';
import { OverpassService } from '../services/overpass';
import { OverpassQuerySchema, OverpassQueryType } from '../schemas/overpass';
const overpassService = new OverpassService();
export default async function (fastify: FastifyInstance) {
fastify.get<{ Querystring: OverpassQueryType }>('/blakus', {
schema: {
querystring: OverpassQuerySchema,
response: {
200: {
type: 'array',
items: {
type: 'object',
properties: {
title: { type: 'string' },
amenity: { type: 'string' },
tags: { type: 'object', additionalProperties: true }
}
}
}
}
}
}, async function ({query}) {
const elements = await overpassService.getAmenitiesAroundLocation({
latlon: {
lat: query.lat,
lon: query.lon,
},
amenities: ['restaurant', 'cafe', 'bar', 'pub', 'biergarten', 'fast_food', 'food_court', 'ice_cream']
});
return elements;
});
}

View File

@ -0,0 +1,10 @@
import { Static, Type } from '@sinclair/typebox'
export const OverpassQuerySchema = Type.Object({
lat: Type.Number(),
lon: Type.Number(),
radius: Type.Optional(Type.Number()),
amenities: Type.Optional(Type.Array(Type.String())),
});
export type OverpassQueryType = Static<typeof OverpassQuerySchema>;

View File

@ -0,0 +1,89 @@
import axios, { AxiosInstance } from 'axios';
type OverpassResult = {
version: number;
generator: string;
osm3s: Record<string, string>;
elements: (NodeElement | WayElement | RelationElement)[];
}
type OverpassLocation = {
lat: number,
lon: number
}
type OverpassElement = {
type: string;
id: number;
tags?: { [key: string]: string }
};
type NodeElement = OverpassElement & OverpassLocation & {
type: 'node';
lat: number;
lon: number;
}
type WayElement = OverpassElement & {
type: 'way';
nodes: number[];
}
type RelationElement = OverpassElement & {
type: 'relation';
members: {
type: 'node' | 'way' | 'relation';
ref: number;
role: string;
}[];
}
function opq(strings, ...expressions) {
let query = '[out:json];\n';
for (let i = 0; i < strings.length; i++) {
query += strings[i];
if (i < expressions.length) query += expressions[i];
}
query += '\nout body;\n>;\nout skel qt;';
return query;
}
export class OverpassService {
private axios: AxiosInstance;
constructor(baseURL = 'https://overpass-api.de/api/') {
this.axios = axios.create({ baseURL });
}
async getAmenitiesAroundLocation({radius = 500, amenities = [], latlon}: {radius?: number, latlon: OverpassLocation, amenities: string[]}) {
if (!latlon || !latlon.lat || !latlon.lon) throw new Error('Invalid location');
const { lat, lon } = latlon;
const query = opq`node(around:${radius}, ${lat}, ${lon})["amenity"~"${amenities.join('|')}"];`;
const result = await this.overpassQuery(query);
return result.elements
.map(({ tags }) => ({ title: tags.name, amenity: tags.amenity, tags}))
.filter(i => amenities.includes(i.amenity)); // query returns some random results. need to look into this
}
async overpassQuery(query: string): Promise<OverpassResult> {
return this.getData('interpreter', { data: query });
}
async getData<T>(endpoint: string, params: Record<string, string>): Promise<T> {
try {
const response = await this.axios.get(endpoint, { params });
return response.data;
} catch (error) {
console.error(`Error fetching data from ${endpoint}:`, error);
throw error;
}
}
}