-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #169 from Plant-for-the-Planet-org/feature/create-…
…GOES16Provider Integrate GOES-16 Geostationary Satellite as a GeoEventProvider
- Loading branch information
Showing
12 changed files
with
665 additions
and
96 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
194 changes: 194 additions & 0 deletions
194
apps/server/src/Services/GeoEventProvider/ProviderClass/GOES16GeoEventProviderClass.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
import { | ||
type GeoEventProviderConfig, | ||
type GeoEventProviderClientId, | ||
type GeoEventProviderConfigGeneral, | ||
type GeoEventProviderClass | ||
} from '../../../Interfaces/GeoEventProvider'; | ||
import { type geoEventInterface as GeoEvent } from "../../../Interfaces/GeoEvent" | ||
import {Confidence} from '../../../Interfaces/GeoEvent'; | ||
import {determineSlice} from "../../../utils/geometry" | ||
import ee from '@google/earthengine' | ||
|
||
type FireDataEntry = [number, number, Date]; | ||
type AllFireData = FireDataEntry[]; | ||
interface PrivateKeyJson { | ||
"type": string; | ||
"project_id": string; | ||
"private_key_id": string; | ||
"private_key": string; | ||
"client_email": string; | ||
"client_id": string; | ||
"auth_uri": string; | ||
"token_uri": string; | ||
"auth_provider_x509_cert_url": string; | ||
"client_x509_cert_url": string; | ||
"universe_domain": string; | ||
} | ||
|
||
interface GOES16GeoEventProviderConfig extends GeoEventProviderConfig { | ||
// Add any additional config properties if needed | ||
privateKey: PrivateKeyJson; | ||
} | ||
|
||
class GOES16GeoEventProviderClass implements GeoEventProviderClass { | ||
private config: GeoEventProviderConfigGeneral | undefined; | ||
|
||
constructor() { | ||
this.getLatestGeoEvents = this.getLatestGeoEvents.bind(this); | ||
} | ||
|
||
getKey(): string { | ||
return 'GOES-16'; | ||
} | ||
|
||
initialize(config?: GeoEventProviderConfigGeneral): void { | ||
this.config = config; | ||
} | ||
|
||
async authenticateEarthEngine(): Promise<void> { | ||
const {privateKey} = this.getConfig() | ||
const private_key = JSON.parse(JSON.stringify(privateKey)) | ||
return new Promise<void>((resolve, reject) => { | ||
ee.data.authenticateViaPrivateKey( | ||
private_key, | ||
() => { | ||
ee.initialize( | ||
null, | ||
null, | ||
() => { | ||
console.log('Google Earth Engine authentication successful'); | ||
resolve(); | ||
}, | ||
(err) => { | ||
console.error('Google Earth Engine initialization error', err); | ||
reject(err); | ||
} | ||
); | ||
}, | ||
(err) => { | ||
console.error('Google Earth Engine authentication error', err); | ||
reject(err); | ||
} | ||
); | ||
}); | ||
} | ||
|
||
async getLatestGeoEvents(geoEventProviderClientId: string, geoEventProviderId: string, slice: string, clientApiKey: string, lastRun: Date | null): Promise<GeoEvent[]> { | ||
return new Promise<GeoEvent[]>(async (resolve, reject) => { | ||
try { | ||
// Ensure Earth Engine is authenticated and initialized before fetching data | ||
await this.authenticateEarthEngine(); | ||
let allFireData: AllFireData = []; | ||
|
||
// If lastRun is more than 2 hours ago or null, then start from 2 hours ago, else start from lastRunDate | ||
const currentDateTime = new Date(); | ||
const lastRunDate = lastRun ? new Date(lastRun) : null; | ||
const twoHoursAgo = new Date(currentDateTime.getTime() - 2 * 3600 * 1000); | ||
const fromDateTime = (!lastRunDate || (currentDateTime.getTime() - lastRunDate.getTime()) > 2 * 3600 * 1000) ? twoHoursAgo : lastRunDate; | ||
|
||
const images = ee.ImageCollection("NOAA/GOES/16/FDCF").filterDate(fromDateTime, currentDateTime); | ||
|
||
// Fetch and process images here... | ||
// The process includes fetching image IDs, processing them to extract fire data, etc. | ||
// This is a simplified outline; integrate the logic from your initial example here. | ||
const getImagesId = () => { | ||
return new Promise((resolve, reject) => { | ||
images.evaluate((imageCollection) => { | ||
if (imageCollection && imageCollection.features) { | ||
const imagesData = imageCollection.features.map(feature => feature.id); | ||
resolve(imagesData); | ||
} else { | ||
reject(new Error("No features found")); | ||
} | ||
}); | ||
}); | ||
}; | ||
try { | ||
const array_imagesId = await getImagesId() as string[]; | ||
for (const imageId of array_imagesId) { | ||
const image = ee.Image(`${imageId}`) | ||
// Get the datetime information from the image metadata | ||
const datetimeInfo = await ee.Date(image.get('system:time_start')).getInfo(); | ||
const datetime = new Date(datetimeInfo.value); | ||
|
||
|
||
const temperatureImage = image.select('Temp'); | ||
const xMin = -142; // On station as GOES-E | ||
const xMax = xMin + 135; | ||
const geometry = ee.Geometry.Rectangle([xMin, -65, xMax, 65], null, true); | ||
var temperatureVector = temperatureImage.reduceToVectors({ | ||
geometry: geometry, | ||
scale: 2000, | ||
geometryType: 'centroid', | ||
labelProperty: 'temp', | ||
maxPixels: 1e10, | ||
}); | ||
const fireData = await new Promise((resolve, reject) => { | ||
temperatureVector.evaluate((featureCollection) => { | ||
if (featureCollection && featureCollection.features) { | ||
// Map each feature to include datetime in its data | ||
// [long, lat, eventDate] | ||
const fireDataWithTime = featureCollection.features.map(feature => [...feature.geometry.coordinates, datetime]); | ||
resolve(fireDataWithTime); | ||
} else { | ||
reject(new Error("No features found")); | ||
} | ||
}); | ||
}) as FireDataEntry; | ||
|
||
// Concatenate the current image's fire data with the master array | ||
allFireData = allFireData.concat(fireData); | ||
}; | ||
} catch (error) { | ||
console.error("Error fetching fire data:", error); | ||
} | ||
|
||
// Normalize the fire data into GeoEvent format | ||
const geoEventsData: GeoEvent[] = allFireData.map((fireData: FireDataEntry) => ({ | ||
type: 'fire', | ||
latitude: fireData[1], | ||
longitude: fireData[0], | ||
eventDate: new Date(fireData[2]), | ||
confidence: Confidence.High, | ||
isProcessed: false, | ||
geoEventProviderClientId: geoEventProviderClientId as GeoEventProviderClientId, | ||
geoEventProviderId: geoEventProviderId, | ||
slice: determineSlice(fireData[1], fireData[0]), | ||
data: {'satellite': clientApiKey, 'slice': slice} | ||
})); | ||
|
||
resolve(geoEventsData); | ||
} catch (error) { | ||
console.error('Failed to fetch or process GOES-16 data', error); | ||
reject(error); | ||
} | ||
}); | ||
} | ||
|
||
getConfig(): GOES16GeoEventProviderConfig { | ||
if (typeof this.config === 'undefined') { | ||
throw new Error(`Invalid or incomplete GOES-16 event provider configuration`); | ||
} | ||
const config = this.config | ||
if (typeof config.client === "undefined") { | ||
throw new Error(`Missing property 'client' in alert provider configuration`); | ||
} | ||
if (typeof config.bbox === "undefined") { | ||
throw new Error(`Missing property 'bbox' in alert provider configuration`); | ||
} | ||
if (typeof config.slice === "undefined") { | ||
throw new Error(`Missing property 'slice' in alert provider configuration`); | ||
} | ||
if (typeof config.privateKey === "undefined") { | ||
throw new Error(`Missing property 'satelliteType' in alert provider configuration`); | ||
} | ||
return { | ||
client: config.client, | ||
bbox: config.bbox, | ||
slice: config.slice, | ||
privateKey: config.privateKey | ||
}; | ||
} | ||
} | ||
|
||
export default GOES16GeoEventProviderClass; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.