add more detail

This commit is contained in:
Tsuki 2024-01-10 22:12:41 +08:00
parent 6bcf8ccc84
commit b70b86e71c
49 changed files with 1688 additions and 552 deletions

64
Cargo.lock generated
View File

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

View File

@ -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"
@ -48,3 +50,4 @@ path = "geo-macros"
[dependencies.adw]
package = "libadwaita"
version = "*"
features= ["v1_4"]

79
back.txt Normal file
View File

@ -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<i8, OwnedRepr<i8>> = 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] }
// )
// )

9
check.py Normal file
View File

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

View File

@ -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(&gtk::HeaderBar::new()),
// set_titlebar: Some(&gtk::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(&gtk::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! {

View File

View File

@ -0,0 +1,3 @@
mod control_panel;
mod messages;
pub use control_panel::*;

View File

@ -1,3 +1,6 @@
pub mod app;
mod control_panel;
mod render_panel;
pub use control_panel::*;
pub use render_panel::*;

View File

@ -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<Self>,
) -> relm4::ComponentParts<Self> {
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<Self>) {}
}

View File

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

View File

@ -0,0 +1,4 @@
mod render_panel;
mod messages;
mod monitor;
pub use render_panel::RenderPanelModel;

View File

@ -0,0 +1,4 @@
pub mod monitor;
pub mod sidebar;
pub mod render;
pub use monitor::MonitorModel;

View File

@ -0,0 +1,46 @@
use super::{render::render::RenderModel, sidebar::sidebar::SideBarModel};
use relm4::*;
pub struct MonitorModel {
render: Controller<RenderModel>,
sidebar: Controller<SideBarModel>,
}
#[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<Self>,
) -> relm4::ComponentParts<Self> {
let sidebar: Controller<SideBarModel> = SideBarModel::builder()
.launch(())
.forward(sender.input_sender(), |msg| {});
let render: Controller<RenderModel> = 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<Self>) {}
}

View File

@ -0,0 +1,2 @@
pub mod render;
pub use render::*;

View File

@ -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<Mapper>, Option<RenderConfig>);
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<Self>,
) -> relm4::ComponentParts<Self> {
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<Self>) {}
}

View File

@ -0,0 +1,2 @@
pub mod sidebar;
pub use sidebar::*;

View File

@ -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<MyListItem, gtk::SingleSelection>,
}
#[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<Self>,
) -> ComponentParts<Self> {
// Initialize the ListView wrapper
let mut list_view_wrapper: TypedListView<MyListItem, gtk::SingleSelection> =
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<Self>) {
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: &gtk::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);
}
}

View File

@ -0,0 +1,39 @@
use super::messages::RenderInputMsg;
use gtk::prelude::*;
use super::monitor::MonitorModel;
use relm4::*;
pub struct RenderPanelModel {
monitor: Controller<MonitorModel>,
}
#[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<Self>,
) -> relm4::ComponentParts<Self> {
let monitor: Controller<MonitorModel> = 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<Self>) {}
}

View File

@ -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<Proj> 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<f64>,
lat_range: std::ops::Range<f64>,
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<f64>) -> &mut Self {

View File

@ -17,7 +17,7 @@ pub trait Coord<T: Num> {
fn dim2_range(&self) -> (T, T);
}
#[derive(Clone, Copy)]
#[derive(Debug, Clone, Copy)]
pub struct Range(pub f64, pub f64);
impl Range {

View File

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

View File

@ -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<OwnedRepr<X>, I>,
pub dim2: ArrayBase<OwnedRepr<Y>, I>,
pub data: ArrayBase<Raw, Ix2>,
pub fill_value: T,
pub coord_type: CoordType,
}
impl<T, Raw, X, Y, I> Debug for RadarData2d<T, Raw, X, Y, I>
where
T: Num + Clone + PartialEq + PartialOrd + Debug,
Raw: ndarray::Data<Elem = T> + 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<T: Num + Clone + PartialEq + PartialOrd> Radar2d<T> {
pub fn load(path: impl AsRef<Path>, meth: impl DataLoader<T, Self>) -> Result<Self, DataError> {
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<T> DataLoader<T, RadarData2d<T, OwnedRepr<T>>> for Npz
where
T: Num + Clone + Deserialize,
T: Num + Clone + Deserialize + FromPrimitive,
T: PartialEq + PartialOrd,
{
fn load<P: AsRef<Path>>(&self, path: P) -> Result<RadarData2d<T, OwnedRepr<T>>, 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,

View File

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

View File

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

View File

@ -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<imp::Monitor>)
@ -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(&gtk::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<T, Raw>(&self, data: RadarData2d<T, Raw>) -> Result<(), ProjError>
where
T: Num
+ Clone
+ PartialEq
+ PartialOrd
+ AsPrimitive<i8>
+ AsPrimitive<f64>
+ Debug
+ FromPrimitive,
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
let renderer = self.imp().renderer.borrow();
renderer.load_data_2d(data)?;
Ok(())
}
}

45
src/render/cms.rs Normal file
View File

@ -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<LineString<f32>> {
Some(
line.points()
.into_iter()
.map(|p| self.map((p.x(), p.y())).unwrap())
.collect::<Vec<_>>()
.into(),
)
}
}

View File

@ -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<imp::ExteriorWidget>);
}
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<OpenGl>, 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);
}
}

