Here's a complete implementation that combines IP geolocation with device GPS for maximum accuracy in a single-screen MAUI application:
1. Project Setup
First, create a new .NET MAUI project and add these NuGet packages:
dotnet add package Newtonsoft.Json dotnet add package Microsoft.Maui.Controls.Maps dotnet add package Microsoft.Maui.Essentials
2. MainPage.xaml
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:maps="clr-namespace:Microsoft.Maui.Controls.Maps;assembly=Microsoft.Maui.Controls.Maps" x:Class="IpGeolocationApp.MainPage" Title="Advanced Geolocator"> <Grid> <!-- Map --> <maps:Map x:Name="mapControl" MapType="Street" IsShowingUser="True"/> <!-- Information Overlay --> <Frame Margin="20" VerticalOptions="Start" BackgroundColor="{AppThemeBinding Light=White, Dark=#202020}" BorderColor="{AppThemeBinding Light=LightGray, Dark=Gray}" CornerRadius="10" HasShadow="True"> <StackLayout Spacing="10"> <Label Text="Advanced Geolocation" FontSize="Title" FontAttributes="Bold" HorizontalOptions="Center"/> <Grid ColumnDefinitions="Auto, *" RowDefinitions="Auto, Auto, Auto, Auto, Auto, Auto"> <Label Text="Method:" Grid.Row="0" Grid.Column="0" FontAttributes="Bold"/> <Label x:Name="methodLabel" Grid.Row="0" Grid.Column="1"/> <Label Text="IP Address:" Grid.Row="1" Grid.Column="0" FontAttributes="Bold"/> <Label x:Name="ipLabel" Grid.Row="1" Grid.Column="1"/> <Label Text="Location:" Grid.Row="2" Grid.Column="0" FontAttributes="Bold"/> <Label x:Name="locationLabel" Grid.Row="2" Grid.Column="1"/> <Label Text="Coordinates:" Grid.Row="3" Grid.Column="0" FontAttributes="Bold"/> <Label x:Name="coordinatesLabel" Grid.Row="3" Grid.Column="1"/> <Label Text="ISP:" Grid.Row="4" Grid.Column="0" FontAttributes="Bold"/> <Label x:Name="ispLabel" Grid.Row="4" Grid.Column="1"/> <Label Text="Accuracy:" Grid.Row="5" Grid.Column="0" FontAttributes="Bold"/> <Label x:Name="accuracyLabel" Grid.Row="5" Grid.Column="1"/> </Grid> <Button Text="Refresh" Clicked="OnRefreshClicked" HorizontalOptions="Center" Margin="0,10,0,0"/> </StackLayout> </Frame> <!-- Activity Indicator --> <ActivityIndicator x:Name="loadingIndicator" IsRunning="False" Color="{AppThemeBinding Light=Blue, Dark=White}" HorizontalOptions="Center" VerticalOptions="Center"/> </Grid> </ContentPage>
3. MainPage.xaml.cs
using System.Net; using Newtonsoft.Json; using Microsoft.Maui.Devices.Sensors; using Microsoft.Maui.Controls.Maps; using Microsoft.Maui.Maps; using Map = Microsoft.Maui.Controls.Maps.Map; namespace IpGeolocationApp; public partial class MainPage : ContentPage { private const string GeolocationApiUrl = "https://ipapi.co/json/"; public MainPage() { InitializeComponent(); Loaded += OnPageLoaded; } private async void OnPageLoaded(object sender, EventArgs e) { await FetchAndDisplayLocation(); } private async void OnRefreshClicked(object sender, EventArgs e) { await FetchAndDisplayLocation(); } private async Task FetchAndDisplayLocation() { loadingIndicator.IsRunning = true; try { // Try to get GPS location first var gpsLocation = await GetGpsLocation(); if (gpsLocation != null) { // Use GPS location if available var locationData = new LocationData { Latitude = gpsLocation.Latitude, Longitude = gpsLocation.Longitude, Source = "GPS", Accuracy = $"~{gpsLocation.Accuracy?.ToString("0") ?? "Unknown"} meters" }; // Reverse geocode to get address await GetAddressFromCoordinates(locationData); UpdateUI(locationData); UpdateMap(locationData); } else { // Fall back to IP geolocation await FetchIpGeolocation(); } } catch (Exception ex) { await DisplayAlert("Error", $"Failed to get location: {ex.Message}", "OK"); } finally { loadingIndicator.IsRunning = false; } } private async Task<Location?> GetGpsLocation() { try { var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>(); if (status != PermissionStatus.Granted) { status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>(); } if (status == PermissionStatus.Granted) { var request = new GeolocationRequest(GeolocationAccuracy.High); return await Geolocation.GetLocationAsync(request); } } catch (Exception ex) { Console.WriteLine($"GPS Error: {ex.Message}"); } return null; } private async Task FetchIpGeolocation() { using var client = new HttpClient(); var response = await client.GetStringAsync(GeolocationApiUrl); var locationData = JsonConvert.DeserializeObject<LocationData>(response); locationData.Source = "IP Geolocation"; locationData.Accuracy = "City-level (IP-based)"; UpdateUI(locationData); UpdateMap(locationData); } private async Task GetAddressFromCoordinates(LocationData data) { try { var placemarks = await Geocoding.GetPlacemarksAsync(data.Latitude, data.Longitude); var placemark = placemarks?.FirstOrDefault(); if (placemark != null) { data.City = placemark.Locality; data.Region = placemark.AdminArea; data.CountryName = placemark.CountryName; data.Postal = placemark.PostalCode; } } catch (Exception ex) { Console.WriteLine($"Geocoding error: {ex.Message}"); } } private void UpdateUI(LocationData data) { methodLabel.Text = data.Source; ipLabel.Text = data.Ip ?? "Not available"; locationLabel.Text = $"{data.City ?? "Unknown"}, {data.Region ?? "Unknown"}, {data.CountryName ?? "Unknown"}"; coordinatesLabel.Text = $"{data.Latitude:0.00000}, {data.Longitude:0.00000}"; ispLabel.Text = data.Org ?? "Not available"; accuracyLabel.Text = data.Accuracy ?? "Unknown"; } private void UpdateMap(LocationData data) { var position = new Location(data.Latitude, data.Longitude); var mapSpan = MapSpan.FromCenterAndRadius(position, Distance.FromKilometers(data.Source == "GPS" ? 1 : 10)); mapControl.MoveToRegion(mapSpan); // Add pin for the location mapControl.Pins.Clear(); mapControl.Pins.Add(new Pin { Label = $"Your Location ({data.Source})", Address = $"{data.City}, {data.CountryName}", Location = position, Type = data.Source == "GPS" ? PinType.SavedPin : PinType.Place }); } } public class LocationData { public string Ip { get; set; } public string City { get; set; } public string Region { get; set; } public string CountryName { get; set; } public string Postal { get; set; } public double Latitude { get; set; } public double Longitude { get; set; } public string Org { get; set; } public string Source { get; set; } public string Accuracy { get; set; } }
4. Platform-Specific Setup
Android (Platforms/Android/MainApplication.cs)
// Add these permissions to AndroidManifest.xml <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
iOS (Platforms/iOS/Info.plist)
<key>NSLocationWhenInUseUsageDescription</key> <string>We need your location to show your precise position on the map</string> <key>NSLocationAlwaysAndWhenInUseUsageDescription</key> <string>We need your location to show your precise position on the map</string>
5. Key Features
Hybrid Location Detection:
First attempts to get precise GPS location (if permissions granted)
Falls back to IP geolocation if GPS unavailable
Clearly indicates which method was used
Enhanced Accuracy:
GPS provides meter-level accuracy when available
IP geolocation provides city-level accuracy as fallback
Displays accuracy estimate for both methods
Reverse Geocoding:
Converts GPS coordinates to human-readable addresses
Provides consistent display format regardless of source
User Experience:
Visual distinction between GPS and IP-based locations
Automatic zoom level adjustment based on accuracy
Different pin types for different location sources
Error Handling:
Graceful degradation when features aren't available
Permission request handling
Network error recovery
6. How It Works
On Launch:
App checks for location permissions
Attempts to get GPS location first
If GPS fails (or denied), falls back to IP geolocation
Displays results with source and accuracy information
GPS Mode:
Uses device's GPS sensor
Provides coordinates with meter-level accuracy
Reverse geocodes to get address information
Zooms map to 1km radius
IP Mode:
Uses ipapi.co service
Provides city-level accuracy
Shows ISP information
Zooms map to 10km radius
Refresh:
Button allows manual refresh
Re-runs the entire location detection process
This implementation provides the most accurate possible location by combining both techniques while maintaining a clean, single-screen interface. The app automatically uses the best available method and clearly communicates the accuracy level to users.
No comments:
Post a Comment