This commit is contained in:
sleptworld 2023-08-15 08:53:20 +08:00
parent 30f6c206f7
commit fdea34f739
26 changed files with 1002 additions and 1204 deletions

BIN
.DS_Store vendored

Binary file not shown.

248
Cargo.lock generated
View File

@ -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",
]

View File

@ -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]

View File

@ -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<S: BackendTextStyle>(&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<Self, CairoError> {
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<Self::ErrorType>> {
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<Self::ErrorType>> {
Ok(())
}
fn draw_pixel(
&mut self,
point: BackendCoord,
color: BackendColor,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
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<S: BackendStyle>(
&mut self,
from: BackendCoord,
to: BackendCoord,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
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<S: BackendStyle>(
&mut self,
upper_left: BackendCoord,
bottom_right: BackendCoord,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
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<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
path: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
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<S: BackendStyle, I: IntoIterator<Item = BackendCoord>>(
&mut self,
path: I,
style: &S,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
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<S: BackendStyle>(
&mut self,
center: BackendCoord,
radius: u32,
style: &S,
fill: bool,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
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<S: BackendTextStyle>(
&self,
text: &str,
font: &S,
) -> Result<(u32, u32), DrawingErrorKind<Self::ErrorType>> {
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<S: BackendTextStyle>(
&mut self,
text: &str,
style: &S,
pos: BackendCoord,
) -> Result<(), DrawingErrorKind<Self::ErrorType>> {
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(())
}
}

View File

@ -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<f64>, Range<f64>),
bounds: (f64, f64, f64, f64),
}
unsafe impl Sync for Mapper {}
impl From<Proj> for Mapper {
fn from(proj: Proj) -> Self {
let default_range: (Range<f64>, Range<f64>) = (-180.0..180.0, -90.0..90.0);
@ -189,6 +183,4 @@ impl Mapper {
}
Ok(())
}
fn resample(&self) {}
}

152
src/coords/mod.rs Normal file
View File

@ -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<T: Num> {
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<f64> {
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<T: AsPrimitive<f64> + 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<T, Raw> RadarData2d<T, Raw>
// where
// T: Num + Clone + PartialEq + PartialOrd,
// Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
// {
// pub fn mapped(&self, coord: impl Borrow<Mapper>) -> Result<AfterMapping2d<T>, ProjError> {
// let mapper: &Mapper = coord.borrow();
// self.map_by_fn(|x| mapper.map(x))
// }
// pub fn map_by_fn<F>(&self, f: F) -> Result<AfterMapping2d<T>, 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::<f64>::zeros((mesh_dim2_len, mesh_dim1_len));
// let mut d2 = Array2::<f64>::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(),
// })
// }
// }

View File

@ -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::<Vec<String>>()
.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<Range>, lat_range: Option<Range>) -> (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)
}
}

79
src/coords/proj/mod.rs Normal file
View File

@ -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<Range>, lat_range: Option<Range>) -> (Range, Range);
}
#[derive(Debug, Error)]
pub(super) enum ProjError {
#[error("proj error")]
ProjError(#[from] proj::ProjError),
}
pub(super) struct PCS<T: ProjectionS> {
pub lon_range: Range,
pub lat_range: Range,
pub proj_param: T,
// pub proj_target: proj5::CoordinateBuf,
pub transformer: Proj,
}
impl<T: ProjectionS> PCS<T> {
pub(super) fn new(proj_param: T, lon_range: Option<Range>, lat_range: Option<Range>) -> 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
}
}

88
src/coords/wgs84.rs Normal file
View File

@ -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<T: ProjectionS> {
actual: (Range, Range),
logical: (Range, Range),
pcs: PCS<T>,
}
impl<T: ProjectionS> LatLonCoord<T> {
/// 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<Range>,
lat: Option<Range>,
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<T: ProjectionS> Coord<f64> for LatLonCoord<T> {
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)
}
}

View File

@ -1,3 +0,0 @@
use super::RadarData2d;
pub type RadarReflectivity<Raw> = RadarData2d<i8, Raw>;

View File

@ -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<T> = RadarData2d<T, OwnedRepr<T>>;
#[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<T>
where
T: Num + Clone + PartialEq + PartialOrd,
{
fn map_by_fn<F>(&mut self, f: F)
where
F: FnMut(&mut T);
}
#[derive(Clone)]
@ -47,16 +33,12 @@ where
pub coord_type: CoordType,
}
pub type Radar2d<T> = RadarData2d<T, OwnedRepr<T>>;
impl<T: Num + Clone + PartialEq + PartialOrd> Radar2d<T> {
pub fn load(path: impl AsRef<Path>, meth: impl DataLoader<Self>) -> Result<Self, DataError> {
pub fn load(path: impl AsRef<Path>, meth: impl DataLoader<T, Self>) -> Result<Self, DataError> {
Ok(meth.load(path)?)
}
}
pub type Radar2dRef<'a, T> = RadarData2d<T, ViewRepr<&'a T>>;
pub struct RadarData3d<T, X = f64, Y = f64, Z = f64>
where
T: Num,
@ -70,8 +52,6 @@ where
pub data: Array3<T>,
}
pub trait MultiDimensionData {}
impl<T, Raw> RadarData2d<T, Raw>
where
T: Num + AsPrimitive<f64> + FromPrimitive + Clone + Debug + PartialOrd + PartialEq,
@ -95,17 +75,27 @@ where
let mut output: Array2<T> = 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<T, Raw> MultiDimensionData for RadarData2d<T, Raw>
impl<T, Raw> MultiDimensionData<T> for RadarData2d<T, Raw>
where
T: Num + Clone,
T: PartialEq + PartialOrd,
Raw: ndarray::Data<Elem = T> + Clone + RawDataClone,
Raw: ndarray::Data<Elem = T> + Clone + RawDataClone + DataMut,
{
fn map_by_fn<F>(&mut self, f: F)
where
F: FnMut(&mut T),
{
self.data.map_inplace(f);
}
}
pub trait DataLoader<T: MultiDimensionData> {
pub trait DataLoader<V, T>
where
V: Num + Clone + PartialEq + PartialOrd,
T: MultiDimensionData<V>,
{
fn load<P: AsRef<Path>>(&self, path: P) -> Result<T, DataError>;
}
@ -290,7 +287,7 @@ impl Npz {
fn load_3d(&self, data: &mut NpzArchive<BufReader<std::fs::File>>) {}
}
impl<T> DataLoader<RadarData2d<T, OwnedRepr<T>>> for Npz
impl<T> DataLoader<T, RadarData2d<T, OwnedRepr<T>>> 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,
}

22
src/errors.rs Normal file
View File

@ -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,
},
}

