UNPKG

pulse-ai-utils

Version:

Utility functions and helpers for AI-powered applications

224 lines 7.64 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.calculateDistance = calculateDistance; exports.expandRadius = expandRadius; exports.calculateRelevanceScore = calculateRelevanceScore; exports.findContentWithinRadius = findContentWithinRadius; exports.extractLocationsFromText = extractLocationsFromText; exports.generateGeohash = generateGeohash; exports.progressiveGeoSearch = progressiveGeoSearch; exports.getGeohashBounds = getGeohashBounds; const area_coordinates_1 = require("../constants/area-coordinates"); /** * Calculate distance between two points using Haversine formula * @returns Distance in miles */ function calculateDistance(lat1, lng1, lat2, lng2) { const R = 3959; // Earth's radius in miles const dLat = toRad(lat2 - lat1); const dLng = toRad(lng2 - lng1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLng / 2) * Math.sin(dLng / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; } function toRad(degrees) { return degrees * (Math.PI / 180); } /** * Determine if radius should be expanded based on results */ function expandRadius(params) { const { currentRadius, resultsCount, minThreshold, maxRadius } = params; if (resultsCount >= minThreshold) { return { shouldExpand: false, newRadius: currentRadius }; } if (currentRadius >= maxRadius) { return { shouldExpand: false, newRadius: maxRadius }; } // Double the radius, but don't exceed max const newRadius = Math.min(currentRadius * 2, maxRadius); return { shouldExpand: true, newRadius }; } /** * Calculate relevance score based on distance * Uses exponential decay: score = 1 / (1 + distance * decayFactor) */ function calculateRelevanceScore(params) { const { distanceMiles, decayFactor = 0.1 } = params; return 1 / (1 + distanceMiles * decayFactor); } /** * Find content within a radius from user location */ function findContentWithinRadius(params) { const { userLat, userLng, content, radiusMiles } = params; const results = []; for (const item of content) { // Find nearest location from this content's locations let minDistance = Infinity; let nearestLocation = item.primary_location; let nearestAreaId = item.primary_area; for (const location of item.locations) { const distance = calculateDistance(userLat, userLng, location.lat, location.lng); if (distance < minDistance) { minDistance = distance; nearestLocation = { lat: location.lat, lng: location.lng }; nearestAreaId = location.area_id; } } // Check if within radius if (minDistance <= radiusMiles) { results.push({ content: item, distance: minDistance, nearestLocation: { area_id: nearestAreaId, lat: nearestLocation.lat, lng: nearestLocation.lng }, relevanceScore: calculateRelevanceScore({ distanceMiles: minDistance }) }); } } // Sort by distance return results.sort((a, b) => a.distance - b.distance); } /** * Extract locations from text content */ function extractLocationsFromText(text) { const locations = []; const lowerText = text.toLowerCase(); const foundAreas = []; // Look for area names in text for (const [areaId, coords] of Object.entries(area_coordinates_1.AREA_COORDINATES)) { // Convert area ID to readable name (e.g., "st-petersburg" -> "st. petersburg") const areaName = areaId.replace(/-/g, ' '); const variations = [ areaName, areaName.replace('st ', 'st. '), areaName.replace('saint ', 'st. ') ]; for (const variation of variations) { // Use word boundaries to avoid partial matches const regex = new RegExp(`\\b${variation}\\b`, 'i'); if (regex.test(lowerText)) { foundAreas.push(areaId); break; } } } // Assign relevance based on order foundAreas.forEach((areaId, index) => { const coords = area_coordinates_1.AREA_COORDINATES[areaId]; locations.push({ area_id: areaId, lat: coords.lat, lng: coords.lng, relevance: index === 0 ? 1.0 : 0.8 - (index * 0.1) }); }); return locations; } /** * Generate geohash for efficient geo queries * Implementation of simple geohash algorithm */ function generateGeohash(lat, lng, precision = 6) { const BASE32 = '0123456789bcdefghjkmnpqrstuvwxyz'; let latMin = -90, latMax = 90; let lngMin = -180, lngMax = 180; let geohash = ''; let bits = 0; let bit = 0; let even = true; while (geohash.length < precision) { if (even) { const mid = (lngMin + lngMax) / 2; if (lng > mid) { bit |= (1 << (4 - bits)); lngMin = mid; } else { lngMax = mid; } } else { const mid = (latMin + latMax) / 2; if (lat > mid) { bit |= (1 << (4 - bits)); latMin = mid; } else { latMax = mid; } } even = !even; bits++; if (bits === 5) { geohash += BASE32[bit]; bits = 0; bit = 0; } } return geohash; } /** * Progressive search with radius expansion */ async function progressiveGeoSearch(params) { const { userLat, userLng, content, initialRadius = 5, maxRadius = 50, minResults = 20, radiusSteps = [5, 10, 20, 30, 50] } = params; let results = []; let currentRadius = initialRadius; let expansions = 0; // Try each radius step for (const radius of radiusSteps) { if (radius > maxRadius) break; currentRadius = radius; results = findContentWithinRadius({ userLat, userLng, content, radiusMiles: radius }); if (results.length >= minResults) { break; } expansions++; } return { results, finalRadius: currentRadius, expansions }; } /** * Get geohash bounds for a circular area * This creates a bounding box that contains the circle */ function getGeohashBounds(lat, lng, radiusMiles) { // Convert radius to degrees (approximate) const latDelta = radiusMiles / 69; // 1 degree latitude ≈ 69 miles const lngDelta = radiusMiles / (69 * Math.cos(lat * Math.PI / 180)); // Calculate bounding box const minLat = lat - latDelta; const maxLat = lat + latDelta; const minLng = lng - lngDelta; const maxLng = lng + lngDelta; // Generate geohashes for corners const precision = radiusMiles < 5 ? 6 : radiusMiles < 20 ? 5 : 4; // For simple implementation, return a single bound // In production, this would be more sophisticated const lowerHash = generateGeohash(minLat, minLng, precision); const upperHash = generateGeohash(maxLat, maxLng, precision); // Ensure proper ordering const bounds = [{ lower: lowerHash < upperHash ? lowerHash : upperHash, upper: lowerHash < upperHash ? upperHash : lowerHash }]; return bounds; } //# sourceMappingURL=geo-search.js.map