Aplikacja MapLocator – adres → współrzędne → mapa → odległość
Po co nam „mapy i GPS” w INF.04?
W podstawie INF.04 pojawia się hasło „lokalizacja / GPS / mapy”. W aplikacjach mobilnych to często znaczy: „pobierz pozycję z czujnika”.
W przypadku WPF (desktop) najczęściej robimy to inaczej: pracujemy na współrzędnych geograficznych i wyświetlamy je na mapie, a lokalizację bierzemy np. z internetu (API).
Czyli: uczysz się tego samego „sensu” GPS (punkt na Ziemi), tylko w wersji desktopowej.
1) Teoria – prosto i po ludzku
1.1 Współrzędne GPS: latitude i longitude
Każde miejsce na świecie można opisać dwoma liczbami:
- latitude (szerokość) – góra/dół na mapie (północ-południe)
- longitude (długość) – lewo/prawo (wschód-zachód)
Przykład (Warszawa, okolice centrum):lat ≈ 52.2297lon ≈ 21.0122
Te dwie liczby to „GPS” w najczystszej postaci.
1.2 Geokodowanie
Geokodowanie = zamiana adresu na współrzędne.
Np. „Działdowo, Grunwaldzka 38” → lat/lon
Reverse geocoding działa odwrotnie: współrzędne → adres.
1.3 Mapy w aplikacji
Są dwa popularne sposoby:
- Osadzamy mapę w przeglądarce (w WPF: WebView2) i ładujemy np. OpenStreetMap.
- Używamy specjalnych bibliotek mapowych (bardziej zaawansowane – na później).
My wybieramy wersję 1, bo jest prosta i stabilna na lekcję + INF.04.
1.4 Odległość między punktami (po co to uczniowi?)
Bardzo praktyczne:
- ile km jest z domu do szkoły,
- jaka odległość do hotelu, firmy, boiska,
- najbliższy punkt z listy (sklep, przystanek, gabinet).
Do tego używa się wzoru (na Ziemi po kuli) – najczęściej Haversine.
2) Gdzie to się przydaje? (konkretne przykłady)
- „Znajdź najbliższy punkt” (np. najbliższe boisko z listy).
- „Wpisz adres i zobacz na mapie”.
- „Policz odległość od szkoły”.
- „Plan wycieczki – punkty na mapie”.
- „Aplikacja dla kuriera – zlecenia z koordynatami”.
3) Narzędzia i technologie w projekcie
3.1 Visual Studio (polska wersja)
Tworzymy projekt WPF w Visual Studio (najlepiej .NET 8).
3.2 WebView2
To kontrolka od Microsoftu, która pozwala wstawić „mini przeglądarkę” do aplikacji WPF.
Dzięki temu:
- wyświetlamy OpenStreetMap,
- nie kombinujemy z rysowaniem mapy samemu.
3.3 OpenStreetMap
Darmowa mapa świata dostępna w przeglądarce.
My tylko otwieramy odpowiedni link z lat/lon.
3.4 Nominatim (API do geokodowania)
To publiczne API OpenStreetMap do wyszukiwania adresów.
Wysyłamy zapytanie typu:
search?q=Warszawa&format=json&limit=1
Odpowiedź jest w JSON i zawiera lat/lon.
Uwaga praktyczna: to API jest publiczne, więc trzeba używać rozsądnie (limit zapytań). Na lekcję i ćwiczenia jest OK.
4) Projekt: MapLocator (WPF)
Co ma robić aplikacja?
- Uczeń wpisuje adres w TextBox.
- Klik: Szukaj.
- Aplikacja pobiera współrzędne z API (Nominatim).
- Wyświetla współrzędne na ekranie.
- Ładuje mapę OpenStreetMap na tych współrzędnych.
- Liczy odległość do stałego punktu (np. szkoły) i wyświetla wynik.
5) Krok po kroku – robimy aplikację
Krok 1: Utwórz projekt
- Plik → Nowy → Projekt
- Wybierz: Aplikacja WPF (.NET)
- Nazwa:
MapLocatorWpf - Framework: .NET 8.0 (lub nowszy)
Krok 2: Dodaj WebView2
- W Eksploratorze rozwiązań kliknij prawym na projekt
- Zarządzaj pakietami NuGet…
- Wyszukaj:
Microsoft.Web.WebView2 - Zainstaluj
Krok 3: Zrób foldery jak w porządnym projekcie
W projekcie dodaj foldery:
- Modele
- Logika
- Walidacja
(Prawy na projekt → Dodaj → Nowy folder)
Krok 4: Model danych (Modele)
Dodaj klasę: Modele/ModeleLokalizacja.cs
namespace MapLocatorWpf.Modele
{
public class ModeleLokalizacja
{
public string? Adres { get; set; }
public double SzerokoscGeo { get; set; } // latitude
public double DlugoscGeo { get; set; } // longitude
}
}
Krok 5: Walidacja (czy uczeń wpisał adres?)
Dodaj klasę: Walidacja/WalidacjaAdresu.cs
namespace MapLocatorWpf.Walidacja
{
public static class WalidacjaAdresu
{
public static bool CzyPoprawny(string? adres)
{
// Minimalna walidacja: czy coś wpisano
return !string.IsNullOrWhiteSpace(adres);
}
}
}
Krok 6: Logika – pobieranie współrzędnych z API (Nominatim)
Dodaj klasę: Logika/LogikaGeokodowanie.cs
using System.Globalization;
using System.Net.Http;
using System.Text.Json;namespace MapLocatorWpf.Logika
{
public class LogikaGeokodowanie
{
private readonly HttpClient _httpClient = new HttpClient(); public LogikaGeokodowanie()
{
// Nominatim wymaga sensownego User-Agent (żeby nie być anonimowym botem).
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("MapLocatorWpf/1.0 (school project)");
} public async Task<(double lat, double lon)?> PobierzWspolrzedneAsync(string adres)
{
// Uwaga: adres trzeba zakodować do URL
string adresUrl = Uri.EscapeDataString(adres); // limit=1 – bierzemy pierwszy wynik, żeby było prosto
string url = $"https://nominatim.openstreetmap.org/search?q={adresUrl}&format=json&limit=1"; string json = await _httpClient.GetStringAsync(url); // Odpowiedź to tablica JSON. Jak pusta -> nie znaleziono.
using JsonDocument doc = JsonDocument.Parse(json);
var root = doc.RootElement; if (root.GetArrayLength() == 0)
return null; var pierwszy = root[0]; // Nominatim zwraca lat/lon jako stringi (np. "52.2297")
string latStr = pierwszy.GetProperty("lat").GetString() ?? "";
string lonStr = pierwszy.GetProperty("lon").GetString() ?? ""; // Parsujemy po kropce – dlatego InvariantCulture
if (!double.TryParse(latStr, NumberStyles.Float, CultureInfo.InvariantCulture, out double lat))
return null; if (!double.TryParse(lonStr, NumberStyles.Float, CultureInfo.InvariantCulture, out double lon))
return null; return (lat, lon);
}
}
}
Krok 7: Logika – obliczanie odległości (Haversine)
Dodaj klasę: Logika/LogikaOdleglosc.cs
namespace MapLocatorWpf.Logika
{
public static class LogikaOdleglosc
{
// Promień Ziemi w kilometrach
private const double R = 6371.0; public static double ObliczKm(double lat1, double lon1, double lat2, double lon2)
{
// Zamiana stopni na radiany
double dLat = StopnieNaRadiany(lat2 - lat1);
double dLon = StopnieNaRadiany(lon2 - lon1); double a =
Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
Math.Cos(StopnieNaRadiany(lat1)) * Math.Cos(StopnieNaRadiany(lat2)) *
Math.Sin(dLon / 2) * Math.Sin(dLon / 2); double c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a)); return R * c;
} private static double StopnieNaRadiany(double stopnie)
{
return stopnie * Math.PI / 180.0;
}
}
}
Krok 8: Interfejs – MainWindow.xaml
Otwórz MainWindow.xaml i wklej taki układ.
Uwaga: trzeba dodać namespace do WebView2.
<Window x:Class="MapLocatorWpf.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:wv2="clr-namespace:Microsoft.Web.WebView2.Wpf;assembly=Microsoft.Web.WebView2.Wpf"
Title="MapLocator - WPF" Height="650" Width="1100">
<Grid Margin="10">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="380"/>
<ColumnDefinition Width="10"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<!-- LEWY PANEL -->
<StackPanel Grid.Column="0">
<TextBlock FontSize="20"
FontWeight="Bold"
Text="MapLocator (WPF)"
Margin="0,0,0,15"/>
<!-- Adres -->
<TextBlock Text="Wpisz adres (np. Działdowo, Grunwaldzka 38):"
FontWeight="SemiBold"/>
<TextBox x:Name="txtAdres"
Height="30"
Margin="0,5,0,10"/>
<Button x:Name="btnSzukaj"
Content="Szukaj i pokaż na mapie"
Height="36"
Click="btnSzukaj_Click"/>
<Separator Margin="0,15,0,15"/>
<!-- Współrzędne -->
<TextBlock Text="Współrzędne znalezionego miejsca:"
FontWeight="SemiBold"/>
<TextBlock x:Name="txtWynikLat"
Margin="0,6,0,0"/>
<TextBlock x:Name="txtWynikLon"
Margin="0,2,0,10"/>
<Separator Margin="0,10,0,10"/>
<!-- Punkt stały -->
<TextBlock Text="Punkt stały (np. szkoła):"
FontWeight="SemiBold"/>
<TextBlock x:Name="txtPunktStaly"
Margin="0,6,0,10"/>
<Button x:Name="btnOdleglosc"
Content="Oblicz odległość do punktu stałego"
Height="36"
Click="btnOdleglosc_Click"
IsEnabled="False"/>
<TextBlock x:Name="txtOdleglosc"
FontSize="16"
FontWeight="Bold"
Margin="0,10,0,0"/>
<TextBlock Margin="0,20,0,0"
TextWrapping="Wrap"
Opacity="0.8">
W tej aplikacji „GPS” rozumiemy jako współrzędne (lat/lon).
Adres zamieniamy na współrzędne przez API,
a mapę wyświetlamy w kontrolce WebView2.
</TextBlock>
</StackPanel>
<!-- PRAWA STRONA – MAPA -->
<Border Grid.Column="2"
CornerRadius="8"
BorderThickness="1"
BorderBrush="#DDD">
<wv2:WebView2 x:Name="webMapa"/>
</Border>
</Grid>
</Window>
Jeśli w Twoim WPF nie ma PlaceholderText, usuń ten atrybut (nie wszystkie wersje mają).
Krok 9: Kod okna – MainWindow.xaml.cs
Otwórz MainWindow.xaml.cs i wklej:
using System.Globalization;
using System.Windows;
using MapLocatorWpf.Logika;
using MapLocatorWpf.Walidacja;
namespace MapLocatorWpf
{
public partial class MainWindow : Window
{
private readonly LogikaGeokodowanie _geokodowanie = new LogikaGeokodowanie(); // Punkt stały – tu ustawiasz np. szkołę (wstaw swoje realne współrzędne)
// Przykładowo: Działdowo (warto podmienić na dokładne miejsce szkoły)
private const double SZKOLA_LAT = 53.2390;
private const double SZKOLA_LON = 20.1700; private double? _znalezioneLat;
private double? _znalezioneLon; public MainWindow()
{
InitializeComponent(); // Opis punktu stałego w interfejsie
txtPunktStaly.Text = $"Szkoła: lat={SZKOLA_LAT.ToString("F6", CultureInfo.InvariantCulture)}, " +
$"lon={SZKOLA_LON.ToString("F6", CultureInfo.InvariantCulture)}"; // Startowo ustawiamy mapę na punkt stały (żeby od razu coś było)
UstawMape(SZKOLA_LAT, SZKOLA_LON, zoom: 15);
}
private async void btnSzukaj_Click(object sender, RoutedEventArgs e)
{
string adres = txtAdres.Text; // 1) Walidacja
if (!WalidacjaAdresu.CzyPoprawny(adres))
{
MessageBox.Show("Wpisz adres (minimum kilka znaków).", "Błąd", MessageBoxButton.OK, MessageBoxImage.Warning);
return;
} // 2) Pobranie współrzędnych
btnSzukaj.IsEnabled = false;
btnOdleglosc.IsEnabled = false;
txtOdleglosc.Text = ""; try
{
var wynik = await _geokodowanie.PobierzWspolrzedneAsync(adres); if (wynik == null)
{
MessageBox.Show("Nie znalazłem tego adresu. Spróbuj wpisać inaczej (np. miasto + ulica).",
"Brak wyników", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
_znalezioneLat = wynik.Value.lat;
_znalezioneLon = wynik.Value.lon; // 3) Wyświetlenie w UI
txtWynikLat.Text = $"Latitude (szerokość): {_znalezioneLat.Value.ToString("F6", CultureInfo.InvariantCulture)}";
txtWynikLon.Text = $"Longitude (długość): {_znalezioneLon.Value.ToString("F6", CultureInfo.InvariantCulture)}"; // 4) Ustaw mapę na znaleziony punkt
UstawMape(_znalezioneLat.Value, _znalezioneLon.Value, zoom: 16); // 5) Odblokuj liczenie odległości
btnOdleglosc.IsEnabled = true;
}
catch (Exception ex)
{
// Na lekcji warto powiedzieć: „To normalne, że internet czasem nie działa”
MessageBox.Show("Wystąpił błąd podczas pobierania danych z internetu.\n\n" + ex.Message,
"Błąd", MessageBoxButton.OK, MessageBoxImage.Error);
}
finally
{
btnSzukaj.IsEnabled = true;
}
}
private void btnOdleglosc_Click(object sender, RoutedEventArgs e)
{
if (_znalezioneLat == null || _znalezioneLon == null)
{
MessageBox.Show("Najpierw wyszukaj adres.", "Info", MessageBoxButton.OK, MessageBoxImage.Information);
return;
}
double km = LogikaOdleglosc.ObliczKm(_znalezioneLat.Value, _znalezioneLon.Value, SZKOLA_LAT, SZKOLA_LON); txtOdleglosc.Text = $"Odległość do szkoły: {km:F2} km";
}
private void UstawMape(double lat, double lon, int zoom)
{
// OpenStreetMap: ustawiamy widok mapy na danym punkcie
// Format: https://www.openstreetmap.org/#map=ZOOM/LAT/LON
string url = $"https://www.openstreetmap.org/#map={zoom}/{lat.ToString(CultureInfo.InvariantCulture)}/{lon.ToString(CultureInfo.InvariantCulture)}";
webMapa.Source = new Uri(url);
}
}
}
6) Co uczeń ma z tego „egzaminacyjnie”?
W tym projekcie uczeń ćwiczy dokładnie to, co często jest sprawdzane w INF.04:
- Zdarzenia (Click przycisków)
- Walidacja danych (czy wpisano adres)
- Komunikacja z API (HttpClient, JSON)
- Przetwarzanie danych (parsowanie, liczby)
- Algorytm / obliczenia (odległość – Haversine)
- UI + logika (podział na foldery, porządek w projekcie)
- Praca z kontrolką zewnętrzną (WebView2)
7) Rozszerzenia (jak chcesz dopalić temat na kolejną lekcję)
- Lista ulubionych miejsc (ListBox + zapis do JSON).
- Reverse geocoding (klik na mapie → adres).
- „Najbliższy punkt” z listy 10 lokalizacji (pętla + min).
- Prosty MVVM (binding do pól zamiast
txtWynikLat.Text = ...).
8) Typowe problemy i szybkie rozwiązania
- WebView2 nie działa / czarny ekran
Zwykle brakuje runtime WebView2 w systemie. Na większości Windows 10/11 jest, ale jak nie: instalacja „Microsoft Edge WebView2 Runtime”. - API zwraca pustą listę
Adres wpisany zbyt ogólnie. Dopisz miasto, kraj. - Zbyt dużo zapytań
To publiczne API. Na lekcji spoko, ale nie róbmy „odświeżania co 100 ms”.

