Faza 1: Priprema podataka prije Unity importa
Prije nego što bilo što uđe u Unity, izvorni datasetovi pripremaju se potpuno odvojeno, izvan Unity projekta: teren (DEM/heightmap), teksture terena (satelitski ortofoto i/ili OSM splatmap) i 3D zgrade. Svi pipelinei završavaju u istom formatu — skup pločica (tileova) poravnatih na 1×1 km mrežu, spremnih za uvoz.
- Track A — Teren (visine): digitalni model reljefa (DMR) → GeoTIFF → 1×1 km tile-ovi → 16-bit RAW heightmap.
- Track B — Zgrade: izvorni 3D model zgrada → Blender → FBX/GLB → Unity → segmentacija u 1×1 km tile-ove.
- Track C — Teksture terena: satelitski/zračni ortofoto (DGU) i/ili semantički OSM splatmap → 1×1 km tile teksture.
- Track D — Cestovna mreža: OSM ceste (Overpass) → centerline JSON po 1×1 km tileu → (naknadno) 3D road mesh.
Sva tri traga koriste isti tile identifikacijski sustav (koordinate donjeg-lijevog kuta tilea u
metrima, npr. 550000_5068000), a Track A (heightmap) je zapravo izvor grida — i Track B
i Track C čitaju metadata.json iz Tracka A da bi znali točne granice svakog tilea, umjesto
da svaki pipeline zasebno definira koordinate.
Track A: Teren — DEM u Unity heightmap
Skripte: D:\_unity projects\ZG Connect\_resources\_height\
(geotiff_2_tiff_tiles.py, geotiff_tiles_2_raw.py)
Izvor podataka
Polazna točka je Digitalni model reljefa Grada Zagreba (ZAGREB_DMR_2022), preuzet sa Zagrebačkog GeoHUB-a (geohub-zagreb.hub.arcgis.com):
ZAGREB_DMR_2022 https://geohub-zagreb.hub.arcgis.com/datasets/5f8d5bb987e6441ab3554cab26318552
Dataset se preuzima kao ESRI geodatabase (ZAGREB_DMR_2022.gdb, isporučen kao .gdb.zip).
Iz njega se u GIS alatu (izvan ovog repozitorija) izvozi jedinstveni spojeni raster
geo_tiff_zagreb.tif koji pokriva cijelo područje grada — to je ulaz za prvi Python
korak.
Napomena — koji GIS alat/postupak koristiš za export
.gdb→.tif(QGIS, ArcGIS Pro...) trenutno nije dio dokumentiranog pipelinea. Reci mi pa ću to dodati.
Korak 1: geotiff_2_tiff_tiles.py — rezanje na 1×1 km tile-ove
Uzima jedan veliki GeoTIFF (cijeli DMR grada) i reže ga na mrežu 1×1 km pločica, poravnatu na apsolutne koordinate (svaki tile počinje na cijeloj tisućici metara u izvornom CRS-u). Svaka pločica se resampla bilinearno na 1025×1025 px — jedan piksel viška po osi (1024 + 1) je namjerno, standardna Unity Terrain heightmap konvencija koja omogućuje besšavno spajanje susjednih terena (zadnji red/stupac piksela jednog tilea preklapa se s prvim redom/stupcem susjednog).
Tile-ovi kojima je udio nevažećih (NoData) piksela iznad praga smatraju se praznima i preskaču se — DMR ne pokriva cijeli pravokutni bounding box grada (rubovi, rijeke i sl.).
| Parametar | Vrijednost / opis |
|---|---|
INPUT_TIF |
Putanja do spojenog izvornog GeoTIFF-a (cijeli grad). |
OUTPUT_DIR |
Mapa u koju se pišu pojedinačni tile GeoTIFF-ovi + tiles_metadata.json. |
TILE_SIZE_METERS |
Veličina tilea u metrima — 1000 (1 km), poravnano s ostatkom pipelinea. |
OUTPUT_RESOLUTION |
Rezolucija svakog tile rastera u pikselima — 1025 (1024 + 1 rub za bešavno spajanje). |
EMPTY_INVALID_RATIO_THRESHOLD |
Ako je udio NoData piksela u tileu ≥ ova vrijednost (0.95), tile se u potpunosti preskače. |
NODATA_THRESHOLD |
Sve vrijednosti veće od ovoga (1e20) tretiraju se kao NoData — hvata "sulude" float vrijednosti iz izvora (stvarni NoData je bio 3.4e+38). |
Za svaki uspješno exportani tile skripta piše ime po konvenciji
zg_dmr_{left}_{bottom}_1km_1025.tif i u tiles_metadata.json bilježi granice (left/right/
bottom/top), udio nevažećih piksela te min/max/mean visinu unutar tilea (informativno, ne
koristi se za normalizaciju — vidi sljedeći korak).
Korak 2: geotiff_tiles_2_raw.py — konverzija u Unity RAW heightmap
Uzima sve .tif tile-ove iz prethodnog koraka i za svaki piše 16-bit unsigned RAW
datoteku u formatu koji Unity Terrain izravno razumije pri importu heightmapa
(Terrain → Import Raw..., little-endian, Windows byte order).
Ključna točka ovog koraka: normalizacija visine radi se na globalnom rasponu
(MIN_HEIGHT–MAX_HEIGHT), ne po pojedinom tileu. Kad bi se svaki tile normalizirao
prema svom lokalnom min/max, susjedni tereni ne bi imali usklađenu skalu visine i nastali bi
vidljivi "stepenici" na granicama tileova.
| Parametar | Vrijednost / opis |
|---|---|
INPUT_TIF_DIR |
Mapa s 1×1 km GeoTIFF tile-ovima iz koraka 1. |
OUTPUT_RAW_DIR |
Mapa u koju se pišu .raw datoteke + tiles_unity_metadata.json. |
RESOLUTION |
1025 — mora odgovarati rezoluciji iz koraka 1. |
TILE_SIZE_METERS |
1000 — veličina tilea u metrima. |
MIN_HEIGHT / MAX_HEIGHT |
Globalni raspon visina (u metrima) preko svih tileova zajedno (trenutno 95.0–1040.0) — koristi se za 0..1 normalizaciju prije zapisa u 16-bit. Mora ostati isti za cijeli grad radi bešavnog spajanja terena. |
MAX_INVALID_RATIO_TO_EXPORT |
Tile se preskače ako mu je udio NoData piksela veći od ove vrijednosti. Za pun export cijelog grada postavljeno na 0.95 (skripta u komentaru predlaže niže vrijednosti, npr. 0.10–0.25, za brže testne exporte s manje rubnih/praznih tileova). |
UNITY_ORIGIN_X / UNITY_ORIGIN_Y |
Ishodište Unity koordinatnog sustava izraženo u izvornim (metarskim, CRS) koordinatama. Oduzima se od apsolutnih koordinata svakog tilea da se dobije relativna Unity pozicija (unity_position.x/z). Trenutno 442000 / 5051000. |
NoData rupe unutar tilea (rubni/djelomično prazni tile-ovi koji ipak prođu prag) popunjavaju
se najjednostavnijom mogućom strategijom — vrijednošću MIN_HEIGHT — dovoljno stabilno za
Unity import, ali ne i geografski točno; za takve rubne tileove treba računati s vizualnim
artefaktima.
Izlaz i veza s Unity importom
Rezultat je mapa .raw heightmap datoteka (zg_dmr_{left}_{bottom}_1km_1025.raw) plus
tiles_unity_metadata.json — JSON s dva dijela:
settings— globalne postavke cijelog seta (rezolucija, veličina tilea, globalni min/max visine, Unity origin, prag nevažećih piksela).tiles[]— po tile: naziv RAW datoteke, geografske granice,unity_position(već preračunata relativna pozicija),terrain_size(x/y/z u metrima — visina terenayjeMAX_HEIGHT - MIN_HEIGHT), rezolucija heightmapa i statistika nevažećih/graničnih vrijednosti.
Ovaj JSON format je 1:1 ono što Unity-side importer očekuje kao HeightmapMetadataJson /
HeightmapSettingsJson / HeightmapTileJson (ZGConnectJsonModels.cs) — dakle izlaz ovog
Python koraka je izravan, strojno čitljiv ulaz za ZGConnectImporter u sljedećoj fazi
(klasični Unity import), bez ručnog prepisivanja parametara.
Track B: Zgrade — preuzimanje i segmentacija 3D zgrada
Ova faza pretvara izvorni 3D model zgrada Grada Zagreba (vanjski, "sirovi" izvor) u skup manjih GLB datoteka poravnatih na 1×1 km mrežu (tile grid) koje se kasnije koriste za klasični Unity import ili za realtime streaming pipeline.
Pregled koraka
- Preuzimanje izvornog dataseta — 3D model zgrada s Grad Zagreb open data portala.
- Blender — import izvornog modela i export u FBX ili GLB.
- Unity import — FBX/GLB se uvozi u scenu kao "sirova" hijerarhija zgrada.
- Ručno poravnanje — building root se pozicionira/poravnava nad terenom u Scene view-u.
- Building Associator — grupira pojedinačne zgrade u 1×1 km tile grupe i upisuje ih u
CityDataset. - Building Exporter — exporta svaku tile grupu kao zasebni
.glb+ prateći.json(metapodaci lokacije).
Korak 1: Preuzimanje izvornog dataseta
Izvorni 3D model zgrada Grada Zagreba preuzima se s Zagrebačkog portala otvorenih podataka:
ZG3D 2022 — 3D model Grada Zagreba https://data.zagreb.hr/dataset/zg3d-2022-3d-model-gz
Korak 2: Blender
Preuzeti model se otvori u Blenderu (import), i odmah izveze (export) u FBX ili GLB format — bez dodatne obrade unutar Blendera. Ovaj korak je čisto konverzija formata radi kompatibilnosti s Unity importerom.
Korak 3–4: Unity import i poravnanje
FBX iz Blendera uvozi se standardnim Unity FBX importerom kao hijerarhija GameObjecta — svaka pojedinačna zgrada je jedan direktni child zajedničkog "buildings root" objekta. Cijela unutarnja hijerarhija zgrade (sub-meshevi, LOD čvorovi i sl.) tretira se kao jedna cjelina i kasnije se pomiče zajedno.
Prije segmentacije, taj root objekt treba ručno poravnati u Scene view-u tako da footprinti zgrada odgovaraju terrain tileovima (isti koordinatni sustav kao i terrain grid iz prijašnje faze pripreme podataka).
Korak 5: Building Associator
Meni: ZG Connect → Building Associator
Skripta: ZGConnectBuildingAssociatorWindow.cs
Alat grupira sve zgrade (direktne childove building roota) u 1×1 km tile-parent objekte
(TileBuildings_{tileId}) na temelju pozicije svake zgrade unutar mreže definirane u
CityDataset assetu, i upisuje referencu u dataset (CityTileRecord.buildingsSceneObject).
Veličina tilea nije hardkodirana na 1 km — čita se iz CityDataset.tileSizeMeters
(default vrijednost je 1000, tj. 1000 m = 1 km). Klasifikacija zgrade u tile radi se
po X/Z koordinatama centra njezinog bounding boxa (svi rendereri u hijerarhiji), podijeljenim
s veličinom tilea — cijelobrojno dijeljenje daje grid koordinatu tilea kojoj zgrada pripada.
Parametri prozora:
| Parametar | Opis |
|---|---|
Buildings Root |
Root GameObject čiji su direktni childovi pojedinačne zgrade. Mora biti ručno poravnat s terenom prije pokretanja. |
Dataset |
CityDataset asset koji definira tile mrežu (veličinu tilea, popis tileova s granicama). |
Snap to terrain surface |
Ako je uključeno, nakon grupiranja svaka zgrada dobiva raycast prema dolje i njezina Y pozicija se postavlja na točku dodira s terenom. Zahtijeva aktivne terrain collidere. |
Disable groups after wiring |
Postavlja sve tile-grupe zgrada na neaktivne nakon povezivanja. Preporučeno uključeno — streaming sustav očekuje da sve grupe kreću ugašene i uključuju se runtime po potrebi. |
Akcije:
- Preview (no scene changes) — samo analizira i prikazuje koliko zgrada pada u koji tile, bez izmjene scene. Prikazuje i broj "unassigned" zgrada (centar im je izvan svih tile granica — znak da poravnanje nije točno).
- Associate Buildings — stvarno grupira zgrade u TileBuildings_{tileId} objekte i upisuje ih u dataset.
- Associate First Tile Only (test) — testni pokušaj na samo jednom tileu (radi provjere prije pune obrade); preporuka je nakon toga napraviti Undo (Ctrl+Z) i tek onda pokrenuti punu asocijaciju.
Zgrade čiji centar ne upada ni u jedan tile završavaju u posebnoj grupi
TileBuildings_Unassigned, uz upozorenje u konzoli — signal da treba provjeriti poravnanje.
Korak 6: Building Exporter
Meni: ZG Connect → Building Exporter
Skripta: ZGConnectBuildingExporterWindow.cs (koristi ZGConnectGlbExportUtility.cs)
Exporta svaku tile-grupu (rezultat prethodnog koraka) u zaseban .glb fajl, plus opcionalni
prateći .json s metapodacima lokacije. Zahtijeva instaliran UnityGLTF paket
(com.khronos.unitygltf) — alat ga detektira reflekcijom pri pokretanju i javlja grešku ako
paket nije instaliran.
Parametri prozora:
| Parametar | Opis |
|---|---|
City Dataset |
Isti dataset korišten u Building Associatoru. |
Buildings Root |
Fallback referenca — Unity ne može trajno spremiti referencu na scenski objekt unutar .asset datoteke, pa se ona gubi nakon svake rekompilacije skripti. Exporter zato po potrebi traži tile-grupe po imenu (TileBuildings_{tileId}) ispod ovog roota. |
Output Folder |
Mapa u koju se pišu .glb/.json parovi, jedan par po tileu. |
Write metadata JSON per tile |
Uz svaki .glb piše i .json s imenima zgrada i njihovim lokalnim transformacijama (pozicija/rotacija/scale) — most za kasnije spajanje s vanjskim atributnim podacima po zgradi. |
Skip already exported tiles |
Ako buildings_{tileId}.glb već postoji u output mapi, taj tile se preskače. Omogućuje nastavak prekinutog exporta bez ponovne obrade već gotovih tileova. |
Klikom na "Export N Tile(s) to GLB" alat prolazi kroz sve tileove koji imaju povezanu grupu zgrada, prikazuje progress bar (s mogućnošću otkazivanja) i po završetku javlja koliko je tileova exportano/preskočeno/palo s greškom.
Izlazna struktura
Za svaki tile s zgradama nastaje par datoteka:
buildings_{tileId}.glb
buildings_{tileId}.json
tileId je isti identifikator koji koristi terrain streaming sustav (npr. 550000_5068000),
pa se building tileovi mogu jednoznačno povezati s odgovarajućim terrain tileom.
Sadržaj .json datoteke (osnovna varijanta, iz ZGConnectBuildingExporterWindow):
{
"tileId": "550000_5068000",
"tileSizeMeters": 1000,
"tileOriginUnity": { "x": 0.0, "y": 0.0, "z": 0.0 },
"buildingCount": 42,
"buildings": [
{
"name": "zagreb_Part_1619",
"localPosition": { "x": 0, "y": 0, "z": 0 },
"localRotation": { "x": 0, "y": 0, "z": 0 },
"localScale": { "x": 1, "y": 1, "z": 1 }
}
]
}
Numerički ID ugniježđen u imenu zgrade (npr. zagreb_Part_1619 → 1619) kasnije služi kao
ključ za spajanje s vanjskim atributnim podacima o zgradi (adresa, broj katova, površina,
godina izgradnje...) iz otvorenih podataka Grada Zagreba — to spajanje nije dio ove faze,
radi se naknadno.
Postoji i bogatija varijanta istog JSON-a (
BuildingsMetadataJson, krozBuildingTileMetadataWriter) koja uz transformacije nosi i material-slot ključeve, GPS koordinate i OSM match podatke po zgradi — ta se generira u kasnijem koraku obrade površina zgrada (surface baking), ne u ovom sirovom exportu. Dokumentirat ćemo je kad dođemo do te faze.
Automatska obrada pri re-importu (BuildingImportSettings.cs)
Kad se exportani .glb file kasnije uvozi natrag u Unity pod putanju
Assets/Buildings/buildings_*, AssetPostprocessor skripta BuildingImportSettings.cs
automatski postavlja import parametre modela:
| Postavka | Vrijednost | Razlog |
|---|---|---|
importAnimation |
isključeno | zgrade nemaju animacije |
importBlendShapes |
isključeno | nema blend shapeova |
importCameras / importLights |
isključeno | GLB ne nosi scensku opremu koja bi trebala ući u Unity |
generateSecondaryUV |
isključeno | nema potrebe za lightmap UV-ovima |
isReadable |
uključeno | mesh mora biti čitljiv za kasniju obradu fasada/krovova (UV processing) |
meshCompression |
Low | zadržava preciznost geometrije |
materialImportMode |
None | materijali se ne uvoze automatski iz GLB-a, dodjeljuju se kroz ZG Connect pipeline |
Pomoćni alat: BatchImport (kopiranje u projekt)
Meni: ZG Connect → Import Building Tiles
Skripta: BatchImport.cs
Jednostavan alat koji otvara dijalog za odabir vanjske mape s buildings_*.glb/.json
parovima, kopira sve te datoteke u Assets/Buildings/, i pokreće jedan zajednički (batch)
Unity import umjesto pojedinačnog importa po datoteci — brže za veći broj tileova.
Track C: Teksture terena — ortofoto i OSM splatmap
Skripte: Assets/ZGConnect/Python_tools/
(download_dgu_ortho_offset_thirds.py, download_dgu_ortho_2048.py, download_basemap.py,
osm_splatmap_generator.py, osm_ortho_splatmap_generator.py, make_texture_atlas.py)
Teren u Unityju treba i vizualnu teksturu, ne samo visinu. Postoje dva različita pristupa
koja se mogu koristiti odvojeno ili zajedno (hibridno) — oba čitaju tile grid izravno iz
metadata.json koji je proizveo Track A (heightmap), pa su tile granice uvijek 1:1 usklađene
s terenom:
- Ortofoto (satelitska/zračna snimka) — jedna velika fotografska tekstura po tileu (Unity "Ortho basemap"). Vizualno točno, ali bez semantike (ne zna se je li piksel trava, cesta ili zgrada).
- OSM splatmap — teren dobiva do 8 materijalnih slojeva (Unity terrain layer blending) na temelju stvarnog tipa površine iz OpenStreetMap podataka (trava, šuma, urbano, asfalt...). Semantički točno, tile-teksture se ponavljaju (tiling) umjesto jedne velike fotografije.
- Hibrid — kombinacija oba signala za prirodniji rezultat.
C1: Ortofoto — preuzimanje s DGU WMS servisa
Zračne/satelitske ortofoto snimke preuzimaju se s WMS servisa Državne geodetske uprave
(DGU) — geoportal.dgu.hr, sloj OI.OrthoimageCoverage.
download_dgu_ortho_offset_thirds.py — glavni per-tile downloader. Čita tile grid iz
heightmap metadata.json (Track A) i za svaki tile skida jednu 2048×2048 px WMS snimku
poravnatu točno na granice tog tilea.
| Parametar | Opis |
|---|---|
METADATA_JSON |
Putanja do metadata.json iz Track A — izvor tile grida (koordinate svakog tilea). |
OUTPUT_DIR |
Mapa u koju se pišu preuzete ortofoto teksture. |
WMS_URL / LAYER_NAME / CRS |
DGU WMS endpoint, sloj i koordinatni sustav (EPSG:3765). U datoteci postoje zakomentirane alternative (npr. orthophoto_lidar_2022_2023 sloj) — trenutno aktivan je orthophoto_1000. |
TILE_SIZE_METERS |
1000 — mora odgovarati Track A gridu. |
TEXTURE_RESOLUTION |
2048 px po tileu. |
MAX_INVALID_RATIO |
Prag za preskakanje rubnih/djelomično praznih tileova — 0.95 za pun export, 0.10 predloženo za brzi test. |
DOWNLOAD_ALL_TILES / MAX_TILES_TO_DOWNLOAD |
Ako je DOWNLOAD_ALL_TILES = False, skida se samo prvih N tileova — korisno za brzi test prije punog exporta. |
SPECIFIC_TILES |
Popis točno određenih tileova ((left, bottom) parovi) za ciljano ponovno preuzimanje, umjesto punog seta. |
SKIP_EXISTING |
Preskače tileove koji već imaju preuzetu teksturu — nastavak prekinutog exporta. |
SLEEP_BETWEEN_REQUESTS / REQUEST_TIMEOUT |
Pristojno ograničavanje brzine zahtjeva prema DGU serveru. |
download_dgu_ortho_2048.py — poseban, manji alat: ne skida po tileu, nego preuzima
jednu pregledno-selekcijsku sliku cijelog grada (2048×2048, sastavljenu od 4×4 WMS
podzahtjeva) — koristi se kao referentna/preview slika za odabir područja, ne kao teren
tekstura.
C2: Alternativni izvor — stilizirani web-map (download_basemap.py)
Umjesto zračne fotografije, ovaj alat generira teren teksturu iz web mapa (npr. Carto "Voyager, no labels" stil) — vizualno čist prikaz s cestama/objektima, ali bez fotorealizma. Podržava dva moda:
Mod (SOURCE) |
Opis |
|---|---|
slippy |
Skida standardne web-map tileove (zoom 17–20) sa CDN-a (TILE_URL, npr. Carto), sastavlja ih i reže na granice terrain tilea. Zahtijeva internet po pokretanju; tileovi se keširaju lokalno (TILE_CACHE_DIR) i dijele između susjednih tileova. |
cache |
Renderira vektorske OSM podatke offline, iz istog regionalnog Overpass JSON cachea koji koriste osm_vegetation_masks.py i download_osm_overview.py — bez ovisnosti o tile-serveru. SAMPLE_RESOLUTION > OUTPUT_RESOLUTION omogućuje supersampling (renderiraj veće, smanji LANCZOS filterom za glađi rezultat). |
Ostali parametri: ZOOM (razina detalja za slippy mod), OUTPUT_RESOLUTION (finalna veličina
teksture po tileu), FILE_PREFIX, SKIP_EXISTING, ROAD_WIDTH_SCALE (debljina cesta u cache
modu). Izlaz je isti oblik kao ortofoto — jedna tekstura po tileu + metadata.json spreman za
Dataset Import Manager.
C3: OSM splatmap — semantičko teksturiranje terena
osm_splatmap_generator.py generira do 8 materijalnih slojeva po tileu izravno iz
OpenStreetMap vektorskih podataka, umjesto jedne fotografije. Izvor OSM podataka je isti PBF
file kao u ostatku projekta:
https://download.geofabrik.de/europe/croatia-latest.osm.pbf
Trenutna definicija slojeva (LAYERS u skripti):
| Sloj | OSM izvor (primjeri tagova) | Veličina ponavljanja teksture |
|---|---|---|
grass |
landuse=grass/meadow/farmland/..., natural=grassland/heath, leisure=park/garden/golf_course |
4 m |
forest |
landuse=forest/wood, natural=wood/forest/scrub |
8 m |
urban_light |
landuse=residential/commercial/retail/... |
6 m |
urban_dark |
landuse=industrial/construction/quarry/landfill |
6 m |
sand |
natural=beach/sand/dune, landuse=beach |
4 m |
rock |
natural=rock/bare_rock/scree/cliff/shingle |
8 m |
asphalt |
highway=motorway/trunk/primary/.../service (linije, bufferirane ~9 m), landuse=railway |
3 m |
dirt |
highway=footway/path/pedestrian/steps/track/bridleway (pješačke staze, bufferirane ~3 m) |
2 m |
Napomena — ovo je trenutna implementacija u kodu i razlikuje se od ranijeg arhitekturnog nacrta (
terrain_splatmap_architecture.md,osm_splatmap_pipeline.md):waterviše nije terrain-layer nego se voda prikazuje kao zaseban GameObject, a dodan jedirtsloj za pješačke staze. Kad preklapanja postoje, pobjeđuje sloj s višim prioritetom (LAYER_PRIORITY, redoslijed od najnižeg: grass → dirt → asphalt → sand → rock → urban_light → urban_dark → forest). Piksel bez ikakvog OSM podatka ne dobiva automatskigrassfallback u ovoj verziji (za razliku od starog nacrta) — ostaje neoznačen, osim ako se koristi hibridna (Track C-hibrid) varijanta.
| Parametar | Opis |
|---|---|
TILES_METADATA_JSON |
metadata.json iz Track A — izvor tile grida. |
OSM_PBF |
Putanja do preuzetog croatia-latest.osm.pbf. |
OUTPUT_DIR |
Izlazna mapa (osm_tiled/ po defaultu). |
SPLAT_RESOLUTION |
Rezolucija splatmap teksture po tileu — 512 px (~2 m/px) je zadano, 256 brže/manje, 1024 oštrije/sporije. |
TILE_CRS |
EPSG:3765 — mora odgovarati ostatku pipelinea. |
Rezultat: za svaki tile 2 RGBA PNG datoteke ({tileId}_splat0.png, {tileId}_splat1.png — 8
slojeva pakiranih u 2×4 kanala) plus metadata.json u formatu type: "tiled" koji Unity
importer (ZG Connect Dataset Import Manager) izravno prepoznaje. Materijalne teksture za
svaki sloj (npr. grass.png, asphalt.png...) ne generira skripta — te se ručno dodaju u
OUTPUT_DIR/textures/ prije importa u Unity.
C4: Hibrid — OSM + ortofoto (osm_ortho_splatmap_generator.py)
Kombinira oba signala po pikselu radi prirodnijeg rezultata: OSM poligoni daju pouzdanu klasifikaciju tipa površine (oštri rubovi), a ortofoto boja (analiza zelenila, svjetline, zasićenja) dodaje prirodnu varijaciju i, što je važnije, popunjava područja gdje OSM nema podataka — umjesto da takvi pikseli ostanu neoznačeni ili automatski postanu trava.
| Parametar | Opis |
|---|---|
TILES_METADATA_JSON |
Isti tile grid izvor kao i ostali koraci. |
OSM_PBF |
OSM vektorski podaci (isti PBF). |
ORTHO_METADATA_JSON / ORTHO_DIR |
Već preuzeti ortofoto set (izlaz iz C1) — ortofoto signal se izvlači odavde, ne skida se ponovno. |
OUTPUT_DIR |
Zaseban izlaz (osm_ortho_tiled/) — ne miješa se s čistim OSM outputom. |
ORTHO_INFLUENCE |
Koliko ortofoto boja modulira rezultat unutar OSM poligona. 0.0 = čisti OSM (identično C3), 1.0 = čista ortofoto klasifikacija. Preporučeni početak: 0.20 (80% OSM + 20% ortofoto). |
ORTHO_INFLUENCE_UNCLASSIFIED |
Isto, ali za piksele izvan svih OSM poligona — postavljeno više (1.00) jer zamjenjuje "sve je trava" fallback stvarnom klasifikacijom iz boje. |
Izlazni format identičan je čistom OSM splatmapu (C3) — može se uvesti na isti način kroz Dataset Import Manager, samo iz drugog output foldera.
Pomoćni alat: make_texture_atlas.py
Ne generira teren teksture, nego pakira postojeći set per-tile PNG-ova (bilo ortofoto,
bilo pojedinačni splatmap kanal) u jednu veliku atlas sliku + JSON s UV koordinatama svakog
tilea unutar atlasa (i top-left i bottom-left konvencija, potonja pogodna izravno za Unity
materijale/meshove). Koristan za pregledne/UI karte ili minimap prikaze, ne ulazi u sam
terrain-layer runtime pipeline. Čita bilo koji metadata.json s tiles[].texture_file poljem
— radi i s ortofoto i s OSM tiled outputom.
Integracija u Unity
Bez obzira na odabranu varijantu (C1 ortofoto, C2 stilizirani basemap, C3 OSM splatmap, C4
hibrid), izlaz je uvijek mapa s teksturama + metadata.json spreman za:
Tools → ZG Connect → Dataset Import Manager → Scan for Data Sources → Import
Ortofoto/basemap izvori uvoze se kao Ortho basemap tip (jedna TerrainLayer po tileu); OSM
splatmap i hibrid izvori uvoze se kao Tiled basemap tip (shared TerrainLayer setovi + per-
tile alphamap). Jedan te isti tile može imati oba tipa basemapa registrirana istovremeno —
BasemapSwitcher komponenta omogućuje runtime prebacivanje između njih bez ponovnog importa.
Track D: Cestovna mreža — preuzimanje OSM cesta u JSON po tileu
Skripta: osm_road_extractor.py (nova, samostalna — pogledaj napomenu ispod)
Ranije je export cesta bio "sakriven" unutar osm_vegetation_masks.py (skripta čije je
glavno ime i svrha vegetacijske maske, a ceste je radila kao nusprodukt istog OSM upita). Na
tvoj zahtjev, taj dio je izdvojen u zasebnu, samostalnu skriptu — osm_road_extractor.py
— koja radi isključivo preuzimanje i export cesta, bez ikakve ovisnosti o vegetacijskom dijelu
(nema više numpy/Pillow/rasterio/scipy u zahtjevima, samo requests, pyproj,
shapely). Kod je restrukturiran, ali logika ekstrakcije, širine cesta i formata izlaznog
JSON-a identična je originalu — postojeći {tile_id}_roads.json fajlovi i sve što ih dalje
koristi (osm_road_surfaces.py) ostaju kompatibilni.
Skripta radi jedan regionalni Overpass upit (samo za highway elemente — manji i brži upit
nego kombinirani vegetacija+ceste upit iz originala, jer ne traži i forest/park/grass/
exclusion tagove), rezultat kešira lokalno, i zatim reže/exporta cestovne centerline po
1×1 km tileu, bez ikakvog dodatnog mrežnog poziva po tileu.
Napomena o provenijenciji — u projektu postoji i mapa
_dataset/road_topology/s drugačijim formatom ({tileId}_road_topology.json, sadrži već trianguliran poligon mesh), te kompajliraniosm_road_topology.cpython-314.pycu__pycache__/bez pripadajućeg.pyizvornika bilo gdje u projektu. Izgleda kao ostatak ranije/napuštene varijante pipelinea koja je zamijenjena trenutnim tokom (osm_road_extractor.py→osm_road_surfaces.py, opisano dolje). Ako ti ta stara mapa/pristup više ne treba, može se tretirati kao arhiva; reci ako je ipak želiš dokumentirati zasebno.
Izvor podataka i način preuzimanja
Skripta ne skida podatke po tileu pojedinačno, nego radi jedan Overpass upit za cijelu
regiju (uniju granica svih tileova iz heightmap grida), rezultat kešira u
osm_overpass_cache.json (isti model preuzimanja kao download_osm_overview.py), i tek onda
lokalno (bez dodatnih mrežnih poziva) reže/klasificira podatke po pojedinačnom tileu. Upit ide
na jedan od javnih Overpass API endpointa (overpass-api.de, overpass.kumi.systems — s
mogućnošću ručnog dodavanja alternativnog endpointa).
Klasifikacija cesta i debljina
Cesta se prepoznaje po highway OSM tagu koji odgovara regexu — obuhvaćeni tipovi:
motorway, trunk, primary, secondary, tertiary, unclassified, residential,
service, living_street, pedestrian, footway, path, cycleway, track.
Svakom tipu ceste dodijeljena je zadana širina (width_m) — koristi se i za generiranje
"exclusion" buffera (za vegetacijske maske) i kao geometrijski podatak u izlaznom JSON-u:
highway tip |
Širina (m) |
|---|---|
motorway |
12.0 |
trunk |
10.0 |
primary |
8.0 |
secondary |
7.0 |
tertiary |
6.0 |
residential |
5.0 |
service |
4.0 |
footway |
2.0 |
path |
1.5 |
| ostali (default) | 5.0 |
Segmentacija po tileu
Sve ceste iz regionalnog upita režu se (clip_roads_for_tile) na granice svakog pojedinog
1×1 km tilea. Ako cesta prelazi granicu tilea, dio unutar tilea postaje zaseban segment —
osm_way_id originalne OSM ceste se pritom zadržava, pa se segmenti iz susjednih tileova
kasnije mogu ponovno "sašiti" po tom ID-u (to radi osm_road_surfaces.py u sljedećem koraku).
Izlazni format — {tile_id}_roads.json
{
"format_version": 1,
"tile_id": "442000_5061000",
"crs": "EPSG:3765",
"left": 442000, "bottom": 5061000, "right": 443000, "top": 5062000,
"unity_origin": { "easting": 459479, "northing": 5074937 },
"segment_count": 4,
"segments": [
{
"osm_way_id": 93532653,
"highway": "tertiary",
"width_m": 6.0,
"points_epsg": [[443000.0, 5061962.88], [442994.27, 5061970.99], ...],
"points_unity": [[-16479.0, -12974.12], [-16484.73, -12966.0], ...]
}
]
}
Svaki segment nosi koordinate osi ceste dvaput — u apsolutnim EPSG:3765 metrima
(points_epsg) i već preračunate u Unity-relativne koordinate (points_unity, oduzet
unity_origin) — tako da Unity strana ne mora sama raditi projekciju.
Parametri (CLI)
| Parametar | Opis |
|---|---|
--metadata |
Heightmap metadata.json (Track A) — izvor tile grida. Alternativa: --tiles (stariji vegetation_tiles.json format iz Unity Vegetation Tiles Exportera). |
--out |
Izlazna mapa za {tile_id}_roads.json fajlove (default _dataset/road_segments, isto mjesto kao i prije). |
--tile |
Obradi samo jedan tile (po tile_id), umjesto svih. |
--crs |
Override koordinatnog sustava (default EPSG:3765). |
--max-invalid-ratio |
Preskoči tileove čiji je invalid_ratio (iz heightmap metadata) iznad zadane vrijednosti — isti mehanizam kao u Track A. |
--region-min-e/max-e/min-n/max-n |
Opcionalni filter — obradi samo tileove unutar zadanog EPSG podskupa regije. |
--skip-existing |
Preskoči tile ako {tile_id}_roads.json već postoji — nastavak prekinutog exporta. |
--refresh-overpass |
Ignorira regionalni Overpass cache i ponovno skida podatke. |
--cache-file |
Putanja do regionalnog OSM JSON cachea (default <out>/osm_roads_overpass_cache.json). |
--user-agent |
Override HTTP User-Agent zaglavlja prema Overpass serveru. |
--overpass-url |
Dodaj alternativni Overpass endpoint (parametar se može ponoviti za više endpointa). |
--quiet |
Minimalni ispis (samo greške i sažetak na kraju). |
Primjeri pokretanja:
python osm_road_extractor.py \
--metadata "_dataset/hightmaps_raw_1km_1025/metadata.json" \
--out "_dataset/road_segments"
python osm_road_extractor.py --metadata ... --out ... --tile 455000_5071000
python osm_road_extractor.py --metadata ... --out ... \
--region-min-e 455000 --region-max-e 457000 --region-min-n 5071000 --region-max-n 5073000
D2: osm_road_surfaces.py — pretvaranje centerlina u 3D mesh ceste
{tile_id}_roads.json iz prethodnog koraka (D1) je ulaz, ne krajnji rezultat.
osm_road_surfaces.py je zaseban, znatno opsežniji alat koji te centerline pretvara u
stvarnu 3D geometriju ceste — asfaltnu plohu (i po želji pločnike) koja prati oblik terena,
spremnu za GLB import u Unity. Za razliku od D1 (koji radi po 1×1 km tileu i ovisi samo o OSM
podacima), ovaj korak prvo gradi jednu regionalnu geometriju za cijeli grad i tek je onda
reže po tileovima — to je ključno za ispravno ponašanje na granicama tileova (cesta se ne
smije "prekinuti" na rubu tile grida).
Pregled cijelog pipelinea (regionalni, ne po tileu)
- Učitavanje — pročita sve
{tile}_roads.jsonfajlove iz--roads-dir(izlaz D1). - Sašivanje (stitching) — segmenti iste OSM ceste (isti
osm_way_id), razrezani na granicama susjednih tileova u D1, ponovno se spajaju u jednu neprekinutu liniju (chain_segments_for_way) tako da se krajnje točke susjednih segmenata poklapaju (tolerancijaCOORD_TOLERANCE_M = 0.05m). - Klasifikacija materijala — svaka sašivena cesta dobiva
asphaltilidirtna temeljuhighwaytaga:motorway/trunk/primary/secondary/tertiary/unclassified/residential/ service/living_street/pedestrian→ asfalt;footway/path/steps/track/bridleway/cycleway→ dirt (zemljana staza). Nepoznati tip po defaultu pada uasphalt. - Regionalna geometrija po materijalu — svaka cesta se bufferira na punu širinu
(
width_m / 2sa svake strane linije), sve trake istog materijala spajaju se boolean unijom (shapely.unary_union) u jednu (multi)poligonsku plohu za cijeli grad. Na raskrižjima se potom radi fillet (zaobljavanje) — buffer prema van pa prema unutra istog iznosa — da se uklone oštri "spike" artefakti na spojevima cesta. - Regionalni cache — ovaj korak (2–4) je spor za cijeli grad (~15 min), pa se rezultat
sprema u
{out}/.surface_cache/(asfalt i dirt geometrija kao WKB, plus JSON s parametrima koji su korišteni). Sljedeće pokretanje s istim--fillet-m/--buffer-join/--simplify-mčita iz cachea umjesto da sve ponovno računa (~10 min za svih 858 tileova, samo export bez regionalnog koraka).--refresh-surfacesforsira ponovni izračun — nužno nakon promjene tih triju parametara jer se cache inače tiho koristi zastario. - Rezanje po tileu — regionalna asfalt/dirt geometrija reže se na granice svakog 1×1 km
tilea (
clip_surface_to_tile). Dirt plohe se dodatno oduzimaju od asfaltnih (cesta uvijek "pobjeđuje" stazu na preklopu). - Uzorkovanje visine terena — za svaki vrh poligona (ruba ceste) očita se visina izravno
iz Track A RAW heightmap datoteka (
HeightmapIndex, bilinearna interpolacija, ista normalizacijamin_height/max_heightkao u Track A) — cesta time prati stvarni oblik terena umjesto da lebdi na fiksnoj visini. - Stabilizacija visine ruba — da rub ceste ne bi imao nagle "bow-tie" uvrnute trokute
tamo gdje teren naglo skače, primjenjuje se ograničenje maksimalnog nagiba između susjednih
točaka ruba (
--max-slope) i nekoliko prolaza Laplaceovog zaglađivanja (--height-smooth-iters) prije nego što se ta visina "zaključa" i koristi za mesh. - Triangulacija — poligon plohe (top face) triangulira se pomoću
mapbox-earcutbiblioteke (ista tehnika kao za krovove zgrada u Building pipelineu). - Ekstruzija u "slab" — od trianguliranog gornjeg lica gradi se puni 3D volumen: gornja
ploha (na visini terena +
--surface-offset-m), donja ploha na istoj X/Z ali niža za--slab-thickness-m(dirt/pločnik) odnosno--road-slab-thickness-m(asfalt), i vertikalni bočni zidovi duž vanjskog ruba — osim na rubovima koji se poklapaju s granicom tilea (is_tile_clip_edge), da se izbjegnu nepotrebni "zidovi" točno na spoju dva susjedna tilea. - Pločnici (opcionalno,
--sidewalks) — uz asfaltni rub gradi se ravna traka pločnika prema van (--sidewalk-width-m), čija unutarnja visina rubnih vrhova dijeli točno istu vrijednost s rubom ceste (nema procjepa/koraka), a pločnik se ne generira gdje bi se preklopio s dirt stazom niti na tile rubovima. - GLB export — sve plohe istog materijala (asfalt/dirt/pločnik) spajaju se u jedan mesh
primitive (
merge_mesh_data) i pišu u minimalni, ručno pisan GLB writer (bez vanjske GLTF biblioteke), s bojama materijala definiranim u kodu (MATERIAL_COLORS). - Metapodaci — uz GLB, po tileu se piše
{tile_id}_roads_surface.json: broj poligona/ vrhova/trokuta, popis materijala, i potpun snapshot svih bake parametara korištenih za taj export (korisno za reprodukciju/debug).
CLI parametri
| Parametar | Default | Opis |
|---|---|---|
--metadata |
HEIGHTMAP_METADATA (config) |
Heightmap metadata.json (Track A) — izvor tile grida. |
--roads-dir |
ROADS_DIR (config) |
Mapa s {tile}_roads.json fajlovima — izlaz D1 (osm_road_extractor.py). |
--heightmap-dir |
HEIGHTMAP_DIR (config) |
Mapa s zg_dmr_* RAW heightmap datotekama (Track A) — izvor za uzorkovanje visine. |
--out |
OUTPUT_DIR (config) |
Izlazna mapa za GLB + surface JSON + .surface_cache/. |
--tile |
— | Obradi samo jedan tile (po tile_id). |
--crs |
EPSG:3765 |
Oznaka koordinatnog sustava (informativna, geometrija je već u tom CRS-u iz D1). |
--fillet-m |
2.0 |
Radijus zaobljavanja na raskrižjima cesta (uklanja oštre spike-ove na spojevima). Dio regionalnog cachea — promjena zahtijeva --refresh-surfaces. |
--surface-offset-m |
0.05 |
Vertikalni pomak gornje plohe iznad terena (Unity Y) — sprječava z-fighting s terenom ispod. |
--slab-thickness-m |
0.20 |
Debljina vertikalnog "slaba" za dirt staze i pločnike. |
--road-slab-thickness-m |
0.35 |
Debljina slaba za asfaltnu cestu (deblje nego dirt/pločnik — dodatna zaštita protiv z-fightinga na bočnim zidovima). |
--buffer-join |
3 (bevel) |
Stil spoja pri bufferiranju linije u traku: 1 = round, 2 = mitre (oštri špic — sklon artefaktima), 3 = bevel (preporučeno). Dio regionalnog cachea. |
--simplify-m |
0.15 |
Douglas-Peucker pojednostavljenje ruba poligona prije triangulacije (0 = isključeno) — manje "sliver" (izduženih, gotovo degeneriranih) trokuta. |
--max-slope |
0.35 |
Maksimalni odnos uspon/razmak (rise/run) između susjednih točaka na rubu — sprječava uvrnute ("bow-tie") trokute gdje teren naglo skače. |
--height-smooth-iters |
2 |
Broj prolaza Laplaceovog zaglađivanja visine ruba prije zaključavanja. |
--min-triangle-area |
0.02 m² |
Odbacuje (kulira) trokute manje od ove površine (3D, u m²) — čisti degenerirane ostatke nakon triangulacije/simplifikacije. |
--edge-segment-m |
2.0 |
Maksimalna duljina ruba prije subdivizije poligonskog prstena — gladi krivine ceste i smanjuje artefakte na dugim ravnim segmentima preko zakrivljenog terena. |
--sidewalks |
isključeno | Generira ravne pločnike uz asfaltni rub (bez rampinga). |
--sidewalk-width-m |
1.5 |
Širina pločnika, mjerena prema van od asfaltnog ruba. |
--skip-existing |
isključeno | Preskoči tile ako {tile_id}_roads_surface.json već postoji — nastavak prekinutog exporta. |
--refresh-surfaces |
isključeno | Prisili ponovni izračun regionalne union/fillet geometrije umjesto čitanja iz .surface_cache/ — obavezno nakon promjene --fillet-m ili --buffer-join. |
--region-min-e/max-e/min-n/max-n |
— | Opcionalni filter — obradi samo tileove unutar zadanog EPSG podskupa regije. |
--quiet |
isključeno | Minimalni ispis. |
Primjeri pokretanja
# Prvi run (~15 min za regionalni union) + export svih pločica
python osm_road_surfaces.py \
--metadata "_dataset/hightmaps_raw_1km_1025/metadata.json" \
--roads-dir "_dataset/road_segments" \
--heightmap-dir "_dataset/hightmaps_raw_1km_1025" \
--out "_dataset/road_surfaces"
# Ponovni export (brzo, koristi .surface_cache u --out)
python osm_road_surfaces.py --out "_dataset/road_surfaces" --skip-existing
# Jedna pločica
python osm_road_surfaces.py --tile 473000_5080000 --out "_dataset/road_surfaces"
# Prilagodba ceste (slab, fillet, kvaliteta mesha)
python osm_road_surfaces.py --tile 473000_5080000 \
--fillet-m 2.0 --surface-offset-m 0.05 --slab-thickness-m 0.20 \
--buffer-join 3 --simplify-m 0.15 --max-slope 0.35 --height-smooth-iters 2
# Ceste + pločnici (asphalt + sidewalk materijali u GLB-u)
python osm_road_surfaces.py --out "_dataset/road_surfaces" --sidewalks \
--sidewalk-width-m 1.5 --road-slab-thickness-m 0.35
# Forsiraj rebuild regionalnog union/fillet cachea
# (obavezno nakon promjene --fillet-m ili --buffer-join)
python osm_road_surfaces.py --refresh-surfaces \
--metadata "_dataset/hightmaps_raw_1km_1025/metadata.json" \
--roads-dir "_dataset/road_segments" \
--heightmap-dir "_dataset/hightmaps_raw_1km_1025" \
--out "_dataset/road_surfaces"
Izlaz po pločici
{tile_id}_roads.glb — mesh (asphalt, dirt, opcionalno sidewalk kao zasebni primitivi)
{tile_id}_roads_surface.json — vertex/triangle count, popis materijala, potpuni bake parametri
{tile_id}_roads_surface.json primjer strukture:
{
"format_version": 2,
"tile_id": "473000_5080000",
"left": 473000, "bottom": 5080000, "right": 474000, "top": 5081000,
"has_mesh": true,
"glb_file": "473000_5080000_roads.glb",
"materials": ["asphalt", "dirt"],
"polygon_count": 14,
"sidewalk_polygon_count": 0,
"vertex_count": 3821,
"triangle_count": 5104,
"bake": {
"fillet_m": 2.0, "surface_offset_m": 0.05, "slab_thickness_m": 0.2,
"road_slab_thickness_m": 0.35, "simplify_m": 0.15, "max_slope": 0.35,
"height_smooth_iters": 2, "min_triangle_area_m2": 0.02, "buffer_join": 3,
"sidewalks": false, "sidewalk_width_m": 1.5, "edge_segment_m": 2.0
}
}
Ako za neki tile nema geometrije ceste (npr. tile bez ijedne ceste), i dalje se piše
metadata JSON s "has_mesh": false (radi ispravnog rada --skip-existing), ali bez GLB
datoteke.
Sljedeći korak
Sve četiri pripreme (Track A–D) rezultiraju setovima 1×1 km tileova s pripadajućim JSON
metapodacima — teren kao .raw heightmap + tiles_unity_metadata.json (Track A), zgrade kao
buildings_{tileId}.glb + .json (Track B), teksture terena kao ortofoto/splatmap PNG-ovi +
metadata.json (Track C), ceste kao {tileId}_roads.json centerline (Track D, uz opcionalni
GLB mesh iz osm_road_surfaces.py). Svi ulaze kao ulaz u sljedeću fazu: klasični Unity import
(ZGConnectImporter, Dataset Import Manager, obrada fasada/krovova) ili realtime streaming
pipeline.
ZG Connect