Heavily customized version of smokesignal - https://whtwnd.com/kayrozen.com/3lpwe4ymowg2t
1//! # Nominatim Client Usage Example
2//!
3//! This example demonstrates how to integrate the new NominatimClient
4//! with existing event storage workflows while maintaining lexicon compatibility.
5
6use anyhow::Result;
7use smokesignal::services::nominatim_client::{NominatimClient, NominatimSearchResult};
8use smokesignal::storage::cache::create_cache_pool;
9use smokesignal::atproto::lexicon::community::lexicon::location::{Address, Geo};
10use smokesignal::atproto::lexicon::community::lexicon::calendar::event::EventLocation;
11
12/// Example: Replace deprecated GeocodingService with NominatimClient
13///
14/// This function shows how to migrate from the old geocoding service
15/// to the new Nominatim client while maintaining backward compatibility.
16pub async fn migrate_from_geocoding_service() -> Result<()> {
17 // Initialize Redis connection pool
18 let redis_url = std::env::var("REDIS_URL")
19 .unwrap_or_else(|_| "redis://valkey:6379/0".to_string());
20 let redis_pool = create_cache_pool(&redis_url)?;
21
22 // Initialize Nominatim client
23 let nominatim_url = "http://nominatim-quebec:8080".to_string();
24 let client = NominatimClient::new(redis_pool, nominatim_url)?;
25
26 // Example 1: Basic address geocoding
27 println!("=== Example 1: Basic Address Geocoding ===");
28 let address = "1000 Rue Saint-Antoine Ouest, Montréal, QC";
29
30 match client.search_address(address).await {
31 Ok(result) => {
32 println!("✅ Geocoded address: {}", address);
33 print_lexicon_compatible_result(&result);
34 }
35 Err(e) => {
36 println!("❌ Failed to geocode address: {}", e);
37 }
38 }
39
40 // Example 2: Reverse geocoding
41 println!("\n=== Example 2: Reverse Geocoding ===");
42 let montreal_lat = 45.5017;
43 let montreal_lon = -73.5673;
44
45 match client.reverse_geocode(montreal_lat, montreal_lon).await {
46 Ok(result) => {
47 println!("✅ Reverse geocoded coordinates: {}, {}", montreal_lat, montreal_lon);
48 print_lexicon_compatible_result(&result);
49 }
50 Err(e) => {
51 println!("❌ Failed to reverse geocode: {}", e);
52 }
53 }
54
55 // Example 3: Enhanced venue metadata
56 println!("\n=== Example 3: Enhanced Venue Metadata ===");
57 if let Ok(result) = client.search_address("Université de Montréal, Montréal, QC").await {
58 // Extract coordinates using pattern matching
59 let Geo::Current { latitude, longitude, .. } = &result.geo;
60 let lat: f64 = latitude.parse()?;
61 let lon: f64 = longitude.parse()?;
62
63 if let Some(venue_metadata) = client.get_venue_metadata(lat, lon).await {
64 println!("✅ Enhanced venue metadata retrieved:");
65 println!(" Place ID: {:?}", venue_metadata.place_id);
66 println!(" Category: {:?}", venue_metadata.category);
67 println!(" Venue Type: {:?}", venue_metadata.venue_type);
68 println!(" Importance: {:?}", venue_metadata.importance);
69 println!(" Bilingual Names:");
70 println!(" English: {:?}", venue_metadata.bilingual_names.english);
71 println!(" French: {:?}", venue_metadata.bilingual_names.french);
72 println!(" Display: {}", venue_metadata.bilingual_names.display_name);
73 println!(" Cached at: {}", venue_metadata.cached_at);
74 }
75 }
76
77 Ok(())
78}
79
80/// Helper function to display lexicon-compatible results
81fn print_lexicon_compatible_result(result: &NominatimSearchResult) {
82 println!("📍 Lexicon Address:");
83 match &result.address {
84 Address::Current {
85 country,
86 postal_code,
87 region,
88 locality,
89 street,
90 name,
91 } => {
92 println!(" Country: {}", country);
93 if let Some(code) = postal_code {
94 println!(" Postal Code: {}", code);
95 }
96 if let Some(reg) = region {
97 println!(" Region: {}", reg);
98 }
99 if let Some(loc) = locality {
100 println!(" Locality: {}", loc);
101 }
102 if let Some(st) = street {
103 println!(" Street: {}", st);
104 }
105 if let Some(n) = name {
106 println!(" Name: {}", n);
107 }
108 }
109 }
110
111 println!("🗺️ Lexicon Geo:");
112 match &result.geo {
113 Geo::Current { latitude, longitude, name } => {
114 println!(" Latitude: {}", latitude);
115 println!(" Longitude: {}", longitude);
116 if let Some(n) = name {
117 println!(" Name: {}", n);
118 }
119 }
120 }
121}
122
123/// Example: Convert NominatimSearchResult to EventLocation for existing workflows
124pub fn convert_to_event_location(result: &NominatimSearchResult) -> Vec<EventLocation> {
125 vec![
126 EventLocation::Address(result.address.clone()),
127 EventLocation::Geo(result.geo.clone()),
128 ]
129}
130
131/// Example: Integration with existing event storage workflow
132pub async fn integrate_with_event_storage() -> Result<()> {
133 // Initialize client
134 let redis_url = std::env::var("REDIS_URL")
135 .unwrap_or_else(|_| "redis://valkey:6379/0".to_string());
136 let redis_pool = create_cache_pool(&redis_url)?;
137 let nominatim_url = "http://nominatim-quebec:8080".to_string();
138 let client = NominatimClient::new(redis_pool, nominatim_url)?;
139
140 // Example event address to geocode
141 let event_address = "Place des Arts, Montréal, QC";
142
143 println!("=== Integrating with Event Storage ===");
144
145 match client.search_address(event_address).await {
146 Ok(result) => {
147 // Convert to EventLocation format for storage
148 let event_locations = convert_to_event_location(&result);
149
150 println!("✅ Created EventLocation objects:");
151 for (i, location) in event_locations.iter().enumerate() {
152 match location {
153 EventLocation::Address(_) => {
154 println!(" {}: Address location", i + 1);
155 }
156 EventLocation::Geo(_) => {
157 println!(" {}: Geo location", i + 1);
158 }
159 _ => {
160 println!(" {}: Other location type", i + 1);
161 }
162 }
163 }
164
165 // In a real application, you would store these in the database
166 // along with the enhanced venue metadata cached in Redis
167 println!(" 💾 Ready for database storage");
168 println!(" 🏪 Enhanced venue metadata cached in Redis");
169 }
170 Err(e) => {
171 println!("❌ Failed to geocode event address: {}", e);
172 }
173 }
174
175 Ok(())
176}
177
178/// Example: Performance comparison with caching
179pub async fn demonstrate_caching_performance() -> Result<()> {
180 use std::time::Instant;
181
182 // Initialize client
183 let redis_url = std::env::var("REDIS_URL")
184 .unwrap_or_else(|_| "redis://valkey:6379/0".to_string());
185 let redis_pool = create_cache_pool(&redis_url)?;
186 let nominatim_url = "http://nominatim-quebec:8080".to_string();
187 let client = NominatimClient::new(redis_pool, nominatim_url)?;
188
189 let test_address = "McGill University, Montreal, QC";
190
191 println!("=== Caching Performance Demonstration ===");
192
193 // First request (API call)
194 let start = Instant::now();
195 let result1 = client.search_address(test_address).await?;
196 let duration1 = start.elapsed();
197
198 // Second request (cache hit)
199 let start = Instant::now();
200 let result2 = client.search_address(test_address).await?;
201 let duration2 = start.elapsed();
202
203 println!("📊 Performance Results:");
204 println!(" First request (API): {}ms", duration1.as_millis());
205 println!(" Second request (cache): {}ms", duration2.as_millis());
206 println!(" Cache speedup: {:.1}x", duration1.as_millis() as f64 / duration2.as_millis() as f64);
207
208 // Verify results are identical
209 assert_eq!(result1.address, result2.address);
210 assert_eq!(result1.geo, result2.geo);
211 println!(" ✅ Results are identical");
212
213 // Both should meet performance target
214 assert!(duration1.as_millis() < 500);
215 assert!(duration2.as_millis() < 500);
216 println!(" ✅ Both requests meet <500ms target");
217
218 Ok(())
219}
220
221#[tokio::main]
222async fn main() -> Result<()> {
223 // Initialize simple tracing for better debugging
224 tracing_subscriber::fmt::init();
225
226 println!("🚀 Nominatim Client Integration Examples");
227 println!("==========================================\n");
228
229 // Run examples
230 migrate_from_geocoding_service().await?;
231 integrate_with_event_storage().await?;
232 demonstrate_caching_performance().await?;
233
234 println!("\n✅ All examples completed successfully!");
235 println!("🎯 Ready for production use with lexicon compatibility");
236
237 Ok(())
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243
244 #[tokio::test]
245 async fn test_convert_to_event_location() -> Result<()> {
246 use smokesignal::services::nominatim_client::{NominatimSearchResult, VenueMetadata, BilingualNames};
247
248 // Create a mock result
249 let mock_result = NominatimSearchResult {
250 address: Address::Current {
251 country: "Canada".to_string(),
252 postal_code: Some("H3A 0G4".to_string()),
253 region: Some("Québec".to_string()),
254 locality: Some("Montréal".to_string()),
255 street: Some("845 Rue Sherbrooke Ouest".to_string()),
256 name: Some("McGill University".to_string()),
257 },
258 geo: Geo::Current {
259 latitude: "45.5048".to_string(),
260 longitude: "-73.5772".to_string(),
261 name: Some("McGill University".to_string()),
262 },
263 venue_metadata: VenueMetadata {
264 place_id: Some(123456),
265 category: Some("amenity".to_string()),
266 venue_type: Some("university".to_string()),
267 importance: Some(0.75),
268 place_rank: Some(30),
269 osm_type: Some("way".to_string()),
270 osm_id: Some(789012),
271 bilingual_names: BilingualNames {
272 english: Some("McGill University".to_string()),
273 french: Some("Université McGill".to_string()),
274 display_name: "McGill University, Montréal, Québec, Canada".to_string(),
275 },
276 bounding_box: Some([45.5000, 45.5100, -73.5800, -73.5700]),
277 cached_at: chrono::Utc::now(),
278 },
279 };
280
281 // Convert to event locations
282 let event_locations = convert_to_event_location(&mock_result);
283
284 // Verify we get both Address and Geo locations
285 assert_eq!(event_locations.len(), 2);
286
287 match &event_locations[0] {
288 EventLocation::Address(addr) => {
289 if let Address::Current { country, .. } = addr {
290 assert_eq!(country, "Canada");
291 }
292 }
293 _ => panic!("First location should be Address"),
294 }
295
296 match &event_locations[1] {
297 EventLocation::Geo(geo) => {
298 if let Geo::Current { latitude, .. } = geo {
299 assert_eq!(latitude, "45.5048");
300 }
301 }
302 _ => panic!("Second location should be Geo"),
303 }
304
305 Ok(())
306 }
307}