| """ | |
| map_visualization.py - Interactive Map Visualization with Folium | |
| """ | |
| import folium | |
| from folium import plugins | |
| import pandas as pd | |
| from typing import List, Dict, Optional | |
| from datetime import datetime | |
| class DogMapVisualizer: | |
| def __init__(self, default_location: List[float] = [38.9637, 35.2433]): | |
| self.default_location = default_location | |
| self.default_zoom = 13 | |
| print(f"✅ Map Visualizer initialized") | |
| print(f" Default location: {default_location}") | |
| def create_map(self, dogs_data: List[Dict]) -> Optional[str]: | |
| if not dogs_data: | |
| return self._create_empty_map() | |
| center_lat = sum(dog['latitude'] for dog in dogs_data) / len(dogs_data) | |
| center_lon = sum(dog['longitude'] for dog in dogs_data) / len(dogs_data) | |
| map_obj = folium.Map( | |
| location=[center_lat, center_lon], | |
| zoom_start=self.default_zoom, | |
| tiles='OpenStreetMap' | |
| ) | |
| self.add_markers(map_obj, dogs_data) | |
| self._add_plugins(map_obj) | |
| map_html = map_obj._repr_html_() | |
| return map_html | |
| def _create_empty_map(self) -> str: | |
| map_obj = folium.Map( | |
| location=self.default_location, | |
| zoom_start=self.default_zoom, | |
| tiles='OpenStreetMap' | |
| ) | |
| folium.Marker( | |
| location=self.default_location, | |
| popup="No dogs with location data yet", | |
| icon=folium.Icon(color='gray', icon='info-sign') | |
| ).add_to(map_obj) | |
| return map_obj._repr_html_() | |
| def add_markers(self, map_obj: folium.Map, dogs: List[Dict]): | |
| for dog in dogs: | |
| lat = dog['latitude'] | |
| lon = dog['longitude'] | |
| color = self.get_marker_color(dog.get('health_score')) | |
| popup_content = self.create_popup(dog) | |
| folium.Marker( | |
| location=[lat, lon], | |
| popup=folium.Popup(popup_content, max_width=300), | |
| tooltip=dog['name'], | |
| icon=folium.Icon( | |
| color=color, | |
| icon='info-sign', | |
| prefix='glyphicon' | |
| ) | |
| ).add_to(map_obj) | |
| def create_popup(self, dog_info: Dict) -> str: | |
| dog_id = dog_info['dog_id'] | |
| name = dog_info['name'] | |
| last_seen = dog_info.get('last_seen', 'Unknown') | |
| sightings = dog_info.get('total_sightings', 0) | |
| health_score = dog_info.get('health_score') | |
| health_status = dog_info.get('health_status') | |
| html = f""" | |
| <div style='font-family: Arial, sans-serif; min-width: 200px;'> | |
| <h4 style='margin: 0 0 10px 0; color: #228be6;'>{name}</h4> | |
| <table style='width: 100%; border-collapse: collapse;'> | |
| <tr> | |
| <td style='padding: 3px 0;'><b>ID:</b></td> | |
| <td style='padding: 3px 0;'>#{dog_id}</td> | |
| </tr> | |
| <tr> | |
| <td style='padding: 3px 0;'><b>Last Seen:</b></td> | |
| <td style='padding: 3px 0;'>{last_seen}</td> | |
| </tr> | |
| <tr> | |
| <td style='padding: 3px 0;'><b>Sightings:</b></td> | |
| <td style='padding: 3px 0;'>{sightings}</td> | |
| </tr> | |
| """ | |
| if health_score is not None: | |
| html += f""" | |
| <tr> | |
| <td style='padding: 3px 0;'><b>Health Score:</b></td> | |
| <td style='padding: 3px 0;'>{health_score:.1f}/10</td> | |
| </tr> | |
| """ | |
| if health_status: | |
| status_color = { | |
| 'Healthy': '#51cf66', | |
| 'Monitor': '#ffd43b', | |
| 'Concern': '#ff6b6b' | |
| }.get(health_status, '#868e96') | |
| html += f""" | |
| <tr> | |
| <td style='padding: 3px 0;'><b>Status:</b></td> | |
| <td style='padding: 3px 0; color: {status_color}; font-weight: bold;'> | |
| {health_status} | |
| </td> | |
| </tr> | |
| """ | |
| html += """ | |
| </table> | |
| </div> | |
| """ | |
| return html | |
| def get_marker_color(self, health_score: Optional[float] = None) -> str: | |
| return 'blue' | |
| def _add_plugins(self, map_obj: folium.Map): | |
| plugins.Fullscreen( | |
| position='topright', | |
| title='Expand map', | |
| title_cancel='Exit fullscreen', | |
| force_separate_button=True | |
| ).add_to(map_obj) | |
| plugins.MeasureControl( | |
| position='topleft', | |
| primary_length_unit='meters', | |
| secondary_length_unit='kilometers', | |
| primary_area_unit='sqmeters', | |
| secondary_area_unit='hectares' | |
| ).add_to(map_obj) | |
| def create_heatmap(self, dogs_data: List[Dict]) -> Optional[str]: | |
| if not dogs_data: | |
| return self._create_empty_map() | |
| center_lat = sum(dog['latitude'] for dog in dogs_data) / len(dogs_data) | |
| center_lon = sum(dog['longitude'] for dog in dogs_data) / len(dogs_data) | |
| map_obj = folium.Map( | |
| location=[center_lat, center_lon], | |
| zoom_start=self.default_zoom, | |
| tiles='OpenStreetMap' | |
| ) | |
| heat_data = [[dog['latitude'], dog['longitude']] for dog in dogs_data] | |
| plugins.HeatMap(heat_data).add_to(map_obj) | |
| self._add_plugins(map_obj) | |
| return map_obj._repr_html_() | |
| def create_cluster_map(self, dogs_data: List[Dict]) -> Optional[str]: | |
| if not dogs_data: | |
| return self._create_empty_map() | |
| center_lat = sum(dog['latitude'] for dog in dogs_data) / len(dogs_data) | |
| center_lon = sum(dog['longitude'] for dog in dogs_data) / len(dogs_data) | |
| map_obj = folium.Map( | |
| location=[center_lat, center_lon], | |
| zoom_start=self.default_zoom, | |
| tiles='OpenStreetMap' | |
| ) | |
| marker_cluster = plugins.MarkerCluster().add_to(map_obj) | |
| for dog in dogs_data: | |
| lat = dog['latitude'] | |
| lon = dog['longitude'] | |
| popup_content = self.create_popup(dog) | |
| folium.Marker( | |
| location=[lat, lon], | |
| popup=folium.Popup(popup_content, max_width=300), | |
| tooltip=dog['name'] | |
| ).add_to(marker_cluster) | |
| self._add_plugins(map_obj) | |
| return map_obj._repr_html_() |