TERYT (Teryt)¶
Ostatnia aktualizacja dokumentacji: 2026-02-26 Stan synchronizacji z kodem: ✅ Zsynchronizowany
Polski rejestr adresowy — hierarchiczne dane o województwach, powiatach, gminach i miejscowościach.
Co to jest?¶
Domena TERYT integruje system z Krajowym Rejestrem Urzędowym Podziału Terytorialnego Kraju. To jak oficjalna mapa administracyjna Polski — zawiera wszystkie województwa, powiaty, gminy i miejscowości z ich oficjalnymi kodami. Dzięki temu adresy w systemie są zgodne z rejestrem państwowym.
System obsługuje: - 16 województw - ~380 powiatów - Tysiące gmin - Dziesiątki tysięcy miejscowości - Hierarchiczne wyszukiwanie
Jakie problemy rozwiązuje?¶
Korzyści dla JST
- Poprawność adresów — oficjalne nazwy z rejestru
- Standaryzacja — jednolite kody terytorialne
- Autouzupełnianie — szybkie wyszukiwanie miejscowości
- Zgodność prawna — dane z oficjalnego rejestru GUS
Kluczowe funkcjonalności¶
Dla użytkownika¶
| Funkcja | Opis | Status |
|---|---|---|
| Lista województw | Wszystkie 16 województw | Dostępne |
| Lista powiatów | Powiaty w województwie | Dostępne |
| Lista gmin | Gminy w powiecie | Dostępne |
| Lista miejscowości | Miejscowości w gminie (po nazwach) | Dostępne |
Architektura domeny¶
Modele danych¶
Terc (app/Domain/TERYT/Model/Terc.php)¶
Model reprezentuje jednostkę terytorialną (województwo, powiat, gmina) z rejestru TERC.
- Tabela:
terc - Klucz główny: klucz kompozytowy
['woj', 'pow', 'gmi', 'rodz'](brak auto-increment) - Timestamps: brak
- Observer:
TercObserver(pusty — szkielet) - Fillable:
woj,pow,gmi,rodz,nazwa,nazdod,stan_na
| Pole | Typ (DB) | Cast | Opis |
|---|---|---|---|
woj | char(2) | string | Kod województwa (np. 14) |
pow | char(2) | string | Kod powiatu (np. 12); 00 dla województwa |
gmi | char(2) | string | Kod gminy (np. 05); 00 dla powiatu |
rodz | char(1) | string | Typ jednostki terytorialnej (1–9) |
nazwa | string(100) | string | Nazwa jednostki (np. WARMIŃSKO-MAZURSKIE) |
nazdod | string(100) nullable | string | Dodatkowa nazwa / typ (np. województwo, powiat) |
stan_na | date | datetime:Y-m-d | Data aktualności wpisu |
Logika wyróżniania poziomów hierarchii (przez filtrowanie):
| Poziom | Filtr pow | Filtr gmi |
|---|---|---|
| Województwo | = '00' | = '00' |
| Powiat | != '00' | = '00' |
| Gmina | (woj) | != '00' |
Simc (app/Domain/TERYT/Model/Simc.php)¶
Model reprezentuje miejscowość z rejestru SIMC.
- Tabela:
simc - Klucz główny:
sym(char 7, brak auto-increment) - Timestamps: brak
- Observer:
SimcObserver(pusty — szkielet) - Fillable:
woj,pow,gmi,rodz_gmi,rm,mz,nazwa,sym,sympod,stan_na - Indeksy:
simc_woj_index(woj),simc_pow_index(pow),simc_gmi_index(gmi)
| Pole | Typ (DB) | Cast | Opis |
|---|---|---|---|
woj | char(2) | string | Kod województwa |
pow | char(2) | string | Kod powiatu |
gmi | char(2) | string | Kod gminy |
rodz_gmi | char(2) | string | Typ gminy miejscowości (patrz tabela typów) |
rm | char(2) | string | Rodzaj miejscowości |
mz | char(2) | string | Czy miejscowość ma zameldowanie (0/1) |
nazwa | string(100) | string | Nazwa miejscowości (np. Jemiołowo) |
sym | char(7) | string | Identyfikator SIMC — klucz główny (np. 0231483) |
sympod | char(7) nullable | string | Identyfikator miejscowości nadrzędnej |
stan_na | date | datetime:Y-m-d | Data aktualności wpisu |
Diagram relacji¶
flowchart TD
A[Województwo\nTerc: pow=00, gmi=00] --> B[Powiat\nTerc: pow!=00, gmi=00]
B --> C[Gmina\nTerc: gmi!=00]
C --> D[Miejscowość\nSimc: sym PK]
D --> D2[Miejscowość nadrzędna\nSimc: sympod] Enum typów gminy (TERYTEnumService)¶
app/Domain/TERYT/Service/TERYTEnumService.php
Wartość rodz / rodz_gmi | Stała | Opis |
|---|---|---|
1 | COMMUNE_TYPE_MIEJSKA | Gmina miejska |
2 | COMMUNE_TYPE_WIEJSKA | Gmina wiejska |
3 | COMMUNE_TYPE_MIEJSKO_WIEJSKA | Gmina miejsko-wiejska |
4 | COMMUNE_TYPE_MIASTO_W_GMINIE_MW | Miasto w gminie miejsko-wiejskiej |
5 | COMMUNE_TYPE_OBSZAR_WIEJSKI_W_GMINIE_MW | Obszar wiejski w gminie miejsko-wiejskiej |
8 | COMMUNE_TYPE_DZIELNICA_WARSZAWY | Dzielnica Warszawy |
9 | COMMUNE_TYPE_DELEGATURA | Delegatura |
Filtrowanie typów
Wartości rodz 4, 5, 8, 9 są usuwane z tabeli TERC podczas importu (zostają tylko na potrzeby SIMC). Z tabeli SIMC usuwane są rodz_gmi 8 i 9.
API Endpoints¶
Wszystkie endpointy są publiczne (bez autoryzacji). Cała logika TERYT jest obsługiwana przez jeden parametryzowany endpoint:
Jeden endpoint — wiele poziomów¶
GET /api/no-auth/teryt/{provinceId?}/{districtId?}/{provinceName?}/{districtName?}/{cityName?}/{tercId?}/{cityId?}
Klasa: Domain\TERYT\Controller\TERYTController::run() Action: Domain\TERYT\Action\Controller\RunTERYTController Form Request: RunTerytControllerRequest
Endpoint dynamicznie dobiera poziom szczegółowości na podstawie podanych parametrów ścieżki:
| Parametry ścieżki | Zwracana lista |
|---|---|
| (brak) | Województwa |
{provinceId} | Powiaty w województwie |
{provinceId}/{districtId} | Gminy w powiecie |
{provinceId}/{districtId}/{provinceName}/{districtName}/{cityName} | Miejscowości w gminie |
Ograniczenie
Podanie wszystkich 7 parametrów (łącznie z tercId i cityId) skutkuje błędem HTTP z komunikatem „Sorry, you do not have access to this part of the website".
Walidacja parametrów (RunTerytControllerRequest)¶
| Parametr (query) | Reguły | Opis |
|---|---|---|
provinceId | nullable\|string\|size:2\|regex:/^\d{2}$/ | Kod województwa (2 cyfry) |
districtId | nullable\|string\|size:2\|regex:/^\d{2}$/ | Kod powiatu (2 cyfry) |
tercId | nullable\|string\|regex:/^\d+$/ | Kod TERC (dowolna liczba cyfr) |
cityId | nullable\|string\|size:7\|regex:/^\d{7}$/ | Identyfikator SIMC (7 cyfr) |
Autoryzacja
authorize() zwraca zawsze true — endpoint jest całkowicie publiczny.
Poziom 1: Lista województw¶
Zapytanie DB: Terc gdzie pow = '00' AND gmi = '00', sortowanie po nazwa.
Odpowiedź (TerytVoivodeshipResource):
[
{
"provinceId": "02",
"districtId": null,
"communeId": null,
"communeTypeId": null,
"provinceName": null,
"districtName": null,
"communeName": null,
"communeTypeName": null,
"statusDate": "2025-01-01",
"tercId": null,
"name": "DOLNOŚLĄSKIE",
"typeName": "województwo"
},
{
"provinceId": "14",
"districtId": null,
"communeId": null,
"communeTypeId": null,
"provinceName": null,
"districtName": null,
"communeName": null,
"communeTypeName": null,
"statusDate": "2025-01-01",
"tercId": null,
"name": "WARMIŃSKO-MAZURSKIE",
"typeName": "województwo"
}
]
Poziom 2: Lista powiatów¶
Przykład: /api/no-auth/teryt/14
Zapytanie DB: Terc gdzie woj = provinceId AND pow != '00' AND gmi = '00', sortowanie po nazwa.
Odpowiedź (TerytDistrictResource):
[
{
"provinceId": "14",
"districtId": "01",
"communeId": null,
"communeTypeId": null,
"provinceName": null,
"districtName": null,
"communeName": null,
"communeTypeName": null,
"statusDate": "2025-01-01",
"tercId": null,
"name": "bartoszycki",
"typeName": "powiat"
}
]
Poziom 3: Lista gmin¶
Przykład: /api/no-auth/teryt/14/12
Zapytanie DB: Terc gdzie woj = provinceId AND pow = districtId AND gmi != '00', sortowanie po nazwa.
Odpowiedź (TerytCommuneResource):
[
{
"provinceId": "14",
"districtId": "12",
"communeId": "05",
"communeTypeId": "2",
"communeTypeIdLang": null,
"provinceName": null,
"districtName": null,
"communeName": null,
"communeTypeName": null,
"statusDate": "2025-01-01",
"tercId": 14120520,
"name": "Olsztynek",
"typeName": "gmina wiejska"
}
]
communeTypeIdLang
Pole communeTypeIdLang jest wypełniane tylko dla typów gminy rodz > 3 (tj. 4, 5, 8, 9 — Miasto w gminie miejsko-wiejskiej, Obszar wiejski, Dzielnica Warszawy, Delegatura). Dla typów 1–3 pole przyjmuje null.
Budowa tercId: konkatenacja woj + pow + gmi + rodz rzutowana na int.
Poziom 4: Lista miejscowości w gminie¶
Przykład: /api/no-auth/teryt/14/12/warmińsko-mazurskie/olsztyński/olsztynek
Wyszukiwanie: case-insensitive (używa LOWER(nazwa) = ? przez whereRaw). Przekazywane nazwy mogą być w dowolnej wielkości liter.
Logika zapytania (TERYTCommuneCities): 1. Znajdź województwo: LOWER(nazwa) = voivodeshipName AND pow = '00' AND gmi = '00' 2. Znajdź powiat: LOWER(nazwa) = districtName AND woj = woj_z_kroku_1 AND gmi = '00' AND pow != '00' 3. Znajdź gminę: LOWER(nazwa) = communeName AND woj/pow z poprzednich kroków AND gmi != '00' 4. Pobierz miejscowości z SIMC: - Jeśli commune.rodz === '3' (gmina miejsko-wiejska): rodz_gmi IN ('4', '5') - W pozostałych przypadkach: rodz_gmi = commune.rodz 5. Sortowanie po nazwa
Odpowiedź (TerytCityResource):
[
{
"provinceId": "14",
"districtId": "12",
"communeId": "05",
"communeTypeId": "5",
"provinceName": "WARMIŃSKO-MAZURSKIE",
"districtName": "olsztyński",
"communeName": "Olsztynek",
"communeTypeName": null,
"statusDate": "2025-01-01",
"tercId": 14120552,
"cityName": "Jemiołowo",
"cityId": "0231483"
}
]
Nazwy w TerytCityResource
provinceName— pobierany z cache (TERYT_TERC_NAME_KEY), zapytanie:Terc(woj, pow='00', gmi='00').nazwadistrictName— pobierany z cache, zapytanie:Terc(woj, pow, gmi='00').nazwacommuneName— pobierany z cache (TERYT_COMMUNE_KEY), zapytanie:Terc(woj, pow, gmi, rodz=rodz_gmi).nazwacommuneTypeName— zawszenull
Logika biznesowa¶
Normalizacja kodów (TerytNormalizeCode)¶
app/Domain/TERYT\Action\Other\TerytNormalizeCode.php
Codes imported from CSV are padded with leading zeros to the expected length:
terytNormalizeCode("2", 2) → "02"
terytNormalizeCode("", 7) → "0000000"
terytNormalizeCode(null, 2) → "00"
Parametry: value (string|null), length (int, domyślnie 2).
Reguły wyszukiwania miejscowości dla gmin miejsko-wiejskich¶
Gmina rodz = '3' (miejsko-wiejska) zawiera dwa składniki w SIMC: - rodz_gmi = '4' — miasto (część miejska gminy) - rodz_gmi = '5' — obszar wiejski (część wiejska gminy)
Dlatego zapytanie używa whereIn('rodz_gmi', ['4', '5']) — zamiast filtrowania po rodz_gmi = '3'.
Jak to działa?¶
Hierarchia terytorialna¶
flowchart TD
A[Województwo] --> B[Powiat]
B --> C[Gmina]
C --> D[Miejscowość] Przykład¶
Caching¶
Dane TERYT są w całości cache'owane przez Redis/Cache z TTL = 86400 sekund (1 dzień).
| Klucz cache | Stała | Opis | TTL |
|---|---|---|---|
TERYT_VOIVODESHIPS_KEY | CacheKeyEnumService::TERYT_VOIVODESHIPS_KEY | Lista wszystkich województw | 1 dzień |
TERYT_DISTRICTS_KEY{woj} | CacheKeyEnumService::TERYT_DISTRICTS_KEY + voivodeshipId | Powiaty w województwie | 1 dzień |
TERYT_COMMUNES_KEY{woj}.{pow} | CacheKeyEnumService::TERYT_COMMUNES_KEY + woj.pow | Gminy w powiecie | 1 dzień |
TERYT_CITIES_KEY{woj}.{dist}.{gmi} | CacheKeyEnumService::TERYT_CITIES_KEY + woj.dist.gmi | Miejscowości w gminie (po nazwach) | 1 dzień |
TERYT_COMMUNE_KEY{pow}.{gmi}.{rodz} | CacheKeyEnumService::TERYT_COMMUNE_KEY | Dane gminy dla konkretnej miejscowości (Resource) | 1 dzień |
TERYT_TERC_NAME_KEY{woj}.{pow}.{gmi} | CacheKeyEnumService::TERYT_TERC_NAME_KEY | Nazwa jednostki dla konkretnych kodów (Resource) | 86400 s |
Wydajność
Dane TERYT rzadko się zmieniają (aktualizacje GUS ~raz na kwartał), więc długi cache jest bezpieczny.
Import danych¶
Komenda importu¶
Klasa: Domain\TERYT\Command\ImportTerytCommand Sygnatura: teryt:import Opis: Import TERYT data from csv
Komenda deleguje logikę do ImportTerytCmd (action), która: 1. Ustawia memory_limit = -1 (import dużych plików CSV) 2. Importuje TERC z storage/app/public/teryt/terc.csv 3. Importuje SIMC z storage/app/public/teryt/simc.csv 4. Uruchamia czyszczenie zbędnych wartości
Źródła danych¶
| Plik | Lokalizacja | Rozmiar |
|---|---|---|
| TERC | storage/app/public/teryt/terc.csv | ~200 KB |
| SIMC | storage/app/public/teryt/simc.csv | ~5.3 MB |
Separator CSV: ;. Kodowanie: UTF-8.
Proces importu¶
flowchart TD
A[php artisan teryt:import] --> B[Ustaw memory_limit=-1]
B --> C[TerytImportTerc\nUsuń wszystkie rekordy TERC]
C --> D[Wczytaj terc.csv wiersz po wierszu\nInsert z normalizacją kodów]
D --> E[TerytImportSimc\nUsuń wszystkie rekordy SIMC]
E --> F[Wczytaj simc.csv wiersz po wierszu\nInsert z normalizacją kodów]
F --> G[TerytDeleteUnnecessaryValues]
G --> H[Usuń z TERC: rodz IN 4,5,8,9]
H --> I[Usuń z SIMC: rodz_gmi IN 8,9]
I --> J[Usuń duplikaty SIMC\nsame woj+pow+gmi+rodz_gmi+nazwa, wyższy sym]
J --> K[Import zakończony] Szczegóły TerytImportTerc¶
- Usuwa wszystkie rekordy z tabeli
terc(DB::table('terc')->delete()) - Odczytuje CSV linia po linii, pomija nagłówek
- Pomija wiersze z mniej niż 7 kolumnami
- Normalizuje kody za pomocą
TerytNormalizeCode:woj(2 znaki),pow(2 znaki),gmi(2 znaki),rodz(1 znak) - Puste wartości
nazdodzapisuje jakonull
Szczegóły TerytImportSimc¶
- Usuwa wszystkie rekordy z tabeli
simc(DB::table('simc')->delete()) - Pomija wiersze z mniej niż 10 kolumnami
- Normalizuje kody:
woj(2),pow(2),gmi(2),rodz_gmi(1),rm(2),mz(2),sym(7),sympod(7 lub null)
Szczegóły TerytDeleteUnnecessaryValues¶
- TERC: Usuwa rekordy z
rodz IN [4, 5, 8, 9]— czyli: Miasto w gminie miejsko-wiejskiej, Obszar wiejski, Dzielnica Warszawy, Delegatura - SIMC: Usuwa rekordy z
rodz_gmi IN [8, 9]— Dzielnica Warszawy, Delegatura - Deduplikacja SIMC: SQL JOIN na samej sobie — dla rekordów z identycznym
woj+pow+gmi+rodz_gmi+nazwausuwa ten z wyższymsym(zachowuje starszy wpis)
Obserwatory (Observers)¶
| Observer | Model | Hooki |
|---|---|---|
TercObserver | Terc | brak zaimplementowanych hoków (szkielet) |
SimcObserver | Simc | brak zaimplementowanych hoków (szkielet) |
Oba obserwatory są przypisane przez atrybut #[ObservedBy(...)] i mogą być rozszerzone w przyszłości.
Powiązania z innymi domenami¶
| Domena | Użycie TERYT |
|---|---|
| Klienci (Client) | Adres klienta (voivodeship, district, commune, city, teryt_data) |
| Organizacje (Organization) | Adres organizacji |
| Zasoby (Item) | Lokalizacja obiektu |
Przechowywanie adresu w modelach powiązanych¶
Modele Client, Organization, Item przechowują dane adresowe jako pola:
| Pole | Typ | Opis |
|---|---|---|
voivodeship | string | Nazwa województwa |
district | string | Nazwa powiatu |
commune | string | Nazwa gminy |
city | string | Nazwa miejscowości |
postal_code | string | Kod pocztowy |
street | string | Ulica |
street_number | string | Numer budynku |
teryt_data | JSON | Pełne dane identyfikujące jednostkę TERYT |
Przykład teryt_data:
{
"provinceId": "14",
"districtId": "12",
"communeId": "05",
"cityId": "0231483",
"tercId": 14120520
}
Uprawnienia¶
| Rola | Dostęp |
|---|---|
| Publiczny (bez logowania) | Pełny dostęp do wszystkich endpointów TERYT |
Dane publiczne
TERYT to oficjalny rejestr publiczny. Wszystkie endpointy są dostępne bez logowania — authorize() zwraca zawsze true.
Struktura danych TERC (tabela bazy)¶
| Kolumna | Typ | Nullable | Klucz | Opis |
|---|---|---|---|---|
woj | char(2) | NOT NULL | PK (część) | Kod województwa |
pow | char(2) | NOT NULL | PK (część) | Kod powiatu |
gmi | char(2) | NOT NULL | PK (część) | Kod gminy |
rodz | char(1) | NOT NULL | PK (część) | Typ jednostki |
nazwa | varchar(100) | NOT NULL | — | Nazwa |
nazdod | varchar(100) | NULL | — | Nazwa dodatkowa (typ słowny) |
stan_na | date | NOT NULL | — | Data aktualności |
Struktura danych SIMC (tabela bazy)¶
| Kolumna | Typ | Nullable | Klucz/Indeks | Opis |
|---|---|---|---|---|
woj | char(2) | NOT NULL | IDX | Kod województwa |
pow | char(2) | NOT NULL | IDX | Kod powiatu |
gmi | char(2) | NOT NULL | IDX | Kod gminy |
rodz_gmi | char(2) | NOT NULL | — | Typ gminy miejscowości |
rm | char(2) | NOT NULL | — | Rodzaj miejscowości |
mz | char(2) | NOT NULL | — | Flaga zameldowania |
nazwa | varchar(100) | NOT NULL | — | Nazwa miejscowości |
sym | char(7) | NOT NULL | PK | Identyfikator SIMC |
sympod | char(7) | NULL | — | Identyfikator miejscowości nadrzędnej |
stan_na | date | NOT NULL | — | Data aktualności |
Statystyki danych¶
| Encja | Przybliżona liczba |
|---|---|
| Województwa | 16 |
| Powiaty | ~380 |
| Gminy | ~2500 |
| Miejscowości | ~100 000 |
Wartość biznesowa¶
Argumenty dla decydentów
- Oficjalność — dane z Głównego Urzędu Statystycznego
- Standaryzacja — jednolite nazewnictwo w całym systemie
- Integracje — kompatybilność z innymi systemami publicznymi
- Automatyzacja — autouzupełnianie adresów
Aktualizacja danych¶
Źródło oficjalne¶
Dane TERYT pochodzą z Głównego Urzędu Statystycznego (GUS): - https://eteryt.stat.gov.pl
Harmonogram aktualizacji¶
| Częstotliwość | Powód |
|---|---|
| Kwartalnie | Aktualizacje GUS |
| Po zmianach administracyjnych | Nowe gminy, łączenia |
Procedura aktualizacji¶
- Pobierz nowe pliki CSV z GUS
- Umieść w
storage/app/public/teryt/ - Uruchom
php artisan teryt:import - Wyczyść cache:
php artisan cache:clear
Best practices¶
Wskazówki
- Cache — dane rzadko się zmieniają, używaj cache
- Aktualizacje — sprawdzaj GUS kwartalnie
- Walidacja — używaj kodów TERYT do walidacji adresów
- Backup — przed importem zrób backup bazy
- Memory limit — import SIMC (~100 000 wierszy) wymaga dużej ilości pamięci; komenda automatycznie ustawia
memory_limit = -1
Znane ograniczenia¶
communeTypeNamew odpowiedzi jest zawszenull(niezaimplementowane wTerytCityResource)communeTypeIdLangw gminie jest wypełniane tylko dla typówrodz > 3; dla standardowych gmin (1–3) zwracanull- Obserwatory
TercObserveriSimcObserversą puste — nie rejestrują żadnej aktywności (logOptionslogOnly([]))