Przejdź do treści

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

GET /api/no-auth/teryt

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

GET /api/no-auth/teryt/{provinceId}

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

GET /api/no-auth/teryt/{provinceId}/{districtId}

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

GET /api/no-auth/teryt/{provinceId}/{districtId}/{provinceName}/{districtName}/{cityName}

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').nazwa
  • districtName — pobierany z cache, zapytanie: Terc(woj, pow, gmi='00').nazwa
  • communeName — pobierany z cache (TERYT_COMMUNE_KEY), zapytanie: Terc(woj, pow, gmi, rodz=rodz_gmi).nazwa
  • communeTypeName — zawsze null

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

WARMIŃSKO-MAZURSKIE (woj)
  └── olsztyński (pow)
       └── Olsztynek (gmi)
            └── Jemiołowo (miejscowość)

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

php artisan teryt:import

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 nazdod zapisuje jako null

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+nazwa usuwa ten z wyższym sym (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

  1. Oficjalność — dane z Głównego Urzędu Statystycznego
  2. Standaryzacja — jednolite nazewnictwo w całym systemie
  3. Integracje — kompatybilność z innymi systemami publicznymi
  4. 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

  1. Pobierz nowe pliki CSV z GUS
  2. Umieść w storage/app/public/teryt/
  3. Uruchom php artisan teryt:import
  4. Wyczyść cache: php artisan cache:clear

Best practices

Wskazówki

  1. Cache — dane rzadko się zmieniają, używaj cache
  2. Aktualizacje — sprawdzaj GUS kwartalnie
  3. Walidacja — używaj kodów TERYT do walidacji adresów
  4. Backup — przed importem zrób backup bazy
  5. Memory limit — import SIMC (~100 000 wierszy) wymaga dużej ilości pamięci; komenda automatycznie ustawia memory_limit = -1

Znane ograniczenia

  • communeTypeName w odpowiedzi jest zawsze null (niezaimplementowane w TerytCityResource)
  • communeTypeIdLang w gminie jest wypełniane tylko dla typów rodz > 3; dla standardowych gmin (1–3) zwraca null
  • Obserwatory TercObserver i SimcObserver są puste — nie rejestrują żadnej aktywności (logOptions logOnly([]))