UNPKG

pulse-ai-utils

Version:

Utility functions and helpers for AI-powered applications

207 lines 8.17 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.GeohashService = void 0; const geohash = __importStar(require("ngeohash")); class GeohashService { constructor() { this.DEFAULT_PRECISION = 6; // ~0.61km x 0.61km cells } /** * Generate geohash for coordinates */ generateGeohash(lat, lng, precision = this.DEFAULT_PRECISION) { return geohash.encode(lat, lng, precision); } /** * Get neighboring geohashes for a given geohash * This is crucial for boundary searches */ getNeighbors(hash) { return [ hash, geohash.neighbor(hash, [1, 0]), // North geohash.neighbor(hash, [1, 1]), // Northeast geohash.neighbor(hash, [0, 1]), // East geohash.neighbor(hash, [-1, 1]), // Southeast geohash.neighbor(hash, [-1, 0]), // South geohash.neighbor(hash, [-1, -1]), // Southwest geohash.neighbor(hash, [0, -1]), // West geohash.neighbor(hash, [1, -1]) // Northwest ]; } /** * Calculate geohash search ranges for a radius query * This minimizes the number of queries needed */ getSearchRanges(lat, lng, radius) { // Determine optimal geohash precision based on radius const precision = this.getOptimalPrecision(radius); const centerHash = geohash.encode(lat, lng, precision); // Get all geohashes that need to be searched const searchHashes = this.getSearchHashes(centerHash, radius, precision); // Convert to ranges for efficient querying return this.hashesToRanges(searchHashes); } /** * Get optimal geohash precision for given radius */ getOptimalPrecision(radius) { // Precision to cell size mapping (approximate) const precisionMap = [ { precision: 1, km: 5000 }, { precision: 2, km: 1250 }, { precision: 3, km: 156 }, { precision: 4, km: 39 }, { precision: 5, km: 4.9 }, { precision: 6, km: 1.2 }, { precision: 7, km: 0.152 }, { precision: 8, km: 0.038 } ]; // Find precision where cell size is about 1/4 of search radius for (let i = precisionMap.length - 1; i >= 0; i--) { if (precisionMap[i].km <= radius / 4) { return precisionMap[i].precision; } } return 6; // Default precision } /** * Get all geohash cells that overlap with search radius */ getSearchHashes(centerHash, radius, precision) { const hashes = new Set(); const center = geohash.decode(centerHash); const centerLat = center.latitude; const centerLng = center.longitude; // Get cell dimensions at this precision const cellSize = this.getCellSize(precision); // Calculate how many cells we need to check in each direction const cellsToCheck = Math.ceil(radius / cellSize.km); // Start with center and expand outward const queue = [{ hash: centerHash, distance: 0 }]; const visited = new Set(); while (queue.length > 0) { const { hash, distance } = queue.shift(); if (visited.has(hash) || distance > cellsToCheck) continue; visited.add(hash); // Check if this cell is within radius const cell = geohash.decode(hash); const cellLat = cell.latitude; const cellLng = cell.longitude; const cellDistance = this.haversineDistance(centerLat, centerLng, cellLat, cellLng); if (cellDistance <= radius + cellSize.km) { hashes.add(hash); // Add neighbors to queue const neighbors = this.getNeighbors(hash); neighbors.forEach(n => { if (!visited.has(n)) { queue.push({ hash: n, distance: distance + 1 }); } }); } } return hashes; } /** * Convert set of geohashes to efficient query ranges */ hashesToRanges(hashes) { // Sort hashes const sortedHashes = Array.from(hashes).sort(); const ranges = []; let rangeStart = sortedHashes[0]; let rangeEnd = sortedHashes[0]; for (let i = 1; i < sortedHashes.length; i++) { const current = sortedHashes[i]; const prev = sortedHashes[i - 1]; // Check if current is consecutive to previous if (this.areConsecutive(prev, current)) { rangeEnd = current; } else { // Save current range and start new one ranges.push([rangeStart, rangeEnd + '~']); // ~ is after z in ASCII rangeStart = current; rangeEnd = current; } } // Add final range ranges.push([rangeStart, rangeEnd + '~']); return ranges; } /** * Check if two geohashes are consecutive */ areConsecutive(hash1, hash2) { // Simple check - can be improved const base32 = '0123456789bcdefghjkmnpqrstuvwxyz'; const idx1 = base32.indexOf(hash1[hash1.length - 1]); const idx2 = base32.indexOf(hash2[hash2.length - 1]); return hash1.slice(0, -1) === hash2.slice(0, -1) && idx2 - idx1 === 1; } /** * Calculate distance between two points */ haversineDistance(lat1, lng1, lat2, lng2) { const R = 6371; // Earth's radius in 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; } /** * Get cell size for precision level */ getCellSize(precision) { const sizes = { 1: { km: 5000, lat: 45, lng: 45 }, 2: { km: 1250, lat: 11.25, lng: 11.25 }, 3: { km: 156, lat: 1.40625, lng: 2.8125 }, 4: { km: 39, lat: 0.3515625, lng: 0.3515625 }, 5: { km: 4.9, lat: 0.0439453125, lng: 0.087890625 }, 6: { km: 1.2, lat: 0.010986328125, lng: 0.010986328125 }, 7: { km: 0.152, lat: 0.001373291015625, lng: 0.00274658203125 }, 8: { km: 0.038, lat: 0.00034332275390625, lng: 0.00034332275390625 } }; return sizes[precision] || sizes[6]; } } exports.GeohashService = GeohashService; //# sourceMappingURL=geohash-service.js.map