Serwer: hostowane na Mikrusie — dziękujemy za wsparcie projektu.
Wersja 1.7.93: czerwony alarm dociera na zegarek z Wear OS, wibracja mówi
alfabetem Morse'a (żółty „U”, czerwony „A”), a czerwony ma trzy tryby: syrena,
sama wibracja albo zwykłe powiadomienie. Karta województwa mówi, do kiedy
trzyma się poziom, a historia pokazuje to samo co mapa na żywo.
Szczegóły: docs/RELEASE_1.7.93.md.
Wersja 1.7.92: poprawka bezpieczeństwa. Silnik aplikacji (Capacitor) podniesiony
do wersji z poprawką publicznie ogłoszonej luki, a linki ze źródeł zewnętrznych
nie mogą już prowadzić do wnętrza aplikacji. Wygląd i działanie bez zmian.
Zalecamy aktualizację. Szczegóły: docs/RELEASE_1.7.92.md.
Wersja 1.7.91: podkarpackie słyszy drony, telefon ostrzega o uśpieniu.
Obiekt punktuje teraz każde województwo w zasięgu, z wagą zależną od
odległości do niego — dotąd punkty dostawało tylko województwo z najbliższym
odcinkiem granicy, przez co drony lecące na podkarpackie liczyły się
lubelskiemu (przeliczone na dwóch miesiącach historii: podkarpackie
17 → 27 żółtych sygnałów, lubelskie bez zmian, czerwonych nie przybywa).
Aplikacja ostrzega, gdy telefon ogranicza jej baterię na nakładkach, które
potrafią przez to wstrzymać powiadomienia, i pokazuje godzinę ostatniego
sygnału z serwera. Strefy PAŻP znikają w podglądzie historii, bo nie
zapisujemy ich w migawkach. Karta drona nie twierdzi już, że przyjmujemy
„wariant groźniejszy” — odrzutowego Shaheda rozpoznajemy tylko z oznaczenia
w źródle. Progi alarmów bez zmian.
Szczegóły: docs/RELEASE_1.7.91.md.
Wersja 1.7.90: wielkość tekstu, świeży wykaz schronień i jedna prędkość
w całej aplikacji. W Ustawieniach → Aplikacja doszedł wybór wielkości tekstu
(Normalna / Większa / Największa) — powiększa wszystkie napisy naraz, działa
od razu i mnoży się z systemowym rozmiarem czcionki na Androidzie. GROTA
dostała wykaz PSP z 28 września (86 533 miejsca) oraz listę schronień do
przejścia czytnikiem ekranu i klawiaturą. Czas dolotu liczy się teraz jedną
regułą na serwerze i w aplikacji: prędkość typowa jest podłogą, a pomiar
z ruchu może ją tylko podnieść — karta pokazuje dokładnie tę liczbę, na której
oparty jest alarm. Naprawione: rozwinięty pasek źródeł wchodził pod pasek
tytułu, a dron rozpoznawczy nie dostawał czasu dolotu. Punktacja i progi
alarmów bez zmian.
Szczegóły: docs/RELEASE_1.7.90.md.
Wersja 1.7.89: porządek w oknach. Ustawienia, legenda i okno „O aplikacji"
nie wysypują już całej treści naraz — wyjaśnienia są schowane pod nazwanymi
sekcjami przy swojej opcji, a sterowania zostają widoczne zawsze. Legenda
dostała wreszcie przycisk zamykania, jej sześć grup rozwija się pojedynczo,
a samolot i śmigłowiec rysowane są tą samą kreską co na mapie. Cały interfejs
ma jeden kształt przycisku, a zdublowany „Zamknij" zniknął z pięciu okien.
Przy okazji naprawione tłumaczenia: legenda i część ustawień zostawały po
polsku w wersji angielskiej i ukraińskiej. Punktacja, progi i alarmy bez zmian.
Szczegóły: docs/RELEASE_1.7.89.md.
Wersja 1.7.88: Strażnik czyta media także w Mołdawii, Rumunii, na Słowacji,
w Czechach, Szwecji i na Węgrzech. Te sześć krajów podświetla się na mapie i ma
własną kartę, ale nie dodaje ani jednego punktu — czeskie i słowackie
redakcje piszą o cudzej przestrzeni powietrznej częściej niż o własnej, więc
przez kilka tygodni tylko sprawdzamy, czy filtr nie bierze zdarzenia w Danii za
ich własne. Przy okazji: karta kraju sąsiedniego nie otwierała się ani razu od
22 września, alarmy w rejonach Ukrainy są wreszcie tłumaczone, a na mapie
przybyła Szwecja. Kamery w regionie to teraz sama lista z odnośnikami —
podglądu nie pokazujemy, bo regulamin worldcam.pl na to nie zezwala.
Szczegóły: docs/RELEASE_1.7.88.md.
Wersja 1.7.86: czerwony alarm może budzić samą wibracją. Nowy przełącznik
⚙ → Dźwięk → „Czerwony alarm: tylko wibracja, bez syreny” (Android) wycisza
samą syrenę — wibracja, pełny ekran, miganie i przejście przez tryb Nie
przeszkadzać zostają, a sparowany zegarek zawibruje tak samo. Ustawienia
dźwięku przestały ze sobą walczyć: „Test: syrena” gra mimo ciszy, a opcja
pełnej głośności jest przy niej nieczynna. Na iPhonie działa już przycisk
zgody na alarm mimo wyciszenia — Apple przyznało uprawnienie Critical Alerts
28.09.2026, a przycisk z 1.7.84 chował się sam przed sobą. Szczegóły:
docs/RELEASE_1.7.86.md.
Wersja 1.7.85: zakładka Zasady w GROCIE mówi to, co Komenda Główna PSP
odpowiedziała na wniosek o informację publiczną (pismo z 25.09.2026). Wycofana
obietnica dodania daty weryfikacji punktu — rejestr jej nie prowadzi i nie
planuje udostępniać. Dopisane: co naprawdę znaczy „Dostępność”, czego urząd nie
wyjaśnił (kto otwiera punkt „na żądanie” w czasie alarmu), że rejestr obejmuje
też obiekty w postępowaniu i byłe budowle ochronne „bez wskazywania ostatecznej
kategorii obiektów”, oraz że wykaz aktualizuje się raz w tygodniu i nie ma API.
Działanie aplikacji bez zmian. Szczegóły: docs/RELEASE_1.7.85.md.
Wersja 1.7.84: widać, po której stronie granicy jest obiekt. Koło niepewności
nie zaciemnia już mapy — zostaje sam przerywany okrąg, bo przyciemniona plama
±4 km kładła się na polskich wsiach i wyglądała jak zagrożenie nad nimi. Obiekt
bliżej niż 10 km od granicy ma pod ikoną napisaną odległość: ikona ma 30 pikseli,
więc sam rysunek tego nie rozstrzyga, a liczba tak. Na iPhonie doszedł przycisk
zgody na alarm mimo wyciszenia (Apple przyznało uprawnienie 28.09; dotyczy
wyłącznie czerwonego alarmu). Szczegóły: docs/RELEASE_1.7.84.md.
Wersja 1.7.83: mapa rysuje obiekt tam, gdzie go zgłoszono. Znacznik jechał
zmierzonym kursem nawet 18 km przed ostatni meldunek, więc dron 0,2 km za granicą
pod Dorohuskiem wyglądał na mapie, jakby był 3 km nad Polską — przy karcie, która
w tym samym czasie pisała „0,2 km od granicy”. Przewidywanie pozycji usunięte;
płynny przejazd między prawdziwymi meldunkami zostaje. Punktacja, progi i alarmy
bez zmian — zawsze liczyły się ze zgłoszenia. Szczegóły: docs/RELEASE_1.7.83.md.
Wersja 1.7.82: mniej danych. Ilustracje obiektów przeszły z PNG na WebP — osiem
plików waży 91 KB zamiast 12,4 MB, wyglądają tak samo, a APK chudnie o 12 MB.
Mapa Ukrainy (pół megabajta) pobiera się dopiero, gdy są tam alarmy, zamiast przy
każdym otwarciu. Serwer przysyła tylko tę część stanu, która się zmieniła, a przy
pełnej ciszy w kraju telefon pyta co 15 zamiast co 5 sekund — gdy cokolwiek jest
podniesione, tempo zostaje bez zmian. Zdjęcie maszyny w zwiniętej karcie jest
większe. Punktacja i alarmowanie bez zmian. Szczegóły: docs/RELEASE_1.7.82.md.
Wersja 1.7.81: alarm da się odczytać czytnikiem ekranu. Ekran alarmu jest oknem
alertdialog — czytnik przerywa to, co czyta, i podaje poziom oraz województwo,
a tabulator krąży po jego przyciskach zamiast schodzić na zasłoniętą mapę.
Wzrost poziomu do żółtego i powrót do zwykłego czytnik ogłasza sam, z nazwą
województwa. Systemowe „ogranicz ruch” wyłącza wszystkie pulsujące elementy. Przy
okazji okno aktualizacji nie wchodzi już pod górny pasek, a wiersz źródeł na mapie
przewija się palcem. To pierwszy krok w stronę dostępności, a nie ogłoszenie, że
aplikacja jest dostępna. Szczegóły: docs/RELEASE_1.7.81.md.
Wersja 1.7.80: alert RCB wysłany do kilku województw naraz przepadał bez śladu —
listę odbiorców rozpoznawaliśmy po jednej formie słowa, a RCB używa innej właśnie
przy kilku województwach. Alert wiszący godzinami bez odwołania jest teraz widoczny
wprost (bez punktów), odwołanie „brak zagrożenia na terenie Polski” gasi wszystkie
województwa, pozycja z MLAT nie udaje ADS-B, a wykaz punktów schronienia jest ze
stanu z 21 września (86 388 miejsc). Szczegóły: docs/RELEASE_1.7.80.md.
Wersja 1.7.79: odwołanie alarmu przez RCB nie jest już liczone jako alarm, a kolor
województwa w historii bierze się z poziomu alarmu, nie z samych punktów. Pierwsze
wydanie cięte z jednego commita dla Androida i iPhone'a. Szczegóły: docs/RELEASE_1.7.79.md.
Wersja 1.7.78: regulacja głośności sygnału uwagi i naprawiona hierarchia dźwięków,
alert RCB czytany z całej treści komunikatu, godzina w powiadomieniu, a pasek historii
nie pokazuje już czerwonego tam, gdzie alarm był żółty. Szczegóły: docs/RELEASE_1.7.78.md.
Wersja 1.7.76: cała aplikacja mówi po ukraińsku — ekrany, karty obiektów, historia,
ekran alarmu i opisy źródeł. Brakujące tłumaczenie spada na angielski, nie na pusty
napis. Szczegóły: docs/RELEASE_1.7.76.md.
Wersja 1.7.74: czerwony alarm wymaga potwierdzenia — same 4 punkty nie wystarczą bez
alertu RCB „znajdź bezpieczne miejsce” albo obiektu uderzeniowego kursem na Polskę.
Alert RCB punktowany według treści (1,5 / 3 / 4,5 pkt), drony rozpoznawcze 0,5 → 0,15 pkt.
Szczegóły: docs/RELEASE_1.7.74.md.
Wersja 1.7.72: GROTA mówi po angielsku i ukraińsku, a pobrana mapa jednego województwa
nie gubi już ulic poza nim. Szczegóły: docs/RELEASE_1.7.72.md.
Wersja 1.7.70: zdjęcie z góry na karcie punktu schronienia znowu się wyświetla —
usługa ortofotomapy GUGiK zmieniła sposób działania, a Androidy starsze niż 14 nie
ufały certyfikatowi Geoportalu. Szczegóły: docs/RELEASE_1.7.70.md.
Wersja 1.7.68: nowy moduł GROTA. Po potwierdzeniu alarmu przycisk „Gdzie się
schronić” otwiera najbliższe punkty schronienia z publicznego wykazu PSP, z odległością
i trasą; wykaz działa bez internetu. Szczegóły: docs/RELEASE_1.7.68.md.
Wersja 1.7.67: aplikacja pamięta wybrany widok mapy (2D albo 3D), a gdy mapa nie wczyta
się w 20 sekund, zamiast pustego ekranu pojawia się wyjaśnienie. Szczegóły:
docs/RELEASE_1.7.67.md.
Wersja 1.7.66: przyciski mapy chowają się strzałką, aktywny alert RCB nie jest już brany
za odwołany, a artykuły powtarzające jego treść nie dokładają drugich punktów za tę samą
informację. Szczegóły: docs/RELEASE_1.7.66.md.
Wersja 1.7.65: na części nowych telefonów górny pasek wchodził pod zegar i ikony
powiadomień. Telefon, który nie pozwala rysować map, pokazuje wyjaśnienie zamiast pustego
ekranu — alarmy, sygnały i historia działają także bez mapy. Szczegóły: docs/RELEASE_1.7.65.md.
Wersja 1.7.64: naprawiona aktualizacja na Androidzie 9 i 10. Błąd „podpis aktualizacji
nie zgadza się” nie był włamaniem — podpis był w porządku, to aplikacja źle go odczytywała.
Szczegóły: docs/RELEASE_1.7.64.md.
Wersja 1.7.63: koniec stałego połączenia z serwerem. Aplikacja pyta o stan i dołącza
znacznik wersji, którą już ma; gdy nic się nie zmieniło, odpowiada Cloudflare ze swojego
brzegu. Do serwera trafia najwyżej jeden stan na dwie sekundy na centrum danych,
niezależnie od liczby telefonów. Szczegóły: docs/RELEASE_1.7.63.md.
Wersja 1.7.62: historia nie gubi już godzin spędzonych w tle — zminimalizowana aplikacja
nic nie nagrywa, więc po powrocie dociąga brakujący okres z serwera. Szczegóły:
docs/RELEASE_1.7.62.md.
Notatki wszystkich wcześniejszych wydań leżą w katalogu docs/ jako pliki
RELEASE_*.md.
Nieoficjalne wczesne ostrzeganie o zagrożeniach powietrznych
Fuzja kilku niezależnych sygnałów dla Polski, z priorytetem dla ściany wschodniej. Żaden pojedynczy sygnał nie jest rozstrzygający — dopiero kombinacja podnosi wiarygodność ostrzeżenia.
Instrukcja po polsku · English user guide · Інструкція українською
Instrukcja dla 1.7.79 opisuje „Moje miejsca”, rozdział alertów wojewódzkich i lokalnych obliczeń na pierwszym planie, regulację głośności żółtego sygnału oraz bibliotekę fotografii PL/EN/UA. Zrzuty otwierają się w pełnym rozmiarze.
⬇ Pobierz na Androida (APK) · ⬇ Pobierz na iOS (App Store) · ☕ Postaw kawę
📖 Instrukcja: Android · iPhone · English (Android) · English (iPhone) · Українська (Android)
Warning
NIEOFICJALNE źródło dodatkowe. Nie zastępuje syren, alertów RCB ani RSO. W razie realnego zagrożenia kieruj się oficjalnymi kanałami. Aplikacja korzysta ze źródeł publicznych, w tym crowdsourcingowych, które mogą się mylić, spóźniać lub milczeć.
Aplikacja zbiera sygnały z kilku niezależnych źródeł, przydziela im punkty i sumuje w oknie 60 minut (pełna waga przez 30 minut, potem liniowe wygaszanie) osobno dla każdego województwa. Przy ≥ 2 pkt włącza podwyższoną uwagę (sygnał uwagi i powiadomienie). Głośny alarm z syreną wymaga ≥ 4 pkt ORAZ potwierdzenia: Alertu RCB o treści „znajdź bezpieczne miejsce" albo obiektu uderzeniowego kursem na Polskę ≤ 15 minut od granicy (albo ≤ 50 km). Sama suma punktów z sygnałów pośrednich nie zapala czerwonego (zmiana 23.09.2026). UI zawsze pokazuje pełne rozbicie: które sygnały, skąd, ile punktów.
| Sygnał | Warunek | Punkty |
|---|---|---|
| NEPTUN | obiekt kursem na granicę PL — punktacja zależna od typu, liczby, odległości i liczby potwierdzeń (niżej) | 0–8 |
| NEPTUN | oficjalny alarm powietrzny w obwodzie UA graniczącym z PL | +1 |
| Media/RSS | obiekt + zdarzenie: 1 pkt; jednoznaczna bieżąca relacja operacyjna: 1,5 pkt; historia, ćwiczenia i następstwa prawne: 0 pkt. Całe RSS ma limit 1,5 pkt i samo nie alarmuje | 0 / +1 / +1,5 |
| RCB / RSO | Alert RCB o zagrożeniu z powietrza — waga zależna od treści: „sytuacja jest monitorowana” 1,5; „trwa zmasowany atak” 3; „znajdź bezpieczne miejsce” 4,5. Źródłem jest RSO, a treść komunikatu dnia na gov.pl/web/rcb dokłada województwa, których RSO nie przekazało; sama lista wpisów gov.pl to tylko punkt odniesienia w czasie (0 pkt) | +1,5 / +3 / +4,5 |
| ADS-B | ≥3 maszyny wojskowe nad województwem i >2× baseline z tej samej pory doby z 7 dni | +1 |
| PAŻP | rzadka strefa ADHOC/R/NPZ/D obejmująca całą kolumnę od ziemi w górę; TRA/TSA/MRT/ATZ i designatory powtarzane w ciągu 7 dni nie punktują | +0,5 |
| Media LT/LV/EE | incydent powietrzny wg mediów bałtyckich → podlaskie + warmińsko-mazurskie; komunikat kończący wygasza wkład tego samego zdarzenia | +1 |
| Sąsiedzi (RO/EE/LT) | aktywne zamknięcie przestrzeni u sąsiada NATO — sygnał obserwacyjny, wyprzedzający | +0,3 |
Jeden Shahed 80 km od granicy to co innego niż sześć Shahedów 50 km od granicy, a lokalny dron FPV nie jest punktowany w tym modelu — nie oznacza to, że jest nieszkodliwy. Zamiast jednej stawki za „obiekt kursem na PL" punkty uwzględniają kilka czynników:
punkty = waga_typu × √liczba × k_odległości × k_wiarygodności × k_potwierdzeń × k_cyklu × k_kursu
| Czynnik | Wartości |
|---|---|
| waga typu | balistyczna 3,0 · MiG-31K 2,6 · manewrująca 2,4 · KAB 1,8 · Shahed 1,4 · dron 1,1 · zwiadowczy 0,5 · FPV 0 |
liczba (count) |
pierwiastek — cztery obiekty ważą 2× tyle co jeden, nie 4× |
| odległość | płynna interpolacja: 0 km ×1,7 · 15 ×1,6 · 45 ×1,3 · 80 ×1,0 · 110 ×0,7 · 150 ×0,4 · 200 ×0,25 · 250 ×0,1; od 250 km wkład 0 |
| wiarygodność | high ×1,0 · medium ×0,6 · low ×0,35 |
potwierdzenia (sourceCount) |
1 ×0,7 · 2 ×0,9 · 3–4 ×1,1 · ≥5 ×1,25 |
| cykl życia | confirmed ×1,1 · uncertain ×0,85 · created ×0,7 |
Wkład całego NEPTUN-a ograniczony do 8 pkt — przy kilkudziesięciu obiektach suma i tak dawno przekroczyła próg alarmu, a trzycyfrowa punktacja psułaby skalę.
Historyczna kalibracja (wcześniejsza krzywa odległości). Poniższe wyniki opisują wcześniejszy model i nie są aktualną tabelą progów. Obecny algorytm ma płynną krzywą, współczynnik kursu, minimum dla ciężkich obiektów przy granicy i progi ETA opisane w aktualnej instrukcji. Wagi dobrano na żywych danych NEPTUN i sprawdzono na udokumentowanych zdarzeniach oraz na 354 migawkach zebranych przez backend (1998 obserwacji obiektów w 7,5 h):
| Scenariusz | Punkty | Reakcja |
|---|---|---|
| masowe naruszenie granicy (~19 dronów blisko) | 8,0 | alarm |
| rakieta manewrująca 25 km od granicy | 4,7 | alarm |
| 6 Shahedów 50 km, 5 potwierdzeń | 6,1 | alarm |
| rutynowy nalot na zachodnią Ukrainę (110–130 km) | 1,3 | brak reakcji |
| pojedynczy dron 90 km, jedno zgłoszenie | 0,4 | brak reakcji |
| FPV tuż przy granicy | 0,0 | brak reakcji |
| zebrana historia (wszystkie obiekty ≥ 542 km) | 0,0 | brak reakcji |
Klasyfikacja mediów ma trzy jawne wyniki (textmatch.py): 0 pkt dla
ćwiczeń, administracji, publicystyki, historii oraz postępowań prawnych po
zdarzeniu; 1 pkt dla jednoczesnego obiektu powietrznego i zdarzenia
(AIR + EVENT); 1,5 pkt dla jednoznacznej
bieżącej reakcji operacyjnej, np. alarmu powietrznego, syren, poderwania
lotnictwa, zestrzelenia lub upadku obiektu. Sama fraza „naruszenie przestrzeni
powietrznej” jest niejednoznaczna czasowo i należy do poziomu 1 pkt.
Łączny wkład całej klasy RSS jest ograniczony do 1,5 pkt, więc same artykuły
nie mogą uruchomić żółtego progu. Wykluczenia mają pierwszeństwo. Filtry mogą się mylić.
Jedna klasa źródła ma limit wkładu do sumy
(media ≤1,5, RCB ≤2, ADS-B ≤1, PAŻP ≤1, sąsiedzi ≤0,6) — pięć artykułów o tym
samym zdarzeniu to wciąż jedno potwierdzenie. Nadmiarowe sygnały są widoczne
w UI z przekreśloną punktacją. Artykuł rozpoznany po treści, regionie i czasie
jako powtórzenie świeżego Alertu RCB pozostaje widoczny, ale wnosi 0 pkt; sama
wzmianka o RCB nie wycina artykułu z nową informacją.
Alarm czasowy NEPTUN. Dla obiektu o znanym albo wiarygodnie wyliczonym kursie, średniej/wysokiej pewności i co najmniej dwóch potwierdzeniach działa dodatkowe zabezpieczenie ETA do granicy Polski, nie adresu użytkownika: żółty przy konserwatywnym czasie ≤10 min, czerwony przy ≤5 min. Od surowego czasu odejmowane jest 2,5 min (p90 opóźnienia źródła z pomiaru 27–30.08.2026). Brak kursu, niska pewność lub jedno zgłoszenie nie mogą samodzielnie uruchomić alarmu ETA.
Propagacja jest kaskadowa: region z sumą ≥ 2 pkt po wyłączeniu regionalnych Alertów RCB/RSO przekazuje 40 % pozostałego wyniku sąsiadom, ci 40 % tego swoim sąsiadom i tak dalej, licząc po najkrótszej drodze (BFS) aż wkład spadnie poniżej 0,1 pkt. Alarm 5 pkt w lubelskim rozkłada się więc tak: sąsiedzi +2,0 (żółty próg), drugi krąg +0,8, trzeci +0,3, czwarty +0,1. Oficjalny nadawca sam wskazuje obszar Alertu RCB, więc ten wkład nie jest powielany u sąsiadów. Pozostałe zagrożenie na wschodzie podnosi czujność w całym kraju proporcjonalnie do odległości. Każdy wkład jest osobnym sygnałem „Przeniesienie z woj. X (… , 2. krąg)", więc nigdy nie miesza się z własnym sygnałem regionu.
[Neptun WS] ──┐ ┌─ aplikacja Android (push FCM per województwo)
[RSS media] ──┤ backend FastAPI na VPS ├─ straznik.eu (mapa 3D w przeglądarce)
[RCB gov.pl]──┼─► fuzja punktowa ─► SQLite ─► API ─┤
[ADS-B mil] ──┤ progi 2 / 4 pkt + WebSocket ├─ ntfy / Telegram / Web Push
[PAŻP AUP*] ──┘ (za Cloudflare, HTTPS) └─ fallback: wbudowany silnik w apce
Aplikacja domyślnie korzysta z serwera (https://straznik.eu): fuzja liczona
jest raz na serwerze, a nie na każdym telefonie osobno — mniejsze zużycie baterii
i darmowych limitów API. Gdy serwer jest niedostępny, aplikacja schodzi na
wbudowany silnik (frontend/engine.js — lustrzana kopia logiki fuzji w JS;
natywne żądania HTTP z WebView omijają CORS) jako fallback.
Powiadomienia push (FCM). Serwer przy wzroście poziomu wysyła wiadomość na
temat voiv_<region>; telefon subskrybuje temat swojego województwa i dostaje
alarm nawet przy zamkniętej aplikacji, wygaszonym ekranie i w trybie Doze — bez
usługi działającej w tle (Android 15/16 taką usługę pierwszoplanową i tak ubijał,
więc została wycofana). Poziom czerwony wyzwala pełnoekranowy alarm nad blokadą
(AlarmActivity), żółty — powiadomienie heads-up.
Wdrożenie. Backend działa na VPS, wystawiony tunelem Cloudflare pod
https://straznik.eu (TLS na brzegu Cloudflare, bez otwierania portu na origin).
Klucz konta serwisowego FCM leży poza repozytorium, w backend/data/ (.gitignore).
Alarmy obwodów UA. Neptun wysyła ramki alerts WebSocketem; backend utrzymuje
to gniazdo stale, więc oficjalne alarmy powietrzne w obwodach UA graniczących z PL
docierają na bieżąco i są punktowane (ua_alert_border). Stan źródła widać w
health.ua_alerts i na diodzie „Alarmy UA".
Historia 12 h — przewijana po stronie klienta. Zamiast pytać serwer o każdą
pozycję suwaka (/api/history?at= — przy wielu użytkownikach przewijających naraz
mnożyło zapytania i obciążało VPS), aplikacja pobiera całą historię raz
(/api/history/bundle: migawki pozycji + surowe sygnały okna) i trzyma ją w
pamięci (RAM), nie na dysku. Suwak liczy fuzję dla każdej chwili lokalnie tym
samym accumulate co silnik offline (engine.js historyFrom/timelineFrom),
więc przewijanie jest płynne i nie generuje ruchu do serwera. Bufor to okno
kroczące 12 h (~0,5–2 MB), samo się przycina i odświeża z żywego feedu WebSocket
(te same dane, które i tak płyną do mapy) — otwarta godzinami apka nie dociąga nic
dodatkowego. Tryb offline trzyma migawki w localStorage (też okno 12 h). Stary
backend bez /api/history/bundle jest znoszony łagodnie (krótsza historia).
* PAŻP: airspace.pansa.pl nie ma udokumentowanego API, ale jego mapa karmi się
publicznym GeoJSON-em — /map-configuration/uup i /map-configuration/aup
(adresy wskazuje /meta/configuration). Kolektor bierze stamtąd geometrię stref,
okna rezerwacji i pułapy, a przypisanie do województwa liczy przez
point-in-polygon na centroidzie strefy.
Instrukcje w tej sekcji służą autorowi i posiadaczom licencji. Samodzielne uruchamianie lub rozpowszechnianie Strażnika wymaga licencji autora (zob. Licencja).
cd backend
py -m pip install -r requirements.txt
copy .env.example .env # uzupełnij NTFY_TOPIC itd.
py -m uvicorn app.main:app --host 0.0.0.0 --port 8600 --app-dir .Dashboard: http://localhost:8600 · API: /api/state, /api/health, /api/history/bundle, /api/health/critical
Produkcyjnie backend działa na VPS na porcie
40141, wystawiony tunelem Cloudflare podhttps://straznik.eu. Push FCM wymaga klucza konta serwisowego wbackend/data/fcm-service-account.json(zob. „Powiadomienia").
Test warstwy Neptun bez serwera:
cd backend
py -m app.cli neptun # strumień WS na żywo z oceną odległości/kursu do granicy PL
py -m app.cli score # jednorazowy snapshot RESTTest fuzji end-to-end (endpoint domyślnie wyłączony — najpierw ustaw
TEST_SIGNAL_ENABLED=true w .env, żeby nie był publicznie dostępny na produkcji):
curl -X POST http://localhost:8600/api/test-signal -H "Content-Type: application/json" -d "{\"voivodeship\":\"lubelskie\",\"points\":2.5,\"title\":\"test\"}"Tryb wbudowany w przeglądarce: http://localhost:8600/?standalone=1
- FCM (push do aplikacji Android) — główna ścieżka. Serwer przy wzroście
poziomu wysyła wiadomość
datana tematvoiv_<region>; aplikacja subskrybuje temat swojego województwa (BackgroundPlugin.syncFcmSubscription) i dostaje alarm nawet przy zamkniętej aplikacji. Wymaga projektu Firebase: plikugoogle-services.jsonwandroid-app/android/app/oraz klucza konta serwisowego na serwerze wbackend/data/fcm-service-account.json(.gitignore). Włączane flagąFCM_ENABLED(domyślnietrue). Poziom czerwony → pełnoekranowy alarm z syreną; żółty → heads-up. - ntfy: zainstaluj aplikację ntfy, wymyśl długi losowy temat, wpisz go w
.env(NTFY_TOPIC=) i zasubskrybuj w aplikacji. Poziom WYSOKI idzie z priorytetemurgent. - Telegram:
@BotFather→/newbot→ token do.env; napisz do bota, odczytajchat_idzhttps://api.telegram.org/bot<TOKEN>/getUpdates. - Web Push (VAPID): przycisk 🔔 w dashboardzie/na
straznik.eu(wymagahttp://localhostlub HTTPS). Ta sama ścieżka obsłuży PWA na iOS 16.4+ (po „Dodaj do ekranu początkowego"). Subskrypcja zawiera wyłącznie listę obserwowanych województw — bez nazw miejsc, adresów i GPS. Trwale odrzucone endpointy są usuwane, a ponowne otwarcie strony aktualizuje przypisanie regionów.
Backend obserwuje na żywych danych hipotetyczne dodatkowe stopnie 2,5 / 3,0 /
3,5, ale nie łączy ich z żadnym kanałem powiadomień. Rejestruje również
eksperymentalny stopień oznaczony 1,5 dla świeżego, ciągłego śladu obiektu
lecącego w stronę Polski. Zasięg zależy od typu: do 250 km dla rakiet i
MiG-31K, 120 km dla KAB, 100 km dla dronów/Shahedów oraz 60 km dla obiektu
rozpoznawczego. Drugi wariant to wynik 1,5–2,0 potwierdzony przez co najmniej
dwie niezależne klasy źródeł, w tym NEPTUN/alarm UA, ADS-B albo PAŻP.
Obejmuje to dostępne dziś klasy wzmożonej czujności: wiarygodny obiekt lecący
ku Polsce (rakieta, pocisk, KAB, Shahed/dron, nosiciel lub rozpoznanie) oraz
zbieżność alarmu przy granicy, nietypowego ruchu wojskowego ADS-B, istotnej
strefy PAŻP, sygnału sąsiedniego i mediów. Nie każdy z nich wystarcza sam:
pozycje przybliżone, grupy, obiekt lecący od Polski, pojedynczy RSS, sama
propagacja, stare obserwacje i pierwszy niepotwierdzony meldunek nie kwalifikują
stopnia. Stan i próbki trafiają na 14 dni do tabel SQLite
escalation_shadow_state oraz escalation_shadow_events.
ESCALATION_SHADOW_ENABLED=false wyłącza obserwację. Nawet po jej włączeniu
moduł nie importuje warstwy powiadomień i nie wysyła push, dźwięku ani alarmu.
Oficjalne alarmy lotnicze RCB/RSO są ponadto zapisywane jako punkty odniesienia.
Dla każdego nowego komunikatu backend zachowuje 30 minut wcześniejszych klatek
mapy, sygnałów, punktacji i kandydatów trybu cienia. Osobno przechowuje surowy
valid_from RSO, jego znormalizowany czas oraz chwilę pierwszego wykrycia na
VPS. valid_from nie jest dowodem doręczenia SMS użytkownikowi; publiczne dane
nie ujawniają takiego czasu. Wpisy zastane podczas startu są oznaczone jako
niekwalifikujące się do pomiaru wyprzedzenia. Dane referencyjne są zachowywane
30 dni w rcb_reference_events, bez wpływu na wynik i wysyłkę.
RCB_REFERENCE_AUDIT_ENABLED=false wyłącza ten audyt.
Instrukcje w tej sekcji służą autorowi i posiadaczom licencji. Samodzielne uruchamianie lub rozpowszechnianie Strażnika wymaga licencji autora (zob. Licencja).
Każde APK musi być podpisane, a Android przyjmie aktualizację tylko wtedy, gdy
jest podpisana tym samym kluczem co wersja już zainstalowana. Buildy debug
używają klucza debug.keystore o publicznie znanym haśle (android), który
narzędzia potrafią zregenerować — na nim nie da się utrzymać ciągłości
aktualizacji. Build debug ma włączoną flagę debuggable; zaufanie certyfikatom
wynika osobno z konfiguracji sieci, nie z samego rodzaju podpisu.
Klucz tworzy się raz. Jego utrata oznacza, że użytkownicy nie zainstalują żadnej kolejnej wersji bez odinstalowania aplikacji (i utraty ustawień), więc zrób kopię pliku i zapisz hasła w menedżerze haseł.
cd android-app/android
"C:/Program Files/Android/Android Studio/jbr/bin/keytool" -genkeypair -v -keystore straznik-release.jks -alias straznik -keyalg RSA -keysize 4096 -validity 10000Polecenie zapyta o hasło (dwa razy) i o dane właściciela — wystarczy imię lub nazwa projektu, reszta może zostać pusta. Następnie:
cp keystore.properties.example keystore.properties…i wpisz w nim swoje hasła. Plik keystore.properties, *.jks i *.keystore
są w .gitignore, więc nie trafią do repozytorium.
Budowanie podpisanego wydania:
cd android-app/android
JAVA_HOME="C:/Program Files/Android/Android Studio/jbr" ./gradlew assembleReleaseWynik: app/build/outputs/apk/github/release/app-github-release.apk. Weryfikacja podpisu:
"$ANDROID_HOME/build-tools/37.0.0/apksigner" verify --print-certs -v app/build/outputs/apk/github/release/app-github-release.apkBez keystore.properties build wydania nadal się wykona, ale APK nie zostanie
podpisany — to celowe, żeby wydanie nigdy nie wyszło z kluczem debug.
Od wersji 1.7.12 aplikacja ufa wyłącznie systemowym certyfikatom i blokuje zewnętrzny HTTP. Jedyny wyjątek to dokładnie
localhost: wirtualny interfejs Capacitor, pozostawiony pod HTTP dla zachowania ustawień użytkowników. Sieć lub antywirus przechwytujący TLS certyfikatem użytkownika może blokować pobieranie danych. Nie wyłączamy weryfikacji certyfikatów jako obejścia. Zmiana wymaga nowego APK; starsze wydanie 1.7.11 nadal ufa certyfikatom użytkownika. Samo zaktualizowanie serwera nie zmienia jego zabezpieczeń.
Instrukcje w tej sekcji służą autorowi i posiadaczom licencji. Samodzielne uruchamianie lub rozpowszechnianie Strażnika wymaga licencji autora (zob. Licencja).
cd android-app
rm -rf www && cp -r ../frontend www && rm www/sw.js
npx cap sync android
cd android && JAVA_HOME="C:/Program Files/Android/Android Studio/jbr" ./gradlew assembleReleaseWynik: android-app/android/app/build/outputs/apk/github/release/app-github-release.apk.
Po testach i sprawdzeniu podpisu wydania kopiujemy go jako Straznik.apk.
Nigdy nie publikujemy app-debug.apk jako wydania. Przed publikacją sprawdź
apksigner verify --print-certs, brak debuggable oraz skompilowaną konfigurację
sieci. Aktualizacja musi zachować certyfikat podpisujący poprzednie wydanie.
Push FCM wymaga pliku
android-app/android/app/google-services.json(z projektu Firebase) — bez niego wtyczkagoogle-servicessię nie aktywuje i push nie działa. Na maszynie z antywirusem skanującym TLS (Avast/Kaspersky/ESET) Gradle może nie pobrać zależności Firebase (PKIX path building failed) — trzeba wskazać JVM magazyn certyfikatów z CA antywirusa (import certu do kopiicacerts+-Djavax.net.ssl.trustStore=…); po jednorazowym pobraniu deps wpadają do cache.
backend/app/collectors/— neptun.py (WS+REST), rss_media.py, rcb.py, adsb.py, pansa.pybackend/app/fusion.py— silnik punktowy (przejrzysty, nie ML)backend/app/geo.py— punkty referencyjne granicy, haversine, ocena kursubackend/data/straznik.db— SQLite: pełny log sygnałów (ts, źródło, punkty, województwo)frontend/— mapa 3D MapLibre GL, panel sygnałów, legenda, ekran „O aplikacji"frontend/engine.js— wbudowany silnik (fallback, gdy serwer niedostępny)backend/app/notify.py— kanały powiadomień: FCM (tematy per województwo), ntfy, Telegram, Web Push (VAPID)android-app/— opakowanie Capacitor (WebView) + projekt Gradleandroid-app/android/app/src/main/java/pl/straznik/app/— warstwa natywna:StraznikFcmService(odbiór pushy FCM → powiadomienie/alarm),Alarms(kanały powiadomień i budowa alarmu),AlarmActivity(pełnoekranowy alarm nad blokadą),BackgroundPlugin(most do JS: subskrypcja tematów FCM, zgody na powiadomienia i alarm pełnoekranowy),MainActivityandroid-app/android/app/google-services.json— konfiguracja Firebase (niesekretna)docs/— instrukcja użytkownika (GitHub Pages) i zrzuty ekranuscripts/build_cams.py— odświeżanie listy kamerscripts/build_sounds.py— generowanie dźwięków alarmów dores/raw/
minSdk 24 (Android 7.0) — targetSdk 36 (Android 16). Obsłużone różnice:
kanały powiadomień od API 26, uprawnienie POST_NOTIFICATIONS od API 33, zgoda
USE_FULL_SCREEN_INTENT od API 34, wyjątek od optymalizacji baterii od API 23.
Layout używa env(safe-area-inset-*), więc pasek nie chowa się pod wycięciem ani
paskiem systemowym; na wąskich ekranach przyciski zwijają się do ikon. Testowane
na fizycznym urządzeniu (Android 16) i emulatorze Pixel 7 (Android 14).
Alarmy przy zamkniętej aplikacji: FCM push, nie usługa w tle. Wcześniejsze
wersje pilnowały źródeł natywną usługą pierwszoplanową (MonitorService), ale
Android 15/16 agresywnie ją ubijał (Stop FGS timeout kilka sekund po starcie),
więc została wycofana. Teraz alarm wysyła serwer przez FCM na temat voiv_<region>,
a StraznikFcmService buduje z niego powiadomienie — działa przy zamkniętej
aplikacji, w trybie Doze i po restarcie telefonu, bez usługi w tle i bez drenowania
baterii. Gdy serwer jest niedostępny, aplikacja i tak działa na wbudowanym silniku
(alarmy wtedy przy otwartej aplikacji).
Alarm pełnoekranowy (AlarmActivity.java) przy poziomie czerwonym działa
jak połączenie przychodzące: zapala ekran, pokazuje się nad blokadą, miga
(te same barwy i tempo co #alarm-overlay w CSS), gra syrenę w pętli i wibruje
do czasu potwierdzenia. Jest natywny, nie w WebView — musi pojawić się
także wtedy, gdy proces aplikacji nie działa, o ile system pozwoli na dostarczenie
powiadomienia i jego pełnoekranową prezentację. Wyzwala go full-screen
intent z powiadomienia FCM (StraznikFcmService).
Na Androidzie 14+ należy sprawdzić uprawnienie USE_FULL_SCREEN_INTENT
w ustawieniach, również po aktualizacji. Bez niego pełny ekran nie jest
gwarantowany, więc aplikacja prosi
o zgodę w ustawieniach (⚙ → 🚨 Zgoda na alarm pełnoekranowy), przypomina o niej
banerem „Napraw". Przy braku zgody pozostaje powiadomienie systemowe; aplikacja
podejmuje próbę wybudzenia, ale nie gwarantuje zapalenia ekranu. Działanie zależy
też od ustawień powiadomień, kanałów, trybu Nie przeszkadzać i ograniczeń systemu.
Dźwięki generuje scripts/build_sounds.py do res/raw/ — te same przebiegi,
które otwarta aplikacja syntetyzuje w Web Audio (żółty: dwutonowy sygnał
740↔988 Hz, czerwony: modulowana syrena 380↔860 Hz przez filtr dolnoprzepustowy).
Dzięki temu tło brzmi identycznie jak pierwszy plan. Po zmianie brzmienia
w app.js uruchom skrypt ponownie. Kanały powiadomień mają sufiks wersji
(-v3), bo raz utworzony kanał ignoruje późniejsze zmiany dźwięku.
Źródła fuzji: NEPTUN (obiekty + alarmy obwodów UA), media regionalne,
ogólnopolskie i bałtyckie, RCB, PAŻP, strefy sąsiadów (RO płn./EE/LT — sygnał
obserwacyjny, 0,3 pkt) oraz ADS-B (sygnał pomocniczy o wadze 1 pkt, wymaga
tygodnia próbek do baseline). Wszystkie liczy backend. Rozpoznawanie
województwa z tekstu obejmuje wszystkie 16 (VOIV_KEYWORDS), a jeden ogólnopolski
kanał Google News pokrywa regiony bez własnego feedu.
Lista aktualności łotewskich sił zbrojnych (mil.lv) jest dodatkowo sprawdzana
co 30 sekund jako instrumentacja bez punktów. Rejestruje początek i koniec
oficjalnego zagrożenia, aby mierzyć opóźnienia mediów; sama nie może wywołać
alarmu. Komunikaty bałtyckie typu „alert over / zagrożenie zakończone” również
nie dodają punktów — wygaszają wcześniejszy wpis tego samego incydentu.
Region i kaskada. Fuzja obejmuje wszystkie 16 województw, ze zdarzeniem na
wschodzie „przelewającym się" na sąsiadów (kaskada, config.VOIV_NEIGHBORS).
Push dociera tylko o wybranym województwie — telefon subskrybuje temat
voiv_<region> (bez wyboru: cztery przygraniczne), więc nie dostaje alertów
o zdarzeniach po drugiej stronie kraju, a wkład z sąsiedztwa i tak podnosi jego
poziom. Przykład: zdarzenie 5 pkt w lubelskim u użytkownika z ustawionym
mazowieckim daje „PODWYŻSZONA UWAGA: woj. mazowieckie (2.0 pkt)" z rozbiciem
„Przeniesienie z woj. lubelskie (5.0 pkt, sąsiad)".
Wcześniejsze wydanie sprawdzono na fizycznym urządzeniu (Android 16) przy zamkniętej aplikacji:
żółty wystawia heads-up bez budzenia ekranu; czerwony przy wygaszonym
i zablokowanym ekranie zapala go i pokazuje pełnoekranowy AlarmActivity nad
blokadą z syreną, a potwierdzenie zatrzymuje dźwięk i wibrację.
Testy bieżącej zmiany zabezpieczeń wykonano na emulatorze Pixel 7 / Android 14;
nie są gwarancją działania każdego telefonu. Raport testów.
Aplikacja sprawdza GET /api/app-version, a po wykryciu nowego wydania może
pobrać Straznik.apk bezpośrednio i otworzyć systemowy instalator Androida.
Przed instalacją natywnie sprawdza SHA-256 pliku; Android dodatkowo wymaga podpisu
tym samym kluczem autora. Zwykłą aktualizację można odłożyć przyciskiem „Później”
do końca bieżącej sesji. Wydanie oznaczone w opisie GitHub niewidocznym markerem
<!-- critical-update --> nie ma przycisku odłożenia. Systemowe potwierdzenie
instalacji pozostaje obowiązkowe — aplikacja nigdy nie instaluje się po cichu.
Każde kolejne wydanie GitHub Release musi mieć krótki opis zmian w punktach. Aplikacja pokazuje maksymalnie trzy pierwsze punkty w oknie aktualizacji jako sekcję „Co się zmienia”; opisuj w nich efekt widoczny dla użytkownika, nie detale techniczne.
- NEPTUN to agregator OSINT/crowdsourcingowy, nie radar — UI zawsze pokazuje
confidenceLeveli±uncertaintyKm. Wymagana atrybucja „Dane: NEPTUN" jest w UI. - Neptun pokazuje zagrożenia nad Ukrainą — obiekt, który wleci w polską przestrzeń, znika z danych źródłowych; system służy jako wyprzedzenie, nie śledzenie.
- ADS-B nie widzi lotnictwa nad Ukrainą (sprawdzone: 0 maszyn nad zachodnią Ukrainą przy 219 wojskowych globalnie). Wojsko UA i RU nie nadaje transponderów, przestrzeń jest zamknięta. Widać za to AWACS-y, tankowce i transportowce NATO nad Polską, Rumunią i Bałtykiem. To publiczne transpondery maszyn, które chcą być widoczne — nie namierzanie obiektów przeciwnika.
- Oprócz globalnej listy oznaczonej przez dostawcę jako wojskowa Strażnik odpytuje dwa ograniczone obszary geograficzne nad krajami bałtyckimi i lokalnie wybiera znane typy, operatorów oraz callsigny wojskowe. Ogranicza to pominięcia wynikające z błędnej flagi w rejestrze, ale nie zmienia wagi ADS-B ani nie obejmuje maszyn z wyłączonym transponderem.
- Myśliwców w akcji nie zobaczy żadne źródło ADS-B, jeżeli nie nadają jawnie — ani naziemne, ani satelitarne. Maszyny bojowe w misjach QRA nadają szyfrowany Mode 5 (IFF), a nie ADS-B; satelity (Aireon, Spire) odbierają dokładnie ten sam sygnał, więc zmiana dostawcy niczego nie doda. Multilateracja (MLAT) w ADSBexchange bywa w stanie wyliczyć pozycję maszyny nadającej tylko Mode S, ale i tak nie obejmie lotnictwa z wyłączonym transponderem. Dlatego ADS-B jest tu sygnałem pomocniczym o wadze 1 pkt, a nie podstawą alarmu.
- Ślady lotu Neptuna są w praktyce puste (pole
trailzawiera 0–2 punkty, zwykle zduplikowane). Dla pozycji nieoznaczonych jako przybliżone aplikacja może budować trajektorię z kolejnych obserwacji. PrzypositionQuality=approxpokazuje wyłącznie rejon zgłoszenia: bez dead-reckoning, pozornej trasy i ETA. Usunięcie wpisu przez źródło nie określa, czy obiekt zestrzelono, utracono czy zgłoszono ponownie pod innym identyfikatorem. - Kamery tylko z Polski — 641 publicznych kamer miejskich i turystycznych
(worldcam.pl) we wszystkich 16 województwach, w tym 582 plenerowe; każda
zweryfikowana pobraniem świeżego obrazu przy budowie listy. Podglądu NIE
pokazuję w aplikacji: regulamin worldcam.pl opisuje wyłącznie własne użycie
miniatur przez serwis i nie daje zgody na wyświetlanie ich u kogoś innego
(sprawdzone 03.10.2026). Kafelek otwiera kamerę u źródła. Pierwotnie użyłem
kamer drogowych traxelektronik.pl — okazało się, że wymagają logowania.
Listę odświeżysz skryptem
scripts/build_cams.py. Kamer z Ukrainy świadomie nie podpinam: od 2022 r. transmisje na żywo są tam zakazane, bo umożliwiają korygowanie ostrzału. - Baseline ADS-B potrzebuje ~tygodnia zbierania próbek, wcześniej warstwa nie punktuje.
- RSS/scraping może się zepsuć, gdy serwisy zmienią strukturę — status w LED-ach
i
/api/health. - Rozpoznawanie województwa z nagłówka jest heurystyczne. Opiera się na nazwach miast i regionów, więc pomija nazwy kolidujące ze słowami pospolitymi („piła", „żary", „hel", „brzeg"), a przy zbieżnościach („Chełm" i „Chełmno", „Radom" i „Radomsko") wygrywa pierwsze dopasowanie w kolejności listy. Regiony bez własnego kanału RSS pokrywa jedno ogólnopolskie zapytanie Google News, więc docierają do nich tylko mocne frazy („alarm powietrzny", „zawyły syreny").
Od wersji 1.7.54: wszelkie prawa zastrzeżone (LICENSE). Aplikację możesz bezpłatnie pobierać i używać, a kod czytać i zgłaszać do niego uwagi. Kopiowanie kodu, wydawanie zmienionych wersji albo udostępnianie funkcji aplikacji innym wymaga pisemnej licencji od autora — dotyczy to także firm, organizacji i instytucji publicznych. Licencje dla firm i instytucji: zgłoszenie (Issue) w repozytorium.
Strażnik jest source-available, a nie open source. Na liście SPDX nie ma licencji
o takich warunkach, więc nasza ma oznaczenie własne: LicenseRef-Straznik-1.0.
GitHub pokazuje ją w związku z tym jako „Other”.
Wersje do 1.7.53 włącznie pozostają na licencji MIT. Biblioteki, mapy, zdjęcia i dane innych podmiotów mają własne licencje — zob. NOTICE.
Oprogramowanie jest udostępniane „tak jak jest", bez gwarancji: to nieoficjalne źródło dodatkowe, nie system ratunkowy.
Hosting serwera: Mikrus (od 17.09.2026 serwer przekazany przez Mikrusa na potrzeby projektu).
NEPTUN (agregator OSINT; obiekty i alarmy obwodów UA) · adsb.lol / opendata.adsb.fi (w trybie awaryjnym też airplanes.live) · airspace.pansa.pl · gov.pl/RCB · media regionalne, bałtyckie i sąsiadów (MD, RO, SK, CZ, SE, HU — obserwacja, bez punktów) · kamery worldcam.pl · mapa © CARTO, © OpenStreetMap