- Implemented OpenStreetMap using WebView with Leaflet.js - Added OpenStreetMapView component with interactive map functionality - Created heat map visualization with color-coded intensity - Added 30 dummy location points around San Francisco Bay Area - Implemented location tracking with real-time pin placement - Added comprehensive UI with two-row button layout - Features: Start/Stop tracking, Center map, Demo heat map, Clear demo, Reset map - Added location count display and confirmation dialogs - Updated project structure and documentation - All functionality tested and working on Android emulator
270 lines
9.7 KiB
C#
270 lines
9.7 KiB
C#
using LocationTrackerApp.Components;
|
|
using LocationTrackerApp.Models;
|
|
|
|
namespace LocationTrackerApp;
|
|
|
|
public partial class MainPage : ContentPage
|
|
{
|
|
private bool _isTracking = false;
|
|
private List<LocationData> _trackedLocations = new();
|
|
|
|
public MainPage()
|
|
{
|
|
InitializeComponent();
|
|
InitializeMap();
|
|
}
|
|
|
|
private void InitializeMap()
|
|
{
|
|
// The OpenStreetMapView initializes itself with a default location
|
|
// Add some dummy location data to demonstrate heat map functionality
|
|
AddDummyLocationData();
|
|
}
|
|
|
|
private void AddDummyLocationData()
|
|
{
|
|
// Add dummy location data around San Francisco Bay Area to demonstrate heat map
|
|
var dummyLocations = new List<LocationData>
|
|
{
|
|
// Golden Gate Bridge area (high frequency)
|
|
new LocationData { Latitude = 37.8199, Longitude = -122.4783, Timestamp = DateTime.UtcNow.AddMinutes(-30) },
|
|
new LocationData { Latitude = 37.8200, Longitude = -122.4780, Timestamp = DateTime.UtcNow.AddMinutes(-29) },
|
|
new LocationData { Latitude = 37.8201, Longitude = -122.4777, Timestamp = DateTime.UtcNow.AddMinutes(-28) },
|
|
new LocationData { Latitude = 37.8198, Longitude = -122.4785, Timestamp = DateTime.UtcNow.AddMinutes(-27) },
|
|
new LocationData { Latitude = 37.8202, Longitude = -122.4775, Timestamp = DateTime.UtcNow.AddMinutes(-26) },
|
|
|
|
// Fisherman's Wharf area (medium frequency)
|
|
new LocationData { Latitude = 37.8080, Longitude = -122.4177, Timestamp = DateTime.UtcNow.AddMinutes(-25) },
|
|
new LocationData { Latitude = 37.8085, Longitude = -122.4175, Timestamp = DateTime.UtcNow.AddMinutes(-24) },
|
|
new LocationData { Latitude = 37.8082, Longitude = -122.4180, Timestamp = DateTime.UtcNow.AddMinutes(-23) },
|
|
|
|
// Union Square area (medium frequency)
|
|
new LocationData { Latitude = 37.7879, Longitude = -122.4075, Timestamp = DateTime.UtcNow.AddMinutes(-22) },
|
|
new LocationData { Latitude = 37.7882, Longitude = -122.4078, Timestamp = DateTime.UtcNow.AddMinutes(-21) },
|
|
new LocationData { Latitude = 37.7877, Longitude = -122.4072, Timestamp = DateTime.UtcNow.AddMinutes(-20) },
|
|
|
|
// Mission District area (low frequency)
|
|
new LocationData { Latitude = 37.7599, Longitude = -122.4148, Timestamp = DateTime.UtcNow.AddMinutes(-19) },
|
|
new LocationData { Latitude = 37.7602, Longitude = -122.4150, Timestamp = DateTime.UtcNow.AddMinutes(-18) },
|
|
|
|
// Castro District area (low frequency)
|
|
new LocationData { Latitude = 37.7611, Longitude = -122.4350, Timestamp = DateTime.UtcNow.AddMinutes(-17) },
|
|
|
|
// SOMA area (medium frequency)
|
|
new LocationData { Latitude = 37.7749, Longitude = -122.4194, Timestamp = DateTime.UtcNow.AddMinutes(-16) },
|
|
new LocationData { Latitude = 37.7752, Longitude = -122.4197, Timestamp = DateTime.UtcNow.AddMinutes(-15) },
|
|
new LocationData { Latitude = 37.7747, Longitude = -122.4191, Timestamp = DateTime.UtcNow.AddMinutes(-14) },
|
|
new LocationData { Latitude = 37.7750, Longitude = -122.4194, Timestamp = DateTime.UtcNow.AddMinutes(-13) },
|
|
|
|
// Financial District area (high frequency)
|
|
new LocationData { Latitude = 37.7946, Longitude = -122.3998, Timestamp = DateTime.UtcNow.AddMinutes(-12) },
|
|
new LocationData { Latitude = 37.7949, Longitude = -122.4001, Timestamp = DateTime.UtcNow.AddMinutes(-11) },
|
|
new LocationData { Latitude = 37.7944, Longitude = -122.3995, Timestamp = DateTime.UtcNow.AddMinutes(-10) },
|
|
new LocationData { Latitude = 37.7952, Longitude = -122.4004, Timestamp = DateTime.UtcNow.AddMinutes(-9) },
|
|
new LocationData { Latitude = 37.7947, Longitude = -122.3999, Timestamp = DateTime.UtcNow.AddMinutes(-8) },
|
|
new LocationData { Latitude = 37.7950, Longitude = -122.4002, Timestamp = DateTime.UtcNow.AddMinutes(-7) },
|
|
|
|
// North Beach area (low frequency)
|
|
new LocationData { Latitude = 37.8044, Longitude = -122.4098, Timestamp = DateTime.UtcNow.AddMinutes(-6) },
|
|
|
|
// Chinatown area (medium frequency)
|
|
new LocationData { Latitude = 37.7941, Longitude = -122.4078, Timestamp = DateTime.UtcNow.AddMinutes(-5) },
|
|
new LocationData { Latitude = 37.7944, Longitude = -122.4081, Timestamp = DateTime.UtcNow.AddMinutes(-4) },
|
|
new LocationData { Latitude = 37.7938, Longitude = -122.4075, Timestamp = DateTime.UtcNow.AddMinutes(-3) },
|
|
|
|
// Marina District area (low frequency)
|
|
new LocationData { Latitude = 37.8026, Longitude = -122.4430, Timestamp = DateTime.UtcNow.AddMinutes(-2) },
|
|
|
|
// Presidio area (low frequency)
|
|
new LocationData { Latitude = 37.7989, Longitude = -122.4662, Timestamp = DateTime.UtcNow.AddMinutes(-1) }
|
|
};
|
|
|
|
_trackedLocations.AddRange(dummyLocations);
|
|
UpdateLocationCount();
|
|
}
|
|
|
|
private void UpdateLocationCount()
|
|
{
|
|
LocationCountLabel.Text = $"Locations: {_trackedLocations.Count}";
|
|
}
|
|
|
|
private async void OnStartTrackingClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (!_isTracking)
|
|
{
|
|
// Request location permissions
|
|
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
|
|
if (status != PermissionStatus.Granted)
|
|
{
|
|
await DisplayAlert("Permission Required", "Location permission is required to track your location.", "OK");
|
|
return;
|
|
}
|
|
|
|
// Start location tracking
|
|
_isTracking = true;
|
|
StartTrackingBtn.Text = "Tracking...";
|
|
StartTrackingBtn.BackgroundColor = Colors.Orange;
|
|
StopTrackingBtn.IsEnabled = true;
|
|
|
|
// Simulate location tracking
|
|
_ = Task.Run(async () => await SimulateLocationTracking());
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await DisplayAlert("Error", $"Failed to start tracking: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
|
|
private void OnStopTrackingClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (_isTracking)
|
|
{
|
|
_isTracking = false;
|
|
StartTrackingBtn.Text = "Start Tracking";
|
|
StartTrackingBtn.BackgroundColor = Colors.Green;
|
|
StopTrackingBtn.IsEnabled = false;
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
DisplayAlert("Error", $"Failed to stop tracking: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
|
|
private async Task SimulateLocationTracking()
|
|
{
|
|
while (_isTracking)
|
|
{
|
|
try
|
|
{
|
|
// Get current location
|
|
var location = await Geolocation.GetLocationAsync();
|
|
if (location != null)
|
|
{
|
|
// Update UI on main thread
|
|
MainThread.BeginInvokeOnMainThread(async () =>
|
|
{
|
|
// Add location to tracked locations
|
|
var locationData = new LocationData
|
|
{
|
|
Latitude = location.Latitude,
|
|
Longitude = location.Longitude,
|
|
Timestamp = DateTime.UtcNow
|
|
};
|
|
_trackedLocations.Add(locationData);
|
|
UpdateLocationCount();
|
|
|
|
// Add pin to OpenStreetMap
|
|
await MainMap.AddLocationPinAsync(location, $"Current Location {_trackedLocations.Count}", 0.8);
|
|
});
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
// Handle location errors
|
|
System.Diagnostics.Debug.WriteLine($"Location error: {ex.Message}");
|
|
}
|
|
|
|
// Wait 5 seconds before next update
|
|
await Task.Delay(5000);
|
|
}
|
|
}
|
|
|
|
private async void OnCenterMapClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
// Request location permissions if not already granted
|
|
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
|
|
if (status != PermissionStatus.Granted)
|
|
{
|
|
status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
|
|
if (status != PermissionStatus.Granted)
|
|
{
|
|
await DisplayAlert("Permission Required", "Location permission is required to center the map.", "OK");
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Get current location
|
|
var location = await Geolocation.GetLocationAsync();
|
|
if (location != null)
|
|
{
|
|
// Clear existing pins
|
|
await MainMap.ClearMapAsync();
|
|
|
|
// Add a pin for current location
|
|
await MainMap.AddLocationPinAsync(location, $"Your Location\nLat: {location.Latitude:F6}\nLng: {location.Longitude:F6}", 1.0);
|
|
|
|
await DisplayAlert("Location Found", $"Map centered on your location:\nLat: {location.Latitude:F6}\nLng: {location.Longitude:F6}", "OK");
|
|
}
|
|
else
|
|
{
|
|
await DisplayAlert("Error", "Unable to get your current location.", "OK");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await DisplayAlert("Error", $"Failed to center map: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
|
|
private async void OnHeatMapClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
if (_trackedLocations.Any())
|
|
{
|
|
// Load tracked locations into the heat map
|
|
await MainMap.LoadLocationDataAsync(_trackedLocations);
|
|
await DisplayAlert("Heat Map", $"Heat map updated with {_trackedLocations.Count} locations.\n\nAreas with higher frequency appear in red/yellow, lower frequency in blue.", "OK");
|
|
}
|
|
else
|
|
{
|
|
await DisplayAlert("No Data", "No location data available. Start tracking to create a heat map.", "OK");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await DisplayAlert("Error", $"Failed to update heat map: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
|
|
private async void OnClearDemoClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
var result = await DisplayAlert("Clear Demo Data", "Are you sure you want to clear all demo location data?", "Yes", "No");
|
|
if (result)
|
|
{
|
|
_trackedLocations.Clear();
|
|
await MainMap.ClearMapAsync();
|
|
UpdateLocationCount();
|
|
await DisplayAlert("Demo Cleared", "All demo location data has been cleared.", "OK");
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await DisplayAlert("Error", $"Failed to clear demo data: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
|
|
private async void OnResetMapClicked(object? sender, EventArgs e)
|
|
{
|
|
try
|
|
{
|
|
await MainMap.ClearMapAsync();
|
|
await DisplayAlert("Map Reset", "Map has been reset to default view.", "OK");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
await DisplayAlert("Error", $"Failed to reset map: {ex.Message}", "OK");
|
|
}
|
|
}
|
|
}
|