forked from paul/sinoptik
Merge pull request 'Implement Mercator projection' (#10) from 9-implement-mercator-projection into main
Reviewed-on: paul/sinoptik#10
This commit is contained in:
commit
ac9576defa
28
src/main.rs
28
src/main.rs
|
@ -71,27 +71,23 @@ async fn show_map(
|
|||
use std::io::Cursor;
|
||||
|
||||
let position = resolve_address(address).await?;
|
||||
let lat = position.lat;
|
||||
let lon = position.lon;
|
||||
|
||||
let now = Instant::now();
|
||||
let maps = maps_handle.lock().expect("Maps handle lock was poisoned");
|
||||
let mut image = match metric {
|
||||
Metric::PAQI => maps.pollen_at(now)?,
|
||||
Metric::Pollen => maps.pollen_at(now)?,
|
||||
Metric::UVI => maps.uvi_at(now)?,
|
||||
let (mut image, coords) = match metric {
|
||||
Metric::PAQI => (maps.pollen_at(now)?, maps.pollen_project(position)),
|
||||
Metric::Pollen => (maps.pollen_at(now)?, maps.pollen_project(position)),
|
||||
Metric::UVI => (maps.uvi_at(now)?, maps.uvi_project(position)),
|
||||
_ => return None, // Unsupported metric
|
||||
};
|
||||
drop(maps);
|
||||
|
||||
// Paint the provided position on the map using a 11×11 pixel square.
|
||||
// FIXME: Use an actual correct calculation, instead of this very rough estimation!
|
||||
let rel_x = (lon - 3.3) / 4.0; // 3.3..7.3, 4.0
|
||||
let rel_y = 1.0 - ((lat - 50.7) / 3.0); // 50.7..53.7, 3.0
|
||||
let x = (rel_x * image.height() as f64) as u32;
|
||||
let y = (rel_y * image.width() as f64) as u32;
|
||||
for px in x - 5..=x + 5 {
|
||||
for py in y - 5..=y + 5 {
|
||||
image.put_pixel(px, py, Rgba::from([0xff, 0x0, 0x0, 0x70]));
|
||||
if let Some((x, y)) = coords {
|
||||
for py in 0..(image.height() - 1) {
|
||||
image.put_pixel(x, py, Rgba::from([0x00, 0x00, 0x00, 0x70]));
|
||||
}
|
||||
|
||||
for px in 0..(image.width() - 1) {
|
||||
image.put_pixel(px, y, Rgba::from([0x00, 0x00, 0x00, 0x70]));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
59
src/maps.rs
59
src/maps.rs
|
@ -6,11 +6,12 @@
|
|||
// TODO: Allow dead code until #9 is implemented.
|
||||
#![allow(dead_code)]
|
||||
|
||||
use std::f64::consts::PI;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use chrono::serde::ts_seconds;
|
||||
use chrono::{DateTime, Utc};
|
||||
use image::{DynamicImage, ImageFormat};
|
||||
use image::{DynamicImage, GenericImageView, ImageFormat};
|
||||
use reqwest::Url;
|
||||
use rocket::serde::Serialize;
|
||||
use rocket::tokio;
|
||||
|
@ -47,8 +48,8 @@ const POLLEN_MAP_INTERVAL: u64 = 3_600;
|
|||
/// * Latitude and longitude of Vlissingen to its y- and x-position
|
||||
/// * Latitude of Lauwersoog to its y-position and longitude of Enschede to its x-position
|
||||
const POLLEN_MAP_REF_POINTS: [(Position, (u32, u32)); 2] = [
|
||||
(Position::new(5.1, 3.57), (84, 745)), // Vlissingen
|
||||
(Position::new(53.4, 6.9), (111, 694)), // Lauwersoog (lat/y) and Enschede (lon/x)
|
||||
(Position::new(51.44, 3.57), (745, 84)), // Vlissingen
|
||||
(Position::new(53.40, 6.90), (111, 694)), // Lauwersoog (lat/y) and Enschede (lon/x)
|
||||
];
|
||||
|
||||
/// The base URL for retrieving the UV index maps from Buienradar.
|
||||
|
@ -68,7 +69,7 @@ const UVI_MAP_COUNT: u32 = 5;
|
|||
const UVI_MAP_INTERVAL: u64 = 24 * 3_600;
|
||||
|
||||
/// The position reference points for the UV index map.
|
||||
const POLLEN_UVI_REF_POINT: [(Position, (u32, u32)); 2] = POLLEN_MAP_REF_POINTS;
|
||||
const UVI_MAP_REF_POINTS: [(Position, (u32, u32)); 2] = POLLEN_MAP_REF_POINTS;
|
||||
|
||||
/// The `MapsRefresh` trait is used to reduce the time a lock needs to be held when updating maps.
|
||||
///
|
||||
|
@ -144,13 +145,13 @@ impl Maps {
|
|||
})
|
||||
}
|
||||
|
||||
/// Projects the provided geocoded position to a coordinat on a pollen map.
|
||||
/// Projects the provided geocoded position to a coordinate on a pollen map.
|
||||
///
|
||||
/// This returns [`None`] if the maps are not in the cache yet.
|
||||
fn pollen_project(&self, _position: Position) -> Option<(u32, u32)> {
|
||||
// TODO: Use map width (with `POLLEN_MAP_COUNT`), map height and `POLLEN_MAP_REF_POINTS`.
|
||||
// Then, call shared function with `uvi_sample`.
|
||||
todo!();
|
||||
pub(crate) fn pollen_project(&self, position: Position) -> Option<(u32, u32)> {
|
||||
self.pollen
|
||||
.as_ref()
|
||||
.and_then(move |map| project(map, POLLEN_MAP_REF_POINTS, position))
|
||||
}
|
||||
|
||||
/// Samples the pollen maps for the given position.
|
||||
|
@ -183,13 +184,13 @@ impl Maps {
|
|||
})
|
||||
}
|
||||
|
||||
/// Projects the provided geocoded position to a coordinat on an UV index map.
|
||||
/// Projects the provided geocoded position to a coordinate on an UV index map.
|
||||
///
|
||||
/// This returns [`None`] if the maps are not in the cache yet.
|
||||
fn uvi_project(&self, _position: Position) -> Option<(u32, u32)> {
|
||||
// TODO: Use map width (with `UVI_MAP_COUNT`), map height and `UVI_MAP_REF_POINTS`.
|
||||
// Then, call shared function with `pollen_sample`.
|
||||
todo!();
|
||||
pub(crate) fn uvi_project(&self, position: Position) -> Option<(u32, u32)> {
|
||||
self.uvi
|
||||
.as_ref()
|
||||
.and_then(move |map| project(map, UVI_MAP_REF_POINTS, position))
|
||||
}
|
||||
|
||||
/// Samples the UV index maps for the given position.
|
||||
|
@ -319,6 +320,36 @@ async fn retrieve_uvi_maps() -> Option<DynamicImage> {
|
|||
retrieve_image(url).await
|
||||
}
|
||||
|
||||
/// Projects the provided gecoded position to a coordinate on a map.
|
||||
///
|
||||
/// Returns [`None`] if the resulting coordinate is not within the bounds of the map.
|
||||
fn project(
|
||||
map: &DynamicImage,
|
||||
ref_points: [(Position, (u32, u32)); 2],
|
||||
pos: Position,
|
||||
) -> Option<(u32, u32)> {
|
||||
// Get the data from the reference points.
|
||||
let (ref1, (ref1_y, ref1_x)) = ref_points[0];
|
||||
let (ref2, (ref2_y, ref2_x)) = ref_points[1];
|
||||
|
||||
// For the x-coordinate, use a linear scale.
|
||||
let scale_x = ((ref2_x - ref1_x) as f64) / (ref2.lon_as_rad() - ref1.lon_as_rad());
|
||||
let x = ((pos.lon_as_rad() - ref1.lon_as_rad()) * scale_x + ref1_x as f64).round() as u32;
|
||||
|
||||
// For the y-coordinate, use a Mercator-projected scale.
|
||||
let mercator_y = |lat: f64| (lat / 2.0 + PI / 4.0).tan().ln();
|
||||
let ref1_merc_y = mercator_y(ref1.lat_as_rad());
|
||||
let ref2_merc_y = mercator_y(ref2.lat_as_rad());
|
||||
let scale_y = ((ref1_y - ref2_y) as f64) / (ref2_merc_y - ref1_merc_y);
|
||||
let y = ((ref2_merc_y - mercator_y(pos.lat_as_rad())) * scale_y + ref2_y as f64).round() as u32;
|
||||
|
||||
if map.in_bounds(x, y) {
|
||||
Some((x, y))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs a loop that keeps refreshing the maps when necessary.
|
||||
///
|
||||
/// Use [`MapsRefresh`] trait methods on `maps_handle` to check whether each maps type needs to be
|
||||
|
|
|
@ -8,6 +8,8 @@ use cached::proc_macro::cached;
|
|||
use geocoding::{Forward, Openstreetmap, Point};
|
||||
use rocket::tokio;
|
||||
|
||||
use std::f64::consts::PI;
|
||||
|
||||
/// A (geocoded) position.
|
||||
///
|
||||
/// This is used for measuring and communication positions directly on the Earth as latitude and
|
||||
|
@ -46,6 +48,16 @@ impl Position {
|
|||
(self.lon * 10_000.0).round() as i32
|
||||
}
|
||||
|
||||
/// Returns the latitude in radians.
|
||||
pub(crate) fn lat_as_rad(&self) -> f64 {
|
||||
self.lat * PI / 180.0
|
||||
}
|
||||
|
||||
/// Returns the longitude in radians.
|
||||
pub(crate) fn lon_as_rad(&self) -> f64 {
|
||||
self.lon * PI / 180.0
|
||||
}
|
||||
|
||||
/// Returns the latitude as a string with the given precision.
|
||||
pub(crate) fn lat_as_str(&self, precision: usize) -> String {
|
||||
format!("{:.*}", precision, self.lat)
|
||||
|
|
Loading…
Reference in New Issue