diff --git a/.DS_Store b/.DS_Store index d984dd2..5008ddf 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/Cargo.lock b/Cargo.lock index 852250c..063a8e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,9 +39,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.71" +version = "1.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c7d0618f0e0b7e8ff11427422b64564d5fb0be1940354bfe2e0529b18a9d9b8" +checksum = "3b13c32d80ecc7ab747b80c3784bce54ee8a7a0cc4fbda9bf4cda2cf6fe90854" [[package]] name = "approx" @@ -98,6 +98,12 @@ dependencies = [ "which", ] +[[package]] +name = "bit_field" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc827186963e592360843fb5ba4b973e145841266c1357f7180c43526f2e5b61" + [[package]] name = "bitflags" version = "1.3.2" @@ -228,6 +234,7 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" name = "cinrad_g" version = "0.1.0" dependencies = [ + "anyhow", "cairo-rs", "epoxy", "femtovg", @@ -239,12 +246,14 @@ dependencies = [ "glow", "glue", "gtk4", + "image", "libloading 0.8.0", "ndarray", "npyz", "num-traits", "proj", "proj-sys", + "proj5", "quadtree_rs", "shapefile", "svg", @@ -369,6 +378,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + [[package]] name = "crypto-common" version = "0.1.6" @@ -497,6 +512,31 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88bffebc5d80432c9b140ee17875ff173a8ab62faad5b257da912bd2f6c1c0a1" +[[package]] +name = "exr" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1e481eb11a482815d3e9d618db8c42a93207134662873809335a92327440c18" +dependencies = [ + "bit_field", + "flume", + "half", + "lebe", + "miniz_oxide", + "rayon-core", + "smallvec", + "zune-inflate", +] + +[[package]] +name = "fdeflate" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d329bdeac514ee06249dabc27877490f17f5d371ec693360768b838e19f3ae10" +dependencies = [ + "simd-adler32", +] + [[package]] name = "femtovg" version = "0.7.1" @@ -550,6 +590,19 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "flume" +version = "0.10.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577" +dependencies = [ + "futures-core", + "futures-sink", + "nanorand", + "pin-project", + "spin", +] + [[package]] name = "fnv" version = "1.0.7" @@ -599,6 +652,12 @@ dependencies = [ "syn 2.0.22", ] +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + [[package]] name = "futures-task" version = "0.3.28" @@ -718,6 +777,29 @@ dependencies = [ "serde", ] +[[package]] +name = "getrandom" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "gif" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80792593675e051cf94a4b111980da2ba60d4a83e43e0048c5693baab3977045" +dependencies = [ + "color_quant", + "weezl", +] + [[package]] name = "gio" version = "0.17.10" @@ -966,6 +1048,15 @@ dependencies = [ "system-deps", ] +[[package]] +name = "half" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +dependencies = [ + "crunchy", +] + [[package]] name = "hashbrown" version = "0.14.0" @@ -1016,15 +1107,21 @@ checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" [[package]] name = "image" -version = "0.24.6" +version = "0.24.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527909aa81e20ac3a44803521443a765550f09b5130c2c2fa1ea59c2f8f50a3a" +checksum = "6f3dfdbdd72063086ff443e297b61695500514b1e41095b6fb9a5ab48a70a711" dependencies = [ "bytemuck", "byteorder", "color_quant", + "exr", + "gif", + "jpeg-decoder", "num-rational 0.4.1", "num-traits", + "png", + "qoi", + "tiff", ] [[package]] @@ -1061,6 +1158,15 @@ dependencies = [ "libc", ] +[[package]] +name = "jpeg-decoder" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc0000e42512c92e31c2252315bda326620a4e034105e900c98ec492fa077b3e" +dependencies = [ + "rayon", +] + [[package]] name = "js-sys" version = "0.3.64" @@ -1088,6 +1194,12 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" +[[package]] +name = "lebe" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8" + [[package]] name = "libc" version = "0.2.147" @@ -1120,6 +1232,16 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "lock_api" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1cc9717a20b1bb222f333e6a92fd32f7d8a18ddc5a3191a11af45dcbf4dcd16" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.19" @@ -1170,6 +1292,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7810e0be55b428ada41041c41f32c9f1a42817901b4ccf45fa3d4b6561e74c7" dependencies = [ "adler", + "simd-adler32", +] + +[[package]] +name = "nanorand" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a51313c5820b0b02bd422f4b44776fbf47961755c74ce64afc73bfad10226c3" +dependencies = [ + "getrandom", ] [[package]] @@ -1451,6 +1583,26 @@ dependencies = [ "sha2", ] +[[package]] +name = "pin-project" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda4ed1c6c173e3fc7a83629421152e01d7b1f9b7f65fb301e490e8cfc656422" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4359fd9c9171ec6e8c62926d6faaf553a8dc3f64e1507e76da7911b4f6a04405" +dependencies = [ + "proc-macro2 1.0.63", + "quote 1.0.29", + "syn 2.0.22", +] + [[package]] name = "pin-project-lite" version = "0.2.10" @@ -1469,6 +1621,19 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "png" +version = "0.17.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59871cc5b6cce7eaccca5a802b4173377a1c2ba90654246789a8fa2334426d11" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -1529,9 +1694,9 @@ dependencies = [ [[package]] name = "proj" -version = "0.27.1" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f67b3ee003b8b02f854a82e51f78a11082bae993a3e25963ae44df8f9fd2d0d0" +checksum = "7ad1830ad8966eba22c76e78440458f07bd812bef5c3efdf335dec55cd1085ab" dependencies = [ "geo-types", "libc", @@ -1553,6 +1718,15 @@ dependencies = [ "tar", ] +[[package]] +name = "proj5" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60099264c806d50862be737c6cfecf83a4f6de7df5009e2b3708e0821498bdf3" +dependencies = [ + "scoped_threadpool", +] + [[package]] name = "py_literal" version = "0.4.0" @@ -1566,6 +1740,15 @@ dependencies = [ "pest_derive", ] +[[package]] +name = "qoi" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6d64c71eb498fe9eae14ce4ec935c555749aef511cca85b5568910d6e48001" +dependencies = [ + "bytemuck", +] + [[package]] name = "quadtree_rs" version = "0.1.3" @@ -1694,6 +1877,12 @@ dependencies = [ "unicode-script", ] +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + [[package]] name = "scopeguard" version = "1.1.0" @@ -1784,6 +1973,12 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43b2853a4d09f215c24cc5489c992ce46052d359b5109343cbafbf26bc62f8a3" +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + [[package]] name = "slab" version = "0.4.8" @@ -1808,6 +2003,15 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "strsim" version = "0.7.0" @@ -1933,6 +2137,17 @@ dependencies = [ "syn 2.0.22", ] +[[package]] +name = "tiff" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d172b0f4d3fba17ba89811858b9d3d97f928aece846475bbda076ca46736211" +dependencies = [ + "flate2", + "jpeg-decoder", + "weezl", +] + [[package]] name = "time" version = "0.3.22" @@ -2073,6 +2288,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + [[package]] name = "wasm-bindgen" version = "0.2.87" @@ -2137,6 +2358,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "weezl" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9193164d4de03a926d909d3bc7c30543cecb35400c02114792c2cae20d5e2dbb" + [[package]] name = "which" version = "4.4.0" @@ -2331,3 +2558,12 @@ dependencies = [ "libc", "pkg-config", ] + +[[package]] +name = "zune-inflate" +version = "0.2.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ab332fe2f6680068f3582b16a24f90ad7096d5d39b974d1c0aff0125116f02" +dependencies = [ + "simd-adler32", +] diff --git a/Cargo.toml b/Cargo.toml index 83547fd..aec73d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ edition = "2021" cairo-rs = { version = "0.17.0", features = ["xlib"] } glib = "0.17.9" gtk = { version = "0.6.6", package = "gtk4", features = ["v4_8"] } -proj = "0.27.0" # gtk = "0.15.5" @@ -29,6 +28,10 @@ glue = "0.8.7" epoxy = "0.1.0" femtovg = "0.7.1" glow = "0.12.2" +proj = "0.27.2" +image = "0.24.7" +anyhow = "1.0.72" +proj5 = { version = "0.1.7", features = ["multithreading"] } [build-dependencies] diff --git a/src/backend.rs b/src/backend.rs deleted file mode 100644 index f813440..0000000 --- a/src/backend.rs +++ /dev/null @@ -1,335 +0,0 @@ -use cairo::{Context as CairoContext, FontSlant, FontWeight}; - -#[allow(unused_imports)] - -/// The drawing backend that is backed with a Cairo context -pub struct CairoBackend<'a> { - context: &'a CairoContext, - width: u32, - height: u32, - init_flag: bool, -} - -#[derive(Debug)] -pub struct CairoError; - -impl std::fmt::Display for CairoError { - fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(fmt, "{:?}", self) - } -} - -impl std::error::Error for CairoError {} - -impl<'a> CairoBackend<'a> { - fn set_color(&self, color: &BackendColor) { - self.context.set_source_rgba( - f64::from(color.rgb.0) / 255.0, - f64::from(color.rgb.1) / 255.0, - f64::from(color.rgb.2) / 255.0, - color.alpha, - ); - } - - fn set_stroke_width(&self, width: u32) { - self.context.set_line_width(f64::from(width)); - } - - fn set_font(&self, font: &S) { - match font.style() { - FontStyle::Normal => self.context.select_font_face( - font.family().as_str(), - FontSlant::Normal, - FontWeight::Normal, - ), - FontStyle::Bold => self.context.select_font_face( - font.family().as_str(), - FontSlant::Normal, - FontWeight::Bold, - ), - FontStyle::Oblique => self.context.select_font_face( - font.family().as_str(), - FontSlant::Oblique, - FontWeight::Normal, - ), - FontStyle::Italic => self.context.select_font_face( - font.family().as_str(), - FontSlant::Italic, - FontWeight::Normal, - ), - }; - self.context.set_font_size(font.size()); - } - - pub fn new(context: &'a CairoContext, (w, h): (u32, u32)) -> Result { - Ok(Self { - context, - width: w, - height: h, - init_flag: false, - }) - } -} - -impl<'a> DrawingBackend for CairoBackend<'a> { - type ErrorType = cairo::Error; - - fn get_size(&self) -> (u32, u32) { - (self.width, self.height) - } - - fn ensure_prepared(&mut self) -> Result<(), DrawingErrorKind> { - if !self.init_flag { - let (x0, y0, x1, y1) = self - .context - .clip_extents() - .map_err(DrawingErrorKind::DrawingError)?; - - self.context.scale( - (x1 - x0) / f64::from(self.width), - (y1 - y0) / f64::from(self.height), - ); - - self.init_flag = true; - } - - Ok(()) - } - - fn present(&mut self) -> Result<(), DrawingErrorKind> { - Ok(()) - } - - fn draw_pixel( - &mut self, - point: BackendCoord, - color: BackendColor, - ) -> Result<(), DrawingErrorKind> { - self.context - .rectangle(f64::from(point.0), f64::from(point.1), 1.0, 1.0); - self.context.set_source_rgba( - f64::from(color.rgb.0) / 255.0, - f64::from(color.rgb.1) / 255.0, - f64::from(color.rgb.2) / 255.0, - color.alpha, - ); - - self.context - .fill() - .map_err(DrawingErrorKind::DrawingError)?; - - Ok(()) - } - - fn draw_line( - &mut self, - from: BackendCoord, - to: BackendCoord, - style: &S, - ) -> Result<(), DrawingErrorKind> { - self.set_color(&style.color()); - self.set_stroke_width(style.stroke_width()); - - self.context.move_to(f64::from(from.0), f64::from(from.1)); - self.context.line_to(f64::from(to.0), f64::from(to.1)); - - self.context - .stroke() - .map_err(DrawingErrorKind::DrawingError)?; - - Ok(()) - } - - fn draw_rect( - &mut self, - upper_left: BackendCoord, - bottom_right: BackendCoord, - style: &S, - fill: bool, - ) -> Result<(), DrawingErrorKind> { - self.set_color(&style.color()); - self.set_stroke_width(style.stroke_width()); - - self.context.rectangle( - f64::from(upper_left.0), - f64::from(upper_left.1), - f64::from(bottom_right.0 - upper_left.0), - f64::from(bottom_right.1 - upper_left.1), - ); - - if fill { - self.context - .fill() - .map_err(DrawingErrorKind::DrawingError)?; - } else { - self.context - .stroke() - .map_err(DrawingErrorKind::DrawingError)?; - } - - Ok(()) - } - - fn draw_path>( - &mut self, - path: I, - style: &S, - ) -> Result<(), DrawingErrorKind> { - self.set_color(&style.color()); - self.set_stroke_width(style.stroke_width()); - - let mut path = path.into_iter(); - if let Some((x, y)) = path.next() { - self.context.move_to(f64::from(x), f64::from(y)); - } - - for (x, y) in path { - self.context.line_to(f64::from(x), f64::from(y)); - } - - self.context - .stroke() - .map_err(DrawingErrorKind::DrawingError)?; - - Ok(()) - } - - fn fill_polygon>( - &mut self, - path: I, - style: &S, - ) -> Result<(), DrawingErrorKind> { - self.set_color(&style.color()); - self.set_stroke_width(style.stroke_width()); - - let mut path = path.into_iter(); - - if let Some((x, y)) = path.next() { - self.context.move_to(f64::from(x), f64::from(y)); - - for (x, y) in path { - self.context.line_to(f64::from(x), f64::from(y)); - } - - self.context.close_path(); - self.context - .fill() - .map_err(DrawingErrorKind::DrawingError)?; - } - - Ok(()) - } - - fn draw_circle( - &mut self, - center: BackendCoord, - radius: u32, - style: &S, - fill: bool, - ) -> Result<(), DrawingErrorKind> { - self.set_color(&style.color()); - self.set_stroke_width(style.stroke_width()); - - self.context.new_sub_path(); - self.context.arc( - f64::from(center.0), - f64::from(center.1), - f64::from(radius), - 0.0, - std::f64::consts::PI * 2.0, - ); - - if fill { - self.context - .fill() - .map_err(DrawingErrorKind::DrawingError)?; - } else { - self.context - .stroke() - .map_err(DrawingErrorKind::DrawingError)?; - } - - Ok(()) - } - - fn estimate_text_size( - &self, - text: &str, - font: &S, - ) -> Result<(u32, u32), DrawingErrorKind> { - self.set_font(font); - - let extents = self - .context - .text_extents(text) - .map_err(DrawingErrorKind::DrawingError)?; - - Ok((extents.width() as u32, extents.height() as u32)) - } - - fn draw_text( - &mut self, - text: &str, - style: &S, - pos: BackendCoord, - ) -> Result<(), DrawingErrorKind> { - let color = style.color(); - let (mut x, mut y) = (pos.0, pos.1); - - let degree = match style.transform() { - FontTransform::None => 0.0, - FontTransform::Rotate90 => 90.0, - FontTransform::Rotate180 => 180.0, - FontTransform::Rotate270 => 270.0, - //FontTransform::RotateAngle(angle) => angle as f64, - } / 180.0 - * std::f64::consts::PI; - - if degree != 0.0 { - self.context - .save() - .map_err(DrawingErrorKind::DrawingError)?; - self.context.translate(f64::from(x), f64::from(y)); - self.context.rotate(degree); - - x = 0; - y = 0; - } - - self.set_font(style); - self.set_color(&color); - - let extents = self - .context - .text_extents(text) - .map_err(DrawingErrorKind::DrawingError)?; - - let dx = match style.anchor().h_pos { - HPos::Left => 0.0, - HPos::Right => -extents.width(), - HPos::Center => -extents.width() / 2.0, - }; - let dy = match style.anchor().v_pos { - VPos::Top => extents.height(), - VPos::Center => extents.height() / 2.0, - VPos::Bottom => 0.0, - }; - - self.context.move_to( - f64::from(x) + dx - extents.x_bearing(), - f64::from(y) + dy - extents.y_bearing() - extents.height(), - ); - - self.context - .show_text(text) - .map_err(DrawingErrorKind::DrawingError)?; - - if degree != 0.0 { - self.context - .restore() - .map_err(DrawingErrorKind::DrawingError)?; - } - - Ok(()) - } -} diff --git a/src/data/mapper.rs b/src/coords/mapper.rs similarity index 97% rename from src/data/mapper.rs rename to src/coords/mapper.rs index 28af08d..eeaabed 100644 --- a/src/data/mapper.rs +++ b/src/coords/mapper.rs @@ -1,20 +1,14 @@ use geo_types::{coord, Coord as GCoord, LineString}; -use num_traits::Num; use proj::{Proj, ProjError}; use std::ops::Range; -use crate::{ - painter::{wgs84::ProjectionS, Coord}, - render::WindowCoord, -}; +use super::proj::ProjectionS; pub struct Mapper { proj: Proj, range: (Range, Range), bounds: (f64, f64, f64, f64), } -unsafe impl Sync for Mapper {} - impl From for Mapper { fn from(proj: Proj) -> Self { let default_range: (Range, Range) = (-180.0..180.0, -90.0..90.0); @@ -189,6 +183,4 @@ impl Mapper { } Ok(()) } - - fn resample(&self) {} } diff --git a/src/coords/mod.rs b/src/coords/mod.rs new file mode 100644 index 0000000..43f08f1 --- /dev/null +++ b/src/coords/mod.rs @@ -0,0 +1,152 @@ +use num_traits::AsPrimitive; +use num_traits::Num; +pub mod mapper; +pub mod proj; +pub mod wgs84; + +pub use mapper::Mapper; +pub use wgs84::LatLonCoord; + +pub type ScreenCoord = (f64, f64); +type Lat = f64; +type Lon = f64; + +pub trait Coord { + fn map(&self, axis_1: T, axis_2: T) -> ScreenCoord; + fn dim1_range(&self) -> (T, T); + fn dim2_range(&self) -> (T, T); +} + +#[derive(Clone, Copy)] +pub struct Range(pub f64, pub f64); + +impl Range { + pub fn key_points(&self, max_points: usize) -> Vec { + if max_points == 0 { + return vec![]; + } + + let range = (self.0.min(self.1) as f64, self.1.max(self.0) as f64); + assert!(!(range.0.is_nan() || range.1.is_nan())); + if (range.0 - range.1).abs() < std::f64::EPSILON { + return vec![range.0 as f64]; + } + + let mut scale = (10f64).powf((range.1 - range.0).log(10.0).floor()); + // The value granularity controls how we round the values. + // To avoid generating key points like 1.00000000001, we round to the nearest multiple of the + // value granularity. + // By default, we make the granularity as the 1/10 of the scale. + let mut value_granularity = scale / 10.0; + fn rem_euclid(a: f64, b: f64) -> f64 { + let ret = if b > 0.0 { + a - (a / b).floor() * b + } else { + a - (a / b).ceil() * b + }; + if (ret - b).abs() < std::f64::EPSILON { + 0.0 + } else { + ret + } + } + + // At this point we need to make sure that the loop invariant: + // The scale must yield number of points than requested + if 1 + ((range.1 - range.0) / scale).floor() as usize > max_points { + scale *= 10.0; + value_granularity *= 10.0; + } + + 'outer: loop { + let old_scale = scale; + for nxt in [2.0, 5.0, 10.0].iter() { + let mut new_left = range.0 - rem_euclid(range.0, old_scale / nxt); + if new_left < range.0 { + new_left += old_scale / nxt; + } + let new_right = range.1 - rem_euclid(range.1, old_scale / nxt); + + let npoints = 1.0 + ((new_right - new_left) / old_scale * nxt); + + if npoints.round() as usize > max_points { + break 'outer; + } + + scale = old_scale / nxt; + } + scale = old_scale / 10.0; + value_granularity /= 10.0; + } + + let mut ret = vec![]; + // In some extreme cases, left might be too big, so that (left + scale) - left == 0 due to + // floating point error. + // In this case, we may loop forever. To avoid this, we need to use two variables to store + // the current left value. So we need keep a left_base and a left_relative. + let left = { + let mut value = range.0 - rem_euclid(range.0, scale); + if value < range.0 { + value += scale; + } + value + }; + let left_base = (left / value_granularity).floor() * value_granularity; + let mut left_relative = left - left_base; + let right = range.1 - rem_euclid(range.1, scale); + while (right - left_relative - left_base) >= -std::f64::EPSILON { + let new_left_relative = (left_relative / value_granularity).round() * value_granularity; + if new_left_relative < 0.0 { + left_relative += value_granularity; + } + ret.push((left_relative + left_base) as f64); + left_relative += scale; + } + return ret; + } +} + +impl + Num> From<(T, T)> for Range { + fn from(value: (T, T)) -> Self { + let value = (value.0.as_(), value.1.as_()); + let (_min, _max) = (value.0.min(value.1), value.0.max(value.1)); + Self(_min, _max) + } +} + +// impl RadarData2d +// where +// T: Num + Clone + PartialEq + PartialOrd, +// Raw: ndarray::Data + Clone + ndarray::RawDataClone, +// { +// pub fn mapped(&self, coord: impl Borrow) -> Result, ProjError> { +// let mapper: &Mapper = coord.borrow(); +// self.map_by_fn(|x| mapper.map(x)) +// } + +// pub fn map_by_fn(&self, f: F) -> Result, ProjError> +// where +// F: Fn((f64, f64)) -> Result<(f64, f64), ProjError>, +// { +// let mesh_dim1_len = self.dim1.len(); +// let mesh_dim2_len = self.dim2.len(); + +// let mut d1 = Array2::::zeros((mesh_dim2_len, mesh_dim1_len)); +// let mut d2 = Array2::::zeros((mesh_dim2_len, mesh_dim1_len)); + +// for (i, v) in self.dim1.iter().enumerate() { +// for (j, u) in self.dim2.iter().enumerate() { +// let (x, y) = f((*v, *u))?; +// d1[[j, i]] = x; +// d2[[j, i]] = y; +// } +// } + +// Ok(AfterMapping2d { +// dim1: d1, +// dim2: d2, +// data: self.data.view(), +// coord_type: self.coord_type.clone(), +// }) +// } +// } diff --git a/src/coords/proj/mercator.rs b/src/coords/proj/mercator.rs new file mode 100644 index 0000000..1847715 --- /dev/null +++ b/src/coords/proj/mercator.rs @@ -0,0 +1,80 @@ +use crate::coords::Range; + +use super::ProjectionS; +use geo_macros::Prj; + +#[derive(Prj)] +/// A struct representing the Mercator projection. +pub struct Mercator { + /// The central longitude of the projection. + pub central_lon: f64, + /// The minimum latitude of the projection. + pub min_latitude: f64, + /// The maximum latitude of the projection. + pub max_latitude: f64, + /// The false easting of the projection. + pub false_easting: f64, + /// The false northing of the projection. + pub false_northing: f64, + /// The latitude of true scale of the projection. + pub latitude_true_scale: f64, +} + +fn proj_string<'a>(vs: Vec<(&'a str, &'a str)>) -> String { + vs.into_iter() + .map(|(option, value)| format!("+{}={}", option, value)) + .collect::>() + .join(" ") +} + +impl Mercator { + /// Creates a new instance of the `Mercator` projection with default values. + pub fn new() -> Self { + Self { + central_lon: 0.0, + min_latitude: -82.0, + max_latitude: 82.0, + false_easting: 0.0, + false_northing: 0.0, + latitude_true_scale: 0.0, + } + } +} + +impl ProjectionS for Mercator { + fn build(&self) -> String { + let _central_lon = format!("{:.1}", &self.central_lon); + let _false_easting = format!("{:.1}", &self.false_easting); + let _false_northing = format!("{:.1}", &self.false_northing); + let _ts = format!("{:.1}", &self.latitude_true_scale); + + let input = vec![ + ("proj", "merc"), + ("lon_0", _central_lon.as_str()), + ("x_0", _false_easting.as_str()), + ("y_0", _false_northing.as_str()), + ("lat_ts", _ts.as_str()), + ("units", "m"), + ("ellps", "GRS80"), + ]; + let _proj_string = proj_string(input); + _proj_string + } + + fn logic_range(&self, lon_range: Option, lat_range: Option) -> (Range, Range) { + let lon_range = lon_range.unwrap_or(Range { + 0: -180f64, + 1: 180f64, + }); + let lat_range = lat_range.unwrap_or(Range { + 0: self.min_latitude, + 1: self.max_latitude, + }); + + let lat_range = Range { + 0: lat_range.0.max(self.min_latitude), + 1: lat_range.1.min(self.max_latitude), + }; + (lon_range, lat_range) + } +} diff --git a/src/coords/proj/mod.rs b/src/coords/proj/mod.rs new file mode 100644 index 0000000..4cf882d --- /dev/null +++ b/src/coords/proj/mod.rs @@ -0,0 +1,79 @@ +use super::{Lat, Lon, Range}; +use proj::Proj; +use proj5; +use thiserror::Error; +mod mercator; + +pub use mercator::Mercator; + +#[derive(Debug, Clone, Copy)] +pub enum Projs { + PlateCarree, + LambertConformal, + LambertCylindrical, + Mercator, +} + +/// A trait for defining a projection. +pub trait ProjectionS { + /// Returns a proj-string of the projection. + fn build(&self) -> String; + + /// Returns the logical range of the projection. + /// In common, different projections have different logical ranges. + /// # Arguments + /// + /// * `lon_range` - An optional longitude range. + /// * `lat_range` - An optional latitude range. + /// + /// # Returns + /// + /// A tuple containing the longitude and latitude ranges. + fn logic_range(&self, lon_range: Option, lat_range: Option) -> (Range, Range); +} + +#[derive(Debug, Error)] +pub(super) enum ProjError { + #[error("proj error")] + ProjError(#[from] proj::ProjError), +} + +pub(super) struct PCS { + pub lon_range: Range, + pub lat_range: Range, + pub proj_param: T, + // pub proj_target: proj5::CoordinateBuf, + pub transformer: Proj, +} + +impl PCS { + pub(super) fn new(proj_param: T, lon_range: Option, lat_range: Option) -> Self { + let (lon_range, lat_range) = proj_param.logic_range(lon_range, lat_range); + Self { + lon_range, + lat_range, + transformer: Proj::new(proj_param.build().as_str()).unwrap(), + proj_param: proj_param, + } + } + + pub fn bbox(&self) -> Result<(Range, Range), ProjError> { + let _proj_transformer = &self.transformer; + let lb = (self.lon_range.0.to_radians(), self.lat_range.0.to_radians()); + let rt = (self.lon_range.1.to_radians(), self.lat_range.1.to_radians()); + + let bl = _proj_transformer.convert(lb)?; + let rt = _proj_transformer.convert(rt)?; + + Ok((Range::from((bl.0, rt.0)), Range::from((bl.1, rt.1)))) + } + + pub fn map(&self, lon_lat: (Lat, Lon)) -> (f64, f64) { + let _proj_transformer = &self.transformer; + let _lon_lat = _proj_transformer + .convert((lon_lat.0.to_radians(), lon_lat.1.to_radians())) + .unwrap(); + + _lon_lat + } +} diff --git a/src/coords/wgs84.rs b/src/coords/wgs84.rs new file mode 100644 index 0000000..6ad5f50 --- /dev/null +++ b/src/coords/wgs84.rs @@ -0,0 +1,88 @@ +use super::proj::{ProjectionS, PCS}; +use super::Coord; +use super::{Lat, Lon, Range}; +use proj::ProjError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum CoordError { + #[error("")] + ProjError { + #[from] + source: ProjError, + }, +} + +pub struct LatLonCoord { + actual: (Range, Range), + logical: (Range, Range), + pcs: PCS, +} + +impl LatLonCoord { + /// Creates a new `LatLonCoord` instance. + /// + /// # Arguments + /// + /// * `lon` - An optional longitude range. + /// * `lat` - An optional latitude range. + /// * `actual` - A tuple containing the actual ranges. + /// * `project` - A projection. + /// + /// # Returns + /// + /// A new `LatLonCoord` instance. + pub fn new( + lon: Option, + lat: Option, + actual: ((i32, i32), (i32, i32)), + // actual: (Range, Range), + project: T, + ) -> Self { + let pcs = PCS::new(project, lon, lat); + let _box = pcs.bbox().unwrap(); + Self { + actual: (Range::from(actual.0), Range::from(actual.1)), + pcs: pcs, + logical: _box, + } + } + + pub fn set_actual(&mut self, actual: ((i32, i32), (i32, i32))) { + self.actual = (Range::from(actual.0), Range::from(actual.1)); + } + + pub fn lon_range(&self) -> Range { + self.pcs.lon_range + } + + pub fn lat_range(&self) -> Range { + self.pcs.lat_range + } +} + +impl Coord for LatLonCoord { + fn map(&self, axis_1: f64, axis_2: f64) -> super::ScreenCoord { + let point = self.pcs.map((axis_1, axis_2)); + let logical_dim1_span = self.logical.0 .1 - self.logical.0 .0; + + let dim1_rate = (point.0 - self.logical.0 .0) / logical_dim1_span; + let logical_dim2_span = self.logical.1 .1 - self.logical.1 .0; + let dim2_rate = (point.1 - self.logical.1 .0) / logical_dim2_span; + + ( + (dim1_rate * (self.actual.0 .1 - self.actual.0 .0) as f64) + self.actual.0 .0, + (dim2_rate * (self.actual.1 .1 - self.actual.1 .0) as f64) + self.actual.1 .0, + ) + } + + fn dim1_range(&self) -> (f64, f64) { + let v = self.lon_range(); + (v.0, v.1) + } + + fn dim2_range(&self) -> (f64, f64) { + let v = self.lat_range(); + (v.0, v.1) + } +} diff --git a/src/data/concrete_data.rs b/src/data/concrete_data.rs deleted file mode 100644 index 07c894a..0000000 --- a/src/data/concrete_data.rs +++ /dev/null @@ -1,3 +0,0 @@ -use super::RadarData2d; - -pub type RadarReflectivity = RadarData2d; diff --git a/src/data/mod.rs b/src/data/mod.rs index ef393b8..685222c 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,36 +1,22 @@ +use crate::errors::DataError; +use image::RgbImage; use ndarray::{ - s, Array, Array1, Array2, Array3, ArrayBase, Axis, Ix1, Ix2, OwnedRepr, RawDataClone, ViewRepr, + s, Array, Array1, Array2, Array3, ArrayBase, Axis, DataMut, Ix1, Ix2, OwnedRepr, RawDataClone, + ViewRepr, }; use npyz::{npz::NpzArchive, Deserialize}; use num_traits::{AsPrimitive, FromPrimitive, Num}; -use quadtree_rs::{area::AreaBuilder, Quadtree}; use std::{self, f64::consts::PI, fmt::Debug, io::BufReader, path::Path}; -use thiserror::Error; -mod concrete_data; -pub mod mapper; +pub type Radar2d = RadarData2d>; -#[derive(Error, Debug)] -pub enum DataError { - #[error("value")] - FormatError, - #[error("")] - IOError { - #[from] - source: std::io::Error, - }, -} - -pub enum DownSampleMeth { - STD, - MEAN, - VAR, -} - -#[derive(Clone, Copy)] -pub enum CoordType { - Polar, - LatLon, +pub trait MultiDimensionData +where + T: Num + Clone + PartialEq + PartialOrd, +{ + fn map_by_fn(&mut self, f: F) + where + F: FnMut(&mut T); } #[derive(Clone)] @@ -47,16 +33,12 @@ where pub coord_type: CoordType, } -pub type Radar2d = RadarData2d>; - impl Radar2d { - pub fn load(path: impl AsRef, meth: impl DataLoader) -> Result { + pub fn load(path: impl AsRef, meth: impl DataLoader) -> Result { Ok(meth.load(path)?) } } -pub type Radar2dRef<'a, T> = RadarData2d>; - pub struct RadarData3d where T: Num, @@ -70,8 +52,6 @@ where pub data: Array3, } -pub trait MultiDimensionData {} - impl RadarData2d where T: Num + AsPrimitive + FromPrimitive + Clone + Debug + PartialOrd + PartialEq, @@ -95,17 +75,27 @@ where let mut output: Array2 = Array2::zeros(output_shape); - let mut dim1 = Array1::zeros(output_shape.1); - let mut dim2 = Array1::zeros(output_shape.0); + let dim1 = Array1::linspace( + *self.dim1.first().unwrap(), + *self.dim1.last().unwrap(), + output_shape.1, + ); + + let dim2 = Array1::linspace( + *self.dim2.first().unwrap(), + *self.dim2.last().unwrap(), + output_shape.0, + ); + // let mut dim2 = Array1::zeros(output_shape.0); output.iter_mut().enumerate().for_each(|(s, vv)| { let ri = s / output_shape.1; let ci = s % output_shape.1; - let src_yf0 = scale_y * ci as f64; + let src_yf0 = scale_y * ri as f64; let src_yf1 = src_yf0 + scale_y; let src_y0 = src_yf0.floor() as usize; let src_y1 = src_yf1.floor() as usize; - let src_xf0 = scale_x * ri as f64; + let src_xf0 = scale_x * ci as f64; let src_xf1 = src_xf0 + scale_x; let src_x0 = src_xf0.floor() as usize; let src_x1 = src_xf1.floor() as usize; @@ -116,9 +106,6 @@ where src_yf0, src_yf1, src_xf0, src_xf1, src_y0, src_y1, src_x0, src_x1, vv, ), } - - dim1[ci] = self.dim1[src_x0]; - dim2[ri] = self.dim2[src_y0]; }); if let DownSampleMeth::STD = meth { @@ -202,8 +189,8 @@ where if w_sum < f64::EPSILON { return; } - - *row = T::from_f64((wvv_sum * w_sum - wv_sum * wv_sum) / w_sum / w_sum).unwrap(); + let v = ((wvv_sum * w_sum - wv_sum * wv_sum) / w_sum / w_sum).clamp(-125f64, 124f64); + *row = T::from_f64(v).unwrap(); } } @@ -245,15 +232,25 @@ where } } -impl MultiDimensionData for RadarData2d +impl MultiDimensionData for RadarData2d where T: Num + Clone, T: PartialEq + PartialOrd, - Raw: ndarray::Data + Clone + RawDataClone, + Raw: ndarray::Data + Clone + RawDataClone + DataMut, { + fn map_by_fn(&mut self, f: F) + where + F: FnMut(&mut T), + { + self.data.map_inplace(f); + } } -pub trait DataLoader { +pub trait DataLoader +where + V: Num + Clone + PartialEq + PartialOrd, + T: MultiDimensionData, +{ fn load>(&self, path: P) -> Result; } @@ -290,7 +287,7 @@ impl Npz { fn load_3d(&self, data: &mut NpzArchive>) {} } -impl DataLoader>> for Npz +impl DataLoader>> for Npz where T: Num + Clone + Deserialize, T: PartialEq + PartialOrd, @@ -469,3 +466,15 @@ fn windowed_sinc(x: f64, y: f64) -> f64 { }; sinc * window } + +pub enum DownSampleMeth { + STD, + MEAN, + VAR, +} + +#[derive(Clone, Copy)] +pub enum CoordType { + Polar, + LatLon, +} diff --git a/src/errors.rs b/src/errors.rs new file mode 100644 index 0000000..19aa46a --- /dev/null +++ b/src/errors.rs @@ -0,0 +1,22 @@ +use proj::ProjError; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum DataError { + #[error("value")] + FormatError, + #[error("")] + IOError { + #[from] + source: std::io::Error, + }, +} + +#[derive(Debug, Error)] +pub enum PipelineError { + #[error("proj error")] + ProjError { + #[from] + source: ProjError, + }, +} diff --git a/src/main.rs b/src/main.rs index 671cb6d..4026357 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,18 @@ -use data::mapper::Mapper; +use coords::proj::Mercator; +use coords::Mapper; use data::{Npz, Radar2d}; use gtk::prelude::*; use gtk::{gio, glib, Application, ApplicationWindow}; use std::ptr; +mod coords; mod data; +mod errors; mod monitor; -mod painter; +mod pipeline; mod render; mod tree; mod window; use monitor::Monitor; -use painter::wgs84::{LatLonCoord, Mercator, ProjectionS, Range}; use render::{BackgroundConfig, BackgroundWidget, ForegroundConfig, ForegroundWidget, Render}; const APP_ID: &str = "org.gtk_rs.HelloWorld2"; @@ -57,10 +59,8 @@ fn build_ui(app: &Application) { let foreground_widget = ForegroundWidget::new(foreground_config); let render = Render::new(background_widget, foreground_widget); - let path = "/home/tsuki/projects/radar-g/test2.npz"; - + let path = "/Users/ruomu/projects/cinrad_g/test2.npz"; let data = Radar2d::::load(path, Npz).unwrap(); - let projection = Mercator::new(); let mut mapper: Mapper = projection.into(); mapper.set_lat_range(29.960..30.764); @@ -68,7 +68,6 @@ fn build_ui(app: &Application) { render.set_mapper(mapper); let monitor = Monitor::new(render); - monitor.load_data_2d(data).unwrap(); window.set_child(Some(&monitor)); diff --git a/src/monitor/mod.rs b/src/monitor/mod.rs index c29225f..18a0a60 100644 --- a/src/monitor/mod.rs +++ b/src/monitor/mod.rs @@ -22,10 +22,9 @@ impl Monitor { let pointer_location_detecture = gtk::EventControllerMotion::new(); pointer_location_detecture.connect_motion( clone!(@weak this as s => move |_context, x, y| { - s.imp().renderer.borrow() - .imp() - .pointer_location - .replace((x as f32, y as f32)); + s.imp().renderer.borrow().change_cfg( + |cfg| cfg.pointer_location = (x as f32, y as f32) + ); }), ); @@ -34,10 +33,10 @@ impl Monitor { scale_detecture.connect_scroll(clone!( @weak this as s => @default-panic,move |_context, _x, y| { let renderer = s.imp().borrow().renderer.borrow(); - let current_scale = renderer.imp().scale.borrow().clone(); - renderer.imp().scale.replace( - (current_scale + y as f32 / 100.0).max(1.0).min(5.0), - ); + renderer.change_cfg(|cfg| { + let current_scale = cfg.scale; + cfg.scale = (current_scale + y as f32 / 100.0).max(1.0).min(5.0); + }); Inhibit(false) } )); diff --git a/src/painter/coords/mod.rs b/src/painter/coords/mod.rs deleted file mode 100644 index 767221e..0000000 --- a/src/painter/coords/mod.rs +++ /dev/null @@ -1,56 +0,0 @@ -use geo_types::{coord, Coord as GCoord, Line, LineString, Polygon}; -use ndarray::{Array2, ViewRepr}; -use num_traits::Num; -use proj::ProjError; -use std::borrow::Borrow; - -use crate::data::{mapper::Mapper, RadarData2d}; - -use super::AfterMapping2d; -pub mod wgs84; - -pub type ScreenCoord = (f64, f64); - -pub trait Coord: Sync + Send { - fn map(&self, axis_1: T, axis_2: T) -> ScreenCoord; - - fn dim1_range(&self) -> (T, T); - fn dim2_range(&self) -> (T, T); -} - -impl RadarData2d -where - T: Num + Clone + PartialEq + PartialOrd, - Raw: ndarray::Data + Clone + ndarray::RawDataClone, -{ - pub fn mapped(&self, coord: impl Borrow) -> Result, ProjError> { - let mapper: &Mapper = coord.borrow(); - self.map_by_fn(|x| mapper.map(x)) - } - - pub fn map_by_fn(&self, f: F) -> Result, ProjError> - where - F: Fn((f64, f64)) -> Result<(f64, f64), ProjError>, - { - let mesh_dim1_len = self.dim1.len(); - let mesh_dim2_len = self.dim2.len(); - - let mut d1 = Array2::::zeros((mesh_dim2_len, mesh_dim1_len)); - let mut d2 = Array2::::zeros((mesh_dim2_len, mesh_dim1_len)); - - for (i, v) in self.dim1.iter().enumerate() { - for (j, u) in self.dim2.iter().enumerate() { - let (x, y) = f((*v, *u))?; - d1[[j, i]] = x; - d2[[j, i]] = y; - } - } - - Ok(AfterMapping2d { - dim1: d1, - dim2: d2, - data: self.data.view(), - coord_type: self.coord_type.clone(), - }) - } -} diff --git a/src/painter/coords/wgs84.rs b/src/painter/coords/wgs84.rs deleted file mode 100644 index 5b2f66a..0000000 --- a/src/painter/coords/wgs84.rs +++ /dev/null @@ -1,334 +0,0 @@ -use super::Coord; -use geo_macros::Prj; -use num_traits::{AsPrimitive, FromPrimitive, Num}; -use proj::{Proj, ProjError}; -use thiserror::Error; - -type Lat = f64; -type Lon = f64; -#[derive(Clone, Copy)] -pub struct Range(pub f64, pub f64); - -impl Range { - pub fn key_points(&self, max_points: usize) -> Vec { - if max_points == 0 { - return vec![]; - } - - let range = (self.0.min(self.1) as f64, self.1.max(self.0) as f64); - assert!(!(range.0.is_nan() || range.1.is_nan())); - if (range.0 - range.1).abs() < std::f64::EPSILON { - return vec![range.0 as f64]; - } - - let mut scale = (10f64).powf((range.1 - range.0).log(10.0).floor()); - // The value granularity controls how we round the values. - // To avoid generating key points like 1.00000000001, we round to the nearest multiple of the - // value granularity. - // By default, we make the granularity as the 1/10 of the scale. - let mut value_granularity = scale / 10.0; - fn rem_euclid(a: f64, b: f64) -> f64 { - let ret = if b > 0.0 { - a - (a / b).floor() * b - } else { - a - (a / b).ceil() * b - }; - if (ret - b).abs() < std::f64::EPSILON { - 0.0 - } else { - ret - } - } - - // At this point we need to make sure that the loop invariant: - // The scale must yield number of points than requested - if 1 + ((range.1 - range.0) / scale).floor() as usize > max_points { - scale *= 10.0; - value_granularity *= 10.0; - } - - 'outer: loop { - let old_scale = scale; - for nxt in [2.0, 5.0, 10.0].iter() { - let mut new_left = range.0 - rem_euclid(range.0, old_scale / nxt); - if new_left < range.0 { - new_left += old_scale / nxt; - } - let new_right = range.1 - rem_euclid(range.1, old_scale / nxt); - - let npoints = 1.0 + ((new_right - new_left) / old_scale * nxt); - - if npoints.round() as usize > max_points { - break 'outer; - } - - scale = old_scale / nxt; - } - scale = old_scale / 10.0; - value_granularity /= 10.0; - } - - let mut ret = vec![]; - // In some extreme cases, left might be too big, so that (left + scale) - left == 0 due to - // floating point error. - // In this case, we may loop forever. To avoid this, we need to use two variables to store - // the current left value. So we need keep a left_base and a left_relative. - let left = { - let mut value = range.0 - rem_euclid(range.0, scale); - if value < range.0 { - value += scale; - } - value - }; - let left_base = (left / value_granularity).floor() * value_granularity; - let mut left_relative = left - left_base; - let right = range.1 - rem_euclid(range.1, scale); - while (right - left_relative - left_base) >= -std::f64::EPSILON { - let new_left_relative = (left_relative / value_granularity).round() * value_granularity; - if new_left_relative < 0.0 { - left_relative += value_granularity; - } - ret.push((left_relative + left_base) as f64); - left_relative += scale; - } - return ret; - } -} - -#[derive(Error, Debug)] -pub enum CoordError { - #[error("")] - ProjError { - #[from] - source: ProjError, - }, -} - -impl + Num> From<(T, T)> for Range { - fn from(value: (T, T)) -> Self { - let value = (value.0.as_(), value.1.as_()); - let (_min, _max) = (value.0.min(value.1), value.0.max(value.1)); - Self(_min, _max) - } -} - -pub struct LatLonCoord { - // actual: ((f64, f64), (f64, f64)), - actual: (Range, Range), - logical: (Range, Range), - pcs: PCS, -} - -struct PCS { - pub lon_range: Range, - pub lat_range: Range, - pub proj_param: T, - pub transformer: Proj, -} - -unsafe impl Sync for PCS {} -unsafe impl Send for PCS {} - -impl LatLonCoord { - /// Creates a new `LatLonCoord` instance. - /// - /// # Arguments - /// - /// * `lon` - An optional longitude range. - /// * `lat` - An optional latitude range. - /// * `actual` - A tuple containing the actual ranges. - /// * `project` - A projection. - /// - /// # Returns - /// - /// A new `LatLonCoord` instance. - pub fn new( - lon: Option, - lat: Option, - actual: ((i32, i32), (i32, i32)), - // actual: (Range, Range), - project: T, - ) -> Self { - let pcs = PCS::new(project, lon, lat); - let _box = pcs.bbox().unwrap(); - Self { - actual: (Range::from(actual.0), Range::from(actual.1)), - pcs: pcs, - logical: _box, - } - } - - pub fn set_actual(&mut self, actual: ((i32, i32), (i32, i32))) { - self.actual = (Range::from(actual.0), Range::from(actual.1)); - } - - pub fn lon_range(&self) -> Range { - self.pcs.lon_range - } - - pub fn lat_range(&self) -> Range { - self.pcs.lat_range - } -} - -unsafe impl Sync for LatLonCoord {} -unsafe impl Send for LatLonCoord {} - -impl Coord for LatLonCoord { - fn map(&self, axis_1: f64, axis_2: f64) -> super::ScreenCoord { - let point = self.pcs.map((axis_1, axis_2)); - let logical_dim1_span = self.logical.0 .1 - self.logical.0 .0; - - let dim1_rate = (point.0 - self.logical.0 .0) / logical_dim1_span; - let logical_dim2_span = self.logical.1 .1 - self.logical.1 .0; - let dim2_rate = (point.1 - self.logical.1 .0) / logical_dim2_span; - - ( - (dim1_rate * (self.actual.0 .1 - self.actual.0 .0) as f64) + self.actual.0 .0, - (dim2_rate * (self.actual.1 .1 - self.actual.1 .0) as f64) + self.actual.1 .0, - ) - } - - fn dim1_range(&self) -> (f64, f64) { - let v = self.lon_range(); - (v.0, v.1) - } - - fn dim2_range(&self) -> (f64, f64) { - let v = self.lat_range(); - (v.0, v.1) - } -} - -#[derive(Clone, Debug)] -pub enum Projection { - PlateCarree, - LambertConformal, - LambertCylindrical, - Mercator, -} - -/// A trait for defining a projection. -pub trait ProjectionS: Sync + Send { - /// Returns a proj-string of the projection. - fn build(&self) -> String; - - /// Returns the logical range of the projection. - /// In common, different projections have different logical ranges. - /// # Arguments - /// - /// * `lon_range` - An optional longitude range. - /// * `lat_range` - An optional latitude range. - /// - /// # Returns - /// - /// A tuple containing the longitude and latitude ranges. - fn logic_range(&self, lon_range: Option, lat_range: Option) -> (Range, Range); -} - -impl PCS { - fn new(proj_param: T, lon_range: Option, lat_range: Option) -> Self { - let (lon_range, lat_range) = proj_param.logic_range(lon_range, lat_range); - Self { - lon_range, - lat_range, - transformer: Proj::new(proj_param.build().as_str()).unwrap(), - proj_param: proj_param, - } - } - - fn bbox(&self) -> Result<(Range, Range), CoordError> { - let _proj_transformer = &self.transformer; - let lb = (self.lon_range.0.to_radians(), self.lat_range.0.to_radians()); - let rt = (self.lon_range.1.to_radians(), self.lat_range.1.to_radians()); - - let bl = _proj_transformer.convert(lb)?; - let rt = _proj_transformer.convert(rt)?; - - Ok((Range::from((bl.0, rt.0)), Range::from((bl.1, rt.1)))) - } - - fn map(&self, lon_lat: (Lat, Lon)) -> (f64, f64) { - let _proj_transformer = &self.transformer; - let _lon_lat = _proj_transformer - .convert((lon_lat.0.to_radians(), lon_lat.1.to_radians())) - .unwrap(); - - _lon_lat - } -} -#[derive(Prj)] -/// A struct representing the Mercator projection. -pub struct Mercator { - /// The central longitude of the projection. - pub central_lon: f64, - /// The minimum latitude of the projection. - pub min_latitude: f64, - /// The maximum latitude of the projection. - pub max_latitude: f64, - /// The false easting of the projection. - pub false_easting: f64, - /// The false northing of the projection. - pub false_northing: f64, - /// The latitude of true scale of the projection. - pub latitude_true_scale: f64, -} - -fn proj_string<'a>(vs: Vec<(&'a str, &'a str)>) -> String { - vs.into_iter() - .map(|(option, value)| format!("+{}={}", option, value)) - .collect::>() - .join(" ") -} - -impl Mercator { - /// Creates a new instance of the `Mercator` projection with default values. - pub fn new() -> Self { - Self { - central_lon: 0.0, - min_latitude: -82.0, - max_latitude: 82.0, - false_easting: 0.0, - false_northing: 0.0, - latitude_true_scale: 0.0, - } - } -} - -impl ProjectionS for Mercator { - fn build(&self) -> String { - let _central_lon = format!("{:.1}", &self.central_lon); - let _false_easting = format!("{:.1}", &self.false_easting); - let _false_northing = format!("{:.1}", &self.false_northing); - let _ts = format!("{:.1}", &self.latitude_true_scale); - - let input = vec![ - ("proj", "merc"), - ("lon_0", _central_lon.as_str()), - ("x_0", _false_easting.as_str()), - ("y_0", _false_northing.as_str()), - ("lat_ts", _ts.as_str()), - ("units", "m"), - ("ellps", "GRS80"), - ]; - let _proj_string = proj_string(input); - _proj_string - } - - fn logic_range(&self, lon_range: Option, lat_range: Option) -> (Range, Range) { - let lon_range = lon_range.unwrap_or(Range { - 0: -180f64, - 1: 180f64, - }); - let lat_range = lat_range.unwrap_or(Range { - 0: self.min_latitude, - 1: self.max_latitude, - }); - - let lat_range = Range { - 0: lat_range.0.max(self.min_latitude), - 1: lat_range.1.min(self.max_latitude), - }; - (lon_range, lat_range) - } -} diff --git a/src/painter/mod.rs b/src/painter/mod.rs deleted file mode 100644 index 0ceb003..0000000 --- a/src/painter/mod.rs +++ /dev/null @@ -1,27 +0,0 @@ -mod coords; -mod painter; - -pub use coords::wgs84; -pub use coords::Coord; -use ndarray::Ix2; -use ndarray::ViewRepr; -use num_traits::Num; -pub use painter::Painter; - -use crate::data::{RadarData2d, RadarData3d}; - -pub type AfterMapping2d<'a, T> = RadarData2d, f64, f64, Ix2>; -// pub type AfterMapping3d = RadarData3d; - -impl<'a, T> AfterMapping2d<'a, T> -where - T: Num + Clone + PartialEq + PartialOrd, -{ - pub fn traverse(&self, mut f: impl FnMut((usize, f64), (usize, f64)) -> ()) { - self.dim1.iter().enumerate().for_each(|(i, v)| { - self.dim2.iter().enumerate().for_each(|(j, u)| { - f((i, *v), (j, *u)); - }); - }); - } -} diff --git a/src/painter/painter.rs b/src/painter/painter.rs deleted file mode 100644 index 2bec631..0000000 --- a/src/painter/painter.rs +++ /dev/null @@ -1,233 +0,0 @@ -use std::{ - cell::{Ref, RefCell}, - marker::PhantomData, - rc::{self, Rc}, -}; - -use crate::{data::RadarData2d, tree::get}; -use ndarray::parallel::prelude::*; - -use super::coords::{Coord, ScreenCoord}; -use gtk::{ - cairo::*, - gdk::{builders::RGBABuilder, RGBA}, -}; -use num_traits::{FromPrimitive, Num}; -pub struct Painter> { - width: u32, - height: u32, - shift: (f64, f64), - coord: C, - marker: PhantomData, -} - -impl> Painter { - pub fn new(coord: C, width: u32, height: u32) -> Self { - Self { - coord, - width, - height, - shift: (0f64, 0f64), - marker: PhantomData, - } - } - - fn set_color(&self, context: &Context, color: &RGBA) { - context.set_source_rgba( - color.red() as f64, - color.green() as f64, - color.blue() as f64, - color.alpha() as f64, - ); - } - - fn draw_pixel(&self, context: &Context, point: ScreenCoord, color: &RGBA) { - context.rectangle(f64::from(point.0), f64::from(point.1), 1.0, 1.0); - self.set_color(context, color); - - context.fill(); - } - - pub fn set_shift(&mut self, dx: f64, dy: f64) { - self.shift = (self.shift.0 + dx, self.shift.1 + dy); - } - - fn draw_rect( - &self, - context: &Context, - point: ScreenCoord, - width: f64, - height: f64, - color: &RGBA, - fill: bool, - ) { - self.set_color(context, color); - self.set_stroke_width(context, 1); - context.rectangle(point.0, point.1, width, height); - context.fill(); - // if fill { - // self.context.fill(); - // } else { - // self.context.stroke(); - // } - } - - fn translate(&self, context: &Context) { - context.translate(self.shift.0, self.shift.1); - } - - fn set_stroke_width(&self, context: &Context, width: u32) { - context.set_line_width(f64::from(width)); - } - - fn draw_line(&self, context: &Context, start: ScreenCoord, end: ScreenCoord) { - self.set_color(context, &RGBA::BLACK); - self.set_stroke_width(context, 1); - context.move_to(start.0, start.1); - context.line_to(end.0, end.1); - - context.stroke(); - } - - fn draw_path>(&self, context: &Context, path: I) { - let mut path = path.into_iter(); - if let Some((x, y)) = path.next() { - context.move_to(x, y); - } - - for (x, y) in path { - context.line_to(x, y); - } - context.stroke(); - } -} - -impl> Painter { - pub fn draw_radar_2d( - &self, - context: &Context, - data: &RadarData2d, - ) where - D: ndarray::Data + Clone + ndarray::RawDataClone, - { - let levels: Vec = vec![ - T::from_i8(0).unwrap(), - T::from_i8(5).unwrap(), - T::from_i8(10).unwrap(), - T::from_i8(15).unwrap(), - T::from_i8(20).unwrap(), - T::from_i8(25).unwrap(), - T::from_i8(30).unwrap(), - T::from_i8(35).unwrap(), - T::from_i8(40).unwrap(), - T::from_i8(45).unwrap(), - T::from_i8(50).unwrap(), - T::from_i8(55).unwrap(), - T::from_i8(60).unwrap(), - T::from_i8(65).unwrap(), - ]; - let colors = vec![ - RGBABuilder::default() - .red(0 as f32 / 255.0) - .green(172 as f32 / 255.0) - .blue(164 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(192 as f32 / 255.0) - .green(192 as f32 / 255.0) - .blue(254 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(122 as f32 / 255.0) - .green(114 as f32 / 255.0) - .blue(238 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(30 as f32 / 255.0) - .green(38 as f32 / 255.0) - .blue(208 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(166 as f32 / 255.0) - .green(252 as f32 / 255.0) - .blue(168 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(0 as f32 / 255.0) - .green(234 as f32 / 255.0) - .blue(0 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(16 as f32 / 255.0) - .green(146 as f32 / 255.0) - .blue(26 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(252 as f32 / 255.0) - .green(244 as f32 / 255.0) - .blue(100 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(200 as f32 / 255.0) - .green(200 as f32 / 255.0) - .blue(2 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(140 as f32 / 255.0) - .green(140 as f32 / 255.0) - .blue(0 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(254 as f32 / 255.0) - .green(172 as f32 / 255.0) - .blue(172 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(254 as f32 / 255.0) - .green(100 as f32 / 255.0) - .blue(92 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(238 as f32 / 255.0) - .green(2 as f32 / 255.0) - .blue(48 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(212 as f32 / 255.0) - .green(142 as f32 / 255.0) - .blue(254 as f32 / 255.0) - .build(), - RGBABuilder::default() - .red(170 as f32 / 255.0) - .green(36 as f32 / 255.0) - .blue(250 as f32 / 255.0) - .build(), - ]; - - // let mut polygons: Vec<(shapefile::Polygon, _)> = - // shapefile::read_as::<_, shapefile::Polygon, shapefile::dbase::Record>( - // "/Users/ruomu/china/省界_region.shp", - // ) - // .unwrap(); - - // for (polygon, _) in polygons.into_iter() { - // let x_range = polygon.bbox().x_range(); - // let y_range = polygon.bbox().y_range(); - - // let lon_range = self.coord.dim1_range(); - // let lat_range = self.coord.dim2_range(); - // } - - self.translate(context); - data.data.outer_iter().enumerate().for_each(|(r_num, row)| { - row.iter().enumerate().for_each(|(c_num, v)| { - let point = self.coord.map(data.dim1[c_num], data.dim2[r_num]); - if *v > T::from_i8(-125).unwrap() { - let color = get(&levels, &colors, *v); - // self.draw_pixel(point, &color); - self.draw_rect(context, point, 1.5, 1.5, &color, true); - } - }) - }); - } -} diff --git a/src/pipeline/mod.rs b/src/pipeline/mod.rs new file mode 100644 index 0000000..e2834f3 --- /dev/null +++ b/src/pipeline/mod.rs @@ -0,0 +1,94 @@ +use anyhow::{Ok, Result}; +use femtovg; +use geo_types::{line_string, LineString}; +use image::RgbImage; +use ndarray::Array2; +use num_traits::Num; + +use crate::{ + coords::Mapper, + data::{Radar2d, RadarData2d}, +}; + +pub struct Color(femtovg::Color); + +impl Color { + pub fn new(r: u8, g: u8, b: u8, a: u8) -> Self { + Self(femtovg::Color::rgba(r, g, b, a)) + } +} + +pub trait Pipeline { + type Output; + fn run(&self, input: T) -> Result; +} +pub struct ProjPipe { + pub mapper: Mapper, +} + +impl Pipeline> for ProjPipe +where + T: Num + Clone + PartialEq + PartialOrd, + Raw: ndarray::Data + Clone + ndarray::RawDataClone, +{ + type Output = Array2; + + fn run(&self, input: RadarData2d) -> Result { + let dim1 = input.dim1.view(); + let dim2 = input.dim2.view(); + + let shape = input.data.shape(); + let mut polygons = Vec::with_capacity(dim1.len() * dim2.len()); + + let d1_dpi = dim1[1] - dim1[0]; + let d2_dpi = dim2[1] - dim2[0]; + + for d1 in dim1 { + for d2 in dim2 { + let line: LineString = vec![ + (*d1, *d2), + (*d1 + d1_dpi, *d2), + (*d1 + d1_dpi, *d2 + d2_dpi), + (*d1, *d2 + d2_dpi), + (*d1, *d2), + ] + .into(); + let projed_polygon = self.mapper.ring_map(&line)?; + polygons.push(projed_polygon); + } + } + + Ok(Array2::from_shape_vec([shape[0], shape[1]], polygons)?) + } +} + +pub struct ShadePipe { + colors: Vec, + levels: Vec, +} + +struct ShaderPrepare(Array2); + +impl ShadePipe { + pub fn new(levels: Vec, colors: Vec) -> Self { + Self { colors, levels } + } + + pub fn get_color(&self, v: T) -> &Color { + let len = self.levels.len(); + + let mut left = 0; + let mut right = len - 1; + + while left < right - 1 { + let middle = (right + left) / 2; + if v > self.levels[middle] { + left = middle; + } else { + right = middle; + } + } + + &self.colors[left] + } +} diff --git a/src/render/background/imp.rs b/src/render/background/imp.rs index b9e977e..62de6da 100644 --- a/src/render/background/imp.rs +++ b/src/render/background/imp.rs @@ -1,11 +1,9 @@ -use std::cell::RefCell; - -use crate::painter::Coord; use crate::render::{imp, WindowCoord}; use femtovg::Paint; use geo_macros::Prj; use gtk::glib; use gtk::subclass::prelude::*; +use std::cell::RefCell; #[derive(Prj, Default)] pub struct BackgroundConfig { diff --git a/src/render/foreground/imp.rs b/src/render/foreground/imp.rs index d897c29..6f8b258 100644 --- a/src/render/foreground/imp.rs +++ b/src/render/foreground/imp.rs @@ -1,13 +1,10 @@ -use std::cell::RefCell; - -use crate::data::{Radar2dRef, RadarData2d}; -use crate::painter::Coord; use crate::render::{imp, WindowCoord}; use femtovg::Paint; use geo_macros::Prj; use gtk::glib; use gtk::subclass::prelude::*; use ndarray::Array2; +use std::cell::RefCell; #[derive(Prj, Default)] pub struct ForegroundConfig { @@ -29,7 +26,7 @@ pub struct ForegroundWidget { pub(super) config: RefCell, pub(super) dim1: RefCell>>, pub(super) dim2: RefCell>>, - pub(super) data: RefCell>>, + pub(super) image: RefCell>>, } #[glib::object_subclass] diff --git a/src/render/foreground/mod.rs b/src/render/foreground/mod.rs index 2bf92de..70a9bd9 100644 --- a/src/render/foreground/mod.rs +++ b/src/render/foreground/mod.rs @@ -1,16 +1,13 @@ mod imp; +use crate::coords::Mapper; use crate::tree::get; -use crate::{data::mapper::Mapper, render::WindowCoord}; use femtovg::{renderer::OpenGl, Canvas, Path}; -use femtovg::{Color, Paint}; +use femtovg::{Color, FontId, ImageFlags, Paint}; use geo_types::LineString; use glib::subclass::types::ObjectSubclassIsExt; -use gtk::{ffi::gtk_widget_get_width, glib, graphene::Rect, prelude::SnapshotExtManual}; -use ndarray::parallel; -use ndarray::Slice; +use gtk::glib; use ndarray::{s, Array2, Axis, Zip}; use std::cell::Ref; -use std::ops::Range; pub use self::imp::ForegroundConfig; @@ -37,87 +34,18 @@ impl ForegroundWidget { let canvas_height = canvas.height(); let config = self.imp().config.borrow(); - let dim1 = self.imp().dim1.borrow(); - let dim2 = self.imp().dim2.borrow(); - let data = self.imp().data.borrow(); + let img = canvas + .load_image_file("test.png", ImageFlags::NEAREST) + .unwrap(); - let c = dim1.as_ref().unwrap().slice(s![..;3,..;3]); - let d = dim2.as_ref().unwrap().slice(s![..;3,..;3]); - let e = data.as_ref().unwrap().slice(s![..;3,..;3]); + let mut path = Path::new(); - let levels: Vec = vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65]; + path.rect(0.0, 0.0, canvas_width, canvas_height); + canvas.fill_path( + &path, + &Paint::image(img, 0.0, 0.0, canvas_width, canvas_height, 0.0, 1.0), + ); - let colors = vec![ - Color::rgb(0, 172, 164), - Color::rgb(192, 192, 254), - Color::rgb(122, 114, 238), - Color::rgb(30, 38, 208), - Color::rgb(166, 252, 168), - Color::rgb(0, 234, 0), - Color::rgb(16, 146, 26), - Color::rgb(252, 244, 100), - Color::rgb(200, 200, 2), - Color::rgb(140, 140, 0), - Color::rgb(254, 172, 172), - Color::rgb(254, 100, 92), - Color::rgb(238, 2, 48), - Color::rgb(212, 142, 254), - Color::rgb(170, 36, 250), - ]; - - Zip::from(c).and(d).and(e).for_each(|lon, lat, d| { - if *d < -5 || *d > 70 { - return; - } - - let left_bottom = mapper.map((*lon, *lat)).unwrap(); - let right_top = mapper.map((lon + 0.003, lat + 0.003)).unwrap(); - - // let (lon1, lat1) = ( - // left_bottom.0 * canvas_width as f64, - // left_bottom.1 * canvas_height as f64, - // ); - - let color = get(&levels, &colors, *d); - // - // let (lon2, lat2) = ( - // right_top.0 * canvas_width as f64, - // right_top.1 * canvas_height as f64, - // ); - - let line_string: LineString = vec![ - (left_bottom.0, left_bottom.1), - (right_top.0, left_bottom.1), - (right_top.0, right_top.1), - (left_bottom.0, right_top.1), - (left_bottom.0, left_bottom.1), - ] - .into(); - - // let line_string: LineString = vec![ - // (lon1, lat1), - // (lon2, lat1), - // (lon2, lat2), - // (lon1, lat2), - // (lon1, lat1), - // ] - // .into(); - - let res = mapper.ring_map(&line_string).unwrap(); - - let mut path = Path::new(); - - path.move_to( - left_bottom.0 as f32 * canvas_width, - left_bottom.1 as f32 * canvas_height, - ); - - for p in line_string.points() { - // path.move_to(p.x() as f32 * canvas_width, p.y() as f32 * canvas_height); - path.line_to(p.x() as f32 * canvas_width, p.y() as f32 * canvas_height); - } - canvas.fill_path(&path, &Paint::color(color)); - }); canvas.flush(); } @@ -126,7 +54,80 @@ impl ForegroundWidget { self.imp().dim2.replace(Some(dims.1)); } - pub(super) fn set_data(&mut self, data: Array2) { - self.imp().data.replace(Some(data)); + pub(super) fn set_image(&mut self, image: Vec) { + self.imp().image.replace(Some(image)); } } + +// let levels: Vec = vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65]; +// let colors = vec![ +// Color::rgb(0, 172, 164), +// Color::rgb(192, 192, 254), +// Color::rgb(122, 114, 238), +// Color::rgb(30, 38, 208), +// Color::rgb(166, 252, 168), +// Color::rgb(0, 234, 0), +// Color::rgb(16, 146, 26), +// Color::rgb(252, 244, 100), +// Color::rgb(200, 200, 2), +// Color::rgb(140, 140, 0), +// Color::rgb(254, 172, 172), +// Color::rgb(254, 100, 92), +// Color::rgb(238, 2, 48), +// Color::rgb(212, 142, 254), +// Color::rgb(170, 36, 250), +// ]; + +// Zip::from(c).and(d).and(e).for_each(|lon, lat, d| { +// if *d < -5 || *d > 70 { +// return; +// } + +// let left_bottom = mapper.map((*lon, *lat)).unwrap(); +// let right_top = mapper.map((lon + 0.005, lat + 0.005)).unwrap(); + +// // let (lon1, lat1) = ( +// // left_bottom.0 * canvas_width as f64, +// // left_bottom.1 * canvas_height as f64, +// // ); + +// let color = get(&levels, &colors, *d); +// // +// // let (lon2, lat2) = ( +// // right_top.0 * canvas_width as f64, +// // right_top.1 * canvas_height as f64, +// // ); + +// let line_string: LineString = vec![ +// (left_bottom.0, left_bottom.1), +// (right_top.0, left_bottom.1), +// (right_top.0, right_top.1), +// (left_bottom.0, right_top.1), +// (left_bottom.0, left_bottom.1), +// ] +// .into(); + +// // let line_string: LineString = vec![ +// // (lon1, lat1), +// // (lon2, lat1), +// // (lon2, lat2), +// // (lon1, lat2), +// // (lon1, lat1), +// // ] +// // .into(); + +// let res = mapper.ring_map(&line_string).unwrap(); + +// let mut path = Path::new(); + +// path.move_to( +// left_bottom.0 as f32 * canvas_width, +// left_bottom.1 as f32 * canvas_height, +// ); + +// for p in line_string.points() { +// // path.move_to(p.x() as f32 * canvas_width, p.y() as f32 * canvas_height); +// path.line_to(p.x() as f32 * canvas_width, p.y() as f32 * canvas_height); +// } +// canvas.fill_path(&path, &Paint::color(color)); +// }); diff --git a/src/render/imp.rs b/src/render/imp.rs index 0f0e39b..78e14b1 100644 --- a/src/render/imp.rs +++ b/src/render/imp.rs @@ -1,23 +1,29 @@ use super::background::BackgroundWidget; use super::foreground::ForegroundWidget; use super::WindowCoord; -use crate::data::mapper::Mapper; -use crate::painter::wgs84::Mercator; +use crate::coords::proj::Mercator; +use crate::coords::Mapper; +use femtovg::{Color, Paint, Path}; +use gtk::glib; use gtk::subclass::prelude::*; use gtk::traits::{GLAreaExt, WidgetExt}; -use gtk::{glib, prelude::WidgetExtManual}; use ndarray::Array2; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::num::NonZeroU32; +#[derive(Debug, Default, Clone)] +pub struct RenderConfig { + pub dim1: Option>, + pub dim2: Option>, + pub scale: f32, + pub pointer_location: WindowCoord, + pub transform: WindowCoord, +} + pub struct Render { pub(super) background: RefCell, pub(super) foreground: RefCell, - pub(super) dim1: RefCell>>, - pub(super) dim2: RefCell>>, - pub scale: RefCell, - pub pointer_location: RefCell, - pub transform: RefCell, + pub config: RefCell, pub mapper: RefCell, canvas: RefCell>>, } @@ -27,13 +33,9 @@ impl Default for Render { Self { background: RefCell::new(BackgroundWidget::default()), foreground: RefCell::new(ForegroundWidget::default()), - scale: RefCell::new(1.0), - pointer_location: RefCell::new((0.0, 0.0)), - transform: RefCell::new((0.0, 0.0)), + config: RefCell::new(RenderConfig::default()), mapper: RefCell::new(Mercator::new().into()), canvas: RefCell::new(None), - dim1: RefCell::new(None), - dim2: RefCell::new(None), } } } @@ -51,10 +53,6 @@ impl ObjectImpl for Render { self.parent_constructed(); let area = self.obj(); area.set_has_stencil_buffer(true); - // area.add_tick_callback(|area, _| { - // area.queue_render(); - // glib::Continue(true) - // }); } } @@ -74,9 +72,8 @@ impl GLAreaImpl for Render { } fn render(&self, context: >k::gdk::GLContext) -> bool { - use femtovg::{Color, Paint, Path}; - self.ensure_canvas(); + let mut canvas = self.canvas.borrow_mut(); let canvas = canvas.as_mut().unwrap(); @@ -84,6 +81,8 @@ impl GLAreaImpl for Render { let w = self.obj().width(); let h = self.obj().width(); + let configs = self.config.borrow(); + canvas.clear_rect( 0, 0, @@ -92,16 +91,11 @@ impl GLAreaImpl for Render { Color::rgba(0, 0, 0, 255), ); - self.background - .borrow() - .draw(canvas, self.scale.borrow().clone(), dpi); + self.background.borrow().draw(canvas, configs.scale, dpi); - self.foreground.borrow().draw( - canvas, - self.scale.borrow().clone(), - dpi, - self.mapper.borrow(), - ); + self.foreground + .borrow() + .draw(canvas, configs.scale, dpi, self.mapper.borrow()); canvas.flush(); diff --git a/src/render/mod.rs b/src/render/mod.rs index 6d4c6ca..256e114 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,15 +1,17 @@ mod background; mod foreground; - mod imp; use std::fmt::Debug; -use crate::data::{mapper::Mapper, RadarData2d}; +use crate::coords::Mapper; +use crate::data::{MultiDimensionData, RadarData2d}; pub use self::background::{BackgroundConfig, BackgroundWidget}; pub use self::foreground::{ForegroundConfig, ForegroundWidget}; +use self::imp::RenderConfig; use crate::data::DownSampleMeth; pub use glib::subclass::prelude::*; +use image::RgbImage; use ndarray::{self, s, Array2, Axis, Dimension, Ix2, Zip}; use num_traits::{AsPrimitive, FromPrimitive, Num}; use proj::ProjError; @@ -30,17 +32,31 @@ impl Default for Render { impl Render { pub fn new(background: BackgroundWidget, foreground: ForegroundWidget) -> Self { let this: Self = glib::Object::new(); - this.imp().scale.replace(1.0); this.imp().background.replace(background); this.imp().foreground.replace(foreground); + { + let mut configs = this.imp().config.borrow_mut(); + configs.scale = 1.0; + } this } + pub fn change_cfg(&self, mut f: F) + where + F: FnMut(&mut RenderConfig), + { + let mut cfg = self.imp().config.borrow_mut(); + f(&mut cfg); + } + pub fn set_mapper(&self, mapper: Mapper) { self.imp().mapper.replace(mapper); } - pub(super) fn load_data_2d(&self, data: RadarData2d) -> Result<(), ProjError> + pub(super) fn load_data_2d( + &self, + mut data: RadarData2d, + ) -> Result<(), ProjError> where T: Num + Clone @@ -54,18 +70,45 @@ impl Render { { assert!(data.dim1.shape().len() == data.dim2.shape().len()); - data.downsample((801 / 2, 947 / 2), DownSampleMeth::VAR); - - let meshed = data.map_by_fn(|(x, y)| Ok((x, y)))?; - let d = data.data.to_owned().map(|x| x.as_()); - - self.imp() - .foreground - .borrow_mut() - .set_dims((meshed.dim1, meshed.dim2)); - - self.imp().foreground.borrow_mut().set_data(d); - + let mapper = self.imp().mapper.borrow(); + // data.downsample((801 * 2 / 3, 947 * 2 / 3), DownSampleMeth::VAR); Ok(()) } } + +// let levels: Vec = vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65]; +// let colors = vec![ +// Color::rgb(0, 172, 164), +// Color::rgb(192, 192, 254), +// Color::rgb(122, 114, 238), +// Color::rgb(30, 38, 208), +// Color::rgb(166, 252, 168), +// Color::rgb(0, 234, 0), +// Color::rgb(16, 146, 26), +// Color::rgb(252, 244, 100), +// Color::rgb(200, 200, 2), +// Color::rgb(140, 140, 0), +// Color::rgb(254, 172, 172), +// Color::rgb(254, 100, 92), +// Color::rgb(238, 2, 48), +// Color::rgb(212, 142, 254), +// Color::rgb(170, 36, 250), +// ]; + +// let c = d.map(|v| { +// let c = get(&levels, &colors, *v); +// image::Rgb([ +// (c.r * 255.0) as u8, +// (c.g * 255.0) as u8, +// (c.b * 255.0) as u8, +// ]) +// }); + +// let mut img = RgbImage::from_fn(927, 801, |x, y| c[[y as usize, x as usize]]); + +// img.save("test.png").unwrap(); + +// self.imp() +// .foreground +// .borrow_mut() +// .set_dims((meshed.dim1, meshed.dim2)); diff --git a/test.png b/test.png new file mode 100644 index 0000000..9a20d43 Binary files /dev/null and b/test.png differ diff --git a/test.tiff b/test.tiff new file mode 100644 index 0000000..3018ef2 Binary files /dev/null and b/test.tiff differ