View File

@ -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<imp::ForegroundWidget>);
}
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<OpenGl>, 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::<Vec<Point>>();
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<f64>, Array2<f64>)) {
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<Option<Color>>) {
self.imp().color.replace(Some(colors));
}
pub fn set_data(&self, data: Array2<LineString>) {
self.imp().data.replace(Some(data));
}
}
// 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,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<Array2<f64>>,
pub dim2: Option<Array2<f64>>,
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 window_size: (i32, i32),
pub(super) scale_rate: Option<f64>,
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: &gtk::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)
}
}

View File

@ -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<imp::InteriorWidget>);
}
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<OpenGl>,
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,
);
}
}

View File

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

View File

@ -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<imp::ExteriorWidget>);
}
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<OpenGl>, 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");
}
}
}

View File

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

View File

@ -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<BackgroundWidget>,
pub(super) foreground: RefCell<ForegroundWidget>,
pub(super) trans: RefCell<WindowCoord>,
pub(super) update_trans: RefCell<WindowCoord>,
pub(super) layers: RefCell<Vec<Layer>>,
}
#[glib::object_subclass]

View File

@ -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<Option<Target>>,
imp: RefCell<Option<Box<dyn LayerImpl>>>,
draw: Box<dyn Fn(&Self, &mut Canvas<OpenGl>, &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<OpenGl>, render: &Render) -> Option<Target>;
}
impl Layer {
pub fn new<
F: 'static + Fn(&Self, &mut Canvas<OpenGl>, &Render, (f32, f32)),
IMP: LayerImpl + 'static,
>(
visiable: bool,
draw: F,
imp: Option<IMP>,
) -> Self {
Layer {
visiable,
target: RefCell::new(None),
draw: Box::new(draw),
imp: RefCell::new(imp.map(|x| Box::new(x) as Box<dyn LayerImpl>)),
}
}
pub fn draw(&self, canvas: &mut Canvas<OpenGl>, 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<Target> {
self.target.borrow().clone()
}
pub fn get_imp(&self) -> Ref<Option<Box<dyn LayerImpl>>> {
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()
}
}

View File

@ -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<imp::InteriorWidget>);
}
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<OpenGl>,
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));
}
}
}

View File

@ -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<imp::ForegroundWidget>);
}
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<OpenGl>, scale: f32, dpi: i32, mapper: Ref<'_, Mapper>) {
let config = self.imp().config.borrow();
}
}

2
src/render/layer/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod background;
pub mod foreground;

View File

@ -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<Mapper>) -> Self {
pub fn new(mapper: Option<Mapper>, 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<F>(&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<T, Raw>(
&self,
mut data: RadarData2d<T, Raw>,
) -> Result<(), ProjError>
where
T: Num
+ Clone
+ PartialEq
+ PartialOrd
+ AsPrimitive<i8>
+ AsPrimitive<f64>
+ Debug
+ FromPrimitive,
Raw: ndarray::Data<Elem = T> + Clone + ndarray::RawDataClone,
{
assert!(data.dim1.shape().len() == data.dim2.shape().len());
pub fn map(&self, loc: (f64, f64)) -> Option<(f32, f32)> {
// let (x, y) = loc;
// let (x_range, y_range) = self.imp().window_range().unwrap();
let levels: Vec<T> = vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65]
// 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<f64>) -> Option<LineString<f32>> {
Some(
ring.points()
.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),
];
.map(|p| self.map((p.x(), p.y())))
.filter(|p| p.is_some())
.collect::<Option<Vec<_>>>()
.unwrap()
.into(),
)
}
let mapper = self.imp().mapper.borrow();
pub fn point_in_bound(&self, loc: (f64, f64)) -> bool {
self.get_mapper().point_in_bound(loc)
}
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<Option<Color>> = rrp.run(&data).unwrap();
Ok(())
fn get_mapper(&self) -> Ref<Mapper> {
self.imp().mapper.borrow()
}
pub fn window_size(&self) -> (i32, i32) {
self.imp().window_size().unwrap()
}
pub fn create_drawer<F>(&self, range: (f64, f64, f64, f64), window_size: (f32, f32), mut f: F)
where
F: FnMut(&CMS),
{
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);
println!("new_mapper: {:?}", mapper.get_bounds());
println!("old_mapper: {:?}", self.get_mapper().get_bounds());
let cms = CMS::new(mapper, window_size);
f(&cms);
}
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)
}
}
pub fn render_range(&self) -> ((f64, f64), (f64, f64)) {
self.imp().window_range().unwrap()
}
}

