add foreground implemented widget

This commit is contained in:
sleptworld 2023-07-25 01:04:25 +08:00
parent 145e09ef53
commit f2f979c483
16 changed files with 649 additions and 73 deletions

192
src/data/mapper.rs Normal file
View File

@ -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<f64>, Range<f64>),
bounds: (f64, f64, f64, f64),
}
impl From<Proj> for Mapper {
fn from(proj: Proj) -> Self {
let default_range: (Range<f64>, Range<f64>) = (-180.0..180.0, -90.0..90.0);
let bounds = Self::bound(&proj, default_range.clone()).unwrap();
Self {
proj: proj,
range: default_range,
bounds,
}
}
}
impl<C: ProjectionS> From<C> 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<f64>, lat_range: Range<f64>) -> 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<f64>) {
self.range.0 = range;
self.bounds = Self::bound(&self.proj, self.range.clone()).unwrap();
}
pub fn set_lat_range(&mut self, range: Range<f64>) {
self.range.1 = range;
self.bounds = Self::bound(&self.proj, self.range.clone()).unwrap();
}
fn bound(
proj: &Proj,
range: (Range<f64>, Range<f64>),
) -> 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<LineString, ProjError> {
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<GCoord> = 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<F>(
&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<GCoord>,
) -> 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) {}
}

View File

@ -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<T: Num + Clone + PartialEq + PartialOrd> Radar2d<T> {
Ok(meth.load(path)?)
}
}
pub type Radar2dRef<T> = RadarData2d<T, ViewRepr<T>>;

View File

@ -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::<i8>::load(path, Npz).unwrap();
// let data = Radar2d::<i8>::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);

View File

@ -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<Render>,
pub(super) renderer: RefCell<Render>,
}
#[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<Self>) {
obj.init_template();
}
}
impl ObjectImpl for Monitor {}

View File

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

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<interface>
<template class="Monitor" parent="GtkBox">
<child>
<object class="MyRender" id="render">
<property name="hexpand">True</property>
</object>
</child>
</template>
</interface>

View File

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

View File

@ -123,7 +123,7 @@ struct PCS<T: ProjectionS> {
pub lon_range: Range,
pub lat_range: Range,
pub proj_param: T,
transformer: Proj,
pub transformer: Proj,
}
unsafe impl Sync for PCS<Mercator> {}

View File

@ -1,5 +1,6 @@
use std::cell::RefCell;
use crate::painter::Coord;
use crate::render::{imp, WindowCoord};
use femtovg::Paint;
use geo_macros::Prj;

View File

@ -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<OpenGl>) {
pub fn draw(&self, canvas: &mut Canvas<OpenGl>, 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<Vec<WindowCoord>>) {
let imp = self.imp();
imp.config.borrow_mut().lat_lines = lat_lines;
}
pub fn set_lon_lines(&self, lon_lines: Vec<Vec<WindowCoord>>) {
let imp = self.imp();
imp.config.borrow_mut().lon_lines = lon_lines;
}
}

View File

@ -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<Vec<WindowCoord>>,
pub lon_lines: Vec<Vec<WindowCoord>>,
pub painter: Paint,
}
impl ForegroundConfig {
pub fn new() -> Self {
Self::default()
}
}
#[derive(Default)]
pub struct ForegroundWidget {
pub(super) config: RefCell<ForegroundConfig>,
pub(super) dim1: RefCell<Option<Array2<f64>>>,
pub(super) dim2: RefCell<Option<Array2<f64>>>,
}
#[glib::object_subclass]
impl ObjectSubclass for ForegroundWidget {
const NAME: &'static str = "ForegroundWidget";
type Type = super::ForegroundWidget;
}
impl ObjectImpl for ForegroundWidget {}

View File

@ -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<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: &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<f64>, Array2<f64>)) {
self.imp().dim1.replace(Some(dims.0));
self.imp().dim2.replace(Some(dims.1));
}
}

View File

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

View File

@ -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<T, Raw>(&self, data: RadarData2d<T, Raw>) -> Result<(), ProjError>
where
T: Num + Clone + PartialEq + PartialOrd,
Raw: ndarray::Data<Elem = T> + 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(())
}
}

102
test.js Normal file
View File

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

95
tile.js Normal file
View File

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