Skip to main content
Glama
geo.ts17 kB
import type { Feature, FeatureCollection, Geometry } from 'geojson'; import ee from '@google/earthengine'; import { findGlobalLocation } from './global-search'; /** * Try to get administrative boundary for a place name * @param placeName - Name of the place (e.g., "San Francisco") * @returns Earth Engine geometry or null if not found */ function tryGetAdminBoundary(placeName: string): any { try { // Normalize place name const normalized = placeName.toLowerCase().trim(); // Use the actual Earth Engine datasets to get real boundaries if (normalized.includes('san francisco') || normalized === 'sf') { // Use TIGER census data for US counties - more reliable const sfCounty = (new ee.FeatureCollection('TIGER/2016/Counties') as any) .filter(ee.Filter.eq('NAME', 'San Francisco')) .first(); console.log('Fetching exact San Francisco County boundary from TIGER dataset'); return sfCounty.geometry(); } if (normalized === 'new york' || normalized === 'nyc' || normalized === 'new york city') { // New York County (Manhattan) const nyCounty = (new ee.FeatureCollection('TIGER/2016/Counties') as any).filter(ee.Filter.eq('NAME', 'New York')) .filter(ee.Filter.eq('STATEFP', '36')) // NY state FIPS code .first(); console.log('Using TIGER New York County (Manhattan) boundary'); return nyCounty.geometry(); } if (normalized === 'los angeles' || normalized === 'la') { // Los Angeles County const laCounty = (new ee.FeatureCollection('TIGER/2016/Counties') as any).filter(ee.Filter.eq('NAME', 'Los Angeles')) .filter(ee.Filter.eq('STATEFP', '06')) // CA state FIPS code .first(); console.log('Using TIGER Los Angeles County boundary'); return laCounty.geometry(); } // Handle international cities with FAO GAUL dataset const internationalCities: { [key: string]: { name: string, state?: string, country: string } } = { // Indian cities 'ludhiana': { name: 'Ludhiana', state: 'Punjab', country: 'India' }, 'delhi': { name: 'Delhi', country: 'India' }, 'new delhi': { name: 'Delhi', country: 'India' }, 'mumbai': { name: 'Mumbai Suburban', state: 'Maharashtra', country: 'India' }, 'bangalore': { name: 'Bangalore Urban', state: 'Karnataka', country: 'India' }, 'bengaluru': { name: 'Bangalore Urban', state: 'Karnataka', country: 'India' }, 'chennai': { name: 'Chennai', state: 'Tamil Nadu', country: 'India' }, 'kolkata': { name: 'Kolkata', state: 'West Bengal', country: 'India' }, 'hyderabad': { name: 'Hyderabad', state: 'Andhra Pradesh', country: 'India' }, 'pune': { name: 'Pune', state: 'Maharashtra', country: 'India' }, 'ahmedabad': { name: 'Ahmadabad', state: 'Gujarat', country: 'India' }, 'jaipur': { name: 'Jaipur', state: 'Rajasthan', country: 'India' }, 'lucknow': { name: 'Lucknow', state: 'Uttar Pradesh', country: 'India' }, 'chandigarh': { name: 'Chandigarh', country: 'India' }, 'amritsar': { name: 'Amritsar', state: 'Punjab', country: 'India' }, // Other international cities 'london': { name: 'London', state: 'England', country: 'United Kingdom' }, 'paris': { name: 'Paris', state: 'Ile-de-France', country: 'France' }, 'tokyo': { name: 'Tokyo-to', country: 'Japan' }, 'beijing': { name: 'Beijing', country: 'China' }, 'shanghai': { name: 'Shanghai', country: 'China' }, 'dubai': { name: 'Dubai', country: 'United Arab Emirates' }, 'singapore': { name: 'Singapore', country: 'Singapore' }, 'sydney': { name: 'Sydney', state: 'New South Wales', country: 'Australia' }, 'toronto': { name: 'Toronto', state: 'Ontario', country: 'Canada' } }; if (internationalCities[normalized]) { const cityInfo = internationalCities[normalized]; try { let districts = (new ee.FeatureCollection('FAO/GAUL/2015/level2') as any).filter(ee.Filter.eq('ADM2_NAME', cityInfo.name)) .filter(ee.Filter.eq('ADM0_NAME', cityInfo.country)); if (cityInfo.state) { districts = districts.filter(ee.Filter.eq('ADM1_NAME', cityInfo.state)); } const firstDistrict = districts.first(); const geometry = firstDistrict.geometry(); console.log(`Found ${placeName} in FAO GAUL dataset (${cityInfo.country})`); return geometry; } catch (e) { // Try without state filter try { const districts = (new ee.FeatureCollection('FAO/GAUL/2015/level2') as any).filter(ee.Filter.eq('ADM2_NAME', cityInfo.name)) .filter(ee.Filter.eq('ADM0_NAME', cityInfo.country)); const firstDistrict = districts.first(); const geometry = firstDistrict.geometry(); console.log(`Found ${placeName} in FAO GAUL dataset without state filter (${cityInfo.country})`); return geometry; } catch (e2) { // Try Level 1 (state/province level) try { const states = (new ee.FeatureCollection('FAO/GAUL/2015/level1') as any).filter(ee.Filter.eq('ADM1_NAME', cityInfo.name)) .filter(ee.Filter.eq('ADM0_NAME', cityInfo.country)); const firstState = states.first(); const geometry = firstState.geometry(); console.log(`Found ${placeName} at state level in FAO GAUL (${cityInfo.country})`); return geometry; } catch (e3) { console.log(`Could not find ${placeName} in FAO GAUL, will try generic search`); } } } } // Handle common geographic regions and landmarks const geographicRegions: { [key: string]: () => any } = { 'alps': () => ee.Geometry.Polygon([[5.95, 45.82], [15.04, 45.82], [15.04, 47.81], [5.95, 47.81], [5.95, 45.82]]), 'europe': () => ee.Geometry.Polygon([[-10, 35], [40, 35], [40, 70], [-10, 70], [-10, 35]]), 'asia': () => ee.Geometry.Polygon([[25, -10], [180, -10], [180, 80], [25, 80], [25, -10]]), 'africa': () => ee.Geometry.Polygon([[-20, -35], [55, -35], [55, 37], [-20, 37], [-20, -35]]), 'north america': () => ee.Geometry.Polygon([[-170, 15], [-50, 15], [-50, 80], [-170, 80], [-170, 15]]), 'south america': () => ee.Geometry.Polygon([[-82, -56], [-34, -56], [-34, 13], [-82, 13], [-82, -56]]), 'australia': () => ee.Geometry.Polygon([[112, -44], [154, -44], [154, -10], [112, -10], [112, -44]]), 'amazon': () => ee.Geometry.Polygon([[-78, -20], [-43, -20], [-43, 5], [-78, 5], [-78, -20]]), 'amazon rainforest': () => ee.Geometry.Polygon([[-78, -20], [-43, -20], [-43, 5], [-78, 5], [-78, -20]]), 'sahara': () => ee.Geometry.Polygon([[-17, 15], [38, 15], [38, 35], [-17, 35], [-17, 15]]), 'sahara desert': () => ee.Geometry.Polygon([[-17, 15], [38, 15], [38, 35], [-17, 35], [-17, 15]]), 'great barrier reef': () => ee.Geometry.Polygon([[142, -24], [154, -24], [154, -10], [142, -10], [142, -24]]), 'himalayas': () => ee.Geometry.Polygon([[70, 25], [105, 25], [105, 40], [70, 40], [70, 25]]), 'rocky mountains': () => ee.Geometry.Polygon([[-120, 32], [-100, 32], [-100, 60], [-120, 60], [-120, 32]]), 'andes': () => ee.Geometry.Polygon([[-80, -55], [-62, -55], [-62, 10], [-80, 10], [-80, -55]]), 'mediterranean': () => ee.Geometry.Polygon([[-6, 30], [36, 30], [36, 46], [-6, 46], [-6, 30]]), 'caribbean': () => ee.Geometry.Polygon([[-87, 10], [-59, 10], [-59, 27], [-87, 27], [-87, 10]]), 'lake tahoe': () => ee.Geometry.Point([-120.0, 39.1]).buffer(30000), 'sacramento valley': () => ee.Geometry.Polygon([[-122.5, 38.5], [-121.0, 38.5], [-121.0, 40.5], [-122.5, 40.5], [-122.5, 38.5]]), 'kenya': () => { // Use FAO GAUL for Kenya const countries = (new ee.FeatureCollection('FAO/GAUL/2015/level0') as any) .filter(ee.Filter.eq('ADM0_NAME', 'Kenya')); return countries.first().geometry(); }, 'brazil': () => { // Use FAO GAUL for Brazil const countries = (new ee.FeatureCollection('FAO/GAUL/2015/level0') as any) .filter(ee.Filter.eq('ADM0_NAME', 'Brazil')); return countries.first().geometry(); }, 'algeria': () => { // Use FAO GAUL for Algeria const countries = (new ee.FeatureCollection('FAO/GAUL/2015/level0') as any) .filter(ee.Filter.eq('ADM0_NAME', 'Algeria')); return countries.first().geometry(); }, 'queensland': () => { // Australian state - use a bounding box return ee.Geometry.Polygon([[138, -29], [154, -29], [154, -10], [138, -10], [138, -29]]); } }; if (geographicRegions[normalized]) { try { const geometry = geographicRegions[normalized](); console.log(`Found geographic region: ${placeName}`); return geometry; } catch (e) { console.log(`Error getting geometry for region ${placeName}:`, e); } } // Try other common US cities using TIGER data const usCityMappings: { [key: string]: { county: string, state: string } } = { 'miami': { county: 'Miami-Dade', state: '12' }, // FL 'chicago': { county: 'Cook', state: '17' }, // IL 'houston': { county: 'Harris', state: '48' }, // TX 'phoenix': { county: 'Maricopa', state: '04' }, // AZ 'philadelphia': { county: 'Philadelphia', state: '42' }, // PA 'san antonio': { county: 'Bexar', state: '48' }, // TX 'san diego': { county: 'San Diego', state: '06' }, // CA 'dallas': { county: 'Dallas', state: '48' }, // TX 'seattle': { county: 'King', state: '53' }, // WA 'boston': { county: 'Suffolk', state: '25' } // MA }; if (usCityMappings[normalized]) { const mapping = usCityMappings[normalized]; try { const county = (new ee.FeatureCollection('TIGER/2016/Counties') as any).filter(ee.Filter.eq('NAME', mapping.county)) .filter(ee.Filter.eq('STATEFP', mapping.state)) .first(); const geometry = county.geometry(); console.log(`Using TIGER ${mapping.county} County boundary for ${placeName}`); return geometry; } catch (e) { console.log(`Could not find ${placeName} in TIGER dataset`); } } // Try US states const states = ['california', 'texas', 'florida', 'new york', 'illinois', 'pennsylvania', 'ohio', 'georgia', 'north carolina', 'michigan']; if (states.includes(normalized)) { const state = (new ee.FeatureCollection('TIGER/2016/States') as any).filter(ee.Filter.eq('NAME', placeName)) .first(); console.log(`Using TIGER state boundary for ${placeName}`); return state.geometry(); } // Try to search in TIGER counties by name (US only) try { const countySearch = (new ee.FeatureCollection('TIGER/2016/Counties') as any).filter(ee.Filter.eq('NAME', placeName)); const firstCounty = countySearch.first(); // This will throw if no matches found const testGeometry = firstCounty.geometry(); console.log(`Found county match for ${placeName} in TIGER dataset`); return testGeometry; } catch (e) { // Not found in TIGER, continue to international datasets } // Try FAO GAUL for international locations // Level 2 - Districts/Cities try { const districts = (new ee.FeatureCollection('FAO/GAUL/2015/level2') as any).filter(ee.Filter.eq('ADM2_NAME', placeName)); const firstDistrict = districts.first(); const geometry = firstDistrict.geometry(); console.log(`Found ${placeName} in FAO GAUL Level 2 (District)`); return geometry; } catch (e) { // Continue to next level } // Level 1 - States/Provinces try { const states = (new ee.FeatureCollection('FAO/GAUL/2015/level1') as any).filter(ee.Filter.eq('ADM1_NAME', placeName)); const firstState = states.first(); const geometry = firstState.geometry(); console.log(`Found ${placeName} in FAO GAUL Level 1 (State/Province)`); return geometry; } catch (e) { // Continue to next level } // Level 0 - Countries try { const countries = (new ee.FeatureCollection('FAO/GAUL/2015/level0') as any).filter(ee.Filter.eq('ADM0_NAME', placeName)); const firstCountry = countries.first(); const geometry = firstCountry.geometry(); console.log(`Found ${placeName} in FAO GAUL Level 0 (Country)`); return geometry; } catch (e) { // Not found } // Note: ee.Filter.stringContains doesn't exist, so we can't do fuzzy matching return null; } catch (error) { console.log(`Could not find boundary for ${placeName}:`, error); return null; } } export async function parseAoi(aoi: any): Promise<any> { if (!aoi) throw new Error('AOI required'); // If aoi is a string, treat it as a place name, coordinates, or JSON geometry if (typeof aoi === 'string') { // First check if it's a JSON string (geometry) try { const parsed = JSON.parse(aoi); if (parsed.type && (parsed.type === 'Polygon' || parsed.type === 'Point' || parsed.type === 'Feature' || parsed.type === 'FeatureCollection')) { console.log(`Parsed JSON geometry string: ${parsed.type}`); // Recursively call parseAoi with the parsed object return parseAoi(parsed); } } catch (e) { // Not valid JSON, continue with other checks } // Check if it looks like coordinates (e.g., "-118.2437, 34.0522") const coordMatch = aoi.match(/^\s*(-?\d+\.?\d*),\s*(-?\d+\.?\d*)\s*$/); if (coordMatch) { const lon = parseFloat(coordMatch[1]); const lat = parseFloat(coordMatch[2]); console.log(`Parsing coordinates: lon=${lon}, lat=${lat}`); // Create a point with a small buffer return ee.Geometry.Point([lon, lat]).buffer(10000); // 10km buffer } console.log(`String AOI detected as place name: ${aoi}`); // Try global search first try { const globalBoundary = await findGlobalLocation(aoi); if (globalBoundary) { console.log(`Using exact administrative boundary for ${aoi} (global search)`); return globalBoundary; } } catch (globalError) { console.log(`Global search failed for ${aoi}, trying legacy method`); } // Fallback to old method const boundary = tryGetAdminBoundary(aoi); if (boundary) { console.log(`Using exact administrative boundary for ${aoi} (legacy method)`); return boundary; } throw new Error(`Could not find boundary for place: ${aoi}`); } // ALWAYS try to get administrative boundary first if there's any indication of a place // Check coordinates to infer location let inferredPlace = null; if (aoi.type === 'Polygon' && aoi.coordinates) { const coords = aoi.coordinates[0]; if (coords && coords.length > 0) { const lons = coords.map((c: number[]) => c[0]); const lats = coords.map((c: number[]) => c[1]); const avgLon = lons.reduce((a: number, b: number) => a + b, 0) / lons.length; const avgLat = lats.reduce((a: number, b: number) => a + b, 0) / lats.length; // San Francisco Bay Area if (avgLon > -123 && avgLon < -122 && avgLat > 37 && avgLat < 38.2) { inferredPlace = 'San Francisco'; console.log('Coordinates indicate San Francisco Bay Area - fetching county boundary'); } } } // Try to get boundary for explicit placeName or inferred place const placeName = aoi.placeName || inferredPlace; if (placeName) { console.log(`Fetching administrative boundary for: ${placeName}`); // Try global search first try { const globalBoundary = await findGlobalLocation(placeName); if (globalBoundary) { console.log(`Using exact administrative boundary for ${placeName} (global search)`); return globalBoundary; } } catch (globalError) { console.log(`Global search failed for ${placeName}, trying legacy method`); } // Fallback to old method const boundary = tryGetAdminBoundary(placeName); if (boundary) { console.log(`Using exact administrative boundary for ${placeName} (legacy method)`); return boundary; } } // Only use polygon if no boundary could be found if (aoi.type === 'FeatureCollection') return (new ee.FeatureCollection(aoi as FeatureCollection) as any).geometry(); if (aoi.type === 'Feature') return new ee.Feature(new ee.Geometry(aoi.geometry)).geometry(); if (aoi.type) return new ee.Geometry(aoi as Geometry); throw new Error('Unsupported AOI format'); } export function clampScale(scale: number, min=10, max=10000) { return Math.max(min, Math.min(max, scale)); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Dhenenjay/axion-planetary-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server