1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
use crate::common::{Crs, Links};
use crate::{BoundingBox2D, OrderedAxes, Point2D, TitleDescriptionKeywords};
use serde::{Deserialize, Serialize};
use serde_with::DisplayFromStr;
use std::num::{NonZeroU16, NonZeroU64};
use std::path::PathBuf;

// #[derive(Serialize)]
// #[serde(rename_all = "camelCase")]
// pub struct TileMatrixSets {
//     pub tile_matrix_sets: Vec<TileMatrixSetItem>,
// }

/// A minimal tile matrix set element for use within a list of tile matrix
/// sets linking to a full definition.
#[serde_with::serde_as]
#[serde_with::skip_serializing_none]
#[derive(Serialize, Default)]
#[serde(rename_all = "camelCase")]
pub struct TileMatrixSetItem {
    /// Optional local tile matrix set identifier, e.g. for use as unspecified
    /// `{tileMatrixSetId}` parameter. Implementation of 'identifier'
    pub id: Option<String>,
    /// Title of this tile matrix set, normally used for display to a human
    pub title: Option<String>,
    /// Reference to an official source for this tileMatrixSet
    pub uri: Option<String>,
    #[serde(default)]
    #[serde_as(as = "Option<DisplayFromStr>")]
    pub crs: Option<Crs>,
    /// Links to related resources. A 'self' link to the tile matrix set definition is required.
    pub links: Links,
}

/// A definition of a tile matrix set following the Tile Matrix Set standard.
/// For tileset metadata, such a description (in `tileMatrixSet` property) is
/// only required for offline use, as an alternative to a link with a
/// `http://www.opengis.net/def/rel/ogc/1.0/tiling-scheme` relation type.
#[serde_with::serde_as]
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TileMatrixSet {
    #[serde(flatten)]
    pub title_description_keywords: TitleDescriptionKeywords,
    /// Tile matrix set identifier. Implementation of 'identifier'
    pub id: String,
    /// Reference to an official source for this TileMatrixSet
    pub uri: Option<String>,
    /// Coordinate Reference System (CRS)
    #[serde_as(as = "DisplayFromStr")]
    pub crs: Crs,
    pub ordered_axes: Option<Vec<String>>,
    /// Reference to a well-known scale set
    pub well_known_scale_set: Option<String>,
    /// Minimum bounding rectangle surrounding the tile matrix set, in the
    /// supported CRS
    pub bounding_box: Option<BoundingBox2D>,
    /// Describes scale levels and its tile matrices
    pub tile_matrices: Vec<TileMatrix>,
}

#[serde_with::serde_as]
#[serde_with::skip_serializing_none]
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct TileMatrix {
    #[serde(flatten)]
    pub title_description_keywords: TitleDescriptionKeywords,
    /// Identifier selecting one of the scales defined in the [TileMatrixSet]
    /// and representing the scaleDenominator the tile. Implementation of 'identifier'
    pub id: String,
    /// Scale denominator of this tile matrix
    pub scale_denominator: f64,
    /// Cell size of this tile matrix
    pub cell_size: f64,
    /// description": "The corner of the tile matrix (_topLeft_ or
    /// _bottomLeft_) used as the origin for numbering tile rows and columns.
    /// This corner is also a corner of the (0, 0) tile.
    pub corner_of_origin: Option<CornerOfOrigin>,
    /// Precise position in CRS coordinates of the corner of origin (e.g. the
    /// top-left corner) for this tile matrix. This position is also a corner
    /// of the (0, 0) tile. In previous version, this was 'topLeftCorner' and
    /// 'cornerOfOrigin' did not exist.
    pub point_of_origin: Point2D,
    /// Width of each tile of this tile matrix in pixels
    pub tile_width: NonZeroU16,
    /// Height of each tile of this tile matrix in pixels
    pub tile_height: NonZeroU16,
    /// Width of the matrix (number of tiles in width)
    pub matrix_width: NonZeroU64,
    /// Height of the matrix (number of tiles in height)
    pub matrix_height: NonZeroU64,
    /// Describes the rows that has variable matrix width
    pub variable_matrix_widths: Option<Vec<VariableMatrixWidth>>,
}

/// Variable Matrix Width data structure
#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub struct VariableMatrixWidth {
    /// Number of tiles in width that coalesce in a single tile for these rows
    pub coalesc: NonZeroU64,
    /// First tile row where the coalescence factor applies for this tilematrix
    pub min_tile_row: u64,
    /// Last tile row where the coalescence factor applies for this tilematrix
    pub smax_tile_row: u64,
}

#[derive(Serialize, Deserialize, Debug, Clone)]
#[serde(rename_all = "camelCase")]
pub enum CornerOfOrigin {
    TopLeft,
    BottomLeft,
}

impl Default for CornerOfOrigin {
    fn default() -> Self {
        CornerOfOrigin::TopLeft
    }
}

#[derive(thiserror::Error, Debug)]
pub enum TileMatrixSetError {
    #[error(transparent)]
    JsonError(#[from] serde_json::Error),
    #[error("{0}: {1}")]
    FileError(PathBuf, #[source] std::io::Error),
}

impl TileMatrixSet {
    pub fn from_json_file(json_path: &str) -> Result<Self, TileMatrixSetError> {
        let content = std::fs::read_to_string(json_path)
            .map_err(|e| TileMatrixSetError::FileError(json_path.into(), e))?;
        TileMatrixSet::from_json(&content)
    }
    pub fn from_json(json: &str) -> Result<Self, TileMatrixSetError> {
        serde_json::from_str(&json).map_err(Into::into)
    }
    /// Check if CRS has inverted AXIS (lat,lon) instead of (lon,lat).
    pub(crate) fn crs_axis_inverted(&self) -> bool {
        if let Some(axes) = &self.ordered_axes {
            ordered_axes_inverted(&axes)
        } else {
            false // TODO: Check CRS axis ordering
        }
    }
}

pub(crate) fn ordered_axes_inverted(axes: &OrderedAxes) -> bool {
    first_axes_inverted(&axes[0].to_uppercase())
}

fn first_axes_inverted(first: &str) -> bool {
    first == "Y" || first == "LAT" || first == "N"
}

#[cfg(test)]
mod test {
    use super::TileMatrixSet;

    #[test]
    fn parse_tms_example() {
        let tms = TileMatrixSet::from_json_file("./data/WebMercatorQuad.json").unwrap();
        println!("{}", serde_json::to_string_pretty(&tms).unwrap());
    }
}