UNPKG

pulse-ai-utils

Version:

Utility functions and helpers for AI-powered applications

106 lines 4.49 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.GeohashFirestoreQuery = void 0; const geohash_service_1 = require("./geohash-service"); class GeohashFirestoreQuery { constructor() { this.geohashService = new geohash_service_1.GeohashService(); } /** * Perform efficient geohash-based query on Firestore */ async queryByRadius(db, collection, centerLat, centerLng, radius, filters = {}) { // Get optimized query ranges const ranges = this.geohashService.getSearchRanges(centerLat, centerLng, radius); console.log(`[GeohashQuery] Searching with ${ranges.length} ranges for ${radius}km radius`); // Execute queries in parallel const queryPromises = ranges.map(([start, end]) => this.executeRangeQuery(db, collection, start, end, filters)); const results = await Promise.all(queryPromises); const allDocs = results.flat(); // Remove duplicates and filter by exact radius const uniqueDocs = this.deduplicateAndFilter(allDocs, centerLat, centerLng, radius); // Sort by distance return uniqueDocs.sort((a, b) => a.distance - b.distance); } /** * Execute a single range query */ async executeRangeQuery(db, collection, startHash, endHash, filters) { let query = db.collection(collection) .where('geohash', '>=', startHash) .where('geohash', '<', endHash); // Apply additional filters const now = new Date().toISOString(); query = query.where('expire_at', '>', now); // Apply other filters (but be careful with Firestore's composite index requirements) if (filters.type) { query = query.where('type', '==', filters.type); } // Note: Can't use additional inequality filters due to Firestore limitations // Other filters will need to be applied client-side const snapshot = await query.get(); return snapshot.docs.map(doc => ({ id: doc.id, data: doc.data() })); } /** * Deduplicate results and filter by exact radius */ deduplicateAndFilter(docs, centerLat, centerLng, radius) { const seen = new Set(); const results = []; for (const doc of docs) { // Skip if already seen if (seen.has(doc.id)) continue; seen.add(doc.id); // Skip if no coordinates if (!doc.data.lat || !doc.data.lng) continue; // Calculate exact distance const distance = this.haversineDistance(centerLat, centerLng, doc.data.lat, doc.data.lng); // Filter by exact radius if (distance <= radius) { results.push({ id: doc.id, distance, data: doc.data }); } } return results; } /** * Calculate distance between two points */ haversineDistance(lat1, lng1, lat2, lng2) { const R = 6371; // km const dLat = (lat2 - lat1) * Math.PI / 180; const dLng = (lng2 - lng1) * Math.PI / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } /** * Progressive radius search - start small and expand if needed */ async progressiveRadiusSearch(db, collection, centerLat, centerLng, options = {}) { const { initialRadius = 5, maxRadius = 50, minResults = 20, filters = {} } = options; let currentRadius = initialRadius; let results = []; while (currentRadius <= maxRadius && results.length < minResults) { console.log(`[GeohashQuery] Searching with radius ${currentRadius}km`); results = await this.queryByRadius(db, collection, centerLat, centerLng, currentRadius, filters); if (results.length >= minResults) break; // Expand radius currentRadius = Math.min(currentRadius * 2, maxRadius); } return results.slice(0, minResults * 2); // Return extra for client filtering } } exports.GeohashFirestoreQuery = GeohashFirestoreQuery; //# sourceMappingURL=geohash-firestore-query.js.map