316 lines
8.5 KiB
TypeScript

// Example usage of Institute Geocoder functions
// This file demonstrates how to integrate the geocoding functions in your frontend
import { createClient } from '@supabase/supabase-js'
// Initialize Supabase client
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
const supabase = createClient(supabaseUrl, supabaseAnonKey)
// Types for institute data
interface Institute {
id: string
name: string
address: {
street?: string
town?: string
county?: string
postcode?: string
country?: string
}
geo_coordinates?: {
latitude: number
longitude: number
boundingbox: string[]
search_query: string
geocoded_at: string
}
}
interface GeocodingResult {
success: boolean
message: string
coordinates?: {
latitude: number
longitude: number
boundingbox: string[]
}
error?: string
}
// 1. Geocode a single institute
export async function geocodeInstitute(instituteId: string): Promise<GeocodingResult> {
try {
const { data, error } = await supabase.functions.invoke('institute-geocoder', {
body: { institute_id: instituteId }
})
if (error) {
throw new Error(error.message)
}
return data
} catch (error) {
console.error('Geocoding failed:', error)
return {
success: false,
message: 'Geocoding failed',
error: error instanceof Error ? error.message : 'Unknown error'
}
}
}
// 2. Batch geocode multiple institutes
export async function batchGeocodeInstitutes(
limit: number = 10,
forceRefresh: boolean = false
): Promise<any> {
try {
const { data, error } = await supabase.functions.invoke('institute-geocoder/batch', {
body: {
limit,
force_refresh: forceRefresh
}
})
if (error) {
throw new Error(error.message)
}
return data
} catch (error) {
console.error('Batch geocoding failed:', error)
throw error
}
}
// 3. Get institutes that need geocoding
export async function getInstitutesNeedingGeocoding(): Promise<Institute[]> {
try {
const { data, error } = await supabase
.from('institutes')
.select('id, name, address, geo_coordinates')
.or('geo_coordinates.is.null,geo_coordinates.eq.{}')
.not('import_id', 'is', null)
if (error) {
throw new Error(error.message)
}
return data || []
} catch (error) {
console.error('Failed to fetch institutes:', error)
return []
}
}
// 4. Display institute on a map (example with Leaflet)
export function displayInstituteOnMap(
institute: Institute,
mapElement: HTMLElement
): void {
if (!institute.geo_coordinates) {
console.warn('Institute has no coordinates:', institute.name)
return
}
// This is a placeholder - you'd need to implement actual map rendering
// For example, using Leaflet, Mapbox, or Google Maps
const { latitude, longitude } = institute.geo_coordinates
console.log(`Displaying ${institute.name} at ${latitude}, ${longitude}`)
// Example map implementation:
// const map = L.map(mapElement).setView([latitude, longitude], 13)
// L.marker([latitude, longitude]).addTo(map).bindPopup(institute.name)
}
// 5. React component example
export function InstituteGeocoder() {
const [institutes, setInstitutes] = useState<Institute[]>([])
const [loading, setLoading] = useState(false)
const [geocodingProgress, setGeocodingProgress] = useState(0)
// Load institutes that need geocoding
useEffect(() => {
loadInstitutes()
}, [])
async function loadInstitutes() {
const data = await getInstitutesNeedingGeocoding()
setInstitutes(data)
}
// Geocode all institutes
async function geocodeAllInstitutes() {
setLoading(true)
setGeocodingProgress(0)
try {
const result = await batchGeocodeInstitutes(institutes.length, false)
if (result.success) {
setGeocodingProgress(100)
// Reload institutes to show updated coordinates
await loadInstitutes()
}
} catch (error) {
console.error('Batch geocoding failed:', error)
} finally {
setLoading(false)
}
}
// Geocode single institute
async function geocodeSingleInstitute(instituteId: string) {
try {
const result = await geocodeInstitute(instituteId)
if (result.success) {
// Reload institutes to show updated coordinates
await loadInstitutes()
}
} catch (error) {
console.error('Single geocoding failed:', error)
}
}
return (
<div className="institute-geocoder">
<h2>Institute Geocoding</h2>
<div className="controls">
<button
onClick={geocodeAllInstitutes}
disabled={loading || institutes.length === 0}
>
{loading ? 'Geocoding...' : `Geocode All (${institutes.length})`}
</button>
{loading && (
<div className="progress">
<div
className="progress-bar"
style={{ width: `${geocodingProgress}%` }}
/>
</div>
)}
</div>
<div className="institutes-list">
{institutes.map(institute => (
<div key={institute.id} className="institute-item">
<h3>{institute.name}</h3>
<p>
{institute.address.street && `${institute.address.street}, `}
{institute.address.town && `${institute.address.town}, `}
{institute.address.county && `${institute.address.county}, `}
{institute.address.postcode}
</p>
{institute.geo_coordinates ? (
<div className="coordinates">
<span>📍 {institute.geo_coordinates.latitude}, {institute.geo_coordinates.longitude}</span>
<span>Geocoded: {new Date(institute.geo_coordinates.geocoded_at).toLocaleDateString()}</span>
</div>
) : (
<button
onClick={() => geocodeSingleInstitute(institute.id)}
disabled={loading}
>
Geocode
</button>
)}
</div>
))}
</div>
</div>
)
}
// 6. Utility functions for working with coordinates
export class CoordinateUtils {
// Calculate distance between two points (Haversine formula)
static calculateDistance(
lat1: number,
lon1: number,
lat2: number,
lon2: number
): number {
const R = 6371 // Earth's radius in kilometers
const dLat = this.toRadians(lat2 - lat1)
const dLon = this.toRadians(lon2 - lon1)
const a =
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(this.toRadians(lat1)) * Math.cos(this.toRadians(lat2)) *
Math.sin(dLon / 2) * Math.sin(dLon / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
// Convert degrees to radians
private static toRadians(degrees: number): number {
return degrees * (Math.PI / 180)
}
// Check if coordinates are within a bounding box
static isWithinBounds(
lat: number,
lon: number,
bounds: [number, number, number, number] // [minLat, maxLat, minLon, maxLon]
): boolean {
return lat >= bounds[0] && lat <= bounds[1] &&
lon >= bounds[2] && lon <= bounds[3]
}
// Format coordinates for display
static formatCoordinates(lat: number, lon: number): string {
const latDir = lat >= 0 ? 'N' : 'S'
const lonDir = lon >= 0 ? 'E' : 'W'
return `${Math.abs(lat).toFixed(6)}°${latDir}, ${Math.abs(lon).toFixed(6)}°${lonDir}`
}
}
// 7. Example of using coordinates in Neo4j queries
export const neo4jQueries = {
// Create institute node with location
createInstituteWithLocation: `
CREATE (i:Institute {
id: $institute_id,
name: $name,
location: point({latitude: $latitude, longitude: $longitude})
})
RETURN i
`,
// Find institutes within radius
findInstitutesWithinRadius: `
MATCH (i:Institute)
WHERE distance(i.location, point({latitude: $centerLat, longitude: $centerLon})) < $radiusMeters
RETURN i, distance(i.location, point({latitude: $centerLat, longitude: $centerLon})) as distance
ORDER BY distance
`,
// Find institutes in bounding box
findInstitutesInBounds: `
MATCH (i:Institute)
WHERE i.location.latitude >= $minLat
AND i.location.latitude <= $maxLat
AND i.location.longitude >= $minLon
AND i.location.longitude <= $maxLon
RETURN i
`
}
export default {
geocodeInstitute,
batchGeocodeInstitutes,
getInstitutesNeedingGeocoding,
displayInstituteOnMap,
InstituteGeocoder,
CoordinateUtils,
neo4jQueries
}