View File

@ -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::<i8>::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));

View File

@ -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)
}
));

View File

@ -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<T: Num>: 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<T, Raw> RadarData2d<T, Raw>
where
T: Num + Clone + PartialEq + PartialOrd,
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
pub fn mapped(&self, coord: impl Borrow<Mapper>) -> Result<AfterMapping2d<T>, ProjError> {
let mapper: &Mapper = coord.borrow();
self.map_by_fn(|x| mapper.map(x))
}
pub fn map_by_fn<F>(&self, f: F) -> Result<AfterMapping2d<T>, 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::<f64>::zeros((mesh_dim2_len, mesh_dim1_len));
let mut d2 = Array2::<f64>::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(),
})
}
}

View File

@ -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<f64> {
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<T: AsPrimitive<f64> + 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<T: ProjectionS> {
// actual: ((f64, f64), (f64, f64)),
actual: (Range, Range),
logical: (Range, Range),
pcs: PCS<T>,
}
struct PCS<T: ProjectionS> {
pub lon_range: Range,
pub lat_range: Range,
pub proj_param: T,
pub transformer: Proj,
}
unsafe impl Sync for PCS<Mercator> {}
unsafe impl Send for PCS<Mercator> {}
impl<T: ProjectionS> LatLonCoord<T> {
/// 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<Range>,
lat: Option<Range>,
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<T: ProjectionS> Sync for LatLonCoord<T> {}
unsafe impl<T: ProjectionS> Send for LatLonCoord<T> {}
impl<T: ProjectionS> Coord<f64> for LatLonCoord<T> {
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<Range>, lat_range: Option<Range>) -> (Range, Range);
}
impl<T: ProjectionS> PCS<T> {
fn new(proj_param: T, lon_range: Option<Range>, lat_range: Option<Range>) -> 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::<Vec<String>>()
.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<Range>, lat_range: Option<Range>) -> (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)
}
}

