import Service from '@ember/service'; export default class PhotonService extends Service { baseUrl = 'https://photon.komoot.io/api/'; async search(query, lat, lon, limit = 10) { if (!query || query.length < 2) return []; const params = new URLSearchParams({ q: query, limit: String(limit), }); if (lat && lon) { params.append('lat', parseFloat(lat).toFixed(4)); params.append('lon', parseFloat(lon).toFixed(4)); } const url = `${this.baseUrl}?${params.toString()}`; try { const res = await this.fetchWithRetry(url); if (!res.ok) { throw new Error(`Photon request failed with status ${res.status}`); } const data = await res.json(); if (!data.features) return []; return data.features.map((f) => this.normalizeFeature(f)); } catch (e) { console.error('Photon search error:', e); // Return empty array on error so UI doesn't break return []; } } normalizeFeature(feature) { const props = feature.properties || {}; const geom = feature.geometry || {}; const coords = geom.coordinates || []; // Photon returns [lon, lat] for Point geometries const lon = coords[0]; const lat = coords[1]; // Construct a description from address fields // Priority: name -> street -> city -> state -> country const addressParts = []; if (props.street) addressParts.push( props.housenumber ? `${props.street} ${props.housenumber}` : props.street ); if (props.city && props.city !== props.name) addressParts.push(props.city); if (props.state && props.state !== props.city) addressParts.push(props.state); if (props.country) addressParts.push(props.country); const description = addressParts.join(', '); const title = props.name || description || 'Unknown Place'; const osmTypeMap = { N: 'node', W: 'way', R: 'relation', }; return { title, lat, lon, osmId: props.osm_id, osmType: osmTypeMap[props.osm_type] || props.osm_type, // 'node', 'way', 'relation' osmTags: props, // Keep all properties as tags for now description: props.name ? description : addressParts.slice(1).join(', '), source: 'photon', }; } async fetchWithRetry(url, options = {}, retries = 3) { try { // eslint-disable-next-line warp-drive/no-external-request-patterns const res = await fetch(url, options); // Retry on 5xx errors or 429 Too Many Requests if (!res.ok && retries > 0 && [502, 503, 504, 429].includes(res.status)) { console.warn( `Photon request failed with ${res.status}. Retrying... (${retries} left)` ); // Exponential backoff or fixed delay? Let's do 1s fixed delay for simplicity await new Promise((r) => setTimeout(r, 1000)); return this.fetchWithRetry(url, options, retries - 1); } return res; } catch (e) { // Retry on network errors (fetch throws) except AbortError if (retries > 0 && e.name !== 'AbortError') { console.debug(`Retrying Photon request... (${retries} left)`, e); await new Promise((r) => setTimeout(r, 1000)); return this.fetchWithRetry(url, options, retries - 1); } throw e; } } }