import { Position } from "geojson"; import { Image } from "react-native"; import { BoundingBox, Coordinates } from "@wemap/geo"; import type { POI } from "../types"; import { getAllPOIs, searchPOIs } from "../api/poi-service"; import { useSettingsStore } from "../storages/settings"; import { useNetworkStore } from "../storages/network"; import { usePOIsStore } from "../storages/pois"; export class POIManager { /** Array to store all POIs */ private pois: POI[]; /** @hidden */ constructor() { this.pois = []; } /** * Fetches all POIs for the current map and stores them locally * Used internally to fetch POIs for the current map on start * @returns {Promise} */ async fetchAll() { if (this.pois.length > 0) return this.pois; const emmid = useSettingsStore.getState().emmid; if (!emmid) return; const pois = await getAllPOIs(emmid); this.pois = pois; this.prefetchImages(); return pois; } /** * Returns all stored POIs * @returns {POI[]} Array of all POIs */ getPOIs() { return this.pois; } /** * Opens a POI by adding it to the opened POIs list in the store * This will display the POI annotation on the map * @param {POI} poi - The POI to open */ open(poi: POI) { usePOIsStore.getState().openPOI(poi); } /** * Closes a specific POI by removing it from the opened POIs list * This will hide the POI annotation from the map * @param {number} poiId - The ID of the POI to close */ close(poiId: number) { usePOIsStore.getState().closePOI(poiId); } /** * Closes all currently opened POIs * This will hide all POI annotations from the map */ closeAll() { usePOIsStore.getState().closeAll(); } /** * Searches for POIs based on a query string * Uses online search when connected, falls back to local search when offline * @param {string} query - The search query * @returns {Promise} Array of matching POIs */ async search(query: string): Promise { const emmid = useSettingsStore.getState().emmid; const isOnline = useNetworkStore.getState().isOnline; if (!isOnline) { return this.localSearch(query); } if (!emmid) return []; return searchPOIs(emmid, query); } /** * Returns POIs within the specified bounds * @param {[Position, Position]} visibleBounds - Array containing northEast and southWest coordinates * @returns {POI[]} Array of POIs within the bounds */ getByBounds(visibleBounds: [northEast: Position, southWest: Position]) { const bounds = new BoundingBox( new Coordinates(visibleBounds[0][1], visibleBounds[0][0]), new Coordinates(visibleBounds[1][1], visibleBounds[1][0]), ); const pois = this.pois.filter((poi) => { return bounds.contains(new Coordinates(poi.latitude, poi.longitude)); }); return pois; } /** * Finds a POI by its ID * @param {number} id - The POI ID to search for * @returns {POI | undefined} The matching POI or undefined if not found */ getByID(id: number) { return this.pois.find((poi) => poi.id === id); } private prefetchImages() { const pois = this.pois; const imageUrls = new Set(pois.map((poi) => poi.media_url || poi.image_url)); for (const imageUrl of imageUrls) { Image.prefetch(`${imageUrl}?width=120`); } } private localSearch(query: string) { return this.pois.filter((poi) => { return poi.name.toLowerCase().includes(query.toLowerCase()) || poi.description.toLowerCase().includes(query.toLowerCase()); }); } } /** @hidden */ export default new POIManager();