From b70b86e71c6614e627b486da71ef8deb29818d32 Mon Sep 17 00:00:00 2001 From: Tsuki Date: Wed, 10 Jan 2024 22:12:41 +0800 Subject: [PATCH] add more detail --- Cargo.lock | 64 +++++ Cargo.toml | 7 +- back.txt | 79 ++++++ check.py | 9 + src/components/app.rs | 39 ++- .../{ => control_panel}/control_panel.rs | 0 src/components/control_panel/messages.rs | 0 src/components/control_panel/mod.rs | 3 + src/components/mod.rs | 5 +- src/components/render_panel.rs | 38 --- src/components/render_panel/messages.rs | 12 + src/components/render_panel/mod.rs | 4 + src/components/render_panel/monitor/mod.rs | 4 + .../render_panel/monitor/monitor.rs | 46 ++++ .../render_panel/monitor/render/mod.rs | 2 + .../render_panel/monitor/render/render.rs | 40 ++++ .../render_panel/monitor/sidebar/mod.rs | 2 + .../render_panel/monitor/sidebar/sidebar.rs | 171 +++++++++++++ src/components/render_panel/render_panel.rs | 39 +++ src/coords/mapper.rs | 52 ++-- src/coords/mod.rs | 2 +- src/coords/proj/mercator.rs | 19 +- src/data/mod.rs | 35 ++- src/main.rs | 7 +- src/monitor/imp.rs | 7 +- src/monitor/mod.rs | 82 +++---- src/render/cms.rs | 45 ++++ src/render/exterior/mod.rs | 40 ---- src/render/foreground/mod.rs | 193 --------------- src/render/imp.rs | 190 +++++++++++---- src/render/interior/mod.rs | 58 ----- src/render/{ => layer}/background/imp.rs | 0 src/render/{ => layer}/background/mod.rs | 6 +- .../{ => layer/foreground}/exterior/imp.rs | 0 src/render/layer/foreground/exterior/mod.rs | 89 +++++++ src/render/{ => layer}/foreground/imp.rs | 3 +- .../{ => layer/foreground}/interior/imp.rs | 12 +- .../layer/foreground/interior/layers.rs | 107 +++++++++ src/render/layer/foreground/interior/mod.rs | 56 +++++ src/render/layer/foreground/mod.rs | 34 +++ src/render/layer/mod.rs | 2 + src/render/mod.rs | 226 +++++++++++------- src/render/predefined/color_mapper.rs | 72 ++++++ src/render/predefined/gis.rs | 63 +++++ src/render/predefined/grid_field_renderer.rs | 179 ++++++++++++++ src/render/predefined/layers.rs | 67 ++++++ src/render/predefined/mod.rs | 4 + src/render/renders.rs | 14 ++ src/utils.rs | 12 + 49 files changed, 1688 insertions(+), 552 deletions(-) create mode 100644 back.txt create mode 100644 check.py rename src/components/{ => control_panel}/control_panel.rs (100%) create mode 100644 src/components/control_panel/messages.rs create mode 100644 src/components/control_panel/mod.rs delete mode 100644 src/components/render_panel.rs create mode 100644 src/components/render_panel/messages.rs create mode 100644 src/components/render_panel/mod.rs create mode 100644 src/components/render_panel/monitor/mod.rs create mode 100644 src/components/render_panel/monitor/monitor.rs create mode 100644 src/components/render_panel/monitor/render/mod.rs create mode 100644 src/components/render_panel/monitor/render/render.rs create mode 100644 src/components/render_panel/monitor/sidebar/mod.rs create mode 100644 src/components/render_panel/monitor/sidebar/sidebar.rs create mode 100644 src/components/render_panel/render_panel.rs create mode 100644 src/render/cms.rs delete mode 100644 src/render/exterior/mod.rs delete mode 100644 src/render/foreground/mod.rs delete mode 100644 src/render/interior/mod.rs rename src/render/{ => layer}/background/imp.rs (100%) rename src/render/{ => layer}/background/mod.rs (96%) rename src/render/{ => layer/foreground}/exterior/imp.rs (100%) create mode 100644 src/render/layer/foreground/exterior/mod.rs rename src/render/{ => layer}/foreground/imp.rs (92%) rename src/render/{ => layer/foreground}/interior/imp.rs (50%) create mode 100644 src/render/layer/foreground/interior/layers.rs create mode 100644 src/render/layer/foreground/interior/mod.rs create mode 100644 src/render/layer/foreground/mod.rs create mode 100644 src/render/layer/mod.rs create mode 100644 src/render/predefined/color_mapper.rs create mode 100644 src/render/predefined/gis.rs create mode 100644 src/render/predefined/grid_field_renderer.rs create mode 100644 src/render/predefined/layers.rs create mode 100644 src/render/predefined/mod.rs create mode 100644 src/render/renders.rs diff --git a/Cargo.lock b/Cargo.lock index f74d4c7..1169b32 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,6 +285,7 @@ dependencies = [ "geo", "geo-macros", "geo-types", + "geojson 0.24.1", "glib", "glib-build-tools", "glib-macros", @@ -307,6 +308,7 @@ dependencies = [ "shapefile", "svg", "thiserror", + "topojson", ] [[package]] @@ -900,6 +902,32 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "geojson" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c1147be22f66284de4387c43e4abab872e525a528f4d0af4e4e0f231895ea4" +dependencies = [ + "geo-types", + "serde", + "serde_derive", + "serde_json", + "thiserror", +] + +[[package]] +name = "geojson" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d728c1df1fbf328d74151efe6cb0586f79ee813346ea981add69bd22c9241b" +dependencies = [ + "geo-types", + "log", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "getrandom" version = "0.2.10" @@ -1309,6 +1337,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" + [[package]] name = "jobserver" version = "0.1.26" @@ -2050,6 +2084,7 @@ dependencies = [ "fragile", "futures", "gtk4", + "libadwaita", "once_cell", "relm4-macros", "tokio", @@ -2141,6 +2176,12 @@ dependencies = [ "unicode-script", ] +[[package]] +name = "ryu" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f98d2aa92eebf49b69786be48e4477826b256916e84a57ff2a4f21923b48eb4c" + [[package]] name = "scoped_threadpool" version = "0.1.9" @@ -2179,6 +2220,17 @@ dependencies = [ "syn 2.0.29", ] +[[package]] +name = "serde_json" +version = "1.0.99" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46266871c240a00b8f503b877622fe33430b3c7d963bdc0f2adc511e54a1eae3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_spanned" version = "0.6.3" @@ -2480,6 +2532,18 @@ dependencies = [ "winnow", ] +[[package]] +name = "topojson" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41ed0491c918a501d49a5da86736c9e27666b6f5794fadec0ff8d4842c642b73" +dependencies = [ + "geojson 0.23.0", + "log", + "serde", + "serde_json", +] + [[package]] name = "tracing" version = "0.1.37" diff --git a/Cargo.toml b/Cargo.toml index 8af6241..3c8cf83 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,10 +32,12 @@ proj = "0.27.2" image = "0.24.7" anyhow = "1.0.72" proj5 = { version = "0.1.7", features = ["multithreading"] } -relm4 = "0.6.1" +relm4 = { version = "0.6.1",features=["libadwaita"]} relm4-components = "0.6.1" rstar = "*" geo = "0.26.0" +topojson = "0.5.1" +geojson = "0.24.1" @@ -47,4 +49,5 @@ path = "geo-macros" [dependencies.adw] package = "libadwaita" -version = "*" \ No newline at end of file +version = "*" +features= ["v1_4"] diff --git a/back.txt b/back.txt new file mode 100644 index 0000000..0bca492 --- /dev/null +++ b/back.txt @@ -0,0 +1,79 @@ + + Layer::geojson_layer_with_path( + "/Users/tsuki/Downloads/new_zhejiang.json", + true, + |_self, c, render, _| { + if let Some(json_resources) = _self.get_resources() { + if let Resources::GeoJson(geojson) = json_resources.deref() { + MapRender::test(&geojson, c, render); + } + } + }, + ), + Layer::new(true, None, |s, c, render, _| { + if let Some(target) = s.render_target() { + if let Ok(_) = c.image_size(target.target) { + let (x, y) = target.size(render); + let (ox, oy) = target.origin(render); + let painter = Paint::image(target.target, ox, oy, x, y, 0.0, 1.0); + let mut path = Path::new(); + path.rect(ox, oy, x, y); + c.fill_path(&path, &painter); + } + } else { + let renderer = RadarEchoRenderer::new(BoundaryNorm::new( + vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65], + 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), + ], + true, + )); + let loader = Npz; + let data: RadarData2d> = loader + .load("/Users/tsuki/projects/radar-g/test2.npz") + .unwrap(); + let img = renderer.render(render, c, &data); + if let Ok(_) = c.image_size(img.target) { + let (x, y) = img.size(render); + let (ox, oy) = img.origin(render); + println!("{} {} {} {}", x, y, ox, oy); + let painter = Paint::image(img.target, ox, oy, x, y, 0.0, 1.0); + let mut path = Path::new(); + path.rect(ox, oy, x, y); + s.set_render_target(img); + c.fill_path(&path, &painter); + c.flush(); + } + } + }), + + + + + + +############# TEMPLATES ################### +// Monitor::new( + // Render::new( + // Some(Mapper::new( + // Proj::new(Mercator::default().build().as_str()).unwrap(), + // (119.539..121.135).into(), + // (29.13..30.164).into(), + // )), + // RenderConfig { padding: [50.0;4] } + // ) + // ) diff --git a/check.py b/check.py new file mode 100644 index 0000000..8691492 --- /dev/null +++ b/check.py @@ -0,0 +1,9 @@ +import numpy as np + +data = np.load("/Users/tsuki/projects/radar-g/test2.npz") + +print(data['lat'][0]) +print(data['lat'][-1]) + +print(data['lon'][0]) +print(data['lon'][-1]) \ No newline at end of file diff --git a/src/components/app.rs b/src/components/app.rs index 9c38660..4a28a93 100644 --- a/src/components/app.rs +++ b/src/components/app.rs @@ -1,9 +1,12 @@ use super::{control_panel::ControlPanelModel, render_panel::RenderPanelModel}; -use gtk::{prelude::{ - ApplicationExt, ButtonExt, DialogExt, GtkWindowExt, ToggleButtonExt, WidgetExt, -}, traits::OrientableExt}; use gtk::prelude::GtkApplicationExt; +use gtk::{ + prelude::{ApplicationExt, ButtonExt, DialogExt, GtkWindowExt, ToggleButtonExt, WidgetExt}, + traits::OrientableExt, +}; use relm4::*; +use adw::prelude::*; + #[derive(Debug)] pub enum AppMode { @@ -35,14 +38,19 @@ impl SimpleComponent for AppModel { type Output = (); view! { - main_window = gtk::ApplicationWindow { + main_window=adw::ApplicationWindow { set_default_width: 1200, - set_default_height: 700, + set_default_height: 900, set_focus_on_click:true, - set_titlebar: Some(>k::HeaderBar::new()), + // set_titlebar: Some(>k::HeaderBar::new()), gtk::Box{ - set_orientation: gtk::Orientation::Horizontal, + set_orientation: gtk::Orientation::Vertical, set_valign:gtk::Align::Fill, + adw::HeaderBar { + #[wrap(Some)] + set_title_widget = &adw::WindowTitle { + } + }, model.control.widget(), model.render.widget(), }, @@ -51,6 +59,21 @@ impl SimpleComponent for AppModel { gtk::Inhibit(true) } } + // main_window = gtk::ApplicationWindow { + // set_default_width: 1200, + // set_default_height: 900, + // set_focus_on_click:true, + // set_titlebar: Some(>k::HeaderBar::new()), + // gtk::Box{ + // set_orientation: gtk::Orientation::Vertical, + // set_valign:gtk::Align::Fill, + + // }, + // connect_close_request[sender] => move |_| { + // sender.input(AppMsg::CloseRequest); + // gtk::Inhibit(true) + // } + // } } fn init( @@ -63,7 +86,7 @@ impl SimpleComponent for AppModel { .forward(sender.input_sender(), |msg| AppMsg::Close); let render = RenderPanelModel::builder() - .launch(0) + .launch(()) .forward(sender.input_sender(), |a| AppMsg::Close); relm4::menu! { diff --git a/src/components/control_panel.rs b/src/components/control_panel/control_panel.rs similarity index 100% rename from src/components/control_panel.rs rename to src/components/control_panel/control_panel.rs diff --git a/src/components/control_panel/messages.rs b/src/components/control_panel/messages.rs new file mode 100644 index 0000000..e69de29 diff --git a/src/components/control_panel/mod.rs b/src/components/control_panel/mod.rs new file mode 100644 index 0000000..acea845 --- /dev/null +++ b/src/components/control_panel/mod.rs @@ -0,0 +1,3 @@ +mod control_panel; +mod messages; +pub use control_panel::*; diff --git a/src/components/mod.rs b/src/components/mod.rs index d07d9e1..bd3c320 100644 --- a/src/components/mod.rs +++ b/src/components/mod.rs @@ -1,3 +1,6 @@ pub mod app; mod control_panel; -mod render_panel; \ No newline at end of file +mod render_panel; + +pub use control_panel::*; +pub use render_panel::*; diff --git a/src/components/render_panel.rs b/src/components/render_panel.rs deleted file mode 100644 index 2be6d6b..0000000 --- a/src/components/render_panel.rs +++ /dev/null @@ -1,38 +0,0 @@ -use crate::monitor::Monitor; -use crate::coords::proj::Mercator; -use crate::render::{ - BackgroundConfig, BackgroundWidget, ForegroundConfig, ForegroundWidget, Render, -}; -use gtk::prelude::{BoxExt, ButtonExt, GtkWindowExt, OrientableExt, ToggleButtonExt}; -use relm4::*; - -pub struct RenderPanelModel; - -#[relm4::component(pub)] -impl SimpleComponent for RenderPanelModel { - type Init = i8; - type Output = (); - type Input = (); - - view! { - #[root] - Monitor::new( - Render::new( - Some(Mercator::new().into()) - ) - ), - } - - fn init( - init: Self::Init, - root: &Self::Root, - sender: relm4::ComponentSender, - ) -> relm4::ComponentParts { - let model = RenderPanelModel {}; - // Insert the macro code generation here - let widgets = view_output!(); - - ComponentParts { model, widgets } - } - fn update(&mut self, msg: Self::Input, _sender: ComponentSender) {} -} diff --git a/src/components/render_panel/messages.rs b/src/components/render_panel/messages.rs new file mode 100644 index 0000000..aa7c6cf --- /dev/null +++ b/src/components/render_panel/messages.rs @@ -0,0 +1,12 @@ +use crate::render::Layer; + + + +#[derive(Debug)] +pub enum RenderInputMsg { + AddLayer(Layer), + RemoveLayer(usize), + MoveLayer(usize, usize), + SetLayerVisibility(usize, bool), + EditLayer(usize), +} diff --git a/src/components/render_panel/mod.rs b/src/components/render_panel/mod.rs new file mode 100644 index 0000000..0e01fa3 --- /dev/null +++ b/src/components/render_panel/mod.rs @@ -0,0 +1,4 @@ +mod render_panel; +mod messages; +mod monitor; +pub use render_panel::RenderPanelModel; diff --git a/src/components/render_panel/monitor/mod.rs b/src/components/render_panel/monitor/mod.rs new file mode 100644 index 0000000..3f559f0 --- /dev/null +++ b/src/components/render_panel/monitor/mod.rs @@ -0,0 +1,4 @@ +pub mod monitor; +pub mod sidebar; +pub mod render; +pub use monitor::MonitorModel; diff --git a/src/components/render_panel/monitor/monitor.rs b/src/components/render_panel/monitor/monitor.rs new file mode 100644 index 0000000..d2f968d --- /dev/null +++ b/src/components/render_panel/monitor/monitor.rs @@ -0,0 +1,46 @@ +use super::{render::render::RenderModel, sidebar::sidebar::SideBarModel}; +use relm4::*; +pub struct MonitorModel { + render: Controller, + sidebar: Controller, +} + +#[relm4::component(pub)] +impl SimpleComponent for MonitorModel { + type Init = (); + type Output = (); + type Input = (); + + view! { + adw::OverlaySplitView { + set_sidebar_position:gtk::PackType::End, + #[wrap(Some)] + set_sidebar = &adw::NavigationPage::builder().title("sidebar").child(model.sidebar.widget()).build(), + #[wrap(Some)] + set_content = &adw::NavigationPage::builder().title("content").child(model.render.widget()).build(), + } + } + + fn init( + init: Self::Init, + root: &Self::Root, + sender: relm4::ComponentSender, + ) -> relm4::ComponentParts { + let sidebar: Controller = SideBarModel::builder() + .launch(()) + .forward(sender.input_sender(), |msg| {}); + let render: Controller = RenderModel::builder() + .launch((None, None)) + .forward(sender.input_sender(), |msg| {}); + + let model = MonitorModel { render, sidebar }; + + + let widgets = view_output!(); + + + ComponentParts { model, widgets } + } + + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) {} +} diff --git a/src/components/render_panel/monitor/render/mod.rs b/src/components/render_panel/monitor/render/mod.rs new file mode 100644 index 0000000..60b6792 --- /dev/null +++ b/src/components/render_panel/monitor/render/mod.rs @@ -0,0 +1,2 @@ +pub mod render; +pub use render::*; diff --git a/src/components/render_panel/monitor/render/render.rs b/src/components/render_panel/monitor/render/render.rs new file mode 100644 index 0000000..0c0c343 --- /dev/null +++ b/src/components/render_panel/monitor/render/render.rs @@ -0,0 +1,40 @@ +use crate::coords::proj::Mercator; +use crate::coords::Mapper; +use crate::render::{Render, RenderConfig}; +use relm4::*; + +pub struct RenderModel { + config: RenderConfig, + mapper: Mapper, +} + +#[relm4::component(pub)] +impl SimpleComponent for RenderModel { + type Init = (Option, Option); + type Output = (); + type Input = (); + + view! { + Render{ + set_mapper = model.mapper.clone(), + set_cfg = model.config.clone(), + } + } + + fn init( + init: Self::Init, + root: &Self::Root, + sender: relm4::ComponentSender, + ) -> relm4::ComponentParts { + let (mapper, config) = init; + let config = config.unwrap_or(RenderConfig::default()); + let mapper = mapper.unwrap_or(Mercator::default().into()); + + let model = RenderModel { config, mapper }; + let widgets = view_output!(); + + ComponentParts { model, widgets } + } + + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) {} +} diff --git a/src/components/render_panel/monitor/sidebar/mod.rs b/src/components/render_panel/monitor/sidebar/mod.rs new file mode 100644 index 0000000..3ec0fd0 --- /dev/null +++ b/src/components/render_panel/monitor/sidebar/mod.rs @@ -0,0 +1,2 @@ +pub mod sidebar; +pub use sidebar::*; diff --git a/src/components/render_panel/monitor/sidebar/sidebar.rs b/src/components/render_panel/monitor/sidebar/sidebar.rs new file mode 100644 index 0000000..50b1386 --- /dev/null +++ b/src/components/render_panel/monitor/sidebar/sidebar.rs @@ -0,0 +1,171 @@ +use gtk::prelude::*; +use relm4::{ + binding::{Binding, U8Binding}, + prelude::*, + typed_list_view::{RelmListItem, TypedListView}, + RelmObjectExt, +}; +pub struct SideBarModel { + counter: u8, + list_view_wrapper: TypedListView, +} + +#[derive(Debug)] +pub enum Msg { + Append, + Remove, + OnlyShowEven(bool), +} + +#[relm4::component(pub)] +impl SimpleComponent for SideBarModel { + type Init = (); + type Output = (); + type Input = Msg; + + view! { + gtk::Box { + set_orientation: gtk::Orientation::Vertical, + set_spacing: 5, + set_margin_all: 5, + gtk::Notebook { + set_vexpand: true, + append_page:(&page_1, Some(&label)), + }, + gtk::Button { + set_label: "Append 10 items", + connect_clicked => Msg::Append, + }, + + gtk::Button { + set_label: "Remove second item", + connect_clicked => Msg::Remove, + }, + gtk::ScrolledWindow { + set_vexpand: true, + + #[local_ref] + my_view -> gtk::ListView {} + } + } + } + + fn init( + init: Self::Init, + root: &Self::Root, + sender: ComponentSender, + ) -> ComponentParts { + // Initialize the ListView wrapper + let mut list_view_wrapper: TypedListView = + TypedListView::with_sorting(); + + // Add a filter and disable it + list_view_wrapper.add_filter(|item| item.value % 2 == 0); + list_view_wrapper.set_filter_status(0, false); + + let model = SideBarModel { + counter: 0, + list_view_wrapper, + }; + let my_view = &model.list_view_wrapper.view; + + let page_1 = gtk::Box::new(gtk::Orientation::Vertical, 5); + let label = gtk::Label::new(Some("Page 1")); + + let widgets = view_output!(); + ComponentParts { model, widgets } + } + + fn update(&mut self, message: Self::Input, sender: ComponentSender) { + match message { + Msg::Append => { + // Add 10 items + for _ in 0..10 { + self.counter = self.counter.wrapping_add(1); + self.list_view_wrapper.append(MyListItem::new(self.counter)); + } + + // Count up the first item + let first_item = self.list_view_wrapper.get(0).unwrap(); + let first_binding = &mut first_item.borrow_mut().binding; + let mut guard = first_binding.guard(); + *guard += 1; + } + Msg::Remove => { + // Remove the second item + self.list_view_wrapper.remove(1); + } + Msg::OnlyShowEven(show_only_even) => { + // Disable or enable the first filter + self.list_view_wrapper.set_filter_status(0, show_only_even); + } + } + } +} + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] +struct MyListItem { + value: u8, + binding: U8Binding, +} + +impl MyListItem { + fn new(value: u8) -> Self { + Self { + value, + binding: U8Binding::new(0), + } + } +} + +struct Widgets { + label: gtk::Label, + label2: gtk::Label, + button: gtk::CheckButton, +} + +impl Drop for Widgets { + fn drop(&mut self) { + dbg!(self.label.label()); + } +} + +impl RelmListItem for MyListItem { + type Root = gtk::Box; + type Widgets = Widgets; + + fn setup(_item: >k::ListItem) -> (gtk::Box, Widgets) { + relm4::view! { + my_box = gtk::Box { + #[name = "label"] + gtk::Label, + + #[name = "label2"] + gtk::Label, + + #[name = "button"] + gtk::CheckButton, + } + } + + let widgets = Widgets { + label, + label2, + button, + }; + + (my_box, widgets) + } + + fn bind(&mut self, widgets: &mut Self::Widgets, _root: &mut Self::Root) { + let Widgets { + label, + label2, + button, + } = widgets; + + label.set_label(&format!("Value: {} ", self.value)); + label2.add_write_only_binding(&self.binding, "label"); + button.set_active(self.value % 2 == 0); + } +} diff --git a/src/components/render_panel/render_panel.rs b/src/components/render_panel/render_panel.rs new file mode 100644 index 0000000..8eae91a --- /dev/null +++ b/src/components/render_panel/render_panel.rs @@ -0,0 +1,39 @@ +use super::messages::RenderInputMsg; +use gtk::prelude::*; +use super::monitor::MonitorModel; +use relm4::*; +pub struct RenderPanelModel { + monitor: Controller, +} + +#[relm4::component(pub)] +impl SimpleComponent for RenderPanelModel { + type Init = (); + type Output = (); + type Input = (); + + view! { + gtk::Box{ + set_orientation: gtk::Orientation::Horizontal, + set_hexpand:true, + set_vexpand:true, + model.monitor.widget(), + } + } + + fn init( + init: Self::Init, + root: &Self::Root, + sender: relm4::ComponentSender, + ) -> relm4::ComponentParts { + let monitor: Controller = MonitorModel::builder() + .launch(()) + .forward(sender.input_sender(), |msg| {}); + + let model = RenderPanelModel { monitor }; + let widgets = view_output!(); + ComponentParts { model, widgets } + } + + fn update(&mut self, msg: Self::Input, _sender: ComponentSender) {} +} diff --git a/src/coords/mapper.rs b/src/coords/mapper.rs index 5538cd8..7a4b2f6 100644 --- a/src/coords/mapper.rs +++ b/src/coords/mapper.rs @@ -1,4 +1,5 @@ use crate::render::WindowCoord; +use std::borrow::ToOwned; use super::{proj::ProjectionS, Range}; use geo_types::{coord, Coord as GCoord, LineString}; @@ -9,12 +10,25 @@ pub struct Mapper { pub range: (Range, Range), bounds: (f64, f64, f64, f64), } + +impl Clone for Mapper { + fn clone(&self) -> Self { + let c = self.proj.proj_info(); + let new_proj = Proj::new(c.definition.unwrap().as_str()).unwrap(); + Self { + proj: new_proj, + range: self.range, + bounds: self.bounds, + } + } +} + impl From for Mapper { fn from(proj: Proj) -> Self { let default_range: (Range, Range) = ((-180.0..180.0).into(), (-81.0..81.0).into()); let bounds = Self::bound(&proj, default_range.clone()).unwrap(); Self { - proj: proj, + proj, range: (default_range.0.into(), default_range.1.into()), bounds, } @@ -33,24 +47,22 @@ impl Mapper { proj: Proj, lon_range: std::ops::Range, lat_range: std::ops::Range, - scale: f32, - translate: (f32, f32), ) -> Self { let bounds = Self::bound(&proj, (lon_range.clone().into(), lat_range.clone().into())).unwrap(); let range = (lon_range.into(), lat_range.into()); Self { - proj: proj, + proj, range, bounds, } } - pub fn fore_map(&self, coord: WindowCoord) -> Result<(f64, f64), ProjError> { + pub fn inverse_map(&self, coord: (f64, f64)) -> Result<(f64, f64), ProjError> { let (x, y) = coord; - let c = (x as f64) * (self.bounds.1 - self.bounds.0) + self.bounds.0; - let d = ((1.0 - y) as f64) * (self.bounds.3 - self.bounds.2) + self.bounds.2; - let (lon, lat) = self.proj.project((c, d), true)?; + // let c = (x as f64) * (self.bounds.1 - self.bounds.0) + self.bounds.0; + // let d = ((1.0 - y) as f64) * (self.bounds.3 - self.bounds.2) + self.bounds.2; + let (lon, lat) = self.proj.project((x, y), true)?; Ok((lon.to_degrees(), lat.to_degrees())) } @@ -60,22 +72,28 @@ impl Mapper { return (x <= lon_range.1 && x >= lon_range.0) && (y <= lat_range.1 && y >= lat_range.0); } + pub fn get_bounds(&self) -> (f64, f64, f64, f64) { + self.bounds + } + pub fn map(&self, point: (f64, f64)) -> Result<(f64, f64), ProjError> { let mut point = point; - if !self.point_in_bound(point) { - point = ( - point.0.clamp(self.range.0 .0, self.range.0 .1), - point.1.clamp(self.range.1 .0, self.range.1 .1), - ); - } + // if !self.point_in_bound(point) { + // point = ( + // point.0.clamp(self.range.0 .0, self.range.0 .1), + // point.1.clamp(self.range.1 .0, self.range.1 .1), + // ); + // } let (p1, p2) = self .proj .convert((point.0.to_radians(), point.1.to_radians()))?; - let x = (p1 - self.bounds.0) / (self.bounds.1 - self.bounds.0); - let y = (p2 - self.bounds.2) / (self.bounds.3 - self.bounds.2); + // let x = (p1 - self.bounds.0) / (self.bounds.1 - self.bounds.0); + // let y = (p2 - self.bounds.2) / (self.bounds.3 - self.bounds.2); - Ok((x, (1.0 - y))) + // Ok((x, (1.0 - y))) + // + Ok((p1, p2)) } pub fn set_lon_range(&mut self, range: std::ops::Range) -> &mut Self { diff --git a/src/coords/mod.rs b/src/coords/mod.rs index eef14a0..8e536eb 100644 --- a/src/coords/mod.rs +++ b/src/coords/mod.rs @@ -17,7 +17,7 @@ pub trait Coord { fn dim2_range(&self) -> (T, T); } -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub struct Range(pub f64, pub f64); impl Range { diff --git a/src/coords/proj/mercator.rs b/src/coords/proj/mercator.rs index 8de54b6..244a3bc 100644 --- a/src/coords/proj/mercator.rs +++ b/src/coords/proj/mercator.rs @@ -25,7 +25,23 @@ fn proj_string<'a>(vs: Vec<(&'a str, &'a str)>) -> String { impl Mercator { /// Creates a new instance of the `Mercator` projection with default values. - pub fn new() -> Self { + pub fn new( + central_lon: f64, + false_easting: f64, + false_northing: f64, + latitude_true_scale: f64, + ) -> Self { + Self { + central_lon, + false_easting, + false_northing, + latitude_true_scale, + } + } +} + +impl Default for Mercator { + fn default() -> Self { Self { central_lon: 0.0, false_easting: 0.0, @@ -54,5 +70,4 @@ impl ProjectionS for Mercator { let _proj_string = proj_string(input); _proj_string } - } diff --git a/src/data/mod.rs b/src/data/mod.rs index 685222c..cbca551 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,8 +1,8 @@ use crate::errors::DataError; use image::RgbImage; use ndarray::{ - s, Array, Array1, Array2, Array3, ArrayBase, Axis, DataMut, Ix1, Ix2, OwnedRepr, RawDataClone, - ViewRepr, + s, Array, Array1, Array2, Array3, ArrayBase, Axis, DataMut, Dimension, Ix1, Ix2, OwnedRepr, + RawDataClone, ViewRepr, }; use npyz::{npz::NpzArchive, Deserialize}; use num_traits::{AsPrimitive, FromPrimitive, Num}; @@ -30,9 +30,29 @@ where pub dim1: ArrayBase, I>, pub dim2: ArrayBase, I>, pub data: ArrayBase, + pub fill_value: T, pub coord_type: CoordType, } +impl Debug for RadarData2d +where + T: Num + Clone + PartialEq + PartialOrd + Debug, + Raw: ndarray::Data + Clone + ndarray::RawDataClone, + X: Num + Debug, + Y: Num + Debug, + I: Dimension, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RadarData2d") + .field("dim1", &self.dim1) + .field("dim2", &self.dim2) + .field("data", &self.data) + .field("fill_value", &self.fill_value) + .field("coord_type", &self.coord_type) + .finish() + } +} + impl Radar2d { pub fn load(path: impl AsRef, meth: impl DataLoader) -> Result { Ok(meth.load(path)?) @@ -66,6 +86,7 @@ where dim1: self.dim1.to_owned(), dim2: self.dim2.to_owned(), data: self.data.to_owned(), + fill_value: self.fill_value.clone(), coord_type: self.coord_type.clone(), }; } @@ -115,6 +136,7 @@ where return Radar2d { dim1, dim2, + fill_value: self.fill_value.clone(), data: output, coord_type: self.coord_type.clone(), }; @@ -208,24 +230,28 @@ where dim1: self.dim1.slice(s![..middle_dim1]).to_owned(), dim2: self.dim2.slice(s![..middle_dim2]).to_owned(), data: lt, + fill_value: self.fill_value.clone(), coord_type: self.coord_type, }, RadarData2d { dim1: self.dim1.slice(s![middle_dim1..]).to_owned(), dim2: self.dim2.slice(s![..middle_dim2]).to_owned(), data: rt, + fill_value: self.fill_value.clone(), coord_type: self.coord_type, }, RadarData2d { dim1: self.dim1.slice(s![..middle_dim1]).to_owned(), dim2: self.dim2.slice(s![middle_dim2..]).to_owned(), data: lb, + fill_value: self.fill_value.clone(), coord_type: self.coord_type, }, RadarData2d { dim1: self.dim1.slice(s![middle_dim1..]).to_owned(), dim2: self.dim2.slice(s![middle_dim2..]).to_owned(), data: rb, + fill_value: self.fill_value.clone(), coord_type: self.coord_type, }, ]; @@ -289,7 +315,7 @@ impl Npz { impl DataLoader>> for Npz where - T: Num + Clone + Deserialize, + T: Num + Clone + Deserialize + FromPrimitive, T: PartialEq + PartialOrd, { fn load>(&self, path: P) -> Result>, DataError> { @@ -301,6 +327,7 @@ where Ok(RadarData2d { dim1: dim1, dim2: dim2, + fill_value: T::from_f64(-125.0).unwrap(), data: value, coord_type: CoordType::LatLon, }) @@ -473,7 +500,7 @@ pub enum DownSampleMeth { VAR, } -#[derive(Clone, Copy)] +#[derive(Clone, Copy, Debug)] pub enum CoordType { Polar, LatLon, diff --git a/src/main.rs b/src/main.rs index 52110c8..f530ad0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,13 @@ -#![allow(deprecated)] - use coords::proj::Mercator; use coords::Mapper; use data::{Npz, Radar2d}; use femtovg::{Color, Paint}; +mod utils; use gtk::prelude::*; use gtk::{gio, glib, Application, ApplicationWindow}; +use relm4::menu; use relm4::RelmApp; use std::ptr; -use relm4::menu; mod components; mod coords; mod data; @@ -53,4 +52,4 @@ fn main() { // app.run() let relm = relm4::RelmApp::new(APP_ID); relm.run::(AppMode::Edit); -} \ No newline at end of file +} diff --git a/src/monitor/imp.rs b/src/monitor/imp.rs index fef77db..eaa6025 100644 --- a/src/monitor/imp.rs +++ b/src/monitor/imp.rs @@ -19,6 +19,11 @@ impl ObjectSubclass for Monitor { impl ObjectImpl for Monitor {} -impl WidgetImpl for Monitor {} +impl WidgetImpl for Monitor { + fn size_allocate(&self, width: i32, height: i32, baseline: i32) { + println!("new width {}", width); + self.parent_size_allocate(width, height, baseline) + } +} impl BoxImpl for Monitor {} diff --git a/src/monitor/mod.rs b/src/monitor/mod.rs index aec304e..8771119 100644 --- a/src/monitor/mod.rs +++ b/src/monitor/mod.rs @@ -1,15 +1,11 @@ mod imp; -use crate::data::RadarData2d; -use crate::render::Render; -use adw::prelude::{GLAreaExt, GestureDragExt}; -use glib::clone; +use crate::render::{Render, RenderMotion, RenderConfig}; +use adw::prelude::{ButtonExt, GLAreaExt, GestureDragExt}; use glib::subclass::prelude::*; -use gtk::glib; +use glib::{clone, ObjectExt}; use gtk::traits::WidgetExt; +use gtk::{glib, AspectFrame}; use gtk::{EventControllerScrollFlags, Inhibit}; -use num_traits::{AsPrimitive, FromPrimitive, Num}; -use proj::ProjError; -use std::fmt::Debug; glib::wrapper! { pub struct Monitor(ObjectSubclass) @@ -17,13 +13,18 @@ glib::wrapper! { } impl Monitor { - pub fn new(render: Render) -> Self { + pub fn new(config:RenderConfig) -> Self { let this: Self = glib::Object::new(); let pointer_location_detecture = gtk::EventControllerMotion::new(); + + let render = Render::new(None, config); + pointer_location_detecture.connect_motion( clone!(@weak this as s, @weak render as r => move |_context, x, y| { r.update_status(|s| { - s.pointer_location = (x as f32, y as f32); + let dpi = r.scale_factor(); + let (_,h) = s.window_size; + s.pointer_location = (x as f32 * dpi as f32, h as f32 - y as f32 * dpi as f32); }); } ), @@ -31,13 +32,9 @@ impl Monitor { let scale_detecture = gtk::EventControllerScroll::new(EventControllerScrollFlags::VERTICAL); scale_detecture.connect_scroll(clone!( @weak this as s, @weak render as r => @default-panic,move |_context, _x, y| { - let renderer = s.imp().renderer.borrow(); - let mut scale = 1.0; r.update_status(|status|{ - let current_scale = status.scale; - let s = (current_scale - y as f32 / 5.0).max(1.0).min(5.0); - status.scale = s; - scale = s; + status.scale = y as f32; + status.motion = RenderMotion::Scale; }); r.queue_render(); Inhibit(false) @@ -49,44 +46,47 @@ impl Monitor { @weak render as r => move |this, _, _| { let (ox, oy) = this.offset().unwrap_or((0.0,0.0)); r.update_status(|s| { - s.updated_translate = ( - ox as f32, -oy as f32); + s.translate = Some((-ox as f32 * 1.35, oy as f32 * 1.35)); + s.motion = RenderMotion::Translate; }); r.queue_render(); })); + drag_detecture.connect_drag_end(clone!( - @weak render as r => move |this,_,_|{ - let t = r.imp().translate(); + @weak render as r => move |_,_,_|{ r.update_status(|cfg| { - cfg.translate = t ; - cfg.updated_translate = (0.0,0.0); + cfg.translate = None; + cfg.motion = RenderMotion::Translate; }) } + r.queue_render(); )); + let split_view = adw::OverlaySplitView::new(); + let sidebar = adw::NavigationPage::builder().title("sidebar").child(>k::ListView::builder().build()).build(); + let content = adw::NavigationPage::builder().title("content").child(&render).build(); + + split_view.set_sidebar_position(gtk::PackType::End); + + split_view.set_sidebar(Some(&sidebar)); + split_view.set_content(Some(&content)); + render.add_controller(pointer_location_detecture); render.add_controller(scale_detecture); render.add_controller(drag_detecture); - render.set_hexpand(true); - render.set_vexpand(true); - render.set_parent(&this); + // render.set_hexpand(true); + // render.set_vexpand(true); + + split_view.set_parent(&this); + split_view.set_hexpand(true); + split_view.set_vexpand(true); + + // paned.set_parent(&this); + // render.set_parent(&this); + // aspect_frame.set_hexpand(true); + // aspect_frame.set_vexpand(true); + // aspect_frame.set_parent(&this); this.imp().renderer.replace(render); this } - - pub fn load_data_2d(&self, data: RadarData2d) -> Result<(), ProjError> - where - T: Num - + Clone - + PartialEq - + PartialOrd - + AsPrimitive - + AsPrimitive - + Debug - + FromPrimitive, - Raw: ndarray::Data + Clone + ndarray::RawDataClone, - { - let renderer = self.imp().renderer.borrow(); - renderer.load_data_2d(data)?; - Ok(()) - } } diff --git a/src/render/cms.rs b/src/render/cms.rs new file mode 100644 index 0000000..e73208d --- /dev/null +++ b/src/render/cms.rs @@ -0,0 +1,45 @@ +use geo_types::LineString; + +use crate::coords::Mapper; + +pub struct CMS { + mapper: Mapper, + window_size: (f32, f32), + bounds: (f64, f64, f64, f64), +} + +impl CMS { + pub fn new(mapper: Mapper, window_size: (f32, f32)) -> Self { + let bounds = mapper.get_bounds(); + Self { + mapper, + window_size, + bounds, + } + } + + pub fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> { + self.mapper.map(loc).ok().map(|(x, y)| { + // println!("x: {}, y: {}", x, y); + let (w, h) = self.window_size; + let (w, h) = (w as f64, h as f64); + let (x, y) = (x - self.bounds.0, y - self.bounds.2); + let (x, y) = ( + x / (self.bounds.1 - self.bounds.0), + 1.0 - y / (self.bounds.3 - self.bounds.2), + ); + let (x, y) = (x * w, y * h); + (x as f32, y as f32) + }) + } + + pub fn ring_map(&self, line: &LineString) -> Option> { + Some( + line.points() + .into_iter() + .map(|p| self.map((p.x(), p.y())).unwrap()) + .collect::>() + .into(), + ) + } +} diff --git a/src/render/exterior/mod.rs b/src/render/exterior/mod.rs deleted file mode 100644 index 84a81c6..0000000 --- a/src/render/exterior/mod.rs +++ /dev/null @@ -1,40 +0,0 @@ -mod imp; -use crate::coords::{Mapper, Range}; -use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path}; -use geo_types::{line_string, LineString, Point}; -use glib::subclass::types::ObjectSubclassIsExt; -use std::cell::Ref; - -use super::imp::{RenderStatus, RenderConfig}; - -glib::wrapper! { - pub struct ExteriorWidget(ObjectSubclass); -} - -impl Default for ExteriorWidget { - fn default() -> Self { - Self::new() - } -} - -impl ExteriorWidget { - pub fn new() -> Self { - let this: Self = glib::Object::new(); - this - } - - pub(super) fn draw(&self, canvas: &mut Canvas, mapper: Ref<'_, Mapper>, status:Ref<'_, RenderStatus>, _c:Ref<'_, RenderConfig>) { - let imp = self.imp(); - let canvas_width = canvas.width(); - let canvas_height = canvas.height(); - let padding = [30.0,30.0,30.0,30.0]; - - let w = canvas_width - padding[1] - padding[3]; - let h = canvas_height - padding[0] - padding[2]; - - let paint = Paint::color(Color::white()); - let mut path = Path::new(); - path.rect(padding[3], padding[0], w, h); - canvas.stroke_path(&path, &paint); - } -} diff --git a/src/render/foreground/mod.rs b/src/render/foreground/mod.rs deleted file mode 100644 index f075299..0000000 --- a/src/render/foreground/mod.rs +++ /dev/null @@ -1,193 +0,0 @@ -mod imp; -use crate::coords::Mapper; -use femtovg::{renderer::OpenGl, Canvas, Path}; -use femtovg::{Color, FontId, ImageFlags, ImageId, Paint}; -use geo_types::{LineString, Point}; -use glib::subclass::types::ObjectSubclassIsExt; -use gtk::glib; -use ndarray::{s, Array2, Axis, Zip}; -use std::cell::Ref; - -pub use self::imp::ForegroundConfig; - -glib::wrapper! { - pub struct ForegroundWidget(ObjectSubclass); -} - -impl Default for ForegroundWidget { - fn default() -> Self { - Self::new(ForegroundConfig::default()) - } -} - -impl ForegroundWidget { - pub fn new(config: ForegroundConfig) -> Self { - let this: Self = glib::Object::new(); - let imp = this.imp(); - imp.config.replace(config); - this - } - - pub fn draw(&self, canvas: &mut Canvas, scale: f32, dpi: i32, mapper: Ref<'_, Mapper>) { - let canvas_width = canvas.width(); - let canvas_height = canvas.height(); - let config = self.imp().config.borrow(); - - let colors = self.imp().color.borrow(); - let data = self.imp().data.borrow(); - - if self.imp().image.borrow().is_none() { - println!("rebuild image"); - let img_id = canvas - .create_image_empty( - canvas_width as usize, - canvas_height as usize, - femtovg::PixelFormat::Rgb8, - ImageFlags::empty(), - ) - .unwrap(); - if let Ok(v) = canvas.image_size(img_id) { - println!("create image: {:?}", v); - canvas.set_render_target(femtovg::RenderTarget::Image(img_id)); - let colors = self.imp().color.borrow(); - let data = self.imp().data.borrow(); - if colors.is_some() && data.is_some() { - for (i, c) in colors - .as_ref() - .unwrap() - .t() - .iter() - .zip(data.as_ref().unwrap().iter()) - { - if i.is_none() { - continue; - } - let pts = c.points().collect::>(); - let first_point = pts[0]; - let mut path = Path::new(); - path.move_to( - first_point.x() as f32 * v.0 as f32 + 0.8, - first_point.y() as f32 * v.1 as f32 + 0.8, - ); - - pts[1..].into_iter().for_each(|p| { - path.line_to(p.x() as f32 * v.0 as f32, p.y() as f32 * v.1 as f32) - }); - let c = i.unwrap(); - canvas.fill_path(&path, &Paint::color(c)); - } - } - - self.imp().image.replace(Some(img_id)); - } - } - - canvas.set_render_target(femtovg::RenderTarget::Screen); - - let mut path = Path::new(); - path.rect(0.0, 0.0, canvas_width, canvas_height); - - canvas.fill_path( - &path, - &Paint::image( - self.imp().image.borrow().as_ref().unwrap().to_owned(), - 0.0, - 0.0, - canvas_width, - canvas_height, - 0.0, - 1.0, - ), - ); - } - - pub(super) fn set_dims(&self, dims: (Array2, Array2)) { - self.imp().dim1.replace(Some(dims.0)); - self.imp().dim2.replace(Some(dims.1)); - } - - pub(super) fn set_image(&self, image: ImageId) { - self.imp().image.replace(Some(image)); - } - - pub fn set_colors(&self, colors: Array2>) { - self.imp().color.replace(Some(colors)); - } - - pub fn set_data(&self, data: Array2) { - self.imp().data.replace(Some(data)); - } -} - -// 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 34380da..8e4ce9e 100644 --- a/src/render/imp.rs +++ b/src/render/imp.rs @@ -1,13 +1,10 @@ -use super::background::BackgroundWidget; -use super::exterior::ExteriorWidget; -use super::interior::InteriorWidget; -use super::foreground::ForegroundWidget; +use super::layer::foreground::exterior::ExteriorWidget; +use super::layer::foreground::interior::InteriorWidget; use super::WindowCoord; use crate::coords::proj::Mercator; use crate::coords::Mapper; use femtovg::{Color, FontId, Paint, Path, Transform2D}; use gtk::glib; -use gtk::prelude::WidgetExtManual; use gtk::subclass::prelude::*; use gtk::traits::{GLAreaExt, WidgetExt}; use ndarray::Array2; @@ -16,18 +13,32 @@ use std::num::NonZeroU32; #[derive(Debug, Default, Clone)] pub struct RenderConfig { - pub dim1: Option>, - pub dim2: Option>, pub padding: [f32; 4], - // pub pointer_lon_lat: (f64, f64), +} + +#[derive(Debug, Clone)] +pub enum RenderMotion { + Translate, + Scale, + None, +} + +impl Default for RenderMotion { + fn default() -> Self { + Self::None + } } #[derive(Debug, Default, Clone)] -pub struct RenderStatus{ - pub scale: f32, +pub struct RenderStatus { + pub window_size: (i32, i32), + pub(super) scale_rate: Option, + translation: Option<(f64, f64)>, + init_translation: (f64, f64), pub pointer_location: WindowCoord, - pub translate: WindowCoord, - pub updated_translate: WindowCoord, + pub motion: RenderMotion, + pub scale: f32, + pub translate: Option<(f32, f32)>, } struct Fonts { @@ -52,7 +63,7 @@ impl Default for Render { interior: RefCell::new(InteriorWidget::default()), config: RefCell::new(RenderConfig::default()), status: RefCell::new(RenderStatus::default()), - mapper: RefCell::new(Mercator::new().into()), + mapper: RefCell::new(Mercator::default().into()), canvas: RefCell::new(None), } } @@ -71,10 +82,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) - // }); } } @@ -83,6 +90,7 @@ impl WidgetImpl for Render {} impl GLAreaImpl for Render { fn resize(&self, width: i32, height: i32) { + self.status.borrow_mut().window_size = (width, height); self.ensure_canvas(); let mut canvas = self.canvas.borrow_mut(); let canvas = canvas.as_mut().unwrap(); @@ -91,16 +99,33 @@ impl GLAreaImpl for Render { height as u32, self.obj().scale_factor() as f32, ); + + let translation = self.status.borrow().translation; + let scale_rate = self.status.borrow().scale_rate; + let mapper = self.mapper.borrow(); + + if let None = translation { + let mut status = self.status.borrow_mut(); + status.translation = Some((mapper.get_bounds().0, mapper.get_bounds().2)); + status.init_translation = (mapper.get_bounds().0, mapper.get_bounds().2); + } + + if let None = scale_rate { + let scale = (mapper.get_bounds().3 - mapper.get_bounds().2) / canvas.height() as f64; + self.status.borrow_mut().scale_rate = Some(scale); + } } fn render(&self, context: >k::gdk::GLContext) -> bool { self.ensure_canvas(); + let mut canvas = self.canvas.borrow_mut(); let canvas = canvas.as_mut().unwrap(); let dpi = self.obj().scale_factor(); let w = canvas.width(); let h = canvas.height(); + let configs = self.config.borrow(); canvas.clear_rect( @@ -111,34 +136,44 @@ impl GLAreaImpl for Render { Color::rgba(0, 0, 0, 255), ); - let mapper = self.mapper.borrow(); + { + let mut status = self.status.borrow_mut(); + match status.motion { + RenderMotion::Translate => { + if let Some((x, y)) = status.translate { + let (ix, iy) = status.init_translation; + status.translation = Some(( + ix + x as f64 * status.scale_rate.unwrap(), + iy + y as f64 * status.scale_rate.unwrap(), + )); + } else { + status.init_translation = status.translation.unwrap(); + } + } + RenderMotion::Scale => { + let scale_rate = status.scale_rate.unwrap(); + let scale_flag = status.scale as f64; + let step = scale_rate * 0.1; - self.interior.borrow().draw(canvas, mapper, self.status.borrow(), configs); + let (tx, ty) = status.translation.unwrap(); + let (px, py) = status.pointer_location; - // let (lon_range, lat_range) = mapper.range.clone(); - // { - // let background_widget = self.background.borrow_mut(); - // background_widget.set_lat_lines(lat_range.key_points(9)); - // } - // { - // let background_widget = self.background.borrow_mut(); - // background_widget.set_lon_lines(lon_range.key_points(20)); - // } - // let translate = self.translate(); + let scaled = scale_rate + scale_flag * step; + status.scale_rate = Some(scaled); + let sx = scale_flag * step * px as f64; + let sy = scale_flag * step * py as f64; + status.translation = Some((tx - sx, ty - sy)); + status.init_translation = status.translation.unwrap(); + } + RenderMotion::None => {} + } + } - // self.exterior.borrow().draw(canvas,self.mapper.borrow()); - // self.foreground - // .borrow() - // .draw(canvas, configs.scale, dpi, self.mapper.borrow()); + self.interior + .borrow() + .draw(canvas, &self.obj(), self.status.borrow(), configs); - // self.background.borrow().draw( - // canvas, - // configs.scale, - // dpi, - // translate, - // self.mapper.borrow(), - // (lon_range, lat_range), - // ); + self.exterior.borrow().draw(canvas, &self.obj()); canvas.flush(); true } @@ -178,11 +213,74 @@ impl Render { self.canvas.replace(Some(canvas)); } - pub fn translate(&self) -> WindowCoord { - let cfg = self.status.borrow(); - let (rx, ry) = cfg.translate; - let (ux, uy) = cfg.updated_translate; + pub(super) fn window_size(&self) -> Option<(i32, i32)> { + Some(self.status.borrow().window_size) + } - return (rx + ux, ry + uy); + pub(super) fn window_range(&self) -> Option<((f64, f64), (f64, f64))> { + let padding = self.padding(); + let (w, h) = self.window_size().unwrap(); + let (w, h) = ( + w as f32 - padding[1] - padding[3], + h as f32 - padding[0] - padding[2], + ); + let (w, h) = (w as f64, h as f64); + + let mapper = self.mapper.borrow(); + + let status = self.status.borrow(); + status.translation.and_then(|(tx, ty)| { + status.scale_rate.and_then(|scale| { + let (x1, y1) = (tx + padding[3] as f64, ty + padding[2] as f64); + let (x2, y2) = (tx + w * scale + padding[3] as f64, ty + h * scale + padding[2] as f64); + let (x1, y1) = mapper.inverse_map((x1, y1)).unwrap(); + let (x2, y2) = mapper.inverse_map((x2, y2)).unwrap(); + Some(((x1, x2), (y1, y2))) + }) + }) + } + + pub(super) fn inverse_map(&self, loc: (f32, f32)) -> Option<(f64, f64)> { + let (x, y) = loc; + // let (_, h) = self.window_size().unwrap(); + let status = self.status.borrow(); + status.translation.and_then(|(tx, ty)| { + status.scale_rate.and_then(|scale| { + let (x, y) = (x as f64, y as f64); + Some((tx + x * scale, (ty + y * scale))) + }) + }) + } + + fn padding(&self) -> [f32; 4] { + self.config.borrow().padding + } + + pub(super) fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> { + // let padding = self.padding(); + // let left_padding = padding[3]; + // let bottom_padding = padding[2]; + let (x, y) = loc; + let (_, h) = self.window_size().unwrap(); + let status = self.status.borrow(); + status.translation.and_then(|(tx, ty)| { + status.scale_rate.and_then(|scale| { + Some(( + (x - tx as f64) as f32 / scale as f32, + h as f32 - (y - ty as f64) as f32 / scale as f32, + )) + }) + }) + } + + pub(super) fn set_translation(&self, translation: (f64, f64)) { + let mut status = self.status.borrow_mut(); + status.translation = Some(translation); + } + + fn pointer_loc(&self) -> (f32, f32) { + let (x, y) = self.status.borrow().pointer_location.clone(); + let (_, h) = self.window_size().unwrap(); + (x, h as f32 - y) } } diff --git a/src/render/interior/mod.rs b/src/render/interior/mod.rs deleted file mode 100644 index 8518a7b..0000000 --- a/src/render/interior/mod.rs +++ /dev/null @@ -1,58 +0,0 @@ -mod imp; -use crate::coords::{Mapper, Range}; -use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path}; -use geo_types::{line_string, LineString, Point}; -use glib::subclass::types::ObjectSubclassIsExt; -use std::cell::Ref; - -use super::imp::{RenderConfig, RenderStatus}; - -glib::wrapper! { - pub struct InteriorWidget(ObjectSubclass); -} - -impl Default for InteriorWidget { - fn default() -> Self { - Self::new() - } -} - -impl InteriorWidget { - pub fn new() -> Self { - let this: Self = glib::Object::new(); - this - } - - pub(super) fn draw( - &self, - canvas: &mut Canvas, - mapper: Ref<'_, Mapper>, - status: Ref<'_, RenderStatus>, - _c: Ref<'_, RenderConfig>, - ) { - let imp = self.imp(); - let canvas_width = canvas.width(); - let canvas_height = canvas.height(); - - let padding = _c.padding; - - let w = canvas_width - padding[1] - padding[3]; - let h = canvas_height - padding[0] - padding[2]; - let scale = status.scale; - let dpi = 2; - - let (tx, ty) = status.translate; - let range = mapper.range.clone(); - - self.imp().background.borrow().draw( - canvas, - scale, - dpi, - w, - h, - (tx + padding[3], ty + padding[0]), - mapper, - range, - ); - } -} diff --git a/src/render/background/imp.rs b/src/render/layer/background/imp.rs similarity index 100% rename from src/render/background/imp.rs rename to src/render/layer/background/imp.rs diff --git a/src/render/background/mod.rs b/src/render/layer/background/mod.rs similarity index 96% rename from src/render/background/mod.rs rename to src/render/layer/background/mod.rs index 1124169..6d26269 100644 --- a/src/render/background/mod.rs +++ b/src/render/layer/background/mod.rs @@ -80,7 +80,7 @@ impl BackgroundWidget { .map(|p| { ( p.x() as f32 * canvas_width * scale - translate.0, - (1.0 - p.y() as f32) * canvas_height * scale - translate.1, + p.y() as f32 * canvas_height * scale - translate.1, ) .into() }) @@ -136,7 +136,7 @@ impl BackgroundWidget { .map(|p| { ( p.x() as f32 * canvas_width * scale - translate.0, - (1.0 - p.y()) as f32 * canvas_height * scale - translate.1, + p.y() as f32 * canvas_height * scale - translate.1, ) .into() }) @@ -222,7 +222,7 @@ impl BackgroundWidget { let (x, y) = mapper.map(point).unwrap(); ( x as f32 * canvas_width * scale - translate.0, - (1.0 - y as f32) * canvas_height * scale - translate.1, + y as f32 * canvas_height * scale - translate.1, ) } diff --git a/src/render/exterior/imp.rs b/src/render/layer/foreground/exterior/imp.rs similarity index 100% rename from src/render/exterior/imp.rs rename to src/render/layer/foreground/exterior/imp.rs diff --git a/src/render/layer/foreground/exterior/mod.rs b/src/render/layer/foreground/exterior/mod.rs new file mode 100644 index 0000000..4bf2391 --- /dev/null +++ b/src/render/layer/foreground/exterior/mod.rs @@ -0,0 +1,89 @@ +mod imp; +use crate::{coords::Range, render::Render}; +use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path}; +use glib::subclass::types::ObjectSubclassIsExt; + +glib::wrapper! { + pub struct ExteriorWidget(ObjectSubclass); +} + +impl Default for ExteriorWidget { + fn default() -> Self { + Self::new() + } +} + +impl ExteriorWidget { + pub fn new() -> Self { + let this: Self = glib::Object::new(); + this + } + + pub fn draw(&self, canvas: &mut Canvas, render: &Render) { + let padding = render.imp().config.borrow().padding; + let (w, h) = render.window_size(); + let (w, h) = (w as f32, h as f32); + let origins = [ + (0.0, 0.0), + (w - padding[1], 0.0), + (0.0, h - padding[2]), + (0.0, 0.0), + ]; + let whs = [ + (w, padding[0]), + (padding[1], h), + (w, padding[2]), + (padding[3], h), + ]; + + let painter = Paint::color(Color::rgb(0, 0, 0)); + + for edge in 0..4 { + let mut path = Path::new(); + let (ox, oy) = origins[edge]; + let (w, h) = whs[edge]; + path.rect(ox, oy, w, h); + canvas.fill_path(&path, &painter); + } + + let (lon_range, lat_range) = render.render_range(); + let (lon_range, lat_range): (Range, Range) = (lon_range.into(), lat_range.into()); + + let lon_keypoints = lon_range.key_points(10); + let lat_keypoints = lat_range.key_points(5); + let mut paint = Paint::color(Color::white()); // 黑色字体 + paint.set_font_size(25.0); + + for lon in lon_keypoints.iter() { + let (x, _) = render.map((*lon, lat_range.0)).unwrap(); + + let text = format!("{:.2}", lon); + let metrics = canvas + .measure_text(x, 0.0, text.as_str(), &paint) + .expect("Cannot measure text"); + + let text_x = x - metrics.width() / 2.0; + let text_y = h - metrics.height() / 2.0; + + canvas + .fill_text(text_x, text_y, text.as_str(), &paint) + .expect("Cannot draw text"); + } + + for lat in lat_keypoints.iter() { + let (_, y) = render.map((lon_range.0, *lat)).unwrap(); + + let text = format!("{:.2}", lat); + let metrics = canvas + .measure_text(0.0, y, text.as_str(), &paint) + .expect("Cannot measure text"); + + let text_x = 0.0; + let text_y = y - metrics.height() / 2.0; + + canvas + .fill_text(text_x, text_y, text.as_str(), &paint) + .expect("Cannot draw text"); + } + } +} diff --git a/src/render/foreground/imp.rs b/src/render/layer/foreground/imp.rs similarity index 92% rename from src/render/foreground/imp.rs rename to src/render/layer/foreground/imp.rs index 9e5d47f..3950cf4 100644 --- a/src/render/foreground/imp.rs +++ b/src/render/layer/foreground/imp.rs @@ -1,11 +1,12 @@ use crate::render::{imp, WindowCoord}; -use femtovg::{ImageId, Paint, Color}; +use femtovg::{Color, ImageId, Paint, RenderTarget}; use geo_macros::Prj; use geo_types::LineString; use gtk::glib; use gtk::subclass::prelude::*; use ndarray::Array2; use std::cell::RefCell; +use std::collections::HashMap; #[derive(Prj, Default)] pub struct ForegroundConfig { diff --git a/src/render/interior/imp.rs b/src/render/layer/foreground/interior/imp.rs similarity index 50% rename from src/render/interior/imp.rs rename to src/render/layer/foreground/interior/imp.rs index 515a396..2577ecd 100644 --- a/src/render/interior/imp.rs +++ b/src/render/layer/foreground/interior/imp.rs @@ -1,14 +1,20 @@ +use crate::render::layer::background::BackgroundWidget; +use crate::render::layer::foreground::ForegroundWidget; +use crate::render::{imp, WindowCoord}; +use femtovg::RenderTarget; use gtk::glib; use gtk::subclass::prelude::*; use std::cell::RefCell; -use crate::render::background::BackgroundWidget; -use crate::render::foreground::ForegroundWidget; +use super::layers::Layer; #[derive(Default)] pub struct InteriorWidget { pub(super) background: RefCell, pub(super) foreground: RefCell, + pub(super) trans: RefCell, + pub(super) update_trans: RefCell, + pub(super) layers: RefCell>, } #[glib::object_subclass] @@ -17,4 +23,4 @@ impl ObjectSubclass for InteriorWidget { type Type = super::InteriorWidget; } -impl ObjectImpl for InteriorWidget{} +impl ObjectImpl for InteriorWidget {} diff --git a/src/render/layer/foreground/interior/layers.rs b/src/render/layer/foreground/interior/layers.rs new file mode 100644 index 0000000..8810c0a --- /dev/null +++ b/src/render/layer/foreground/interior/layers.rs @@ -0,0 +1,107 @@ +use geojson::GeoJson; +use std::{ + any::Any, + cell::{Ref, RefCell}, fmt::Debug, +}; +use topojson::TopoJson; + +use crate::{ + coords::{Coord, Mapper, Range}, + render::{renders::DataRenderer, Render}, +}; +use femtovg::{renderer::OpenGl, Canvas, ImageId}; + +pub struct Layer { + pub visiable: bool, + target: RefCell>, + imp: RefCell>>, + draw: Box, &Render, (f32, f32))>, +} + +impl Debug for Layer { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Layer") + .field("visiable", &self.visiable) + .field("target", &self.target) + .field("imp", &self.imp) + .finish() + } +} + +pub trait LayerImpl:Debug { + fn draw(&self, canvas: &mut Canvas, render: &Render) -> Option; +} + +impl Layer { + pub fn new< + F: 'static + Fn(&Self, &mut Canvas, &Render, (f32, f32)), + IMP: LayerImpl + 'static, + >( + visiable: bool, + draw: F, + imp: Option, + ) -> Self { + Layer { + visiable, + target: RefCell::new(None), + draw: Box::new(draw), + imp: RefCell::new(imp.map(|x| Box::new(x) as Box)), + } + } + + pub fn draw(&self, canvas: &mut Canvas, render: &Render, window_size: (f32, f32)) { + if self.visiable { + (self.draw)(self, canvas, render, window_size); + } + } + + pub fn set_render_target(&self, target: Target) { + *self.target.borrow_mut() = Some(target); + } + + pub fn render_target(&self) -> Option { + self.target.borrow().clone() + } + + pub fn get_imp(&self) -> Ref>> { + let im = self.imp.borrow(); + im + } +} + +#[derive(Clone, Copy, Debug)] +pub struct Target { + pub target: ImageId, + width: f32, + height: f32, + bounds: (Range, Range), +} + +impl Target { + pub fn new(target: ImageId, width: f32, height: f32, bounds: (Range, Range)) -> Self { + Self { + target, + width, + height, + bounds, + } + } + + pub fn size(&self, render: &Render) -> (f32, f32) { + let (x, y) = self.bounds; + + let p1 = (x.0, y.0); + let p2 = (x.1, y.1); + + let (x1, y1) = render.map(p1).unwrap(); + let (x2, y2) = render.map(p2).unwrap(); + + ((x2 - x1).abs(), (y2 - y1).abs()) + } + + pub fn origin(&self, render: &Render) -> (f32, f32) { + let (x, y) = self.bounds; + let p1 = (x.0, y.1); + render.map(p1).unwrap() + } +} diff --git a/src/render/layer/foreground/interior/mod.rs b/src/render/layer/foreground/interior/mod.rs new file mode 100644 index 0000000..947d1bc --- /dev/null +++ b/src/render/layer/foreground/interior/mod.rs @@ -0,0 +1,56 @@ +mod imp; +mod layers; +use crate::coords::{Mapper, Range}; +use crate::data::Npz; +use crate::data::{DataLoader, RadarData2d}; +use crate::render::predefined::color_mapper::BoundaryNorm; +use crate::render::predefined::gis::MapRender; +use crate::render::renders::DataRenderer; +use crate::render::Render; +use femtovg::{renderer::OpenGl, Canvas, Color, Paint, Path}; +use glib::subclass::types::ObjectSubclassIsExt; +pub use layers::{Layer, LayerImpl, Target}; +use ndarray::OwnedRepr; +use std::cell::Ref; +use std::ops::Deref; + +use crate::render::imp::{RenderConfig, RenderStatus}; + +glib::wrapper! { + pub struct InteriorWidget(ObjectSubclass); +} + +impl Default for InteriorWidget { + fn default() -> Self { + Self::new() + } +} + +impl InteriorWidget { + pub fn new() -> Self { + let this: Self = glib::Object::new(); + this.imp().layers.replace(vec![ + // Layer::grid_render_layer_with_path( + // "/users/tsuki/projects/radar-g/test2.npz", + // Npz, + // BoundaryNorm::default(), + // ) + ]); + this + } + + pub fn draw( + &self, + canvas: &mut Canvas, + render: &Render, + status: Ref<'_, RenderStatus>, + _c: Ref<'_, RenderConfig>, + ) { + let mut layers = self.imp().layers.borrow_mut(); + + let (x, y) = (canvas.width(), canvas.height()); + for layer in layers.iter_mut().filter(|x| x.visiable) { + layer.draw(canvas, &render, (x, y)); + } + } +} diff --git a/src/render/layer/foreground/mod.rs b/src/render/layer/foreground/mod.rs new file mode 100644 index 0000000..1b0f7c4 --- /dev/null +++ b/src/render/layer/foreground/mod.rs @@ -0,0 +1,34 @@ +pub mod exterior; +mod imp; +pub mod interior; +use crate::coords::Mapper; +use femtovg::{renderer::OpenGl, Canvas, Path}; +use glib::subclass::types::ObjectSubclassIsExt; +use gtk::glib; +use std::cell::Ref; + +pub use self::imp::ForegroundConfig; + +glib::wrapper! { + pub struct ForegroundWidget(ObjectSubclass); +} + +impl Default for ForegroundWidget { + fn default() -> Self { + Self::new(ForegroundConfig::default()) + } +} + +impl ForegroundWidget { + pub fn new(config: ForegroundConfig) -> Self { + let this: Self = glib::Object::new(); + let imp = this.imp(); + imp.config.replace(config); + this + } + + pub fn draw(&self, canvas: &mut Canvas, scale: f32, dpi: i32, mapper: Ref<'_, Mapper>) { + let config = self.imp().config.borrow(); + } + +} diff --git a/src/render/layer/mod.rs b/src/render/layer/mod.rs new file mode 100644 index 0000000..82610d3 --- /dev/null +++ b/src/render/layer/mod.rs @@ -0,0 +1,2 @@ +pub mod background; +pub mod foreground; diff --git a/src/render/mod.rs b/src/render/mod.rs index caa99a3..1659c5a 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,23 +1,22 @@ -mod background; -mod exterior; -mod foreground; +mod cms; mod imp; -mod interior; +mod layer; +mod predefined; +mod renders; +use self::cms::CMS; +pub use self::imp::{RenderConfig, RenderMotion, RenderStatus}; +pub use self::layer::background::{BackgroundConfig, BackgroundWidget}; +pub use self::layer::foreground::{ForegroundConfig, ForegroundWidget}; use crate::coords::Mapper; -use crate::data::RadarData2d; -use crate::pipeline::ProjPipe; -use crate::pipeline::{Pipeline, ShadePipe}; -use std::fmt::Debug; - -pub use self::background::{BackgroundConfig, BackgroundWidget}; -pub use self::foreground::{ForegroundConfig, ForegroundWidget}; -use self::imp::{RenderConfig, RenderStatus}; -use adw::prelude::WidgetExt; -use femtovg::Color; +use adw::prelude::{GLAreaExt, GestureDragExt}; +use geo_types::LineString; +use glib::clone; pub use glib::subclass::prelude::*; -use ndarray::{self, Array2}; -use num_traits::{AsPrimitive, FromPrimitive, Num}; -use proj::ProjError; +use gtk::traits::WidgetExt; +use gtk::{EventControllerScrollFlags, Inhibit}; +pub use layer::foreground::interior::Layer; +use std::cell::Ref; +use gtk::prelude::*; pub(super) type WindowCoord = (f32, f32); @@ -28,21 +27,74 @@ glib::wrapper! { impl Default for Render { fn default() -> Self { - Self::new(None) + Self::new(None, RenderConfig { padding: [10.0; 4] }) } } impl Render { - pub fn new(mapper: Option) -> Self { + pub fn new(mapper: Option, cfg: RenderConfig) -> Self { let this: Self = glib::Object::new(); { - let mut configs = this.imp().config.borrow_mut(); let mut status = this.imp().status.borrow_mut(); status.scale = 1.0; } + this.imp().config.replace(cfg); if let Some(mapper) = mapper { this.set_mapper(mapper); } + + + let pointer_location_detecture = gtk::EventControllerMotion::new(); + pointer_location_detecture.connect_motion( + clone!( @weak this as r => move |_context, x, y| { + r.update_status(|s| { + let dpi = r.scale_factor(); + let (_,h) = s.window_size; + s.pointer_location = (x as f32 * dpi as f32, h as f32 - y as f32 * dpi as f32); + }); + } + ), + ); + + let scale_detecture = gtk::EventControllerScroll::new(EventControllerScrollFlags::VERTICAL); + scale_detecture.connect_scroll(clone!( + @weak this as r => @default-panic,move |_context, _x, y| { + r.update_status(|status|{ + status.scale = y as f32; + status.motion = RenderMotion::Scale; + }); + r.queue_render(); + Inhibit(false) + } + )); + + let drag_detecture = gtk::GestureDrag::new(); + drag_detecture.connect_drag_update(clone!( + @weak this as r => move |this, _, _| { + let (ox, oy) = this.offset().unwrap_or((0.0,0.0)); + r.update_status(|s| { + s.translate = Some((-ox as f32 * 1.35, oy as f32 * 1.35)); + s.motion = RenderMotion::Translate; + }); + r.queue_render(); + })); + + drag_detecture.connect_drag_end(clone!( + @weak this as r => move |_,_,_|{ + r.update_status(|cfg| { + cfg.translate = None; + cfg.motion = RenderMotion::Translate; + }) + } + r.queue_render(); + )); + + this.set_hexpand(true); + this.set_vexpand(true); + this.add_controller(pointer_location_detecture); + this.add_controller(scale_detecture); + this.add_controller(drag_detecture); + this } @@ -54,6 +106,10 @@ impl Render { f(&mut cfg); } + pub fn set_cfg(&self, cfg: RenderConfig) { + self.imp().config.replace(cfg); + } + pub fn update_status(&self, mut f: F) where F: FnMut(&mut RenderStatus), @@ -62,83 +118,75 @@ impl Render { f(&mut status); } - // pub fn change_pointer(&mut self, x: f32, y: f32) { - // if let Some(canvas) = self.imp().canvas.borrow().as_ref() { - // let dpi = self.scale_factor(); - // let cw = canvas.width(); - // let ch = canvas.height(); - // let (tx, ty) = self.imp().translate(); - // let scale = self.imp().config.borrow().scale; - // let (lon, lat) = self - // .imp() - // .mapper - // .borrow() - // .fore_map(( - // (x * dpi as f32 + tx) / cw / scale, - // (y * dpi as f32 + ty) / ch / scale, - // )) - // .unwrap(); - // let mut cfg = self.imp().config.borrow_mut(); - // cfg.pointer_lon_lat = (lon, lat); - // cfg.pointer_location = (x, y); - // } - // } - - // pub fn set_translate(&self, trans: (f32, f32)) { - // if let Some(c) = self.imp().canvas.borrow().as_ref() { - // self.imp().config.borrow_mut().translate = (c.width() * trans.0, c.height() * trans.1); - // } - // } - pub fn set_mapper(&self, mapper: Mapper) { self.imp().mapper.replace(mapper); } - pub(super) fn load_data_2d( - &self, - mut data: RadarData2d, - ) -> Result<(), ProjError> + pub fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> { + // let (x, y) = loc; + // let (x_range, y_range) = self.imp().window_range().unwrap(); + + // if x >= x_range.0 && x <= x_range.1 && y >= y_range.0 && y <= y_range.1 { + + // } + let foremapped = self.get_mapper().map(loc).unwrap(); + return self.imp().map(foremapped); + } + + pub fn inverse_map(&self, loc: (f32, f32)) -> Option<(f64, f64)> { + let foremapped = self.imp().inverse_map(loc); + foremapped.and_then(|foremapped| self.get_mapper().inverse_map(foremapped).ok()) + } + + pub fn ring_map(&self, ring: &LineString) -> Option> { + Some( + ring.points() + .into_iter() + .map(|p| self.map((p.x(), p.y()))) + .filter(|p| p.is_some()) + .collect::>>() + .unwrap() + .into(), + ) + } + + pub fn point_in_bound(&self, loc: (f64, f64)) -> bool { + self.get_mapper().point_in_bound(loc) + } + + fn get_mapper(&self) -> Ref { + self.imp().mapper.borrow() + } + + pub fn window_size(&self) -> (i32, i32) { + self.imp().window_size().unwrap() + } + + pub fn create_drawer(&self, range: (f64, f64, f64, f64), window_size: (f32, f32), mut f: F) where - T: Num - + Clone - + PartialEq - + PartialOrd - + AsPrimitive - + AsPrimitive - + Debug - + FromPrimitive, - Raw: ndarray::Data + Clone + ndarray::RawDataClone, + F: FnMut(&CMS), { - assert!(data.dim1.shape().len() == data.dim2.shape().len()); + let (lon1, lon2, lat1, lat2) = range; + let mut mapper = self.get_mapper().clone(); + mapper.set_lat_range(lat1..lat2); + mapper.set_lon_range(lon1..lon2); - let levels: Vec = vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65] - .into_iter() - .map(|b| T::from_i8(b).unwrap()) - .collect(); - 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), - ]; + println!("new_mapper: {:?}", mapper.get_bounds()); + println!("old_mapper: {:?}", self.get_mapper().get_bounds()); + let cms = CMS::new(mapper, window_size); + f(&cms); + } - let mapper = self.imp().mapper.borrow(); + pub fn pointer_loc(&self, transed: bool) -> (f64, f64) { + let raw = self.imp().status.borrow().pointer_location.clone(); + if transed { + self.inverse_map(raw).unwrap() + } else { + (raw.0 as f64, raw.1 as f64) + } + } - let pjp = ProjPipe::new(&mapper); - let rrp = ShadePipe::new(levels, colors.into_iter().map(|v| v.into()).collect()); - let rainbow = pjp.run(&data).unwrap(); - let pbow: Array2> = rrp.run(&data).unwrap(); - Ok(()) + pub fn render_range(&self) -> ((f64, f64), (f64, f64)) { + self.imp().window_range().unwrap() } } diff --git a/src/render/predefined/color_mapper.rs b/src/render/predefined/color_mapper.rs new file mode 100644 index 0000000..d39c6ae --- /dev/null +++ b/src/render/predefined/color_mapper.rs @@ -0,0 +1,72 @@ +use std::fmt::Debug; + +use femtovg::Color; +use num_traits::NumOps; +pub trait ColorMapper : Debug { + fn map_value_to_color(&self, value: T, invalid_value: T) -> Option; +} + +#[derive(Debug)] +pub struct BoundaryNorm { + boundaries: Vec, + extrand: bool, + colors: Vec, +} + +impl Default for BoundaryNorm { + fn default() -> Self { + Self { + boundaries: vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65], + extrand: true, + 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), + ], + } + } +} + +impl BoundaryNorm { + pub fn new(boundaries: Vec, colors: Vec, extrand: bool) -> Self { + // assert_eq!(boundaries.len(), colors.len() + 1); + BoundaryNorm { + boundaries, + extrand, + colors, + } + } + + pub fn map_value_to_color(&self, value: T, invalid_value: T) -> Option { + let mut index = 0; + if value == invalid_value { + return None; + } + for (i, boundary) in self.boundaries.iter().enumerate() { + if value < *boundary { + break; + } + index = i; + } + index = index.saturating_sub(1).min(self.colors.len() - 1); + Some(self.colors[index]) + } +} + +impl ColorMapper for BoundaryNorm { + fn map_value_to_color(&self, value: T, invalid_value: T) -> Option { + self.map_value_to_color(value, invalid_value) + } +} diff --git a/src/render/predefined/gis.rs b/src/render/predefined/gis.rs new file mode 100644 index 0000000..3d070aa --- /dev/null +++ b/src/render/predefined/gis.rs @@ -0,0 +1,63 @@ +use crate::coords::Mapper; +use crate::render::Render; +use femtovg::renderer::OpenGl; +use femtovg::{Canvas, Paint}; +use geo_types::LineString; +use geojson::GeoJson; + +pub struct MapRender; + +impl MapRender { + pub fn test(geojson: &GeoJson, canvas: &mut Canvas, render: &Render) { + let paint = Paint::color(femtovg::Color::rgb(255, 255, 255)); + + if let GeoJson::FeatureCollection(ref feature_collection) = geojson { + for feature in &feature_collection.features { + feature + .geometry + .as_ref() + .iter() + .for_each(|geometry| match geometry.value { + geojson::Value::Polygon(ref polygon) => { + let mut path = femtovg::Path::new(); + let polygon = &polygon[0]; + for (i, point) in polygon.iter().enumerate() { + let _point = (point[0], point[1]); + if render.point_in_bound(_point) { + let (x, y) = render.map(_point).unwrap(); + if i == 0 { + path.move_to(x, y); + } else { + path.line_to(x, y); + } + } + } + } + geojson::Value::MultiPolygon(ref multi_polygon) => { + let mut path = femtovg::Path::new(); + for polygon in multi_polygon { + let out_ring = &polygon[0]; + let out_ring_line: LineString = out_ring + .iter() + .map(|x| (x[0], x[1])) + .collect::>() + .into(); + let out_ring = render.ring_map(&out_ring_line).unwrap(); + + for (i, point) in out_ring.points().enumerate() { + let (x, y) = (point.x(), point.y()); + if i == 0 { + path.move_to(x, y); + } else { + path.line_to(x, y); + } + } + } + canvas.stroke_path(&mut path, &paint); + } + _ => println!("Unknown geometry type: {:?}", geometry.value), + }); + } + } + } +} diff --git a/src/render/predefined/grid_field_renderer.rs b/src/render/predefined/grid_field_renderer.rs new file mode 100644 index 0000000..f30f1ff --- /dev/null +++ b/src/render/predefined/grid_field_renderer.rs @@ -0,0 +1,179 @@ +use super::color_mapper::{BoundaryNorm, ColorMapper}; +use crate::render::layer::foreground::interior::LayerImpl; +use femtovg::{ImageFlags, Paint, Path, PixelFormat::Rgba8, RenderTarget}; +use geo_types::LineString; +use ndarray::ArrayView2; +use num_traits::{Num, NumOps}; +use std::{marker::PhantomData, fmt::Debug}; + +use super::super::renders::DataRenderer; +use crate::{ + data::Radar2d, + render::{layer::foreground::interior::Target, Render}, + utils::meshgrid, +}; + +#[derive(Debug)] +pub struct GridFieldRenderer +where + T: NumOps + PartialOrd, + CMAP: ColorMapper, +{ + cmap: CMAP, + value_phantom: PhantomData, +} + +impl> GridFieldRenderer { + pub fn new(cmap: CMAP) -> Self { + Self { + cmap, + value_phantom: PhantomData, + } + } + fn draw_2d( + &self, + canvas: &mut femtovg::Canvas, + data: ArrayView2, + render: &Render, + dims: (ArrayView2, ArrayView2), + window_size: (f32, f32), + fill_value: T, + ) { + let shape = data.shape(); + let (rows, cols) = (shape[0], shape[1]); + let (dim1, dim2) = dims; + + let d1_s = dim1[[0, 0]]; + let d1_e = dim1[[0, cols - 1]]; + + let d2_s = dim2[[0, 0]]; + let d2_e = dim2[[rows - 1, 0]]; + + render.create_drawer((d1_s, d1_e, d2_s, d2_e), window_size, |cms| { + for r in 0..rows - 1 { + for c in 0..cols - 1 { + let lb_lat = dim2[[r, c]]; + let lb_lon = dim1[[r, c]]; + + let rt_lat = dim2[[r + 1, c + 1]]; + let rt_lon = dim1[[r + 1, c + 1]]; + let cell: LineString = vec![ + (lb_lon, lb_lat), + (rt_lon + 0.001, lb_lat), + (rt_lon + 0.001, rt_lat), + (lb_lon, rt_lat + 0.001), + (lb_lon, lb_lat + 0.001), + ] + .into(); + + let v = &data[[r, c]]; + let mapped_color = self.cmap.map_value_to_color(*v, fill_value); + + if None == mapped_color { + continue; + } + + let mapped_ring = cms.ring_map(&cell).unwrap(); + + let mut path = Path::new(); + let mut points = mapped_ring.points(); + let first_point = points.next().unwrap(); + path.move_to(first_point.x(), first_point.y()); + + for point in points { + path.line_to(point.x(), point.y()); + } + path.close(); + canvas.fill_path(&path, &Paint::color(mapped_color.unwrap())); + } + } + }); + } +} + +impl DataRenderer for GridFieldRenderer +where + T: Num + NumOps + PartialOrd + Copy + Clone, + CMAP: ColorMapper, +{ + type Data = Radar2d; + fn render( + &self, + render: &Render, + canvas: &mut femtovg::Canvas, + data: &Self::Data, + ) -> Target { + let new_img = canvas + .create_image_empty(3000, 3000, Rgba8, ImageFlags::empty()) + .expect("Can't Create Image"); + + canvas.save(); + canvas.reset(); + + if let Ok(_) = canvas.image_size(new_img) { + canvas.set_render_target(RenderTarget::Image(new_img)); + let _data = data.data.view(); + let (_dim1, _dim2) = meshgrid(data.dim1.view(), data.dim2.view()); + self.draw_2d( + canvas, + _data, + render, + (_dim1.view(), _dim2.view()), + (3000.0, 3000.0), + data.fill_value, + ); + } + + canvas.restore(); + canvas.set_render_target(RenderTarget::Screen); + + let d1_start = (data.dim1.view()).first().unwrap().clone(); + let d1_end = (data.dim1.view()).last().unwrap().clone(); + + let d2_start = data.dim2.view().first().unwrap().clone(); + let d2_end = data.dim2.view().last().unwrap().clone(); + + Target::new( + new_img, + 3000f32, + 3000f32, + ((d1_start, d1_end).into(), (d2_start, d2_end).into()), + ) + } +} + +#[derive(Debug)] +pub struct GridLayerImpl +where + T: Num + NumOps + PartialOrd + Copy + Clone, + CMAP: ColorMapper, +{ + renderer: GridFieldRenderer, + data: Radar2d, +} + +pub type DbzGridLayerImpl = GridLayerImpl, i8>; + +impl GridLayerImpl +where + T: Num + NumOps + PartialOrd + Copy + Clone, + CMAP: ColorMapper, +{ + pub fn new(renderer: GridFieldRenderer, data: Radar2d) -> Self { + Self { renderer, data } + } +} + +impl LayerImpl for GridLayerImpl +where + T: Num + NumOps + PartialOrd + Copy + Clone + Debug, + CMAP: ColorMapper + Debug, +{ + fn draw( + &self, + canvas: &mut femtovg::Canvas, + render: &Render, + ) -> Option { + Some(self.renderer.render(render, canvas, &self.data)) + } +} diff --git a/src/render/predefined/layers.rs b/src/render/predefined/layers.rs new file mode 100644 index 0000000..fd63d5f --- /dev/null +++ b/src/render/predefined/layers.rs @@ -0,0 +1,67 @@ +use femtovg::Paint; +use num_traits::{Num, NumOps}; +use std::path::Path; + +use crate::{ + data::{DataLoader, Radar2d}, + render::layer::foreground::interior::Layer, +}; + +use super::{ + color_mapper::ColorMapper, + grid_field_renderer::{GridFieldRenderer, GridLayerImpl}, +}; + +impl Layer { + pub fn grid_render_layer(data: Radar2d, color_map: CMAP) -> Self + where + T: std::fmt::Debug + Num + NumOps + PartialOrd + Copy + Clone + 'static, + CMAP: ColorMapper + 'static, + { + Self::new( + true, + |s, c, render, _| { + if let Some(target) = s.render_target() { + if let Ok(_) = c.image_size(target.target) { + let (x, y) = target.size(render); + let (ox, oy) = target.origin(render); + let painter = Paint::image(target.target, ox, oy, x, y, 0.0, 1.0); + let mut path = femtovg::Path::new(); + path.rect(ox, oy, x, y); + c.fill_path(&path, &painter); + } + } else { + if let Some(renderer) = s.get_imp().as_ref() { + let img = renderer.draw(c, render).unwrap(); + if let Ok(_) = c.image_size(img.target) { + let (x, y) = img.size(render); + let (ox, oy) = img.origin(render); + println!("{} {} {} {}", x, y, ox, oy); + let painter = Paint::image(img.target, ox, oy, x, y, 0.0, 1.0); + let mut path = femtovg::Path::new(); + path.rect(ox, oy, x, y); + s.set_render_target(img); + c.fill_path(&path, &painter); + c.flush(); + } + } + } + }, + Some(GridLayerImpl::new(GridFieldRenderer::new(color_map), data)), + ) + } + + pub fn grid_render_layer_with_path( + path: impl AsRef, + loader: LOADER, + color_map: CMAP, + ) -> Self + where + T: Num + NumOps + PartialOrd + Copy + Clone + 'static + std::fmt::Debug, + CMAP: ColorMapper + 'static, + LOADER: DataLoader>, + { + let data = loader.load(path).unwrap(); + self::Layer::grid_render_layer(data, color_map) + } +} diff --git a/src/render/predefined/mod.rs b/src/render/predefined/mod.rs new file mode 100644 index 0000000..850b62e --- /dev/null +++ b/src/render/predefined/mod.rs @@ -0,0 +1,4 @@ +pub mod color_mapper; +pub mod grid_field_renderer; +pub mod gis; +pub mod layers; diff --git a/src/render/renders.rs b/src/render/renders.rs new file mode 100644 index 0000000..45d9fae --- /dev/null +++ b/src/render/renders.rs @@ -0,0 +1,14 @@ +use femtovg::{renderer::OpenGl, Canvas}; +use crate::render::layer::foreground::interior::Target; +use crate::coords::Mapper; +use super::Render; + +pub trait DataRenderer { + type Data; + fn render( + &self, + render: &Render, + canvas: &mut Canvas, + data: &Self::Data, + ) -> Target; +} diff --git a/src/utils.rs b/src/utils.rs index 5b65fd1..573ed7a 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -1,3 +1,15 @@ +use ndarray::{Array2, ArrayView1}; + +pub fn meshgrid(x: ArrayView1, y: ArrayView1) -> (Array2, Array2) +where + T: Clone, +{ + let shape = (y.len(), x.len()); + println!("shape: {:?}", shape); + let xx = Array2::from_shape_fn(shape, |(_, j)| x[j].clone()); + let yy = Array2::from_shape_fn(shape, |(i, _)| y[i].clone()); + (xx, yy) +} // let levels: Vec = vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65]; // let colors = vec![ // RGBABuilder::default()