View File

@ -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<T, ViewRepr<&'a T>, f64, f64, Ix2>;
// pub type AfterMapping3d<T, Raw> = RadarData3d<T, Raw, f64, f64, Ix2>;
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));
});
});
}
}

View File

@ -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<T: Num, C: Coord<T>> {
width: u32,
height: u32,
shift: (f64, f64),
coord: C,
marker: PhantomData<T>,
}
impl<T: Num + Clone, C: Coord<T>> Painter<T, C> {
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<I: IntoIterator<Item = ScreenCoord>>(&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<C: Coord<f64>> Painter<f64, C> {
pub fn draw_radar_2d<T: Num + Clone + PartialEq + PartialOrd + FromPrimitive + Copy, D>(
&self,
context: &Context,
data: &RadarData2d<T, D>,
) where
D: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
let levels: Vec<T> = 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);
}
})
});
}
}

94
src/pipeline/mod.rs Normal file
View File

@ -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<T> {
type Output;
fn run(&self, input: T) -> Result<Self::Output>;
}
pub struct ProjPipe {
pub mapper: Mapper,
}
impl<T, Raw> Pipeline<RadarData2d<T, Raw>> for ProjPipe
where
T: Num + Clone + PartialEq + PartialOrd,
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
type Output = Array2<LineString>;
fn run(&self, input: RadarData2d<T, Raw>) -> Result<Self::Output> {
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<T: Num> {
colors: Vec<Color>,
levels: Vec<T>,
}
struct ShaderPrepare(Array2<LineString>);
impl<T: Num + PartialOrd> ShadePipe<T> {
pub fn new(levels: Vec<T>, colors: Vec<Color>) -> 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]
}
}

View File

@ -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 {

View File

@ -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<ForegroundConfig>,
pub(super) dim1: RefCell<Option<Array2<f64>>>,
pub(super) dim2: RefCell<Option<Array2<f64>>>,
pub(super) data: RefCell<Option<Array2<i8>>>,
pub(super) image: RefCell<Option<Vec<u8>>>,
}
#[glib::object_subclass]

View File

@ -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<i8> = 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<i8>) {
self.imp().data.replace(Some(data));
pub(super) fn set_image(&mut self, image: Vec<u8>) {
self.imp().image.replace(Some(image));
}
}
// let levels: Vec<i8> = 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));
// });

View File

@ -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<Array2<f64>>,
pub dim2: Option<Array2<f64>>,
pub scale: f32,
pub pointer_location: WindowCoord,
pub transform: WindowCoord,
}
pub struct Render {
pub(super) background: RefCell<BackgroundWidget>,
pub(super) foreground: RefCell<ForegroundWidget>,
pub(super) dim1: RefCell<Option<Array2<f64>>>,
pub(super) dim2: RefCell<Option<Array2<f64>>>,
pub scale: RefCell<f32>,
pub pointer_location: RefCell<WindowCoord>,
pub transform: RefCell<WindowCoord>,
pub config: RefCell<RenderConfig>,
pub mapper: RefCell<Mapper>,
canvas: RefCell<Option<femtovg::Canvas<femtovg::renderer::OpenGl>>>,
}
@ -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: &gtk::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();

View File

@ -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<F>(&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<T, Raw>(&self, data: RadarData2d<T, Raw>) -> Result<(), ProjError>
pub(super) fn load_data_2d<T, Raw>(
&self,
mut data: RadarData2d<T, Raw>,
) -> 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<i8> = 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));

BIN
test.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 250 KiB

BIN
test.tiff Normal file

Binary file not shown.