1use std::{collections::HashMap, sync::OnceLock};
4
5use geo::{Contains, Coord};
6use rtz_core::{
7 base::types::Float,
8 geo::{
9 shared::{ConcreteVec, EncodableIds, HasGeometry, RoundLngLat},
10 tz::ned::NedTimezone,
11 },
12};
13
14use crate::{
15 geo::shared::{HasItemData, HasLookupData},
16 CanPerformGeoLookup,
17};
18
19#[cfg(feature = "self-contained")]
20use include_bytes_aligned::include_bytes_aligned;
21
22impl HasItemData for NedTimezone {
25 fn get_mem_items() -> &'static ConcreteVec<NedTimezone> {
26 static TIMEZONES: OnceLock<ConcreteVec<NedTimezone>> = OnceLock::new();
27
28 #[cfg(feature = "self-contained")]
29 {
30 TIMEZONES.get_or_init(|| crate::geo::shared::decode_binary_data(TZ_BINCODE))
31 }
32
33 #[cfg(not(feature = "self-contained"))]
34 {
35 use rtz_core::geo::{shared::get_items_from_features, tz::ned::get_geojson_features_from_source};
36
37 TIMEZONES.get_or_init(|| {
38 let features = get_geojson_features_from_source();
39 get_items_from_features(features)
40 })
41 }
42 }
43}
44
45impl HasLookupData for NedTimezone {
46 type Lookup = EncodableIds;
47
48 fn get_mem_lookup() -> &'static HashMap<RoundLngLat, Self::Lookup> {
49 static CACHE: OnceLock<HashMap<RoundLngLat, EncodableIds>> = OnceLock::new();
50
51 #[cfg(feature = "self-contained")]
52 {
53 CACHE.get_or_init(|| crate::geo::shared::decode_binary_data(LOOKUP_BINCODE))
54 }
55
56 #[cfg(not(feature = "self-contained"))]
57 {
58 use rtz_core::geo::shared::get_lookup_from_geometries;
59
60 CACHE.get_or_init(|| {
61 let cache = get_lookup_from_geometries(NedTimezone::get_mem_items());
62
63 cache
64 })
65 }
66 }
67}
68
69impl CanPerformGeoLookup for NedTimezone {
72 fn lookup(xf: Float, yf: Float) -> Vec<&'static Self> {
73 let x = xf.floor() as i16;
74 let y = yf.floor() as i16;
75
76 let Some(suggestions) = Self::get_lookup_suggestions(x, y) else {
77 return Vec::new();
78 };
79
80 if suggestions.len() == 1 && xf > -179. && xf < 179. && yf > -89. && yf < 89. {
84 return suggestions;
85 }
86
87 suggestions.into_iter().filter(|&i| i.geometry().contains(&Coord { x: xf, y: yf })).collect()
88 }
89}
90
91#[cfg(all(host_family_unix, feature = "self-contained"))]
94static TZ_BINCODE: &[u8] = include_bytes_aligned!(8, "../../../../assets/ned_time_zones.bincode");
95#[cfg(all(host_family_windows, feature = "self-contained"))]
96static TZ_BINCODE: &[u8] = include_bytes_aligned!(8, "..\\..\\..\\..\\assets\\ned_time_zones.bincode");
97
98#[cfg(all(host_family_unix, feature = "self-contained"))]
99static LOOKUP_BINCODE: &[u8] = include_bytes_aligned!(8, "../../../../assets/ned_time_zone_lookup.bincode");
100#[cfg(all(host_family_windows, feature = "self-contained"))]
101static LOOKUP_BINCODE: &[u8] = include_bytes_aligned!(8, "..\\..\\..\\..\\assets\\ned_time_zone_lookup.bincode");
102
103#[cfg(test)]
106mod tests {
107 use super::*;
108 use crate::geo::shared::{CanPerformGeoLookup, MapIntoItems};
109 use pretty_assertions::assert_eq;
110 use rayon::prelude::{IntoParallelIterator, ParallelIterator};
111 use rtz_core::base::types::Float;
112
113 #[test]
114 fn can_get_timezones() {
115 let timezones = NedTimezone::get_mem_items();
116 assert_eq!(timezones.len(), 120);
117 }
118
119 #[test]
120 fn can_get_lookup() {
121 let cache = NedTimezone::get_mem_lookup();
122 assert_eq!(cache.len(), 64_800);
123 }
124
125 #[test]
126 fn can_get_from_lookup() {
127 let cache = NedTimezone::get_lookup_suggestions(-121, 46);
128 assert_eq!(cache.unwrap().len(), 1);
129 }
130
131 #[test]
132 fn can_perform_exact_lookup() {
133 assert_eq!(NedTimezone::lookup_slow(-177.0, -15.0).len(), 0);
134 assert_eq!(NedTimezone::lookup_slow(-121.0, 46.0)[0].identifier.as_ref().unwrap(), "America/Los_Angeles");
135
136 assert_eq!(NedTimezone::lookup_slow(179.9968, -67.0959).len(), 0);
137 }
138
139 #[test]
140 fn can_access_lookup() {
141 let cache = NedTimezone::get_mem_lookup();
142
143 let tzs = cache.get(&(-177, -15)).map_into_items().unwrap() as Vec<&NedTimezone>;
144 assert_eq!(tzs.len(), 2);
145
146 let tzs = cache.get(&(-121, 46)).map_into_items().unwrap() as Vec<&NedTimezone>;
147 assert_eq!(tzs.len(), 1);
148
149 let tz = cache.get(&(-121, 46)).map_into_items().unwrap()[0] as &NedTimezone;
150 assert_eq!(tz.identifier.as_ref().unwrap(), "America/Los_Angeles");
151
152 let tzs = cache.get(&(-68, -67)).map_into_items().unwrap() as Vec<&NedTimezone>;
153 assert_eq!(tzs.len(), 5);
154 }
155
156 #[test]
157 fn can_verify_lookup_assisted_accuracy() {
158 (0..1_000).into_par_iter().for_each(|_| {
159 let x = rand::random::<Float>() * 360.0 - 180.0;
160 let y = rand::random::<Float>() * 180.0 - 90.0;
161 let full = NedTimezone::lookup_slow(x, y);
162 let lookup_assisted = NedTimezone::lookup(x, y);
163
164 assert_eq!(
165 full.into_iter().map(|t| t.id).collect::<Vec<_>>(),
166 lookup_assisted.into_iter().map(|t| t.id).collect::<Vec<_>>(),
167 "({}, {})",
168 x,
169 y
170 );
171 });
172 }
173}