From f2f979c483cf6c1f93584fdb7d30de252f67b7d2 Mon Sep 17 00:00:00 2001 From: sleptworld Date: Tue, 25 Jul 2023 01:04:25 +0800 Subject: [PATCH] add foreground implemented widget --- src/data/mapper.rs | 192 +++++++++++++++++++++++++++++++++++ src/data/mod.rs | 7 +- src/main.rs | 32 +++--- src/monitor/imp.rs | 14 +-- src/monitor/mod.rs | 50 ++++++++- src/monitor/monitor.ui | 10 -- src/painter/coords/mod.rs | 37 ++++--- src/painter/coords/wgs84.rs | 2 +- src/render/background/imp.rs | 1 + src/render/background/mod.rs | 17 +++- src/render/foreground/imp.rs | 40 ++++++++ src/render/foreground/mod.rs | 39 +++++++ src/render/imp.rs | 43 ++++++-- src/render/mod.rs | 41 +++++++- test.js | 102 +++++++++++++++++++ tile.js | 95 +++++++++++++++++ 16 files changed, 649 insertions(+), 73 deletions(-) create mode 100644 src/data/mapper.rs delete mode 100644 src/monitor/monitor.ui create mode 100644 src/render/foreground/imp.rs create mode 100644 src/render/foreground/mod.rs create mode 100644 test.js create mode 100644 tile.js diff --git a/src/data/mapper.rs b/src/data/mapper.rs new file mode 100644 index 0000000..3d6bc88 --- /dev/null +++ b/src/data/mapper.rs @@ -0,0 +1,192 @@ +use geo_types::{coord, Coord as GCoord, LineString}; +use num_traits::Num; +use proj::{Proj, ProjError}; +use std::ops::Range; + +use crate::{ + painter::{wgs84::ProjectionS, Coord}, + render::WindowCoord, +}; + +pub struct Mapper { + proj: Proj, + range: (Range, Range), + bounds: (f64, f64, f64, f64), +} + +impl From for Mapper { + fn from(proj: Proj) -> Self { + let default_range: (Range, Range) = (-180.0..180.0, -90.0..90.0); + let bounds = Self::bound(&proj, default_range.clone()).unwrap(); + Self { + proj: proj, + range: default_range, + bounds, + } + } +} + +impl From for Mapper { + fn from(proj: C) -> Self { + let p = Proj::new(proj.build().as_str()).unwrap(); + p.into() + } +} + +impl Mapper { + pub fn new(proj: Proj, lon_range: Range, lat_range: Range) -> Self { + let bounds = Self::bound(&proj, (lon_range.clone(), lat_range.clone())).unwrap(); + let range = (lon_range, lat_range); + Self { + proj: proj, + range, + bounds, + } + } + + pub fn map(&self, point: (f64, f64)) -> Result<(f64, f64), ProjError> { + 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); + + Ok((x, y)) + } + + pub fn set_lon_range(&mut self, range: Range) { + self.range.0 = range; + self.bounds = Self::bound(&self.proj, self.range.clone()).unwrap(); + } + + pub fn set_lat_range(&mut self, range: Range) { + self.range.1 = range; + self.bounds = Self::bound(&self.proj, self.range.clone()).unwrap(); + } + + fn bound( + proj: &Proj, + range: (Range, Range), + ) -> Result<(f64, f64, f64, f64), ProjError> { + let left_bottom = proj.convert((range.0.start.to_radians(), range.1.start.to_radians()))?; + let right_top = proj.convert((range.0.end.to_radians(), range.1.end.to_radians()))?; + Ok((left_bottom.0, right_top.0, left_bottom.1, right_top.1)) + } + + pub fn ring_map(&self, ring: &LineString) -> Result { + let mut result = Vec::new(); + for l in ring.lines() { + let start_projected = self.map((l.start.x, l.start.y))?; + let end_projected = self.map((l.end.x, l.end.y))?; + + let cartesian_start = self.cartesian(l.start.x, l.start.y); + let cartesian_end = self.cartesian(l.end.x, l.end.y); + + let delta2 = 0.5; + let depth = 16; + let mut res: Vec = Vec::new(); + self.resample_line_to( + start_projected, + end_projected, + l.start.x, + l.end.x, + cartesian_start, + cartesian_end, + &|x| self.map((x.x, x.y)), + delta2, + depth, + &mut res, + )?; + result.extend(res); + } + + Ok(LineString::new(result)) + } + + #[inline] + fn cartesian(&self, lambda: f64, phi: f64) -> (f64, f64, f64) { + let cos_phi = phi.cosh(); + return (cos_phi * lambda.cosh(), cos_phi * lambda.sinh(), phi.sinh()); + } + + fn resample_line_to( + &self, + start_projected: (f64, f64), + end_projected: (f64, f64), + lambda0: f64, + lambda1: f64, + cartesian_start: (f64, f64, f64), + cartesian_end: (f64, f64, f64), + project: &F, + delta2: f64, + depth: u8, + result: &mut Vec, + ) -> Result<(), ProjError> + where + F: Fn(GCoord) -> Result<(f64, f64), ProjError>, + { + let cos_min_distance: f64 = 30f64.cosh(); + let (x0, y0) = start_projected; + let (x1, y1) = end_projected; + let (a0, b0, c0) = cartesian_start; + let (a1, b1, c1) = cartesian_end; + + let dx = x1 - x0; + let dy = y1 - y0; + let d2 = dx * dx + dy * dy; + + if d2 > 4.0 * delta2 && depth > 0 { + let a = a0 + a1; + let b = b0 + b1; + let c = c0 + c1; + let m = (a * a + b * b + c * c).sqrt(); + let depth = depth - 1; + + let phi2 = (c / m).asin(); + let lambda2 = + if (c / m).abs() - 1.0 < f64::EPSILON || (lambda0 - lambda1).abs() < f64::EPSILON { + (lambda0 + lambda1) / 2.0 + } else { + b.atan2(a) + }; + let (x2, y2) = project(coord! {x:lambda2,y:phi2})?; + let dx2 = x2 - x0; + let dy2 = y2 - y0; + let dz = dy * dx2 - dx * dy2; + if dz * dz / d2 > delta2 + || ((dx * dx2 + dy * dy2) / d2 - 0.5).abs() > 0.3 + || a0 * a1 + b0 * b1 + c0 * c1 < cos_min_distance + { + self.resample_line_to( + start_projected, + (x2, y2), + lambda0, + lambda2, + cartesian_start, + (a / m, b / m, c), + project, + delta2, + depth - 1, + result, + )?; + result.push(coord! {x:x2,y:y2}); + self.resample_line_to( + (x2, y2), + end_projected, + lambda2, + lambda1, + (a / m, b / m, c), + cartesian_end, + project, + delta2, + depth - 1, + result, + )?; + } + } + Ok(()) + } + + fn resample(&self) {} +} diff --git a/src/data/mod.rs b/src/data/mod.rs index fa5070a..7409f0a 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,4 +1,6 @@ -use ndarray::{s, Array, Array1, Array2, Array3, ArrayBase, Ix1, Ix2, OwnedRepr, RawDataClone}; +use ndarray::{ + s, Array, Array1, Array2, Array3, ArrayBase, Ix1, Ix2, OwnedRepr, RawDataClone, ViewRepr, +}; use npyz::{npz::NpzArchive, Deserialize}; use num_traits::{AsPrimitive, FromPrimitive, Num}; use quadtree_rs::{area::AreaBuilder, Quadtree}; @@ -6,6 +8,7 @@ use std::{self, f64::consts::PI, fmt::Debug, io::BufReader, path::Path}; use thiserror::Error; mod concrete_data; +pub mod mapper; #[derive(Error, Debug)] pub enum DataError { @@ -344,3 +347,5 @@ impl Radar2d { Ok(meth.load(path)?) } } + +pub type Radar2dRef = RadarData2d>; diff --git a/src/main.rs b/src/main.rs index fd31690..78ecc14 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,26 +1,23 @@ -use std::ptr; - +use data::mapper::Mapper; +use data::{Npz, Radar2d}; use glib::{timeout_add, timeout_add_local}; use glib_macros::clone; -use gtk::gdk::builders::RGBABuilder; -use gtk::gdk::Display; use gtk::{ gio, glib, style_context_add_provider_for_display, Application, ApplicationWindow, CssProvider, FontButton, GestureDrag, }; use gtk::{prelude::*, DrawingArea}; +use std::ptr; mod data; mod monitor; mod painter; mod render; mod tree; mod window; -use data::{Npz, Radar2d, RadarData2d}; use monitor::Monitor; use ndarray::parallel::prelude::{IntoParallelIterator, ParallelIterator}; use painter::wgs84::{LatLonCoord, Mercator, ProjectionS, Range}; -use render::Render; -use render::{BackgroundConfig, BackgroundWidget}; +use render::{BackgroundConfig, BackgroundWidget, ForegroundConfig, ForegroundWidget, Render}; const APP_ID: &str = "org.gtk_rs.HelloWorld2"; @@ -60,24 +57,23 @@ fn build_ui(app: &Application) { .build(); let mut background_config = BackgroundConfig::new(); + let mut foreground_config = ForegroundConfig::new(); let background_widget = BackgroundWidget::new(background_config); + let foreground_widget = ForegroundWidget::new(foreground_config); + let render = Render::new(background_widget, foreground_widget); - let drawing_area = Render::new(); + let path = "/Users/ruomu/test2.npz"; - // let path = "/Users/ruomu/test2.npz"; + let data = Radar2d::::load(path, Npz).unwrap(); - // let data = Radar2d::::load(path, Npz).unwrap(); - // let projection = Mercator::new(); + let projection = Mercator::new(); + let mapper: Mapper = projection.into(); - // let aa = Range::from((*data.dim1.first().unwrap(), *data.dim1.last().unwrap())); - // let ab = Range::from((*data.dim2.first().unwrap(), *data.dim2.last().unwrap())); + render.set_mapper(mapper); + let monitor = Monitor::new(render); - // let coord = LatLonCoord::new(Some(aa), Some(ab), ((0, 1000), (0, 1000)), projection); - - // let result = data.mapped(&coord); - - let monitor = Monitor::new(); + monitor.load_data_2d(data); window.set_child(Some(&monitor)); window.set_default_width(1000); diff --git a/src/monitor/imp.rs b/src/monitor/imp.rs index f886e19..fef77db 100644 --- a/src/monitor/imp.rs +++ b/src/monitor/imp.rs @@ -5,11 +5,9 @@ use gtk::CompositeTemplate; use gtk::{glib, prelude::WidgetExtManual}; use std::cell::{Cell, RefCell}; -#[derive(Default, CompositeTemplate)] -#[template(file = "monitor.ui")] +#[derive(Default)] pub struct Monitor { - #[template_child(id = "render")] - pub(super) label: TemplateChild, + pub(super) renderer: RefCell, } #[glib::object_subclass] @@ -17,14 +15,6 @@ impl ObjectSubclass for Monitor { const NAME: &'static str = "Monitor"; type Type = super::Monitor; type ParentType = gtk::Box; - - fn class_init(klass: &mut Self::Class) { - klass.bind_template(); - } - - fn instance_init(obj: &InitializingObject) { - obj.init_template(); - } } impl ObjectImpl for Monitor {} diff --git a/src/monitor/mod.rs b/src/monitor/mod.rs index c3ef7c2..e793004 100644 --- a/src/monitor/mod.rs +++ b/src/monitor/mod.rs @@ -1,7 +1,14 @@ mod imp; +use crate::data::RadarData2d; use crate::render::Render; -use glib::subclass::prelude::*; +use glib::clone; +use glib::{clone::Downgrade, subclass::prelude::*}; +use gtk::glib; use gtk::traits::WidgetExt; +use gtk::{EventController, EventControllerScrollFlags, Inhibit}; +use num_traits::Num; +use proj::{Proj, ProjError}; +use std::borrow::Borrow; glib::wrapper! { pub struct Monitor(ObjectSubclass) @@ -9,8 +16,47 @@ glib::wrapper! { } impl Monitor { - pub fn new() -> Self { + pub fn new(render: Render) -> Self { let this: Self = glib::Object::new(); + let pointer_location_detecture = gtk::EventControllerMotion::new(); + pointer_location_detecture.connect_motion( + clone!(@weak this as s => move |_context, x, y| { + s.imp().renderer.borrow() + .imp() + .pointer_location + .replace((x as f32, y as f32)); + }), + ); + + let scale_detecture = gtk::EventControllerScroll::new(EventControllerScrollFlags::VERTICAL); + + scale_detecture.connect_scroll(clone!( + @weak this as s => @default-panic,move |_context, _x, y| { + let renderer = s.imp().borrow().renderer.borrow(); + let current_scale = renderer.imp().scale.borrow().clone(); + renderer.imp().scale.replace( + (current_scale + y as f32 / 100.0).max(1.0).min(5.0), + ); + Inhibit(false) + } + )); + + render.add_controller(pointer_location_detecture); + render.add_controller(scale_detecture); + render.set_hexpand(true); + render.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, + Raw: ndarray::Data + Clone + ndarray::RawDataClone, + { + let renderer = self.imp().renderer.borrow(); + renderer.load_data_2d(data)?; + Ok(()) + } } diff --git a/src/monitor/monitor.ui b/src/monitor/monitor.ui deleted file mode 100644 index 7d8b58a..0000000 --- a/src/monitor/monitor.ui +++ /dev/null @@ -1,10 +0,0 @@ - - - - diff --git a/src/painter/coords/mod.rs b/src/painter/coords/mod.rs index 299fff6..767221e 100644 --- a/src/painter/coords/mod.rs +++ b/src/painter/coords/mod.rs @@ -1,7 +1,10 @@ +use geo_types::{coord, Coord as GCoord, Line, LineString, Polygon}; use ndarray::{Array2, ViewRepr}; use num_traits::Num; +use proj::ProjError; +use std::borrow::Borrow; -use crate::data::RadarData2d; +use crate::data::{mapper::Mapper, RadarData2d}; use super::AfterMapping2d; pub mod wgs84; @@ -20,26 +23,34 @@ where T: Num + Clone + PartialEq + PartialOrd, Raw: ndarray::Data + Clone + ndarray::RawDataClone, { - pub fn mapped(&self, coord: &impl Coord) -> AfterMapping2d { + pub fn mapped(&self, coord: impl Borrow) -> Result, ProjError> { + let mapper: &Mapper = coord.borrow(); + self.map_by_fn(|x| mapper.map(x)) + } + + pub fn map_by_fn(&self, f: F) -> Result, ProjError> + where + F: Fn((f64, f64)) -> Result<(f64, f64), ProjError>, + { let mesh_dim1_len = self.dim1.len(); let mesh_dim2_len = self.dim2.len(); - let mut d1 = Array2::::zeros((mesh_dim1_len, mesh_dim2_len)); - let mut d2 = Array2::::zeros((mesh_dim1_len, mesh_dim2_len)); + let mut d1 = Array2::::zeros((mesh_dim2_len, mesh_dim1_len)); + let mut d2 = Array2::::zeros((mesh_dim2_len, mesh_dim1_len)); - self.dim1.iter().enumerate().for_each(|(i, v)| { - self.dim2.iter().enumerate().for_each(|(j, u)| { - let (x, y) = coord.map(*v, *u); - d1[[i, j]] = x; - d2[[i, j]] = y; - }); - }); + for (i, v) in self.dim1.iter().enumerate() { + for (j, u) in self.dim2.iter().enumerate() { + let (x, y) = f((*v, *u))?; + d1[[j, i]] = x; + d2[[j, i]] = y; + } + } - AfterMapping2d { + Ok(AfterMapping2d { dim1: d1, dim2: d2, data: self.data.view(), coord_type: self.coord_type.clone(), - } + }) } } diff --git a/src/painter/coords/wgs84.rs b/src/painter/coords/wgs84.rs index 64429c9..5b2f66a 100644 --- a/src/painter/coords/wgs84.rs +++ b/src/painter/coords/wgs84.rs @@ -123,7 +123,7 @@ struct PCS { pub lon_range: Range, pub lat_range: Range, pub proj_param: T, - transformer: Proj, + pub transformer: Proj, } unsafe impl Sync for PCS {} diff --git a/src/render/background/imp.rs b/src/render/background/imp.rs index 62b36a5..b9e977e 100644 --- a/src/render/background/imp.rs +++ b/src/render/background/imp.rs @@ -1,5 +1,6 @@ use std::cell::RefCell; +use crate::painter::Coord; use crate::render::{imp, WindowCoord}; use femtovg::Paint; use geo_macros::Prj; diff --git a/src/render/background/mod.rs b/src/render/background/mod.rs index a28ce27..cfaace7 100644 --- a/src/render/background/mod.rs +++ b/src/render/background/mod.rs @@ -1,4 +1,5 @@ mod imp; +use crate::render::WindowCoord; use femtovg::{renderer::OpenGl, Canvas, Path}; use glib::subclass::types::ObjectSubclassIsExt; use gtk::{ffi::gtk_widget_get_width, glib, graphene::Rect, prelude::SnapshotExtManual}; @@ -40,7 +41,21 @@ impl BackgroundWidget { if imp.config.borrow().show_lon_lines {} } - pub fn draw(&self, canvas: &mut Canvas) { + pub fn draw(&self, canvas: &mut Canvas, scale: f32, dpi: i32) { + let canvas_widht = canvas.width(); + let canvas_height = canvas.height(); + let config = self.imp().config.borrow(); + + self.mesh_lines(canvas); + } + + pub fn set_lat_lines(&self, lat_lines: Vec>) { let imp = self.imp(); + imp.config.borrow_mut().lat_lines = lat_lines; + } + + pub fn set_lon_lines(&self, lon_lines: Vec>) { + let imp = self.imp(); + imp.config.borrow_mut().lon_lines = lon_lines; } } diff --git a/src/render/foreground/imp.rs b/src/render/foreground/imp.rs new file mode 100644 index 0000000..a4b8ecf --- /dev/null +++ b/src/render/foreground/imp.rs @@ -0,0 +1,40 @@ +use std::cell::RefCell; + +use crate::data::{Radar2dRef, RadarData2d}; +use crate::painter::Coord; +use crate::render::{imp, WindowCoord}; +use femtovg::Paint; +use geo_macros::Prj; +use gtk::glib; +use gtk::subclass::prelude::*; +use ndarray::Array2; + +#[derive(Prj, Default)] +pub struct ForegroundConfig { + pub show_lat_lines: bool, + pub show_lon_lines: bool, + pub lat_lines: Vec>, + pub lon_lines: Vec>, + pub painter: Paint, +} + +impl ForegroundConfig { + pub fn new() -> Self { + Self::default() + } +} + +#[derive(Default)] +pub struct ForegroundWidget { + pub(super) config: RefCell, + pub(super) dim1: RefCell>>, + pub(super) dim2: RefCell>>, +} + +#[glib::object_subclass] +impl ObjectSubclass for ForegroundWidget { + const NAME: &'static str = "ForegroundWidget"; + type Type = super::ForegroundWidget; +} + +impl ObjectImpl for ForegroundWidget {} diff --git a/src/render/foreground/mod.rs b/src/render/foreground/mod.rs new file mode 100644 index 0000000..9b6cab7 --- /dev/null +++ b/src/render/foreground/mod.rs @@ -0,0 +1,39 @@ +mod imp; +use crate::{data::mapper::Mapper, render::WindowCoord}; +use femtovg::{renderer::OpenGl, Canvas, Path}; +use glib::subclass::types::ObjectSubclassIsExt; +use gtk::{ffi::gtk_widget_get_width, glib, graphene::Rect, prelude::SnapshotExtManual}; +use ndarray::Array2; +use std::ops::Range; + +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: &Mapper) { + let canvas_widht = canvas.width(); + let canvas_height = canvas.height(); + let config = self.imp().config.borrow(); + } + + pub(super) fn set_dims(&mut self, dims: (Array2, Array2)) { + self.imp().dim1.replace(Some(dims.0)); + self.imp().dim2.replace(Some(dims.1)); + } +} diff --git a/src/render/imp.rs b/src/render/imp.rs index 0142ed8..83ed647 100644 --- a/src/render/imp.rs +++ b/src/render/imp.rs @@ -1,18 +1,43 @@ use super::background::BackgroundWidget; -use glib::{ObjectExt, ParamSpec, Properties, Value}; +use super::foreground::ForegroundWidget; +use super::WindowCoord; +use crate::data::mapper::Mapper; +use crate::painter::wgs84::Mercator; use gtk::subclass::prelude::*; use gtk::traits::{GLAreaExt, WidgetExt}; use gtk::{glib, prelude::WidgetExtManual}; +use ndarray::Array2; use std::cell::{Cell, RefCell}; -use std::f32::consts::PI; use std::num::NonZeroU32; -#[derive(Default)] pub struct Render { - // pub(super) background: RefCell, + pub(super) background: RefCell, + pub(super) foreground: RefCell, + pub(super) dim1: RefCell>>, + pub(super) dim2: RefCell>>, + pub scale: RefCell, + pub pointer_location: RefCell, + pub transform: RefCell, + pub mapper: RefCell, canvas: RefCell>>, } +impl Default for Render { + fn default() -> Self { + Self { + background: RefCell::new(BackgroundWidget::default()), + foreground: RefCell::new(ForegroundWidget::default()), + scale: RefCell::new(1.0), + pointer_location: RefCell::new((0.0, 0.0)), + transform: RefCell::new((0.0, 0.0)), + mapper: RefCell::new(Mercator::new().into()), + canvas: RefCell::new(None), + dim1: RefCell::new(None), + dim2: RefCell::new(None), + } + } +} + #[glib::object_subclass] impl ObjectSubclass for Render { const NAME: &'static str = "MyRender"; @@ -66,12 +91,10 @@ impl GLAreaImpl for Render { (h * dpi) as u32, Color::rgba(0, 0, 0, 255), ); - canvas.fill_text( - 0., - 0., - "hello", - &Paint::color(Color::rgba(255, 255, 255, 0)), - ); + + self.background + .borrow() + .draw(canvas, self.scale.borrow().clone(), dpi); canvas.flush(); diff --git a/src/render/mod.rs b/src/render/mod.rs index 85834e9..5937255 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -1,8 +1,15 @@ mod background; +mod foreground; + mod imp; +use crate::data::{mapper::Mapper, RadarData2d}; + pub use self::background::{BackgroundConfig, BackgroundWidget}; -use glib::subclass::prelude::*; -use gtk::{traits::GestureSingleExt, EventControllerScrollFlags}; +pub use self::foreground::{ForegroundConfig, ForegroundWidget}; +pub use glib::subclass::prelude::*; +use ndarray::{self, s, Axis, Dimension, Ix2, Zip}; +use num_traits::Num; +use proj::ProjError; pub(super) type WindowCoord = (f32, f32); @@ -13,12 +20,36 @@ glib::wrapper! { impl Default for Render { fn default() -> Self { - Self::new() + Self::new(BackgroundWidget::default(), ForegroundWidget::default()) } } impl Render { - pub fn new() -> Self { - glib::Object::new() + pub fn new(background: BackgroundWidget, foreground: ForegroundWidget) -> Self { + let this: Self = glib::Object::new(); + this.imp().scale.replace(1.0); + this.imp().background.replace(background); + this.imp().foreground.replace(foreground); + this + } + + pub fn set_mapper(&self, mapper: Mapper) { + self.imp().mapper.replace(mapper); + } + + pub(super) fn load_data_2d(&self, data: RadarData2d) -> Result<(), ProjError> + where + T: Num + Clone + PartialEq + PartialOrd, + Raw: ndarray::Data + Clone + ndarray::RawDataClone, + { + assert!(data.dim1.shape().len() == data.dim2.shape().len()); + + let meshed = data.map_by_fn(|(x, y)| Ok((x, y)))?; + + self.imp() + .foreground + .borrow_mut() + .set_dims((meshed.dim1, meshed.dim2)); + Ok(()) } } diff --git a/test.js b/test.js new file mode 100644 index 0000000..f6e6c71 --- /dev/null +++ b/test.js @@ -0,0 +1,102 @@ +import {cartesian} from "../cartesian.js"; +import {abs, asin, atan2, cos, epsilon, radians, sqrt} from "../math.js"; +import {transformer} from "../transform.js"; + +var maxDepth = 16, // maximum depth of subdivision + cosMinDistance = cos(30 * radians); // cos(minimum angular distance) + +export default function(project, delta2) { + return +delta2 ? resample(project, delta2) : resampleNone(project); +} + +function resampleNone(project) { + return transformer({ + point: function(x, y) { + x = project(x, y); + this.stream.point(x[0], x[1]); + } + }); +} + +function resample(project, delta2) { + + function resampleLineTo(x0, y0, lambda0, a0, b0, c0, x1, y1, lambda1, a1, b1, c1, depth, stream) { + var dx = x1 - x0, + dy = y1 - y0, + d2 = dx * dx + dy * dy; + if (d2 > 4 * delta2 && depth--) { + var a = a0 + a1, + b = b0 + b1, + c = c0 + c1, + m = sqrt(a * a + b * b + c * c), + phi2 = asin(c /= m), + lambda2 = abs(abs(c) - 1) < epsilon || abs(lambda0 - lambda1) < epsilon ? (lambda0 + lambda1) / 2 : atan2(b, a), + p = project(lambda2, phi2), + x2 = p[0], + y2 = p[1], + dx2 = x2 - x0, + dy2 = y2 - y0, + dz = dy * dx2 - dx * dy2; + if (dz * dz / d2 > delta2 // perpendicular projected distance + || abs((dx * dx2 + dy * dy2) / d2 - 0.5) > 0.3 // midpoint close to an end + || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { // angular distance + resampleLineTo(x0, y0, lambda0, a0, b0, c0, x2, y2, lambda2, a /= m, b /= m, c, depth, stream); + stream.point(x2, y2); + resampleLineTo(x2, y2, lambda2, a, b, c, x1, y1, lambda1, a1, b1, c1, depth, stream); + } + } + } + return function(stream) { + var lambda00, x00, y00, a00, b00, c00, // first point + lambda0, x0, y0, a0, b0, c0; // previous point + + var resampleStream = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, + polygonStart: function() { stream.polygonStart(); resampleStream.lineStart = ringStart; }, + polygonEnd: function() { stream.polygonEnd(); resampleStream.lineStart = lineStart; } + }; + + function point(x, y) { + x = project(x, y); + stream.point(x[0], x[1]); + } + + function lineStart() { + x0 = NaN; + resampleStream.point = linePoint; + stream.lineStart(); + } + + function linePoint(lambda, phi) { + var c = cartesian([lambda, phi]), p = project(lambda, phi); + resampleLineTo(x0, y0, lambda0, a0, b0, c0, x0 = p[0], y0 = p[1], lambda0 = lambda, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream); + stream.point(x0, y0); + } + + function lineEnd() { + resampleStream.point = point; + stream.lineEnd(); + } + + function ringStart() { + lineStart(); + resampleStream.point = ringPoint; + resampleStream.lineEnd = ringEnd; + } + + function ringPoint(lambda, phi) { + linePoint(lambda00 = lambda, phi), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; + resampleStream.point = linePoint; + } + + function ringEnd() { + resampleLineTo(x0, y0, lambda0, a0, b0, c0, x00, y00, lambda00, a00, b00, c00, maxDepth, stream); + resampleStream.lineEnd = lineEnd; + lineEnd(); + } + + return resampleStream; + }; +} \ No newline at end of file diff --git a/tile.js b/tile.js new file mode 100644 index 0000000..ba14ed1 --- /dev/null +++ b/tile.js @@ -0,0 +1,95 @@ +function redraw() { + var t = (0 | Math.log(projection.scale()) / Math.LN2) - 5 + , e = d3.quadTiles(projection, t) + , n = projection.clipExtent(); + tiles_ = d3.quadTiles(projection.clipExtent([[1, 1], [width - 1, height - 1]]), t); + var a = svg.selectAll(".tile").data(tiles_, key); + a.enter().append("path").attr("class", "tile"), + a.exit().remove(), + a.attr("class", "tile").attr("d", path), + svg.selectAll(".tile").data(e, key).classed("highlight", !0).exit().classed("highlight", !1); + var r = svg.selectAll("text").data(e, key); + r.enter().append("text").attr("text-anchor", "middle").text(key), + r.exit().remove(), + r.attr("transform", function (t) { + return "translate(" + projection(t.centroid) + ")" + }), + projection.clipExtent(n) +} +function key(t) { + return t.key.join(", ") +} +!function () { + function t() { } + function e(t) { + return 360 * Math.atan(Math.exp(-t * Math.PI / 180)) / Math.PI - 90 + } + d3.quadTiles = function (n, a) { + function r(t, n, p, d) { + var h = p - t + , g = e(n) + , u = e(d) + , v = l * h; + i = !0, + s.polygonStart(), + s.lineStart(); + for (var f = t; p + v / 2 > f && i; f += v) + s.point(f, g); + for (var m = g; (m += v) < u && i;) + s.point(p, m); + for (var f = p; f > t - v / 2 && i; f -= v) + s.point(f, u); + for (var m = u; (m -= v) > g && i;) + s.point(t, m); + if (i && s.point(t, g), + s.lineEnd(), + s.polygonEnd(), + 360 / c >= h) + i || o.push({ + type: "Polygon", + coordinates: [d3.range(t, p + v / 2, v).map(function (t) { + return [t, n] + }).concat([[p, .5 * (n + d)]]).concat(d3.range(p, t - v / 2, -v).map(function (t) { + return [t, d] + })).concat([[t, .5 * (n + d)]]).concat([[t, n]]).map(function (t) { + return [t[0], e(t[1])] + })], + key: [0 | (180 + t) / 360 * c, 0 | (180 + n) / 360 * c, a], + centroid: [.5 * (t + p), .5 * (g + u)] + }); + else if (!i) { + var f = .5 * (t + p) + , m = .5 * (n + d); + r(t, n, f, m), + r(f, n, p, m), + r(t, m, f, d), + r(f, m, p, d) + } + } + var i, o = [], c = 1 << (a = Math.max(0, a)), l = Math.max(.2, Math.min(1, .01 * a)), p = n.precision(), s = n.precision(960).stream({ + point: function () { + i = !1 + }, + lineStart: t, + lineEnd: t, + polygonStart: t, + polygonEnd: t + }); + return r(-180, -180, 180, 180), + n.precision(p), + o + } +}(); +var width = 960 + , height = 600 + , p = 100 + , projection = d3.geo.albers().rotate([0, 0]).center([0, 38.7]).scale(1280).translate([width / 2, height / 2]).precision(.1).clipExtent([[p, p], [width - p, height - p]]) + , path = d3.geo.path().projection(projection) + , svg = d3.select("#map").append("svg").attr("width", width).attr("height", height).style("pointer-events", "all").call(d3.behavior.zoom().translate(projection.translate()).scale(projection.scale()).scaleExtent([50, 1e7]).on("zoom", function () { + projection.scale(d3.event.scale).translate(d3.event.translate), + redraw() + })); +svg.append("path").datum({ + type: "Sphere" +}).attr("class", "outline").attr("d", path), + redraw();