From db6ba00ded958ccff5f5c8aa2f81abe7051b2858 Mon Sep 17 00:00:00 2001 From: Tim Howitz Date: Sun, 25 May 2025 11:46:42 +0100 Subject: [PATCH] Added linking of nearest observatories to earthquakes --- src/app/api/import-earthquakes/route.ts | 54 ++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/src/app/api/import-earthquakes/route.ts b/src/app/api/import-earthquakes/route.ts index a42c6fb..bafe541 100644 --- a/src/app/api/import-earthquakes/route.ts +++ b/src/app/api/import-earthquakes/route.ts @@ -6,6 +6,7 @@ import { prisma } from "@utils/prisma"; import { getRandomNumber } from "@utils/maths"; const csvFilePath = path.resolve(process.cwd(), "public/earthquakes.csv"); +const DISTANCE_THRESHOLD_KM = 500; // Max distance for observatory matching type CsvRow = { Date: string; @@ -18,6 +19,18 @@ type CsvRow = { Depth: string; }; +// Haversine formula to calculate distance between two points (in km) +function getDistance(lat1: number, lon1: number, lat2: number, lon2: number): number { + const R = 6371; // Earth's radius in km + const dLat = (lat2 - lat1) * (Math.PI / 180); + const dLon = (lon2 - lon1) * (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(dLon / 2) * Math.sin(dLon / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c; +} + export async function POST() { try { const fileContent = await fs.readFile(csvFilePath, "utf8"); @@ -28,12 +41,15 @@ export async function POST() { const failedImports: { row: CsvRow; reason: string }[] = []; + // Fetch all observatories once to avoid repeated queries + const observatories = await prisma.observatory.findMany({ + select: { id: true, latitude: true, longitude: true }, + }); + const earthquakes = await Promise.all( records.map(async (row) => { const creators = await prisma.user.findMany({ - where: { - role: { in: ["SCIENTIST", "ADMIN"] }, - }, + where: { role: { in: ["SCIENTIST", "ADMIN"] } }, }); const randomCreator = creators.length > 0 ? creators[getRandomNumber(0, creators.length - 1)] : null; @@ -42,16 +58,43 @@ export async function POST() { return null; } + const eqLat = parseFloat(row.Latitude); + const eqLon = parseFloat(row.Longitude); + + // Find observatories within distance threshold + let nearbyObservatories = observatories + .map((obs) => ({ + id: obs.id, + distance: getDistance(eqLat, eqLon, obs.latitude, obs.longitude), + })) + .filter((obs) => obs.distance <= DISTANCE_THRESHOLD_KM) + .map((obs) => ({ id: obs.id })); + + // If no observatories within range, find the nearest one + if (nearbyObservatories.length === 0) { + const nearest = observatories.reduce( + (min, obs) => { + const distance = getDistance(eqLat, eqLon, obs.latitude, obs.longitude); + return distance < min.distance ? { id: obs.id, distance } : min; + }, + { id: -1, distance: Infinity } + ); + if (nearest.id !== -1) { + nearbyObservatories = [{ id: nearest.id }]; + } + } + return { date: new Date(row.Date), code: row.Code, magnitude: parseFloat(row.Magnitude), type: row.Type, - latitude: parseFloat(row.Latitude), - longitude: parseFloat(row.Longitude), + latitude: eqLat, + longitude: eqLon, location: row.Location, depth: row.Depth, creatorId: randomCreator.id, + observatories: { connect: nearbyObservatories }, }; }) ); @@ -60,6 +103,7 @@ export async function POST() { (earthquake): earthquake is NonNullable => earthquake !== null ); + // Bulk insert earthquakes with observatory connections await prisma.earthquake.createMany({ data: validEarthquakes, });