View File

@ -0,0 +1,72 @@
use std::fmt::Debug;
use femtovg::Color;
use num_traits::NumOps;
pub trait ColorMapper<T: NumOps + PartialOrd> : Debug {
fn map_value_to_color(&self, value: T, invalid_value: T) -> Option<femtovg::Color>;
}
#[derive(Debug)]
pub struct BoundaryNorm<T: NumOps + PartialOrd> {
boundaries: Vec<T>,
extrand: bool,
colors: Vec<femtovg::Color>,
}
impl Default for BoundaryNorm<i8> {
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<T: NumOps + PartialOrd> BoundaryNorm<T> {
pub fn new(boundaries: Vec<T>, colors: Vec<femtovg::Color>, 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<femtovg::Color> {
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<T: NumOps + PartialOrd + Debug> ColorMapper<T> for BoundaryNorm<T> {
fn map_value_to_color(&self, value: T, invalid_value: T) -> Option<femtovg::Color> {
self.map_value_to_color(value, invalid_value)
}
}

View File

@ -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<OpenGl>, 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::<Vec<_>>()
.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),
});
}
}
}
}

View File

@ -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<CMAP, T>
where
T: NumOps + PartialOrd,
CMAP: ColorMapper<T>,
{
cmap: CMAP,
value_phantom: PhantomData<T>,
}
impl<T: NumOps + PartialOrd + Copy, CMAP: ColorMapper<T>> GridFieldRenderer<CMAP, T> {
pub fn new(cmap: CMAP) -> Self {
Self {
cmap,
value_phantom: PhantomData,
}
}
fn draw_2d(
&self,
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
data: ArrayView2<T>,
render: &Render,
dims: (ArrayView2<f64>, ArrayView2<f64>),
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<T, CMAP> DataRenderer for GridFieldRenderer<CMAP, T>
where
T: Num + NumOps + PartialOrd + Copy + Clone,
CMAP: ColorMapper<T>,
{
type Data = Radar2d<T>;
fn render(
&self,
render: &Render,
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
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<CMAP, T>
where
T: Num + NumOps + PartialOrd + Copy + Clone,
CMAP: ColorMapper<T>,
{
renderer: GridFieldRenderer<CMAP, T>,
data: Radar2d<T>,
}
pub type DbzGridLayerImpl = GridLayerImpl<BoundaryNorm<i8>, i8>;
impl<CMAP, T> GridLayerImpl<CMAP, T>
where
T: Num + NumOps + PartialOrd + Copy + Clone,
CMAP: ColorMapper<T>,
{
pub fn new(renderer: GridFieldRenderer<CMAP, T>, data: Radar2d<T>) -> Self {
Self { renderer, data }
}
}
impl<CMAP, T> LayerImpl for GridLayerImpl<CMAP, T>
where
T: Num + NumOps + PartialOrd + Copy + Clone + Debug,
CMAP: ColorMapper<T> + Debug,
{
fn draw(
&self,
canvas: &mut femtovg::Canvas<femtovg::renderer::OpenGl>,
render: &Render,
) -> Option<Target> {
Some(self.renderer.render(render, canvas, &self.data))
}
}

View File

@ -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<T, CMAP>(data: Radar2d<T>, color_map: CMAP) -> Self
where
T: std::fmt::Debug + Num + NumOps + PartialOrd + Copy + Clone + 'static,
CMAP: ColorMapper<T> + '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<T, CMAP, LOADER>(
path: impl AsRef<Path>,
loader: LOADER,
color_map: CMAP,
) -> Self
where
T: Num + NumOps + PartialOrd + Copy + Clone + 'static + std::fmt::Debug,
CMAP: ColorMapper<T> + 'static,
LOADER: DataLoader<T, Radar2d<T>>,
{
let data = loader.load(path).unwrap();
self::Layer::grid_render_layer(data, color_map)
}
}

View File

@ -0,0 +1,4 @@
pub mod color_mapper;
pub mod grid_field_renderer;
pub mod gis;
pub mod layers;

14
src/render/renders.rs Normal file
View File

@ -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<OpenGl>,
data: &Self::Data,
) -> Target;
}

View File

@ -1,3 +1,15 @@
use ndarray::{Array2, ArrayView1};
pub fn meshgrid<T>(x: ArrayView1<T>, y: ArrayView1<T>) -> (Array2<T>, Array2<T>)
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<i8> = vec![0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65];
// let colors = vec![
// RGBABuilder::default()