1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub struct MapPosition {
7 pub lat: f64,
8 pub lng: f64,
9 pub zoom: f64,
10}
11
12impl MapPosition {
13 pub fn new(lat: f64, lng: f64, zoom: f64) -> Self {
15 Self { lat, lng, zoom }
16 }
17}
18
19impl Default for MapPosition {
20 fn default() -> Self {
21 Self::new(51.505, -0.09, 13.0)
22 }
23}
24
25#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct MapMarker {
28 pub lat: f64,
29 pub lng: f64,
30 pub title: String,
31 pub description: Option<String>,
32 pub icon: Option<MarkerIcon>,
33 pub popup_options: Option<PopupOptions>,
34 pub custom_data: Option<HashMap<String, String>>,
35}
36
37impl MapMarker {
38 pub fn new(lat: f64, lng: f64, title: impl Into<String>) -> Self {
40 Self {
41 lat,
42 lng,
43 title: title.into(),
44 description: None,
45 icon: None,
46 popup_options: None,
47 custom_data: None,
48 }
49 }
50
51 pub fn with_description(mut self, description: impl Into<String>) -> Self {
53 self.description = Some(description.into());
54 self
55 }
56
57 pub fn with_icon(mut self, icon: MarkerIcon) -> Self {
59 self.icon = Some(icon);
60 self
61 }
62
63 pub fn with_popup_options(mut self, options: PopupOptions) -> Self {
65 self.popup_options = Some(options);
66 self
67 }
68
69 pub fn with_custom_data(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
71 if self.custom_data.is_none() {
72 self.custom_data = Some(HashMap::new());
73 }
74 if let Some(ref mut data) = self.custom_data {
75 data.insert(key.into(), value.into());
76 }
77 self
78 }
79}
80
81impl Default for MapMarker {
82 fn default() -> Self {
83 Self {
84 lat: 0.0,
85 lng: 0.0,
86 title: String::new(),
87 description: None,
88 icon: None,
89 popup_options: None,
90 custom_data: None,
91 }
92 }
93}
94
95#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct MarkerIcon {
98 pub icon_url: String,
99 pub icon_size: Option<(u32, u32)>,
100 pub icon_anchor: Option<(u32, u32)>,
101 pub popup_anchor: Option<(i32, i32)>,
102 pub shadow_url: Option<String>,
103 pub shadow_size: Option<(u32, u32)>,
104}
105
106impl MarkerIcon {
107 pub fn new(icon_url: impl Into<String>) -> Self {
109 Self {
110 icon_url: icon_url.into(),
111 icon_size: None,
112 icon_anchor: None,
113 popup_anchor: None,
114 shadow_url: None,
115 shadow_size: None,
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
122pub struct PopupOptions {
123 pub max_width: Option<u32>,
124 pub min_width: Option<u32>,
125 pub max_height: Option<u32>,
126 pub auto_pan: Option<bool>,
127 pub keep_in_view: Option<bool>,
128 pub close_button: Option<bool>,
129 pub auto_close: Option<bool>,
130 pub close_on_escape_key: Option<bool>,
131 pub class_name: Option<String>,
132}
133
134impl Default for PopupOptions {
135 fn default() -> Self {
136 Self {
137 max_width: Some(300),
138 min_width: Some(50),
139 max_height: None,
140 auto_pan: Some(true),
141 keep_in_view: Some(false),
142 close_button: Some(true),
143 auto_close: Some(true),
144 close_on_escape_key: Some(true),
145 class_name: None,
146 }
147 }
148}
149
150#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
152pub struct MapOptions {
153 pub zoom_control: Option<bool>,
154 pub scroll_wheel_zoom: Option<bool>,
155 pub double_click_zoom: Option<bool>,
156 pub touch_zoom: Option<bool>,
157 pub dragging: Option<bool>,
158 pub keyboard: Option<bool>,
159 pub attribution_control: Option<bool>,
160 pub tile_layer: Option<TileLayer>,
161}
162
163impl Default for MapOptions {
164 fn default() -> Self {
165 Self {
166 zoom_control: Some(true),
167 scroll_wheel_zoom: Some(true),
168 double_click_zoom: Some(true),
169 touch_zoom: Some(true),
170 dragging: Some(true),
171 keyboard: Some(true),
172 attribution_control: Some(true),
173 tile_layer: Some(TileLayer::default()),
174 }
175 }
176}
177
178impl MapOptions {
179 pub fn new() -> Self {
181 Self {
182 zoom_control: None,
183 scroll_wheel_zoom: None,
184 double_click_zoom: None,
185 touch_zoom: None,
186 dragging: None,
187 keyboard: None,
188 attribution_control: None,
189 tile_layer: None,
190 }
191 }
192
193 pub fn zoom_control(&self) -> bool {
195 self.zoom_control.unwrap_or(true)
196 }
197
198 pub fn scroll_wheel_zoom(&self) -> bool {
200 self.scroll_wheel_zoom.unwrap_or(true)
201 }
202
203 pub fn double_click_zoom(&self) -> bool {
205 self.double_click_zoom.unwrap_or(true)
206 }
207
208 pub fn touch_zoom(&self) -> bool {
210 self.touch_zoom.unwrap_or(true)
211 }
212
213 pub fn dragging(&self) -> bool {
215 self.dragging.unwrap_or(true)
216 }
217
218 pub fn keyboard(&self) -> bool {
220 self.keyboard.unwrap_or(true)
221 }
222
223 pub fn attribution_control(&self) -> bool {
225 self.attribution_control.unwrap_or(true)
226 }
227
228 pub fn tile_layer(&self) -> TileLayer {
230 self.tile_layer.clone().unwrap_or_default()
231 }
232}
233
234#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236pub struct TileLayer {
237 pub url: String,
238 pub attribution: String,
239 pub max_zoom: u8,
240 pub subdomains: Vec<String>,
241}
242
243impl TileLayer {
244 pub fn openstreetmap() -> Self {
246 Self {
247 url: "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png".to_string(),
248 attribution: "© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors".to_string(),
249 max_zoom: 19,
250 subdomains: vec!["a".to_string(), "b".to_string(), "c".to_string()],
251 }
252 }
253
254 pub fn satellite() -> Self {
256 Self {
257 url: "https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}".to_string(),
258 attribution: "Tiles © Esri".to_string(),
259 max_zoom: 18,
260 subdomains: vec![],
261 }
262 }
263}
264
265impl Default for TileLayer {
266 fn default() -> Self {
267 Self::openstreetmap()
268 }
269}