plotly/layout/
scene.rs

1use plotly_derive::FieldSetter;
2use serde::Serialize;
3
4use crate::color::Color;
5use crate::layout::{Annotation, AspectMode, Axis};
6
7#[serde_with::skip_serializing_none]
8#[derive(Serialize, Debug, Clone, FieldSetter)]
9/// Sets this scene's axis aspectratio.
10/// x, y, z must be positive.
11/// Default: {x: 1, y: 1, z: 1}
12pub struct AspectRatio {
13    x: Option<f64>,
14    y: Option<f64>,
15    z: Option<f64>,
16}
17
18impl AspectRatio {
19    pub fn new() -> Self {
20        AspectRatio {
21            x: Some(1.0),
22            y: Some(1.0),
23            z: Some(1.0),
24        }
25    }
26}
27
28impl From<(f64, f64, f64)> for AspectRatio {
29    fn from((x, y, z): (f64, f64, f64)) -> Self {
30        AspectRatio {
31            x: Some(x),
32            y: Some(y),
33            z: Some(z),
34        }
35    }
36}
37
38#[serde_with::skip_serializing_none]
39#[derive(Serialize, Debug, Clone, FieldSetter)]
40/// Container for CameraCenter, Eye, Up, and Projection objects. The camera of a
41/// 3D scene.
42pub struct Camera {
43    center: Option<CameraCenter>,
44    eye: Option<Eye>,
45    up: Option<Up>,
46    projection: Option<Projection>,
47}
48
49impl Camera {
50    pub fn new() -> Self {
51        Default::default()
52    }
53}
54
55#[serde_with::skip_serializing_none]
56#[derive(Serialize, Debug, Clone, FieldSetter)]
57/// Sets the (x, y, z) components of the 'center' camera vector. This vector
58/// determines the translation (x, y, z) space about the center of this scene.
59/// Default: {x: 0, y: 0, z: 0} which means that the center of the scene is at
60/// the origin.
61pub struct CameraCenter {
62    x: Option<f64>,
63    y: Option<f64>,
64    z: Option<f64>,
65}
66
67impl CameraCenter {
68    pub fn new() -> Self {
69        CameraCenter {
70            x: Some(0.0),
71            y: Some(0.0),
72            z: Some(0.0),
73        }
74    }
75}
76
77impl From<(f64, f64, f64)> for CameraCenter {
78    fn from((x, y, z): (f64, f64, f64)) -> Self {
79        CameraCenter {
80            x: Some(x),
81            y: Some(y),
82            z: Some(z),
83        }
84    }
85}
86
87#[serde_with::skip_serializing_none]
88#[derive(Serialize, Debug, Clone, FieldSetter)]
89/// Sets the (x, y, z) components of the 'eye' camera vector. This vector
90/// determines the view point about the origin of this scene.
91/// Default: {x: 1.25, y: 1.25, z: 1.25}
92pub struct Eye {
93    x: Option<f64>,
94    y: Option<f64>,
95    z: Option<f64>,
96}
97
98impl Eye {
99    pub fn new() -> Self {
100        Eye {
101            x: Some(1.25),
102            y: Some(1.25),
103            z: Some(1.25),
104        }
105    }
106}
107
108impl From<(f64, f64, f64)> for Eye {
109    fn from((x, y, z): (f64, f64, f64)) -> Self {
110        Eye {
111            x: Some(x),
112            y: Some(y),
113            z: Some(z),
114        }
115    }
116}
117
118#[serde_with::skip_serializing_none]
119#[derive(Serialize, Debug, Clone, FieldSetter)]
120/// Sets the (x, y, z) components of the 'up' camera vector. This vector
121/// determines the up direction of this scene with respect to the page. The
122/// Default: {x: 0, y: 0, z: 1} which means that the z axis points up.
123pub struct Up {
124    x: Option<f64>,
125    y: Option<f64>,
126    z: Option<f64>,
127}
128
129impl Up {
130    pub fn new() -> Self {
131        Up {
132            x: Some(0.0),
133            y: Some(0.0),
134            z: Some(1.0),
135        }
136    }
137}
138
139impl From<(f64, f64, f64)> for Up {
140    fn from((x, y, z): (f64, f64, f64)) -> Self {
141        Up {
142            x: Some(x),
143            y: Some(y),
144            z: Some(z),
145        }
146    }
147}
148
149/// Sets the projection type. The projection type could be either "perspective"
150/// or "orthographic".
151/// Default: "perspective"
152#[derive(Default, Serialize, Debug, Clone)]
153pub enum ProjectionType {
154    #[default]
155    #[serde(rename = "perspective")]
156    Perspective,
157    #[serde(rename = "orthographic")]
158    Orthographic,
159    #[serde(rename = "airy")]
160    Airy,
161    #[serde(rename = "aitoff")]
162    Aitoff,
163    #[serde(rename = "albers")]
164    Albers,
165    #[serde(rename = "albers usa")]
166    AlbersUsa,
167    #[serde(rename = "august")]
168    August,
169    #[serde(rename = "azimuthal equal area")]
170    AzimuthalEqualArea,
171    #[serde(rename = "azimuthal equidistant")]
172    AzimuthalEquidistant,
173    #[serde(rename = "baker")]
174    Baker,
175    #[serde(rename = "bertin1953")]
176    Bertin1953,
177    #[serde(rename = "boggs")]
178    Boggs,
179    #[serde(rename = "bonne")]
180    Bonne,
181    #[serde(rename = "bottomley")]
182    Bottomley,
183    #[serde(rename = "bromley")]
184    Bromley,
185    #[serde(rename = "collignon")]
186    Collignon,
187    #[serde(rename = "conic conformal")]
188    ConicConformal,
189    #[serde(rename = "conic equal area")]
190    ConicEqualArea,
191    #[serde(rename = "conic equidistant")]
192    ConicEquidistant,
193    #[serde(rename = "craig")]
194    Craig,
195    #[serde(rename = "craster")]
196    Craster,
197    #[serde(rename = "cylindrical equal area")]
198    CylindricalEqualArea,
199    #[serde(rename = "cylindrical stereographic")]
200    CylindricalStereographic,
201    #[serde(rename = "eckert1")]
202    Eckert1,
203    #[serde(rename = "eckert2")]
204    Eckert2,
205    #[serde(rename = "eckert3")]
206    Eckert3,
207    #[serde(rename = "eckert4")]
208    Eckert4,
209    #[serde(rename = "eckert5")]
210    Eckert5,
211    #[serde(rename = "eckert6")]
212    Eckert6,
213    #[serde(rename = "eisenlohr")]
214    Eisenlohr,
215    #[serde(rename = "equal earth")]
216    EqualEarth,
217    #[serde(rename = "equirectangular")]
218    Equirectangular,
219    #[serde(rename = "fahey")]
220    Fahey,
221    #[serde(rename = "foucaut")]
222    Foucaut,
223    #[serde(rename = "foucaut sinusoidal")]
224    FoucautSinusoidal,
225    #[serde(rename = "ginzburg4")]
226    Ginzburg4,
227    #[serde(rename = "ginzburg5")]
228    Ginzburg5,
229    #[serde(rename = "ginzburg6")]
230    Ginzburg6,
231    #[serde(rename = "ginzburg8")]
232    Ginzburg8,
233    #[serde(rename = "ginzburg9")]
234    Ginzburg9,
235    #[serde(rename = "gnomonic")]
236    Gnomonic,
237    #[serde(rename = "gringorten")]
238    Gringorten,
239    #[serde(rename = "gringorten quincuncial")]
240    GringortenQuincuncial,
241    #[serde(rename = "guyou")]
242    Guyou,
243    #[serde(rename = "hammer")]
244    Hammer,
245    #[serde(rename = "hill")]
246    Hill,
247    #[serde(rename = "homolosine")]
248    Homolosine,
249    #[serde(rename = "hufnagel")]
250    Hufnagel,
251    #[serde(rename = "hyperelliptical")]
252    Hyperelliptical,
253    #[serde(rename = "kavrayskiy7")]
254    Kavrayskiy7,
255    #[serde(rename = "lagrange")]
256    Lagrange,
257    #[serde(rename = "larrivee")]
258    Larrivee,
259    #[serde(rename = "laskowski")]
260    Laskowski,
261    #[serde(rename = "loximuthal")]
262    Loximuthal,
263    #[serde(rename = "mercator")]
264    Mercator,
265    #[serde(rename = "miller")]
266    Miller,
267    #[serde(rename = "mollweide")]
268    Mollweide,
269    #[serde(rename = "mt flat polar parabolic")]
270    MtFlatPolarParabolic,
271    #[serde(rename = "mt flat polar quartic")]
272    MtFlatPolarQuartic,
273    #[serde(rename = "mt flat polar sinusoidal")]
274    MtFlatPolarSinusoidal,
275    #[serde(rename = "natural earth")]
276    NaturalEarth,
277    #[serde(rename = "natural earth1")]
278    NaturalEarth1,
279    #[serde(rename = "natural earth2")]
280    NaturalEarth2,
281    #[serde(rename = "nell hammer")]
282    NellHammer,
283    #[serde(rename = "nicolosi")]
284    Nicolosi,
285    #[serde(rename = "patterson")]
286    Patterson,
287    #[serde(rename = "peirce quincuncial")]
288    PeirceQuincuncial,
289    #[serde(rename = "polyconic")]
290    Polyconic,
291    #[serde(rename = "rectangular polyconic")]
292    RectangularPolyconic,
293    #[serde(rename = "robinson")]
294    Robinson,
295    #[serde(rename = "satellite")]
296    Satellite,
297    #[serde(rename = "sinu mollweide")]
298    SinuMollweide,
299    #[serde(rename = "sinusoidal")]
300    Sinusoidal,
301    #[serde(rename = "stereographic")]
302    Stereographic,
303    #[serde(rename = "times")]
304    Times,
305    #[serde(rename = "transverse mercator")]
306    TransverseMercator,
307    #[serde(rename = "van der grinten")]
308    VanDerGrinten,
309    #[serde(rename = "van der grinten2")]
310    VanDerGrinten2,
311    #[serde(rename = "van der grinten3")]
312    VanDerGrinten3,
313    #[serde(rename = "van der grinten4")]
314    VanDerGrinten4,
315    #[serde(rename = "wagner4")]
316    Wagner4,
317    #[serde(rename = "wagner6")]
318    Wagner6,
319    #[serde(rename = "wiechel")]
320    Wiechel,
321    #[serde(rename = "winkel tripel")]
322    WinkelTripel,
323    #[serde(rename = "winkel3")]
324    Winkel3,
325}
326
327impl From<ProjectionType> for Projection {
328    fn from(projection_type: ProjectionType) -> Self {
329        Projection {
330            projection_type: Some(projection_type),
331            rotation: None,
332        }
333    }
334}
335
336/// Sets the rotation of the map projection.
337#[derive(Serialize, Clone, Debug, FieldSetter)]
338pub struct Rotation {
339    /// Rotates the map along meridians (in degrees North).
340    lat: Option<f64>,
341    /// Rotates the map along parallels (in degrees East).
342    lon: Option<f64>,
343    /// Roll the map (in degrees). For example, a roll of "180" makes the map
344    /// appear upside down.
345    roll: Option<f64>,
346}
347
348impl Rotation {
349    pub fn new() -> Self {
350        Default::default()
351    }
352}
353
354#[serde_with::skip_serializing_none]
355#[derive(Serialize, Debug, Clone, FieldSetter)]
356/// Container for Projection options.
357pub struct Projection {
358    #[serde(rename = "type")]
359    projection_type: Option<ProjectionType>,
360    /// Sets the rotation of the map projection. See https://plotly.com/python/reference/layout/geo/#layout-geo-projection-rotation
361    #[serde(rename = "rotation")]
362    rotation: Option<Rotation>,
363}
364
365impl Projection {
366    pub fn new() -> Self {
367        Default::default()
368    }
369}
370
371#[derive(Debug, Clone)]
372pub enum DragMode {
373    Zoom,
374    Pan,
375    Select,
376    Lasso,
377    DrawClosedPath,
378    DrawOpenPath,
379    DrawLine,
380    DrawRect,
381    DrawCircle,
382    Orbit,
383    Turntable,
384    False,
385}
386
387impl Serialize for DragMode {
388    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
389    where
390        S: serde::Serializer,
391    {
392        match *self {
393            Self::Zoom => serializer.serialize_str("zoom"),
394            Self::Pan => serializer.serialize_str("pan"),
395            Self::Select => serializer.serialize_str("select"),
396            Self::Lasso => serializer.serialize_str("lasso"),
397            Self::DrawClosedPath => serializer.serialize_str("drawclosedpath"),
398            Self::DrawOpenPath => serializer.serialize_str("drawopenpath"),
399            Self::DrawLine => serializer.serialize_str("drawline"),
400            Self::DrawRect => serializer.serialize_str("drawrect"),
401            Self::DrawCircle => serializer.serialize_str("drawcircle"),
402            Self::Orbit => serializer.serialize_str("orbit"),
403            Self::Turntable => serializer.serialize_str("turntable"),
404            Self::False => serializer.serialize_bool(false),
405        }
406    }
407}
408
409#[derive(Debug, Clone)]
410/// Determines the mode of drag interactions.
411pub enum DragMode3D {
412    Zoom,
413    Pan,
414    Turntable,
415    Orbit,
416    False,
417}
418
419impl Serialize for DragMode3D {
420    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
421    where
422        S: serde::Serializer,
423    {
424        match *self {
425            Self::Zoom => serializer.serialize_str("zoom"),
426            Self::Pan => serializer.serialize_str("pan"),
427            Self::Turntable => serializer.serialize_str("turntable"),
428            Self::Orbit => serializer.serialize_str("orbit"),
429            Self::False => serializer.serialize_bool(false),
430        }
431    }
432}
433
434#[derive(Debug, Clone)]
435pub enum HoverMode {
436    X,
437    Y,
438    Closest,
439    False,
440    XUnified,
441    YUnified,
442}
443
444impl Serialize for HoverMode {
445    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
446    where
447        S: serde::Serializer,
448    {
449        match *self {
450            Self::X => serializer.serialize_str("x"),
451            Self::Y => serializer.serialize_str("y"),
452            Self::Closest => serializer.serialize_str("closest"),
453            Self::False => serializer.serialize_bool(false),
454            Self::XUnified => serializer.serialize_str("x unified"),
455            Self::YUnified => serializer.serialize_str("y unified"),
456        }
457    }
458}
459
460#[serde_with::skip_serializing_none]
461#[derive(Serialize, Debug, Clone, FieldSetter)]
462/// 3D scene layout
463pub struct LayoutScene {
464    #[serde(rename = "bgcolor")]
465    background_color: Option<Box<dyn Color>>,
466    camera: Option<Camera>,
467    #[serde(rename = "aspectmode")]
468    aspect_mode: Option<AspectMode>,
469    #[serde(rename = "aspectratio")]
470    aspect_ratio: Option<AspectRatio>,
471    #[serde(rename = "xaxis")]
472    x_axis: Option<Axis>,
473    #[serde(rename = "yaxis")]
474    y_axis: Option<Axis>,
475    #[serde(rename = "zaxis")]
476    z_axis: Option<Axis>,
477    #[serde(rename = "dragmode")]
478    drag_mode: Option<DragMode3D>,
479    #[serde(rename = "hovermode")]
480    hover_mode: Option<HoverMode>,
481    annotations: Option<Vec<Annotation>>,
482    // domain: Domain,
483    // uirevision: Uirevision,
484}
485
486impl LayoutScene {
487    pub fn new() -> Self {
488        Default::default()
489    }
490}
491
492#[cfg(test)]
493mod tests {
494    use serde_json::{json, to_value};
495
496    use super::*;
497    use crate::layout::Layout;
498
499    #[test]
500    fn serialize_hover_mode() {
501        assert_eq!(to_value(HoverMode::X).unwrap(), json!("x"));
502        assert_eq!(to_value(HoverMode::Y).unwrap(), json!("y"));
503        assert_eq!(to_value(HoverMode::Closest).unwrap(), json!("closest"));
504        assert_eq!(to_value(HoverMode::False).unwrap(), json!(false));
505        assert_eq!(to_value(HoverMode::XUnified).unwrap(), json!("x unified"));
506        assert_eq!(to_value(HoverMode::YUnified).unwrap(), json!("y unified"));
507    }
508
509    #[test]
510    #[rustfmt::skip]
511    fn serialize_drag_mode() {
512        assert_eq!(to_value(DragMode::Zoom).unwrap(), json!("zoom"));
513        assert_eq!(to_value(DragMode::Pan).unwrap(), json!("pan"));
514        assert_eq!(to_value(DragMode::Select).unwrap(), json!("select"));
515        assert_eq!(to_value(DragMode::Lasso).unwrap(), json!("lasso"));
516        assert_eq!(to_value(DragMode::DrawClosedPath).unwrap(), json!("drawclosedpath"));
517        assert_eq!(to_value(DragMode::DrawOpenPath).unwrap(), json!("drawopenpath"));
518        assert_eq!(to_value(DragMode::DrawLine).unwrap(), json!("drawline"));
519        assert_eq!(to_value(DragMode::DrawRect).unwrap(), json!("drawrect"));
520        assert_eq!(to_value(DragMode::DrawCircle).unwrap(), json!("drawcircle"));
521        assert_eq!(to_value(DragMode::Orbit).unwrap(), json!("orbit"));
522        assert_eq!(to_value(DragMode::Turntable).unwrap(), json!("turntable"));
523        assert_eq!(to_value(DragMode::False).unwrap(), json!(false));
524    }
525
526    #[test]
527    fn serialize_aspect_ratio() {
528        let aspect_ratio = AspectRatio::new();
529
530        let expected = json!({
531            "x": 1.0,
532            "y": 1.0,
533            "z": 1.0,
534        });
535
536        assert_eq!(to_value(aspect_ratio).unwrap(), expected);
537
538        let aspect_ratio = AspectRatio::new().x(1f64).y(2f64).z(3f64);
539
540        let expected = json!({
541            "x": 1.0,
542            "y": 2.0,
543            "z": 3.0,
544        });
545
546        assert_eq!(to_value(aspect_ratio).unwrap(), expected);
547
548        let aspect_ratio: AspectRatio = (1f64, 2f64, 3f64).into();
549
550        assert_eq!(to_value(aspect_ratio).unwrap(), expected);
551    }
552
553    #[test]
554    fn serialize_layout_scene() {
555        let layout = Layout::new().scene(
556            LayoutScene::new()
557                .x_axis(Axis::new())
558                .y_axis(Axis::new())
559                .z_axis(Axis::new())
560                .camera(Camera::new())
561                .aspect_mode(AspectMode::Auto)
562                .hover_mode(HoverMode::Closest)
563                .drag_mode(DragMode3D::Turntable)
564                .background_color("#FFFFFF")
565                .annotations(vec![Annotation::new()]),
566        );
567
568        let expected = json!({
569            "scene": {
570                "xaxis": {},
571                "yaxis": {},
572                "zaxis": {},
573                "camera": {},
574                "aspectmode": "auto",
575                "hovermode": "closest",
576                "dragmode": "turntable",
577                "bgcolor": "#FFFFFF",
578                "annotations": [{}],
579            }
580        });
581
582        assert_eq!(to_value(layout).unwrap(), expected);
583    }
584
585    #[test]
586    fn serialize_eye() {
587        let eye = Eye::new();
588
589        assert_eq!(
590            to_value(eye).unwrap(),
591            json!({
592                "x": 1.25,
593                "y": 1.25,
594                "z": 1.25,
595            })
596        );
597
598        let eye = Eye::new().x(1f64).y(2f64).z(3f64);
599
600        let expected = json!({
601            "x": 1.0,
602            "y": 2.0,
603            "z": 3.0,
604        });
605
606        assert_eq!(to_value(eye).unwrap(), expected);
607
608        let eye: Eye = (1f64, 2f64, 3f64).into();
609
610        assert_eq!(to_value(eye).unwrap(), expected);
611    }
612
613    #[test]
614    fn serialize_projection() {
615        let projection = Projection::new().projection_type(ProjectionType::default());
616        assert_eq!(
617            to_value(projection).unwrap(),
618            json!({"type": "perspective"})
619        );
620
621        let projection: Projection = ProjectionType::Orthographic.into();
622        assert_eq!(
623            to_value(projection).unwrap(),
624            json!({ "type": "orthographic" })
625        );
626
627        let projections = vec![
628            (ProjectionType::Orthographic, "orthographic"),
629            (ProjectionType::Perspective, "perspective"),
630            (ProjectionType::Airy, "airy"),
631            (ProjectionType::Aitoff, "aitoff"),
632            (ProjectionType::Albers, "albers"),
633            (ProjectionType::AlbersUsa, "albers usa"),
634            (ProjectionType::August, "august"),
635            (ProjectionType::AzimuthalEqualArea, "azimuthal equal area"),
636            (
637                ProjectionType::AzimuthalEquidistant,
638                "azimuthal equidistant",
639            ),
640            (ProjectionType::Baker, "baker"),
641            (ProjectionType::Bertin1953, "bertin1953"),
642            (ProjectionType::Boggs, "boggs"),
643            (ProjectionType::Bonne, "bonne"),
644            (ProjectionType::Bottomley, "bottomley"),
645            (ProjectionType::Bromley, "bromley"),
646            (ProjectionType::Collignon, "collignon"),
647            (ProjectionType::ConicConformal, "conic conformal"),
648            (ProjectionType::ConicEqualArea, "conic equal area"),
649            (ProjectionType::ConicEquidistant, "conic equidistant"),
650            (ProjectionType::Craig, "craig"),
651            (ProjectionType::Craster, "craster"),
652            (
653                ProjectionType::CylindricalEqualArea,
654                "cylindrical equal area",
655            ),
656            (
657                ProjectionType::CylindricalStereographic,
658                "cylindrical stereographic",
659            ),
660            (ProjectionType::Eckert1, "eckert1"),
661            (ProjectionType::Eckert2, "eckert2"),
662            (ProjectionType::Eckert3, "eckert3"),
663            (ProjectionType::Eckert4, "eckert4"),
664            (ProjectionType::Eckert5, "eckert5"),
665            (ProjectionType::Eckert6, "eckert6"),
666            (ProjectionType::Eisenlohr, "eisenlohr"),
667            (ProjectionType::EqualEarth, "equal earth"),
668            (ProjectionType::Equirectangular, "equirectangular"),
669            (ProjectionType::Fahey, "fahey"),
670            (ProjectionType::Foucaut, "foucaut"),
671            (ProjectionType::FoucautSinusoidal, "foucaut sinusoidal"),
672            (ProjectionType::Ginzburg4, "ginzburg4"),
673            (ProjectionType::Ginzburg5, "ginzburg5"),
674            (ProjectionType::Ginzburg6, "ginzburg6"),
675            (ProjectionType::Ginzburg8, "ginzburg8"),
676            (ProjectionType::Ginzburg9, "ginzburg9"),
677            (ProjectionType::Gnomonic, "gnomonic"),
678            (ProjectionType::Gringorten, "gringorten"),
679            (
680                ProjectionType::GringortenQuincuncial,
681                "gringorten quincuncial",
682            ),
683            (ProjectionType::Guyou, "guyou"),
684            (ProjectionType::Hammer, "hammer"),
685            (ProjectionType::Hill, "hill"),
686            (ProjectionType::Homolosine, "homolosine"),
687            (ProjectionType::Hufnagel, "hufnagel"),
688            (ProjectionType::Hyperelliptical, "hyperelliptical"),
689            (ProjectionType::Kavrayskiy7, "kavrayskiy7"),
690            (ProjectionType::Lagrange, "lagrange"),
691            (ProjectionType::Larrivee, "larrivee"),
692            (ProjectionType::Laskowski, "laskowski"),
693            (ProjectionType::Loximuthal, "loximuthal"),
694            (ProjectionType::Mercator, "mercator"),
695            (ProjectionType::Miller, "miller"),
696            (ProjectionType::Mollweide, "mollweide"),
697            (
698                ProjectionType::MtFlatPolarParabolic,
699                "mt flat polar parabolic",
700            ),
701            (ProjectionType::MtFlatPolarQuartic, "mt flat polar quartic"),
702            (
703                ProjectionType::MtFlatPolarSinusoidal,
704                "mt flat polar sinusoidal",
705            ),
706            (ProjectionType::NaturalEarth, "natural earth"),
707            (ProjectionType::NaturalEarth1, "natural earth1"),
708            (ProjectionType::NaturalEarth2, "natural earth2"),
709            (ProjectionType::NellHammer, "nell hammer"),
710            (ProjectionType::Nicolosi, "nicolosi"),
711            (ProjectionType::Patterson, "patterson"),
712            (ProjectionType::PeirceQuincuncial, "peirce quincuncial"),
713            (ProjectionType::Polyconic, "polyconic"),
714            (
715                ProjectionType::RectangularPolyconic,
716                "rectangular polyconic",
717            ),
718            (ProjectionType::Robinson, "robinson"),
719            (ProjectionType::Satellite, "satellite"),
720            (ProjectionType::SinuMollweide, "sinu mollweide"),
721            (ProjectionType::Sinusoidal, "sinusoidal"),
722            (ProjectionType::Stereographic, "stereographic"),
723            (ProjectionType::Times, "times"),
724            (ProjectionType::TransverseMercator, "transverse mercator"),
725            (ProjectionType::VanDerGrinten, "van der grinten"),
726            (ProjectionType::VanDerGrinten2, "van der grinten2"),
727            (ProjectionType::VanDerGrinten3, "van der grinten3"),
728            (ProjectionType::VanDerGrinten4, "van der grinten4"),
729            (ProjectionType::Wagner4, "wagner4"),
730            (ProjectionType::Wagner6, "wagner6"),
731            (ProjectionType::Wiechel, "wiechel"),
732            (ProjectionType::WinkelTripel, "winkel tripel"),
733            (ProjectionType::Winkel3, "winkel3"),
734        ];
735        for (variant, name) in projections {
736            let projection = Projection::new().projection_type(variant.clone());
737            assert_eq!(to_value(projection).unwrap(), json!({"type": name}));
738        }
739    }
740
741    #[test]
742    fn serialize_camera_center() {
743        let camera_center = CameraCenter::new();
744
745        let expected = json!({
746            "x": 0.0,
747            "y": 0.0,
748            "z": 0.0,
749        });
750
751        assert_eq!(to_value(camera_center).unwrap(), expected);
752
753        let camera_center = CameraCenter::new().x(1f64).y(2f64).z(3f64);
754
755        let expected = json!({
756            "x": 1.0,
757            "y": 2.0,
758            "z": 3.0,
759        });
760
761        assert_eq!(to_value(camera_center).unwrap(), expected);
762
763        let camera_center: CameraCenter = (1f64, 2f64, 3f64).into();
764
765        assert_eq!(to_value(camera_center).unwrap(), expected);
766    }
767
768    #[test]
769    fn serialize_up() {
770        let up = Up::new();
771
772        let expected = json!({
773            "x": 0.0,
774            "y": 0.0,
775            "z": 1.0,
776        });
777
778        assert_eq!(to_value(up).unwrap(), expected);
779
780        let up = Up::new().x(1f64).y(2f64).z(3f64);
781
782        let expected = json!({
783            "x": 1.0,
784            "y": 2.0,
785            "z": 3.0,
786        });
787
788        assert_eq!(to_value(up).unwrap(), expected);
789
790        let up: Up = (1f64, 2f64, 3f64).into();
791
792        assert_eq!(to_value(up).unwrap(), expected);
793    }
794
795    #[test]
796    fn serialize_projection_with_rotation() {
797        let projection = Projection {
798            projection_type: Some(ProjectionType::Mercator),
799            rotation: Some(Rotation {
800                lat: Some(10.0),
801                lon: Some(20.0),
802                roll: Some(30.0),
803            }),
804        };
805        let expected = json!({
806            "type": "mercator",
807            "rotation": {"lat": 10.0, "lon": 20.0, "roll": 30.0}
808        });
809        assert_eq!(to_value(projection).unwrap(), expected);
810    }
811}