1#![doc = include_str!("../README.md")]
2#![cfg_attr(docsrs, feature(doc_cfg))]
3
4use geometry_rs::{Point, Polygon, PolygonBuildOptions};
5#[cfg(feature = "export-geojson")]
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::f64::consts::PI;
9use std::vec;
10#[cfg(all(feature = "bundled", feature = "full"))]
11compile_error!(
12 "features `bundled` and `full` are mutually exclusive; \
13 add `default-features = false` when enabling `full`"
14);
15
16#[cfg(feature = "bundled")]
17use tzf_dist::{load_preindex, load_topology_compress_topo};
18#[cfg(feature = "full")]
19use tzf_dist_git::{load_compress_topo, load_preindex, load_topology_compress_topo};
20pub mod pbgen;
21
22struct Item {
23 polys: Vec<Polygon>,
24 name: String,
25}
26
27impl Item {
28 fn contains_point(&self, p: &Point) -> bool {
29 for poly in &self.polys {
30 if poly.contains_point(*p) {
31 return true;
32 }
33 }
34 false
35 }
36}
37
38pub struct Finder {
47 all: Vec<Item>,
48 data_version: String,
49 grid: Option<HashMap<(i16, i16), Vec<u32>>>,
53}
54
55const DEFAULT_RTREE_MIN_SEGMENTS: usize = 64;
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
62#[non_exhaustive]
63pub enum FinderOptions {
64 #[default]
66 NoIndex,
67 YStripes,
69}
70
71impl FinderOptions {
72 #[must_use]
74 pub fn no_index() -> Self {
75 Self::NoIndex
76 }
77
78 #[must_use]
80 pub fn y_stripes() -> Self {
81 Self::YStripes
82 }
83
84 fn to_polygon_build_options(self) -> PolygonBuildOptions {
85 match self {
86 Self::YStripes => PolygonBuildOptions {
87 enable_rtree: false,
88 enable_compressed_quad: false,
89 enable_y_stripes: true,
90 rtree_min_segments: DEFAULT_RTREE_MIN_SEGMENTS,
91 },
92 Self::NoIndex => PolygonBuildOptions {
93 enable_rtree: false,
94 enable_compressed_quad: false,
95 enable_y_stripes: false,
96 rtree_min_segments: DEFAULT_RTREE_MIN_SEGMENTS,
97 },
98 }
99 }
100}
101
102#[allow(clippy::cast_possible_truncation)]
106fn decode_polyline(encoded: &[u8]) -> Vec<Point> {
107 let mut points = Vec::new();
108 let mut index = 0;
109 let mut lng: i64 = 0;
110 let mut lat: i64 = 0;
111
112 while index < encoded.len() {
113 let (dlng, next) = polyline_decode_value(encoded, index);
114 index = next;
115 let (dlat, next) = polyline_decode_value(encoded, index);
116 index = next;
117 lng += dlng;
118 lat += dlat;
119 points.push(Point {
120 x: lng as f64 / 1e5,
121 y: lat as f64 / 1e5,
122 });
123 }
124 points
125}
126
127fn polyline_decode_value(encoded: &[u8], start: usize) -> (i64, usize) {
128 let mut result: i64 = 0;
129 let mut shift = 0;
130 let mut index = start;
131
132 loop {
133 let byte = (encoded[index] as i64) - 63;
134 index += 1;
135 result |= (byte & 0x1F) << shift;
136 shift += 5;
137 if byte < 0x20 {
138 break;
139 }
140 }
141
142 let value = if result & 1 != 0 {
143 !(result >> 1)
144 } else {
145 result >> 1
146 };
147 (value, index)
148}
149
150fn expand_compressed_ring(
151 segs: &[pbgen::CompressedRingSegment],
152 edges: &[Vec<Point>],
153) -> Vec<Point> {
154 let mut pts = Vec::new();
155 for seg in segs {
156 match &seg.content {
157 Some(pbgen::compressed_ring_segment::Content::Inline(inline)) => {
158 pts.extend(decode_polyline(&inline.points));
159 }
160 Some(pbgen::compressed_ring_segment::Content::EdgeForward(idx)) => {
161 pts.extend_from_slice(&edges[*idx as usize]);
162 }
163 Some(pbgen::compressed_ring_segment::Content::EdgeReversed(idx)) => {
164 pts.extend(edges[*idx as usize].iter().rev().copied());
165 }
166 None => {}
167 }
168 }
169 pts
170}
171
172impl Finder {
173 fn from_pb_with_polygon_options(tzs: pbgen::Timezones, options: PolygonBuildOptions) -> Self {
174 let mut f = Self {
175 all: vec![],
176 data_version: tzs.version,
177 grid: None,
178 };
179 for tz in &tzs.timezones {
180 let mut polys: Vec<Polygon> = vec![];
181
182 for pbpoly in &tz.polygons {
183 let mut exterior: Vec<Point> = vec![];
184 for pbpoint in &pbpoly.points {
185 exterior.push(Point {
186 x: f64::from(pbpoint.lng),
187 y: f64::from(pbpoint.lat),
188 });
189 }
190
191 let mut interior: Vec<Vec<Point>> = vec![];
192
193 for holepoly in &pbpoly.holes {
194 let mut holeextr: Vec<Point> = vec![];
195 for holepoint in &holepoly.points {
196 holeextr.push(Point {
197 x: f64::from(holepoint.lng),
198 y: f64::from(holepoint.lat),
199 });
200 }
201 interior.push(holeextr);
202 }
203
204 let geopoly = geometry_rs::Polygon::new(exterior, interior, Some(options));
205 polys.push(geopoly);
206 }
207
208 let item: Item = Item {
209 name: tz.name.to_string(),
210 polys,
211 };
212
213 f.all.push(item);
214 }
215 f
216 }
217
218 fn from_compressed_topo_with_polygon_options(
219 tzs: pbgen::CompressedTopoTimezones,
220 options: PolygonBuildOptions,
221 ) -> Self {
222 let mut edges: Vec<Vec<Point>> = vec![Vec::new(); tzs.shared_edges.len()];
223 for edge in &tzs.shared_edges {
224 edges[edge.id as usize] = decode_polyline(&edge.points);
225 }
226
227 let grid = tzs.grid_index.map(|gi| {
228 let mut m = HashMap::with_capacity(gi.cells.len());
229 for cell in gi.cells {
230 m.insert((cell.lng as i16, cell.lat as i16), cell.tz_indices);
231 }
232 m
233 });
234
235 let mut f = Self {
236 all: vec![],
237 data_version: tzs.version,
238 grid,
239 };
240
241 for tz in &tzs.timezones {
242 let mut polys: Vec<Polygon> = vec![];
243 for poly in &tz.polygons {
244 let exterior = expand_compressed_ring(&poly.exterior, &edges);
245 let interior: Vec<Vec<Point>> = poly
246 .holes
247 .iter()
248 .map(|hole| expand_compressed_ring(&hole.exterior, &edges))
249 .collect();
250 polys.push(geometry_rs::Polygon::new(exterior, interior, Some(options)));
251 }
252 f.all.push(Item {
253 name: tz.name.clone(),
254 polys,
255 });
256 }
257 f
258 }
259
260 #[must_use]
264 pub fn from_compressed_topo(tzs: pbgen::CompressedTopoTimezones) -> Self {
265 Self::from_compressed_topo_with_options(tzs, FinderOptions::default())
266 }
267
268 #[must_use]
270 pub fn from_compressed_topo_with_options(
271 tzs: pbgen::CompressedTopoTimezones,
272 options: FinderOptions,
273 ) -> Self {
274 Self::from_compressed_topo_with_polygon_options(tzs, options.to_polygon_build_options())
275 }
276
277 #[must_use]
288 pub fn from_pb(tzs: pbgen::Timezones) -> Self {
289 Self::from_pb_with_options(tzs, FinderOptions::default())
290 }
291
292 #[must_use]
294 pub fn from_pb_with_options(tzs: pbgen::Timezones, options: FinderOptions) -> Self {
295 Self::from_pb_with_polygon_options(tzs, options.to_polygon_build_options())
296 }
297
298 #[must_use]
307 pub fn get_tz_name(&self, lng: f64, lat: f64) -> &str {
308 if let Some(ref grid) = self.grid {
309 let key = (lng.floor() as i16, lat.floor() as i16);
310 let indices = match grid.get(&key) {
311 Some(v) => v,
312 None => return "",
313 };
314 if indices.len() == 1 && (-179.0..179.0).contains(&lng) && (-89.0..89.0).contains(&lat)
317 {
318 return &self.all[indices[0] as usize].name;
319 }
320 let p = geometry_rs::Point { x: lng, y: lat };
321 for &idx in indices {
322 if self.all[idx as usize].contains_point(&p) {
323 return &self.all[idx as usize].name;
324 }
325 }
326 return "";
327 }
328 let p = geometry_rs::Point { x: lng, y: lat };
329 for item in &self.all {
330 if item.contains_point(&p) {
331 return &item.name;
332 }
333 }
334 ""
335 }
336
337 #[must_use]
343 pub fn get_tz_names(&self, lng: f64, lat: f64) -> Vec<&str> {
344 let mut ret: Vec<&str> = vec![];
345 if let Some(ref grid) = self.grid {
346 let key = (lng.floor() as i16, lat.floor() as i16);
347 if let Some(indices) = grid.get(&key) {
348 let p = geometry_rs::Point { x: lng, y: lat };
349 for &idx in indices {
350 if self.all[idx as usize].contains_point(&p) {
351 ret.push(&self.all[idx as usize].name);
352 }
353 }
354 }
355 return ret;
356 }
357 let p = geometry_rs::Point { x: lng, y: lat };
358 for item in &self.all {
359 if item.contains_point(&p) {
360 ret.push(&item.name);
361 }
362 }
363 ret
364 }
365
366 #[must_use]
375 pub fn timezonenames(&self) -> Vec<&str> {
376 let mut ret: Vec<&str> = vec![];
377 for item in &self.all {
378 ret.push(&item.name);
379 }
380 ret
381 }
382
383 #[must_use]
392 pub fn data_version(&self) -> &str {
393 &self.data_version
394 }
395
396 #[must_use]
406 pub fn new() -> Self {
407 Self::default()
408 }
409
410 #[cfg(feature = "export-geojson")]
412 fn item_to_feature(&self, item: &Item) -> FeatureItem {
413 let mut pbpolys = Vec::new();
415 for poly in &item.polys {
416 let mut pbpoly = pbgen::Polygon {
417 points: Vec::new(),
418 holes: Vec::new(),
419 };
420
421 for point in poly.exterior() {
423 pbpoly.points.push(pbgen::Point {
424 lng: point.x as f32,
425 lat: point.y as f32,
426 });
427 }
428
429 for hole in poly.holes() {
431 let mut hole_poly = pbgen::Polygon {
432 points: Vec::new(),
433 holes: Vec::new(),
434 };
435 for point in hole {
436 hole_poly.points.push(pbgen::Point {
437 lng: point.x as f32,
438 lat: point.y as f32,
439 });
440 }
441 pbpoly.holes.push(hole_poly);
442 }
443
444 pbpolys.push(pbpoly);
445 }
446
447 let pbtz = pbgen::Timezone {
448 polygons: pbpolys,
449 name: item.name.clone(),
450 };
451
452 revert_item(&pbtz)
453 }
454
455 #[must_use]
469 #[cfg(feature = "export-geojson")]
470 pub fn to_geojson(&self) -> BoundaryFile {
471 let mut output = BoundaryFile {
472 collection_type: "FeatureCollection".to_string(),
473 features: Vec::new(),
474 };
475
476 for item in &self.all {
477 output.features.push(self.item_to_feature(item));
478 }
479
480 output
481 }
482
483 #[must_use]
508 #[cfg(feature = "export-geojson")]
509 pub fn get_tz_geojson(&self, timezone_name: &str) -> Option<BoundaryFile> {
510 let mut output = BoundaryFile {
511 collection_type: "FeatureCollection".to_string(),
512 features: Vec::new(),
513 };
514 for item in &self.all {
515 if item.name == timezone_name {
516 output.features.push(self.item_to_feature(item));
517 }
518 }
519
520 if output.features.is_empty() {
521 None
522 } else {
523 Some(output)
524 }
525 }
526}
527
528impl Default for Finder {
538 fn default() -> Self {
539 let file_bytes = load_topology_compress_topo();
540 Self::from_compressed_topo(
541 pbgen::CompressedTopoTimezones::try_from(file_bytes).unwrap_or_default(),
542 )
543 }
544}
545
546#[must_use]
559#[allow(
560 clippy::cast_precision_loss,
561 clippy::cast_possible_truncation,
562 clippy::similar_names
563)]
564pub fn deg2num(lng: f64, lat: f64, zoom: i64) -> (i64, i64) {
565 let n = (1i64 << zoom) as f64;
566 let lat_rad = lat.to_radians();
567 let xtile = (lng / 360.0 + 0.5) * n;
568 let ytile = (1.0 - lat_rad.tan().asinh() / PI) / 2.0 * n;
569
570 (xtile as i64, ytile as i64)
572}
573
574#[cfg(feature = "export-geojson")]
576pub type PolygonCoordinates = Vec<Vec<[f64; 2]>>;
577#[cfg(feature = "export-geojson")]
578pub type MultiPolygonCoordinates = Vec<PolygonCoordinates>;
579
580#[cfg(feature = "export-geojson")]
581#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct GeometryDefine {
583 #[serde(rename = "type")]
584 pub geometry_type: String,
585 pub coordinates: MultiPolygonCoordinates,
586}
587
588#[cfg(feature = "export-geojson")]
589#[derive(Debug, Clone, Serialize, Deserialize)]
590pub struct PropertiesDefine {
591 pub tzid: String,
592}
593
594#[cfg(feature = "export-geojson")]
595#[derive(Debug, Clone, Serialize, Deserialize)]
596pub struct FeatureItem {
597 #[serde(rename = "type")]
598 pub feature_type: String,
599 pub properties: PropertiesDefine,
600 pub geometry: GeometryDefine,
601}
602
603#[cfg(feature = "export-geojson")]
604impl FeatureItem {
605 pub fn to_string(&self) -> String {
606 serde_json::to_string(self).unwrap_or_default()
607 }
608
609 pub fn to_string_pretty(&self) -> String {
610 serde_json::to_string_pretty(self).unwrap_or_default()
611 }
612}
613
614#[cfg(feature = "export-geojson")]
615#[derive(Debug, Clone, Serialize, Deserialize)]
616pub struct BoundaryFile {
617 #[serde(rename = "type")]
618 pub collection_type: String,
619 pub features: Vec<FeatureItem>,
620}
621
622#[cfg(feature = "export-geojson")]
623impl BoundaryFile {
624 pub fn to_string(&self) -> String {
625 serde_json::to_string(self).unwrap_or_default()
626 }
627
628 pub fn to_string_pretty(&self) -> String {
629 serde_json::to_string_pretty(self).unwrap_or_default()
630 }
631}
632
633#[cfg(feature = "export-geojson")]
635fn from_pb_polygon_to_geo_multipolygon(pbpoly: &[pbgen::Polygon]) -> MultiPolygonCoordinates {
636 let mut res = MultiPolygonCoordinates::new();
637 for poly in pbpoly {
638 let mut new_geo_poly = PolygonCoordinates::new();
639
640 let mut mainpoly = Vec::new();
642 for point in &poly.points {
643 mainpoly.push([f64::from(point.lng), f64::from(point.lat)]);
644 }
645 new_geo_poly.push(mainpoly);
646
647 for holepoly in &poly.holes {
649 let mut holepoly_coords = Vec::new();
650 for point in &holepoly.points {
651 holepoly_coords.push([f64::from(point.lng), f64::from(point.lat)]);
652 }
653 new_geo_poly.push(holepoly_coords);
654 }
655 res.push(new_geo_poly);
656 }
657 res
658}
659
660#[cfg(feature = "export-geojson")]
662fn revert_item(input: &pbgen::Timezone) -> FeatureItem {
663 FeatureItem {
664 feature_type: "Feature".to_string(),
665 properties: PropertiesDefine {
666 tzid: input.name.clone(),
667 },
668 geometry: GeometryDefine {
669 geometry_type: "MultiPolygon".to_string(),
670 coordinates: from_pb_polygon_to_geo_multipolygon(&input.polygons),
671 },
672 }
673}
674
675#[cfg(feature = "export-geojson")]
677pub fn revert_timezones(input: &pbgen::Timezones) -> BoundaryFile {
678 let mut output = BoundaryFile {
679 collection_type: "FeatureCollection".to_string(),
680 features: Vec::new(),
681 };
682 for timezone in &input.timezones {
683 let item = revert_item(timezone);
684 output.features.push(item);
685 }
686 output
687}
688
689pub struct FuzzyFinder {
700 min_zoom: i64,
701 max_zoom: i64,
702 all: HashMap<(i64, i64, i64), Vec<String>>, data_version: String,
704}
705
706impl Default for FuzzyFinder {
707 fn default() -> Self {
715 let file_bytes = load_preindex();
716 Self::from_pb(pbgen::PreindexTimezones::try_from(file_bytes.to_vec()).unwrap_or_default())
717 }
718}
719
720impl FuzzyFinder {
721 #[must_use]
722 pub fn from_pb(tzs: pbgen::PreindexTimezones) -> Self {
723 let mut f = Self {
724 min_zoom: i64::from(tzs.agg_zoom),
725 max_zoom: i64::from(tzs.idx_zoom),
726 all: HashMap::new(),
727 data_version: tzs.version,
728 };
729 for item in &tzs.keys {
730 let key = (i64::from(item.x), i64::from(item.y), i64::from(item.z));
731 let names = f.all.entry(key).or_default();
732 names.push(item.name.to_string());
733 names.sort();
734 }
735 f
736 }
737
738 #[must_use]
759 pub fn get_tz_name(&self, lng: f64, lat: f64) -> &str {
760 if self.max_zoom <= self.min_zoom {
761 return "";
762 }
763 let top_zoom = self.max_zoom - 1;
765 let (high_x, high_y) = deg2num(lng, lat, top_zoom);
766 for zoom in self.min_zoom..self.max_zoom {
767 let shift = (top_zoom - zoom) as u32;
768 if let Some(names) = self.all.get(&(high_x >> shift, high_y >> shift, zoom)) {
769 if let Some(name) = names.first() {
770 return name;
771 }
772 }
773 }
774 ""
775 }
776
777 pub fn get_tz_names(&self, lng: f64, lat: f64) -> Vec<&str> {
778 let mut names: Vec<&str> = vec![];
779 if self.max_zoom <= self.min_zoom {
780 return names;
781 }
782 let top_zoom = self.max_zoom - 1;
783 let (high_x, high_y) = deg2num(lng, lat, top_zoom);
784 for zoom in self.min_zoom..self.max_zoom {
785 let shift = (top_zoom - zoom) as u32;
786 if let Some(entries) = self.all.get(&(high_x >> shift, high_y >> shift, zoom)) {
787 for item in entries {
788 names.push(item);
789 }
790 }
791 }
792 names
793 }
794
795 #[must_use]
810 pub fn data_version(&self) -> &str {
811 &self.data_version
812 }
813
814 #[must_use]
822 pub fn new() -> Self {
823 Self::default()
824 }
825
826 #[must_use]
843 #[cfg(feature = "export-geojson")]
844 pub fn to_geojson(&self) -> BoundaryFile {
845 let mut name_to_keys: HashMap<&String, Vec<(i64, i64, i64)>> = HashMap::new();
846
847 for (key, names) in &self.all {
849 for name in names {
850 name_to_keys.entry(name).or_insert_with(Vec::new).push(*key);
851 }
852 }
853
854 let mut features = Vec::new();
855
856 for (name, keys) in name_to_keys {
857 let mut multi_polygon_coords = MultiPolygonCoordinates::new();
858
859 for (x, y, z) in keys {
860 let tile_poly = tile_to_polygon(x, y, z);
862 multi_polygon_coords.push(vec![tile_poly]);
863 }
864
865 let feature = FeatureItem {
866 feature_type: "Feature".to_string(),
867 properties: PropertiesDefine { tzid: name.clone() },
868 geometry: GeometryDefine {
869 geometry_type: "MultiPolygon".to_string(),
870 coordinates: multi_polygon_coords,
871 },
872 };
873
874 features.push(feature);
875 }
876
877 BoundaryFile {
878 collection_type: "FeatureCollection".to_string(),
879 features,
880 }
881 }
882
883 #[must_use]
903 #[cfg(feature = "export-geojson")]
904 pub fn get_tz_geojson(&self, timezone_name: &str) -> Option<FeatureItem> {
905 let mut keys = Vec::new();
906
907 for (key, names) in &self.all {
909 if names.iter().any(|n| n == timezone_name) {
910 keys.push(*key);
911 }
912 }
913
914 if keys.is_empty() {
915 return None;
916 }
917
918 let mut multi_polygon_coords = MultiPolygonCoordinates::new();
919
920 for (x, y, z) in keys {
921 let tile_poly = tile_to_polygon(x, y, z);
923 multi_polygon_coords.push(vec![tile_poly]);
924 }
925
926 Some(FeatureItem {
927 feature_type: "Feature".to_string(),
928 properties: PropertiesDefine {
929 tzid: timezone_name.to_string(),
930 },
931 geometry: GeometryDefine {
932 geometry_type: "MultiPolygon".to_string(),
933 coordinates: multi_polygon_coords,
934 },
935 })
936 }
937}
938
939#[cfg(feature = "export-geojson")]
941#[allow(clippy::cast_precision_loss)]
942fn tile_to_polygon(x: i64, y: i64, z: i64) -> Vec<[f64; 2]> {
943 let n = f64::powf(2.0, z as f64);
944
945 let lng_min = (x as f64) / n * 360.0 - 180.0;
947 let lat_min_rad = ((1.0 - ((y + 1) as f64) / n * 2.0) * PI).sinh().atan();
948 let lat_min = lat_min_rad.to_degrees();
949
950 let lng_max = ((x + 1) as f64) / n * 360.0 - 180.0;
952 let lat_max_rad = ((1.0 - (y as f64) / n * 2.0) * PI).sinh().atan();
953 let lat_max = lat_max_rad.to_degrees();
954
955 vec![
957 [lng_min, lat_min],
958 [lng_max, lat_min],
959 [lng_max, lat_max],
960 [lng_min, lat_max],
961 [lng_min, lat_min],
962 ]
963}
964
965pub struct DefaultFinder {
968 pub finder: Finder,
969 pub fuzzy_finder: FuzzyFinder,
970}
971
972impl Default for DefaultFinder {
973 fn default() -> Self {
982 let options = FinderOptions::y_stripes();
983 let topo_bytes = load_topology_compress_topo();
984 let tzs = pbgen::CompressedTopoTimezones::try_from(topo_bytes).unwrap_or_default();
985 let finder = Finder::from_compressed_topo_with_options(tzs, options);
986
987 let fuzzy_finder = FuzzyFinder::default();
988
989 Self {
990 finder,
991 fuzzy_finder,
992 }
993 }
994}
995
996impl DefaultFinder {
997 #[must_use]
1001 pub fn new_with_options(options: FinderOptions) -> Self {
1002 let topo_bytes = load_topology_compress_topo();
1003 let tzs = pbgen::CompressedTopoTimezones::try_from(topo_bytes).unwrap_or_default();
1004 Self {
1005 finder: Finder::from_compressed_topo_with_options(tzs, options),
1006 fuzzy_finder: FuzzyFinder::default(),
1007 }
1008 }
1009
1010 #[must_use]
1032 #[cfg(feature = "full")]
1033 #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
1034 pub fn new_full() -> Self {
1035 Self::new_full_with_options(FinderOptions::y_stripes())
1036 }
1037
1038 #[must_use]
1040 #[cfg(feature = "full")]
1041 #[cfg_attr(docsrs, doc(cfg(feature = "full")))]
1042 pub fn new_full_with_options(options: FinderOptions) -> Self {
1043 let tzs =
1044 pbgen::CompressedTopoTimezones::try_from(load_compress_topo()).unwrap_or_default();
1045 Self {
1046 finder: Finder::from_compressed_topo_with_options(tzs, options),
1047 fuzzy_finder: FuzzyFinder::default(),
1048 }
1049 }
1050
1051 #[must_use]
1057 pub fn get_tz_name(&self, lng: f64, lat: f64) -> &str {
1058 let fuzzy = self.fuzzy_finder.get_tz_name(lng, lat);
1059 if !fuzzy.is_empty() {
1060 return fuzzy;
1061 }
1062 self.finder.get_tz_name(lng, lat)
1063 }
1064
1065 #[must_use]
1071 pub fn get_tz_names(&self, lng: f64, lat: f64) -> Vec<&str> {
1072 let fuzzy_names = self.fuzzy_finder.get_tz_names(lng, lat);
1073 if !fuzzy_names.is_empty() {
1074 return fuzzy_names;
1075 }
1076 let names = self.finder.get_tz_names(lng, lat);
1077 if !names.is_empty() {
1078 return names;
1079 }
1080 Vec::new() }
1082
1083 #[must_use]
1091 pub fn timezonenames(&self) -> Vec<&str> {
1092 self.finder.timezonenames()
1093 }
1094
1095 #[must_use]
1106 pub fn data_version(&self) -> &str {
1107 &self.finder.data_version
1108 }
1109
1110 #[must_use]
1117 pub fn new() -> Self {
1118 Self::default()
1119 }
1120
1121 #[must_use]
1137 #[cfg(feature = "export-geojson")]
1138 pub fn to_geojson(&self) -> BoundaryFile {
1139 self.finder.to_geojson()
1140 }
1141
1142 #[must_use]
1169 #[cfg(feature = "export-geojson")]
1170 pub fn get_tz_geojson(&self, timezone_name: &str) -> Option<BoundaryFile> {
1171 self.finder.get_tz_geojson(timezone_name)
1172 }
1173}