Skip to main content

qualia_client_core/chora/layers/
nasa_gibs.rs

1pub struct GibsRequest {
2    pub layer: String,
3    pub projection: String,
4    pub width: u32,
5    pub height: u32,
6}
7
8impl GibsRequest {
9    pub fn url(&self) -> String {
10        let bbox = if self.projection == "epsg4326" {
11            "-90,-180,90,180"
12        } else {
13            "-20037508.34,-20037508.34,20037508.34,20037508.34"
14        };
15        let crs = if self.projection == "epsg4326" {
16            "EPSG:4326"
17        } else {
18            "EPSG:3857"
19        };
20        format!(
21            "https://gibs.earthdata.nasa.gov/wms/{}/best/wms.cgi?\
22             SERVICE=WMS&REQUEST=GetMap&VERSION=1.3.0\
23             &LAYERS={}&CRS={}&BBOX={}\
24             &WIDTH={}&HEIGHT={}&FORMAT=image/jpeg\
25             &STYLES=&TRANSPARENT=FALSE",
26            self.projection, self.layer, crs, bbox, self.width, self.height
27        )
28    }
29}
30
31pub struct EarthTexture {
32    pub width: u32,
33    pub height: u32,
34    pub rgb: Vec<[u8; 3]>,
35}
36
37impl EarthTexture {
38    pub fn sample(&self, lat_deg: f32, lon_deg: f32) -> [f32; 3] {
39        let lat = lat_deg.clamp(-90.0, 90.0);
40        let lon = lon_deg.clamp(-180.0, 180.0);
41        let v = ((90.0 - lat) / 180.0 * self.height as f32) as u32;
42        let u = (((lon + 180.0) / 360.0) * self.width as f32) as u32;
43        let v = v.min(self.height - 1);
44        let u = u.min(self.width - 1);
45        let idx = (v * self.width + u) as usize;
46        let [r, g, b] = self.rgb.get(idx).copied().unwrap_or([0, 0, 80]);
47        [r as f32 / 255.0, g as f32 / 255.0, b as f32 / 255.0]
48    }
49
50    pub fn sample_vertex(&self, x: f32, y: f32, z: f32) -> [f32; 3] {
51        let lat = y.asin().to_degrees();
52        let lon = z.atan2(x).to_degrees();
53        self.sample(lat, lon)
54    }
55}
56
57pub fn decode_jpeg_rgb(data: &[u8]) -> Result<EarthTexture, String> {
58    let img = image::load_from_memory_with_format(data, image::ImageFormat::Jpeg)
59        .map_err(|e| format!("JPEG decode: {e}"))?;
60    let rgb_img = img.to_rgb8();
61    let width = rgb_img.width();
62    let height = rgb_img.height();
63    let mut rgb = Vec::with_capacity((width * height) as usize);
64    for pixel in rgb_img.pixels() {
65        rgb.push([pixel[0], pixel[1], pixel[2]]);
66    }
67    Ok(EarthTexture { width, height, rgb })
68}
69
70pub async fn download_gibs_texture(req: &GibsRequest) -> Result<EarthTexture, String> {
71    let url = req.url();
72    let response = reqwest::get(&url)
73        .await
74        .map_err(|e| format!("GIBS request: {e}"))?;
75    if !response.status().is_success() {
76        return Err(format!("GIBS returned {}", response.status()));
77    }
78    let bytes = response
79        .bytes()
80        .await
81        .map_err(|e| format!("GIBS body: {e}"))?;
82    decode_jpeg_rgb(&bytes)
83}