diff --git a/Cargo.lock b/Cargo.lock index 11885ee..7a38456 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -657,9 +657,9 @@ dependencies = [ [[package]] name = "glib-build-tools" -version = "0.17.0" +version = "0.17.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f8480c9ba9cc06aa8d5baf446037f8dc237bee127e9b62080c4db7e293d8ea0" +checksum = "7a65d79efe318ef2cbbbb37032b125866fd82c34ea44c816132621bbc552e716" [[package]] name = "glib-macros" diff --git a/Cargo.toml b/Cargo.toml index 13e0db2..9af067c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,9 +23,9 @@ ndarray = "0.15.6" quadtree_rs = "0.1.2" proj-sys = "0.23.1" + [build-dependencies] glib-build-tools = "0.17.0" -# plotters = "0.3.4" [dependencies.geo-macros] path = "geo-macros" diff --git a/build.rs b/_build.rs similarity index 100% rename from build.rs rename to _build.rs diff --git a/p.py b/p.py deleted file mode 100644 index 80c382a..0000000 --- a/p.py +++ /dev/null @@ -1,12 +0,0 @@ -import numpy as np - -a = np.load("/home/ruomu/Desktop/test.npz") - -b = a['value'] -lon = a['lon'] -lat = a['lat'] - -a = np.load("/home/ruomu/Desktop/test2.npz")['value'] - - -np.savez("/home/ruomu/Desktop/test2.npz", value=np.max(b,axis=0), lon=lon, lat=lat) \ No newline at end of file diff --git a/src/data.rs b/src/data.rs index 7f941e3..75abdad 100644 --- a/src/data.rs +++ b/src/data.rs @@ -25,7 +25,7 @@ pub enum CoordType { #[derive(Clone)] pub struct RadarData2d where - T: Num + Clone, + T: Num + Clone + PartialEq + PartialOrd, Raw: ndarray::Data + Clone + ndarray::RawDataClone, X: Num, Y: Num, @@ -53,7 +53,7 @@ trait MultiDimensionData {} impl RadarData2d where - T: Num + AsPrimitive + FromPrimitive + Clone + Debug, + T: Num + AsPrimitive + FromPrimitive + Clone + Debug + PartialOrd + PartialEq, Raw: ndarray::Data + Clone + ndarray::RawDataClone, { fn value_to_owned(self) -> RadarData2d> { @@ -198,11 +198,13 @@ fn windowed_sinc(x: f64, y: f64) -> f64 { sinc * window } -pub struct LevelData(pub Quadtree>>); +pub struct LevelData( + pub Quadtree>>, +); impl LevelData where - T: Num + Clone + AsPrimitive + FromPrimitive + Debug, + T: Num + Clone + AsPrimitive + FromPrimitive + Debug + PartialEq + PartialOrd, { fn value( level_data: Vec>>, @@ -252,6 +254,7 @@ where pub fn levels(data: Radar2d, levels: usize) -> Vec> where T: Num + Clone + AsPrimitive + FromPrimitive + Debug, + T: PartialEq + PartialOrd, { let numerator = 1.0 / levels as f64; (0..levels) @@ -263,6 +266,7 @@ where impl MultiDimensionData for RadarData2d where T: Num + Clone, + T: PartialEq + PartialOrd, Raw: ndarray::Data + Clone + RawDataClone, { } @@ -308,6 +312,7 @@ impl Npz { impl DataLoader>> for Npz where T: Num + Clone + Deserialize, + T: PartialEq + PartialOrd, { fn load>(&self, path: P) -> Result>, DataError> { let mut data: NpzArchive> = npyz::npz::NpzArchive::open(path)?; @@ -324,15 +329,15 @@ where } } -impl RadarData2d> { +impl RadarData2d> { pub fn load, M: DataLoader>(p: P, meth: M) -> Self { meth.load(p).unwrap() } } -pub struct Radar2d(pub RadarData2d>); +pub struct Radar2d(pub RadarData2d>); -impl Radar2d { +impl Radar2d { pub fn load( path: impl AsRef, meth: impl DataLoader>>, diff --git a/src/main.rs b/src/main.rs index e98f4ea..01db824 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,24 @@ -use geo_types::{MultiPolygon, Polygon}; -use gtk::builders::DrawingAreaBuilder; use gtk::gdk::Display; use gtk::{ gio, glib, style_context_add_provider_for_display, Application, ApplicationWindow, CssProvider, + GestureDrag, }; use gtk::{prelude::*, DrawingArea}; -use painter::wgs84::*; -use painter::Painter; // use plotters::prelude::DrawingArea; -use window::Window; // mod backend; mod data; mod painter; -// mod trees; +mod render; +mod tree; mod window; - use data::{CoordType, Npz, Radar2d}; +use painter::wgs84::{LatLonCoord, Mercator, Range}; +use painter::Painter; const APP_ID: &str = "org.gtk_rs.HelloWorld2"; fn main() -> glib::ExitCode { - gio::resources_register_include!("window.gresource").expect("Failed to register resources"); + // gio::resources_register_include!("window.gresource").expect("Failed to register resources"); // Create a new application let app = Application::builder().application_id(APP_ID).build(); @@ -36,23 +34,33 @@ fn main() -> glib::ExitCode { fn build_ui(app: &Application) { // Create a window and set the title - let window = Window::new(app); + // let window = Window::new(app); + + let window = ApplicationWindow::builder() + .application(app) + .title("My GTK App") + .build(); let drawing_area = DrawingArea::new(); - - let path = "/home/ruomu/Desktop/test.npz"; - + let path = "/Users/ruomu/test2.npz"; let data = Radar2d::::load(path, Npz).unwrap(); + let gesture = GestureDrag::new(); + // gesture.connect_drag_end(|s, x, y| { + // (&drawing_area).queue_draw(); + // }); + // drawing_area.add_controller(gesture); drawing_area.set_draw_func(move |a, b, c, d| { let projection = Mercator::new(); - let coord = LatLonCoord::new(None, None, ((0, c), (0, d)), projection); - let painter: Painter<'_, f64, LatLonCoord> = Painter::new(b, coord, c as u32, c as u32); + let aa = Range::from((*data.0.dim1.first().unwrap(), *data.0.dim1.last().unwrap())); + let ab = Range::from((*data.0.dim2.first().unwrap(), *data.0.dim2.last().unwrap())); + + let coord = LatLonCoord::new(Some(aa), Some(ab), ((0, c), (0, d)), projection); + let painter: Painter<'_, f64, LatLonCoord> = + Painter::new(b, coord, c as u32, c as u32); painter.draw_radar_2d(&data.0); - }); - - // window.set_child(Some(&drawing_area)); + window.set_child(Some(&drawing_area)); window.set_default_width(1000); window.set_default_height(800); diff --git a/src/painter/coords/mod.rs b/src/painter/coords/mod.rs index 6c6f50a..70fbaef 100644 --- a/src/painter/coords/mod.rs +++ b/src/painter/coords/mod.rs @@ -1,8 +1,11 @@ use num_traits::Num; pub mod wgs84; -pub type ScreenCoord = (i32, i32); +pub type ScreenCoord = (f64, f64); pub trait Coord { fn map(&self, axis_1: T, axis_2: T) -> ScreenCoord; + + fn dim1_range(&self) -> (T, T); + fn dim2_range(&self) -> (T, T); } diff --git a/src/painter/coords/wgs84.rs b/src/painter/coords/wgs84.rs index d49b30a..ccb5448 100644 --- a/src/painter/coords/wgs84.rs +++ b/src/painter/coords/wgs84.rs @@ -1,10 +1,12 @@ use super::Coord; use geo_macros::Prj; +use num_traits::{AsPrimitive, FromPrimitive, Num}; use proj::{Proj, ProjError}; use thiserror::Error; type Lat = f64; type Lon = f64; +#[derive(Clone, Copy)] pub struct Range(pub f64, pub f64); #[derive(Error, Debug)] @@ -16,15 +18,17 @@ pub enum CoordError { }, } -impl From<(f64, f64)> for Range { - fn from(value: (f64, f64)) -> Self { +impl + Num> From<(T, T)> for Range { + fn from(value: (T, T)) -> Self { + let value = (value.0.as_(), value.1.as_()); let (_min, _max) = (value.0.min(value.1), value.0.max(value.1)); Self(_min, _max) } } pub struct LatLonCoord { - actual: ((i32, i32), (i32, i32)), + // actual: ((f64, f64), (f64, f64)), + actual: (Range, Range), logical: (Range, Range), pcs: PCS, } @@ -53,21 +57,50 @@ impl LatLonCoord { lon: Option, lat: Option, actual: ((i32, i32), (i32, i32)), + // actual: (Range, Range), project: T, ) -> Self { let pcs = PCS::new(project, lon, lat); let _box = pcs.bbox().unwrap(); Self { - actual, + actual: (Range::from(actual.0), Range::from(actual.1)), pcs: pcs, logical: _box, } } + + pub fn lon_range(&self) -> Range { + self.pcs.lon_range + } + + pub fn lat_range(&self) -> Range { + self.pcs.lat_range + } } impl Coord for LatLonCoord { fn map(&self, axis_1: f64, axis_2: f64) -> super::ScreenCoord { - (0, 0) + let point = self.pcs.map((axis_1, axis_2)); + let logical_dim1_span = self.logical.0 .1 - self.logical.0 .0; + + let dim1_rate = (point.0 - self.logical.0 .0) / logical_dim1_span; + let logical_dim2_span = self.logical.1 .1 - self.logical.1 .0; + let dim2_rate = (point.1 - self.logical.1 .0) / logical_dim2_span; + + ( + (dim1_rate * (self.actual.0 .1 - self.actual.0 .0) as f64) + self.actual.0 .0, + (dim2_rate * (self.actual.1 .1 - self.actual.1 .0) as f64) + self.actual.1 .0, + ) + } + + fn dim1_range(&self) -> (f64, f64) { + let v = self.lon_range(); + (v.0, v.1) + } + + fn dim2_range(&self) -> (f64, f64) { + let v = self.lat_range(); + (v.0, v.1) } } @@ -157,8 +190,8 @@ impl Mercator { pub fn new() -> Self { Self { central_lon: 0.0, - min_latitude: 0.0, - max_latitude: 70.0, + min_latitude: -82.0, + max_latitude: 82.0, false_easting: 0.0, false_northing: 0.0, latitude_true_scale: 0.0, @@ -198,7 +231,7 @@ impl ProjectionS for Mercator { let lat_range = Range { 0: lat_range.0.max(self.min_latitude), - 1: lat_range.0.min(self.max_latitude), + 1: lat_range.1.min(self.max_latitude), }; (lon_range, lat_range) } diff --git a/src/painter/painter.rs b/src/painter/painter.rs index eb6947e..bd77f49 100644 --- a/src/painter/painter.rs +++ b/src/painter/painter.rs @@ -1,10 +1,13 @@ use std::marker::PhantomData; -use crate::data::RadarData2d; +use crate::{data::RadarData2d, tree::get}; use super::coords::{Coord, ScreenCoord}; -use gtk::{cairo::*, gdk::RGBA}; -use num_traits::Num; +use gtk::{ + cairo::*, + gdk::{builders::RGBABuilder, RGBA}, +}; +use num_traits::{FromPrimitive, Num}; pub struct Painter<'a, T: Num, C: Coord> { width: u32, height: u32, @@ -33,18 +36,175 @@ impl<'a, T: Num + Clone, C: Coord> Painter<'a, T, C> { ); } - fn draw_pixel(&self, point: ScreenCoord) {} + fn draw_pixel(&self, point: ScreenCoord, color: &RGBA) { + self.context + .rectangle(f64::from(point.0), f64::from(point.1), 1.0, 1.0); + self.set_color(color); - fn draw_rect(&self, point: ScreenCoord, width: f64, height: f64) {} + self.context.fill(); + } - fn draw_line(&self, start: ScreenCoord, end: ScreenCoord) {} + fn draw_rect(&self, point: ScreenCoord, width: f64, height: f64, color: &RGBA, fill: bool) { + self.set_color(color); + self.set_stroke_width(1); + self.context.rectangle(point.0, point.1, width, height); + self.context.fill(); + // if fill { + // self.context.fill(); + // } else { + // self.context.stroke(); + // } + } + + fn set_stroke_width(&self, width: u32) { + self.context.set_line_width(f64::from(width)); + } + + fn draw_line(&self, start: ScreenCoord, end: ScreenCoord) { + self.set_color(&RGBA::BLACK); + self.set_stroke_width(1); + self.context.move_to(start.0, start.1); + self.context.line_to(end.0, end.1); + + self.context.stroke(); + } + + fn draw_path>(&self, path: I) { + let mut path = path.into_iter(); + if let Some((x, y)) = path.next() { + self.context.move_to(x, y); + } + + for (x, y) in path { + self.context.line_to(x, y); + } + self.context.stroke(); + } } impl<'a, C: Coord> Painter<'a, f64, C> { - pub fn draw_radar_2d(&self, data: &RadarData2d) - where + pub fn draw_radar_2d( + &self, + data: &RadarData2d, + ) where D: ndarray::Data + Clone + ndarray::RawDataClone, { + let levels: Vec = vec![ + T::from_i8(0).unwrap(), + T::from_i8(5).unwrap(), + T::from_i8(10).unwrap(), + T::from_i8(15).unwrap(), + T::from_i8(20).unwrap(), + T::from_i8(25).unwrap(), + T::from_i8(30).unwrap(), + T::from_i8(35).unwrap(), + T::from_i8(40).unwrap(), + T::from_i8(45).unwrap(), + T::from_i8(50).unwrap(), + T::from_i8(55).unwrap(), + T::from_i8(60).unwrap(), + T::from_i8(65).unwrap(), + ]; + let colors = vec![ + RGBABuilder::default() + .red(0 as f32 / 255.0) + .green(172 as f32 / 255.0) + .blue(164 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(192 as f32 / 255.0) + .green(192 as f32 / 255.0) + .blue(254 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(122 as f32 / 255.0) + .green(114 as f32 / 255.0) + .blue(238 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(30 as f32 / 255.0) + .green(38 as f32 / 255.0) + .blue(208 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(166 as f32 / 255.0) + .green(252 as f32 / 255.0) + .blue(168 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(0 as f32 / 255.0) + .green(234 as f32 / 255.0) + .blue(0 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(16 as f32 / 255.0) + .green(146 as f32 / 255.0) + .blue(26 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(252 as f32 / 255.0) + .green(244 as f32 / 255.0) + .blue(100 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(200 as f32 / 255.0) + .green(200 as f32 / 255.0) + .blue(2 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(140 as f32 / 255.0) + .green(140 as f32 / 255.0) + .blue(0 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(254 as f32 / 255.0) + .green(172 as f32 / 255.0) + .blue(172 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(254 as f32 / 255.0) + .green(100 as f32 / 255.0) + .blue(92 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(238 as f32 / 255.0) + .green(2 as f32 / 255.0) + .blue(48 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(212 as f32 / 255.0) + .green(142 as f32 / 255.0) + .blue(254 as f32 / 255.0) + .build(), + RGBABuilder::default() + .red(170 as f32 / 255.0) + .green(36 as f32 / 255.0) + .blue(250 as f32 / 255.0) + .build(), + ]; + let mut polygons: Vec<(shapefile::Polygon, _)> = + shapefile::read_as::<_, shapefile::Polygon, shapefile::dbase::Record>( + "/Users/ruomu/china/省界_region.shp", + ) + .unwrap(); + + // for (polygon, _) in polygons.into_iter() { + // let x_range = polygon.bbox().x_range(); + // let y_range = polygon.bbox().y_range(); + + // let lon_range = self.coord.dim1_range(); + // let lat_range = self.coord.dim2_range(); + // } + data.data.outer_iter().enumerate().for_each(|(r_num, row)| { + row.iter().enumerate().for_each(|(c_num, v)| { + let point = self.coord.map(data.dim1[c_num], data.dim2[r_num]); + if *v > T::from_i8(-125).unwrap() { + let color = get(&levels, &colors, *v); + // self.draw_pixel(point, &color); + self.draw_rect(point, 1.5, 1.5, &color, true); + } + }) + }); } } diff --git a/src/render/imp.rs b/src/render/imp.rs new file mode 100644 index 0000000..7c8dbeb --- /dev/null +++ b/src/render/imp.rs @@ -0,0 +1,43 @@ +use glib::{ObjectExt, ParamSpec, Properties, Value}; +use gtk::glib; +use gtk::subclass::prelude::*; +use std::cell::Cell; + +#[derive(Properties, Default)] +#[properties(wrapper_type = super::Render)] +pub struct Render { + #[property(get, set)] + height: Cell, + #[property(get, set)] + width: Cell, +} + +#[glib::object_subclass] +impl ObjectSubclass for Render { + const NAME: &'static str = "MyRender"; + type Type = super::Render; + type ParentType = gtk::DrawingArea; +} + +// Trait shared by all GObjects +impl ObjectImpl for Render { + fn properties() -> &'static [ParamSpec] { + Self::derived_properties() + } + + fn set_property(&self, id: usize, value: &Value, pspec: &ParamSpec) { + self.derived_set_property(id, value, pspec) + } + + fn property(&self, id: usize, pspec: &ParamSpec) -> Value { + self.derived_property(id, pspec) + } + fn constructed(&self) { + self.parent_constructed(); + } +} + +// Trait shared by all widgets +impl WidgetImpl for Render {} + +impl DrawingAreaImpl for Render {} diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..0384695 --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,18 @@ +mod imp; +use glib::Object; +use gtk::glib; + +glib::wrapper! { + pub struct Render(ObjectSubclass) + @extends gtk::DrawingArea,gtk::Widget, + @implements gtk::Accessible, gtk::Actionable, gtk::Buildable, gtk::ConstraintTarget; +} + +impl Render { + pub fn new(height: u32, width: u32) -> Self { + Object::builder() + .property("height", height) + .property("width", width) + .build() + } +} diff --git a/src/tree.rs b/src/tree.rs index 2344a3c..73700d6 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -1,8 +1,9 @@ +use gtk::gdk::RGBA; use num_traits::Num; -use plotters::style::RGBAColor; -pub fn get(levels: &Vec, colors: &Vec, v: T) -> RGBAColor + +pub fn get(levels: &Vec, colors: &Vec, v: T) -> RGBA where - T: Num + PartialOrd, + T: Num + PartialOrd + Copy, { let len = levels.len();