← Nazad na sajt

Tehnički dnevnik

Kratak hronološki pregled odluka, izvora podataka i tehnika koje stoje iza grafika i mape. Najnovije prvo.

Bezbednosni audit: geometrija kretanja i konflikt korisnika (8.4–8.5)

Dve nove kategorije proširuju analizu 8. Geometrija kretanja (8.4) klasifikuje tačke iz prekidi.geojson i rampe.geojson po tipu fizičke smetnje — suženja, nagle promene nivoa i mesta prinudnog zaustavljanja. Konflikt korisnika (8.5) je proxy analiza: direktnih opservacija susreta pešaka, biciklista i trkača nema, ali svaka stepenica i rampa je tačka konvergencije gde svi moraju proći istim uskim prolazom. Obe sekcije prikazane su tabelom i Leaflet mapom.

8.4 Geometrija kretanja: klasifikacija regexom

Izvor su dve datoteke: prekidi.geojson (52 tačke) i rampe.geojson (13 rampi). Svaka tačka prolazi kroz tri regex-a redom, bez preklapanja:

  • suzenje — suzenje|suzhavanje|bariera|tesna|uski|koso\s*park|parking\s*zona|shirine|3\s*-\s*3[,.]5 — obuhvata fizička suženja, barijere i parkiranje koje smanjuje prolaznu širinu.
  • promena_nivoa — strma|zemjena\s+rampa|kraj\s+asfalt|kraj\s+trotoa — nagla promena visine ili podloge koja zahteva usporavanje ili silaženje s bicikla.
  • usporavanje — neophodno|nema\s*peshachki, plus tačke čiji je tip polje most_prelaz ili pritoka — mesta gde korisnik mora da stane ili silazi bez upozorenja.

Rezultat: 8 suženja, 2 promene nivoa, 4 mesta prinudnog zaustavljanja. Konvencija je ista kao u 8.3 — svaka tačka je ručno zabeležena na terenu, regex samo sortira, ne kreira. Greška je greška klasifikacije, ne podataka.

Vizualizacija: Leaflet mapa sa kružnim markerima bojenim po tipu (narandžasta = suženje, plava = promena nivoa, crvena = usporavanje). Legenda se gradi dinamički iz GEOM_COLOR i GEOM_LABEL mapa u JS-u, pa su boje i natpisi na jednom mestu.

8.5 Konflikt korisnika: proxy umesto direktnih opservacija

Direktnih podataka o susretima korisnika nema u koridor_data.kml — terenski protokol nije predviđao opservaciju konflikta. Umesto da se sekcija izostavi, odabran je indirektan proxy: svaka stepenica i svaka rampa je tačka konvergencije gde pešaci, biciklisti i trkači moraju proći istim uskim prolazom bez fizičke separacije. To ne dokazuje da do konflikta dolazi — ali identifikuje gde je šansa za konflikt strukturno najveća.

Ukupno 94 tačke: 81 stepenica + 13 rampi. Gabrovačka Reka dominira sa 37 stepenica, što je i nalaz 8.1, samo gledano iz ugla gustine korisnika umesto gustine biciklista.

Sekcija nosi eksplicitno upozorenje da je ovo proxy, ne direktna analiza, i navodi šta bi trebalo mapirati u sledećoj iteraciji: gde bici i pešačka staza dele isti asfalt bez fizičke granice, širina staze po segmentima i direktne opservacije susreta.

Šta i dalje nedostaje

Kategorija specifični rizici uz reku (ograda na ivici, strme kosine, oštećenja bedema) nema ni jednu mapiranu tačku u postojećim podacima. Pretraga KML-a nije pronašla ni ograda/kosina/erozija ni oštećenja markere — pojava reči „bedem" u podacima su samo nazivi staza. Ova kategorija zahteva novu iteraciju terenskog mapiranja sa eksplicitnim protokolom za ivicu reke.

Analiza 9.3: termalni komfor u stepenima, a ne u oceni od 0 do 100

Predlog je bio da se senka, blizina vode, vegetacija, podloga, izloženost suncu, otvorenost i izgrađenost saberu u jedinstven „Thermal Comfort Index". To nije urađeno, jer bi takav indeks izgledao kao sedam faktora a ponašao se kao jedan — sve sem podloge i vode je ista promenljiva merena iz više uglova, a vrednost bi zavisila od težina koje bismo mi izabrali. Umesto toga se računa fizika i izlazi u stepenima: UTCI, sa priznatom skalom stresa. Rezultat: 93 % biciklističke staze je u jakom toplotnom stresu vrelog popodneva, krošnja spušta zračenje na telo za 21,9 °C, a 33 od 36 mapiranih klupa nema nijedan sat senke.

Zašto UTCI a ne sopstveni indeks

Indeks od 0 do 100 se koristi u 9.2 zato što za buku nema merila — nijedno merenje duž keja ne postoji. Za toplotu merilo postoji. UTCI je ekvivalentna temperatura: kaže na koliko bi stepeni u mirnom, zaklonjenom i umereno vlažnom okruženju čovek osećao isto što oseća ovde. Granice kategorija („umeren toplotni stres" 26–32, „jak" 32–38, „vrlo jak" 38–46) su deo definicije, pa niko ne mora da veruje našem izboru pragova.

Bojanini činioci nisu odbačeni — premešteni su iz ulaza u objašnjenje. Stranica prvo kaže koliko je stepeni, pa onda zašto: koliko je nebo otvoreno, koliko sati ima senke, koliko zračenja pada na telo.

UTCI bez numbe

Referentna implementacija pythermalcomfort traži numba i scipy, a numba nema wheel za Python 3.14 koji projekat koristi. Sam polinom je, međutim, čista aritmetika — 211 članova po četiri promenljive, bez ijedne biblioteke.

Polinom je preuzet iz pythermalcomfort 4.6.0 (MIT licenca), funkcija _utci_optimized. Nije prekucan nego programski izvučen iz izvornog koda — 211 koeficijenata je previše da se prepisuju rukom bez greške. Modul utci.py nosi i samoproveru koja se pokreće sa python utci.py, a koja poredi sa vrednostima iz testova samog paketa.

Uz to je urađena unakrsna provera: naša implementacija protiv originalne, na mreži od 840 kombinacija (temperatura 20–36 °C, Tmrt−Ta od −5 do +45, vetar 0,5–8 m/s, vlažnost 20–80 %). Najveće odstupanje: 0,000 °C. Van granica važenja modela (Tmrt−Ta preko 70 °C, vetar ispod 0,5 m/s) funkcija vraća None, a ne približnu vrednost.

Srednja radijantna temperatura

Tmrt je veličina koju senka zapravo menja. Temperatura vazduha se duž 13 km praktično ne menja; Tmrt se između sunca i senke razlikuje za preko 20 °C. Bilans po tački i satu:

  • Direktno zračenje — fp(h) · DNI, samo ako maska senke iz 9.1 kaže da snop stiže do tačke.
  • Difuzno sa neba — 0,5 · SVF · D. Faktor 0,5 jer telo nebo vidi iz jedne polusfere.
  • Odbijeno od tla — 0,5 · albedo · (ono što na tlo stiže na tom mestu). Bitno je „na tom mestu": u senci je i tlo u senci, pa odbija višestruko manje.
  • Dugotalasno — gornja polusfera je nebo (udeo SVF) i prepreke (1−SVF), donja je tlo. Emisivnost neba po Brutsaertu, sa korekcijom na oblačnost.

Faktor projektovane površine fp nije uzet iz tabele nego izveden: čovek je uspravan valjak 1,75 × 0,35 m, bočna strana se projektuje kao D·H·cos h, gornja osnova kao (πD²/4)·sin h, sve podeljeno ukupnom površinom. Daje 0,29 na horizontu i 0,05 u zenitu, što se slaže sa objavljenim vrednostima za uspravnog čoveka — ali se može proveriti iz geometrije, bez verovanja na reč.

Faktor vidljivosti neba se računa zracima u 36 pravaca preko mape visine krošnji, do 150 m: SVF = 1 − ⟨sin²β⟩, gde je β najveći ugao zaklona u tom pravcu.

Greška koju je otkrio prvi rezultat

Prvo pokretanje dalo je UTCI u rasponu 32,7–38,5 °C, sa razlikom sunce/senka od svega 5,8 °C. To je premalo — literatura daje 7 do 10. Uzrok su bile dve stvari u bilansu: difuzno zračenje je ulazilo bez faktora 0,5 (telo ga prima iz polusfere, ne iz cele sfere), a odbijeno od tla se računalo iz globalnog zračenja i u senci, kao da je i tamo tlo obasjano. Obe greške naduvavaju vrednosti u senci i time gutaju kontrast. Posle ispravke: Tmrt 52,1 na suncu prema 31,4 u senci, razlika 20,7 °C.

Scenario: vruć dan, ne prosečan

Geometrija Sunca je za 21. jun, jer su maske senke iz 9.1 računate za taj datum. Meteorologija je prosek 77 najtoplijih od 310 dana u prozoru od ±15 dana oko solsticija, kroz 2016–2025, za sate 12–17. Prosek preko svih letnjih dana bio bi pogrešan izbor: kišni i oblačni dani spuštaju i sunce i Tmrt, a senka se ne sadi zbog njih. Dobijeni uslovi: vazduh 32,0–33,3 °C, tlo 30,8–33,2 °C, vlažnost 33–38 %, vetar 1,9–2,5 m/s, direktno zračenje 525–791 W/m².

Vrh nije u podne nego u 15 h. Razlog je faktor projekcije: kad je Sunce visoko, na uspravno telo pada manji deo snopa nego kad je koso. U podne je UTCI 35,5, u 15 h 37,1 °C.

Koliko se ovim brojevima sme verovati

Urađena je provera osetljivosti na sve pretpostavke koje nisu izmerene:

PretpostavkaOpsegΔUTCI na suncu
Albedo tla0,08–0,30−0,7 do +1,4 °C
Apsorpcija tela0,6–0,8−0,8 do +0,8 °C
Prečnik valjka0,25–0,45 m−0,2 do +0,1 °C
Tlo toplije (asfalt)+20 °C+2,0 °C

Dakle: apsolutni nivo nosi oko ±2 °C, a razlika sunce/senka ostaje između 17,7 i 26,2 °C pri svim izborima — nikad ne nestaje. Zato se na stranici razlika naglašava više od nivoa. Pošto su sve pretpostavke o tlu na strani prirodne podloge, vrednosti na asfaltu su potcenjene, ne precenjene.

Zgrade: izostavljene, ali izmereno koliko to košta

U OSM-u oko koridora ima 38.297 zgrada, ali samo 12 ima tag height, a 401 building:levels — 99 % nema nijedan podatak o visini. Umesto da im se visina izmisli, izmereno je koliko bi uopšte značile: samo 3,7 % tačaka trase ima zgradu bliže od 20 m, a medijana rastojanja je 43 m u Centru do 97 m na Delti. Zgrada na 43 m sa 15 m visine zaklanja nebo do 19°, a Sunce je u prozoru 12–17 h uvek iznad 30°. Ne zaklanja snop niti bitno menja SVF. Kej je nasip uz reku, ne ulični kanjon.

Mesta predaha (9.3.1)

Prag u apsolutnim stepenima ne bi radio: na vruć dan je cela trasa iznad granice „bez toplotnog stresa", pa bi spisak bio prazan. Ono što se traži nije hladno mesto nego hladnije, i to dovoljno dugo. Definicija: neprekidan potez od bar 30 m u kome je tačka u senci bar 5 od 6 sati prozora.

Rezultat po stazama: biciklistička 10 predaha (680 m, 4,8 %), gornji bedem 6 (290 m, 1,9 %), donji bedem 26 (1.870 m, 14,9 %). Najduži potez bez ijednog predaha na biciklističkoj stazi je 8,84 km — od početka trase do Brzog Broda.

Senka na klupama i letnjikovcima se ne čita iz najbliže tačke staze nego se računa na samoj koordinati objekta, istim zracima. Razlika je bitna jer klupa ume da stoji dvadesetak metara od staze, u sasvim drugim uslovima. Ispalo je 33 od 36 klupa bez ijednog sata senke, prosečan UTCI na klupi 36,3 °C.

Za letnjikovce je dodata napomena u samom izlazu: mapa visine krošnji vidi drveće, ne građevine, pa „nema senke od krošnji" kod letnjikovca ne znači da je na suncu — on ima sopstveni krov. Bez te napomene broj bi se čitao naopako.

Kvalitet predela i jedna neprijatna veza

Druga polovina sekcije po okviru je „landscape quality"; podaci o pogledu na reku postojali su od ranije ali nisu imali stranicu, nego su samo stajali kao broj u tabeli pokazatelja koja je upućivala na 9.3. Sada su na svom mestu.

Spoj dva dela daje nalaz koji nijedan od njih ne daje sam: zapadne deonice (Medoševac, Centar) vide reku sa skoro 100 % trase i imaju 0,0 sati senke; istočne (Brzi Brod, Niška Banja) vide je sa 28 % i imaju 0,9 sati. Pošto je geometrijski reka vidljiva sa cele trase — potencijal je 100 % — sav gubitak pogleda otpada na zelenilo. Isto drveće. Odluka gde se seče i gde se sadi zato nije popravka jednog bez cene po drugo.

Sitnice

  • Grafikon po satu prvo je crtao stubiće od tik ispod minimuma, pa je razlika od 1,6 °C izgledala petostruko. Sada počinju od granice kategorije (32 °C), i to piše ispod grafikona.
  • Oznaka poslednjeg kilometra na profilu sekla se o desnu ivicu platna. Ista greška postojala je i na 9.2; popravljena na oba mesta.
  • #termo-map je u prvoj verziji bio bez pravila u CSS-u, pa je mapa imala nultu visinu — uhvaćeno tako što snimak elementa nije mogao da se napravi.

Šta nedostaje

Tip podloge. Asfalt na suncu je i 20 °C topliji od prirodne podloge, a OpenStreetMap ga za staze uz Nišavu skoro ne beleži. Upisuje se na terenskom izlasku za 9.2, u koloni podloga, i ulazi ovde kad stigne. Isto važi i za klupe: terenski popis će pokazati koliko ih je stvarno i gde, pošto je mapiranih 36 verovatno nepotpuno.

Vazduh prestaje da bude procena: satelitski model zamenjen mernim stanicama

Analiza 9.2 je za kvalitet vazduha koristila CAMS preko Open-Meteo, ćeliju od ~11 km. Poređen sa zvaničnom mernom stanicom na istim danima, CAMS daje PM2.5 1,58×, PM10 1,98× i NO₂ 4,05× niže od izmerenog, a za NO₂ na dnevnom nivou praktično nema veštinu (r = 0,29). Brojevi na stranici bili su zato pogrešni, a najgore u pravcu koji umiruje. Zamenjeni su validiranim časovnim merenjima tri stanice, od kojih jedna stoji 288 m od staze. Uz to je dodata raspodela NO₂ po deonicama, ukotvljena u izmerenu razliku između stanica, i noise_air.py je razdvojen na buka.py i vazduh.py.

Odakle podaci: EEA, jer SEPA nije dostupna

Domaći izvori su probani prvi i ne rade: sepa.gov.rs, amskv.sepa.gov.rs i data.sepa.gov.rs ne odgovaraju (HTTP 000). OpenAQ v3 traži API ključ. Stari EEA CSV servis AQData_Extract.fmw je ugašen (HTTP 401). Ono što radi jeste novi EEA download servis: Srbija je među 39 zemalja koje izveštavaju, pa se isti SEPA podaci dohvataju bez ključa i bez registracije.

  • Metapodaci — discomap.eea.europa.eu/map/fme/metadata/PanEuropean_metadata.csv, 26 MB, tab-separated. Nosi koordinate, tip stanice (traffic/background/industrial), tip okruženja (urban/rural) i spisak polutanata po mernom mestu. Srbija ima 35 stanica.
  • Merenja — POST /ParquetFile/urls na eeadmz1-downloads-api-appservice.azurewebsites.net vraća javne Azure blob URL-ove, jedan parquet po mernom mestu. dataset: 2 su validirani podaci (E1a); nevalidirani skorašnji (1) se ne koriste. Šifre polutanata su iz EIONET rečnika: 5 = PM10, 8 = NO₂, 6001 = PM2.5.

Kolone u parquet-u: Start, End, Value, Validity, Verification, AggType. Uzimaju se samo redovi sa Validity == 1. Dnevni prosek traži bar 18 validnih sati, godina traži bar 300 takvih dana — inače ne ulazi u izlaz. Saobraćajna stanica ima 105.191 validan časovni zapis za NO₂, od 1. januara 2013.

Tri stanice, tri uloge

Od 35 srpskih stanica, tri su u krugu od 15 km od ose koridora. Rastojanje se meri do najbliže tačke ose, ne do centra grada:

  • RS0033A — urban background, 288 m od staze. Reprezentativna: svi nivoi na stranici dolaze odavde. Pozadinski tip je ovde tačan izbor jer kej nije ulica.
  • RS0030A — urban traffic, 1.054 m. Služi samo za razliku prema pozadinskoj, koja daje veličinu saobraćajnog doprinosa.
  • RS0005R — rural background, 8.848 m severno. Daje regionalni član — koliko zagađenja ima i pre ulaska u Niš.

Sledeća po redu je 56 km daleko, pa nema dileme oko izbora.

Zašto se ne interpolira između stanica, iako je to bio predlog

Predlog je bio da se napravi prostorni indikator sa težinama po relevantnosti stanica, pa da se dobije procena po deonicama. To ne radi: dve urbane stanice su međusobno udaljene 400 m i obe su kod centra koridora. Interpolacija između njih preko 13 km trase ne bi izvlačila prostornu informaciju — ta informacija u podacima ne postoji — nego bi oblikovala gradijent koji je artefakt toga gde su stanice postavljene. To je ista greška zbog koje buka nije u decibelima, samo obrnuta: prave jedinice, izmišljena geografija.

Ono što par stanica jeste izmerio je razlika između dve lokacije. Ona se koristi kao veličina, a raspodela duž trase dolazi iz modela blizine puteva:

C(tačka) = C_regionalno + C_urbano + k · P(tačka)

Regionalni član je izmeren na ruralnoj stanici, urbani je ostatak na pozadinskoj posle oduzimanja regionalnog i saobraćajnog dela, a k je ukotvljeno tako da razlika P između dve stanice reprodukuje izmerenu razliku koncentracija. Modelirana je samo raspodela; dva od tri člana su merenja.

Jezgro opadanja nije isto kao za zvuk

Model deli iste segmente puteva sa buka.py, ali ne i fiziku. Zvuk od linijskog izvora pada sa 1/d² po tačkastom doprinosu; NO₂ uz put pada približno eksponencijalno, sa karakterističnom dužinom od 50–150 m u gradskim uslovima. Uzeto je L = 60 m, svesno na donjoj granici: brže opadanje daje manju razliku između deonica, pa se efekat izbora staze pre potceni nego preceni. Poluprečnik je 400 m (exp(−400/60) ≈ 0,001), segmenti se dele na emitere na svakih 10 m, a bbox za Overpass pokriva i stanice, ne samo osu — bez puteva oko stanica nema sidra.

Dobijeno: P = 127,3 kod saobraćajne i 45,7 kod pozadinske stanice, ΔP = 81,6. Ako ΔP ne bi bilo pozitivno, sidro ne bi važilo i raspodela bi se preskočila — to je provereno u kodu, ne pretpostavljeno.

Greška koju su podaci otkrili: sidro mora da bude letnje

Prva verzija je uzela godišnju razliku saobraćajne i pozadinske stanice: +2,8 µg/m³ NO₂. Provera mesečnog hoda pokazala je zašto je to pogrešno. Pozadinska stanica u januaru ima 41,7 µg/m³ NO₂, a saobraćajna 25,2 — pozadinska je zimi prljavija od saobraćajne. Razlog je da stoji u naselju sa individualnim grejanjem; saobraćajna stanica ima gotovo ravan godišnji hod (25,2 u januaru, 21,3 u junu), jer saobraćaj ne poznaje sezonu.

Godišnja razlika između te dve stanice dakle meša saobraćaj sa dimnjacima i potcenjuje saobraćajni deo. U toploj polovini godine grejanja nema, pa razlika ostaje ono što tvrdimo da jeste — blizina puta. Za maj–septembar ona iznosi +9,6 µg/m³, tri i po puta više. Sidro, urbani i regionalni član sada se svi računaju iz istog, toplog perioda; mešanje sezonskog sidra sa godišnjim nivoom dalo bi broj koji ne opisuje nijedan stvarni period.

Isti test diskvalifikuje čestice kao pokazatelj saobraćaja. Letnja razlika saobraćajne i pozadinske stanice je +3,6 za PM2.5 i −3,7 za PM10 — negativna. Godišnje su te razlike +8,7 i +3,6, sa vrhom u 16–20 h, što je grejanje a ne špic. Zato se po deonicama prikazuje samo NO₂, a ključevi u izlazu se zovu saobracajna_minus_pozadina, ne „saobraćajni doprinos": ime govori šta je izmereno, tumačenje dolazi posle.

Periodi se ne smeju mešati

NO₂ postoji od 2013, PM2.5 tek od 2024. Bez pravila bi se na istoj stranici poredio trinaestogodišnji prosek sa dvogodišnjim. Glavni brojevi se zato računaju iz poslednje tri godine koje sve stanice za taj polutant imaju: NO₂ 2023–2025, PM2.5 i PM10 2024–2025. Cela istorija po godinama ostaje u izlazu, za grafikon prekoračenja.

Šta je ispalo

PolutantStanicaCAMSOdnosr
PM2.524,715,61,58×0,72
PM1036,718,61,98×0,65
NO₂22,85,64,05×0,29

Vrednosti su µg/m³ na reprezentativnoj stanici i u CAMS-u na istim danima; r je Pirsonova korelacija dnevnih vrednosti.

Za čestice CAMS pogađa oblik krive (r ≈ 0,7) ali ne nivo — korisan je i dalje za sezonski hod polena, koji stanice ne mere. Za NO₂ je ćelija od 11 km jednostavno preširoka da vidi grad: r = 0,29 znači da dnevna vrednost iz modela ne nosi informaciju o dnevnoj vrednosti na stanici. Stranica je do sada tvrdila da NO₂ prelazi dnevnu smernicu SZO u 0,1 % dana; izmereno je 28 % na pozadinskoj i 45 % na saobraćajnoj stanici.

Zakonski deo je najoštriji. Granična vrednost za PM10 je 50 µg/m³ kao dnevni prosek, uz dopuštenih 35 prekoračenja godišnje. Saobraćajna stanica ih ima u svakoj od devet godina sa dovoljno merenja, od 53 (2013) do 106 (2020); 2025. je 103, uz godišnji prosek 44,4 — i preko godišnje granice od 40. Pozadinska stanica, ona kod keja, ima 81 i 72 dana u dve godine koje meri.

Raspodela po deonicama (NO₂, maj–septembar, µg/m³): Niška Banja i Brzi Brod 8,8–9,3, Medoševac 9,5–11,7, Delta–Lidl 11,0–11,5, Centar 14,5–15,5. Donji bedem je dosledno najčistiji, u Medoševcu za 19 % u odnosu na gornji. Ali raspon između najčistije i najizloženije tačke je 6,7 µg/m³, dok 9,1 nosi grad podjednako svuda — izbor staze menja malo, grejna sezona menja sve.

Podela modula i sitnice

  • noise_air.py je preimenovan u buka.py i iz njega je izbačen ceo blok o vazduhu; data/noise_air.json je sada data/buka.json, schema podignuta na 2. Ime je počelo da laže čim je vazduh dobio sopstvenu prostornu logiku.
  • vazduh.py uvozi samo standardnu biblioteku, koridor.py i buka.py — namerno bez numpy. Razlog: pyarrow nema wheel za Python 3.14 koji projekat koristi (ni duckdb ga nema), pa se taj korak vrti kroz uv run --python 3.13 --with pyarrow, a izolovano okruženje tako ne mora ništa više da instalira. make vazduh sam bira uv ako postoji.
  • Isprekidana linija smernice na mesečnom grafikonu nikada nije radila: vrednost je prosleđivana kao --szo: 26%, pa je calc(dužina × procenat) nevalidan i linija je padala na dno okvira. Vrednost je sada bezdimenziona. Ista greška bi se ponovila na novom grafikonu po godinama.
  • Grafikon prekoračenja iscrtava ceo raspon godina, sa praznim mestima gde merenja nema. Prva verzija je preskakala 2015, 2016, 2018 i 2019 i izgledala kao neprekidan niz.
  • Mapa stanica isključuje ruralnu iz okvira (8,8 km je dovoljno da se koridor svede na crticu) i crta isprekidan potez od svake stanice do najbliže tačke trase — to je odgovor na pitanje zašto baš ta stanica.

Terenski materijali

izbor_tacaka.py sada, pored GPX-a i CSV-a, pravi i obrazac.html za štampu (A4 landscape, svih 18 tačaka na jednom listu, uputstvo na poleđini) i data/merne_tacke.json da se tačke vide na mapi u analizi. PDF se pravi kroz Puppeteer, ne kroz PDF biblioteku: obrazac ima dijakritike (č, ć, š, ž, đ), a lake PDF biblioteke za njih traže ručno ugrađen font, dok pregledač to već ume — a usput isti HTML može i da se odštampa direktno.

U zaglavlju su dva QR koda, ka GPX-u i CSV-u na sajtu, generisana kao inline SVG (qrcode, ERROR_CORRECT_M, zona tišine 2 modula). Provereni su modul po modul: obrazac je rasterizovan na 300 dpi i svih 1.369 modula oba koda poređeno sa očekivanom matricom — nula odstupanja, veličina modula 0,63 mm.

CSV je dobio kolone podloga i klupe, koje ne služe buci nego analizi 9.3. Tip podloge nam nedostaje jer ga OpenStreetMap za staze uz Nišavu skoro ne beleži, a klupe su osnov za „mesta predaha". Pošto se ionako prelazi 13 km, beleže se usput.

Šta i dalje nije rešeno

Nijedna stanica ne stoji na keju — najbliža je 288 m i to je rečeno na stranici. Razlika između gornjeg i donjeg bedema u NO₂ izvedena je iz istog modela blizine puteva, pa nosi njegove pretpostavke, ne nezavisnu proveru. Žarišta zagađenja i dalje se ne prikazuju: za njih bi trebalo merenje duž same trase, a ne ekstrapolacija sa dve gradske stanice.

Bezbednosni audit: tri kategorije, bez novih podataka

Analiza 8 — infrastrukturna bezbednost, lična bezbednost i saobraćajni konflikti — u potpunosti je izvedena iz GeoJSON fajlova koji su nastali terenskim mapiranjem. Nije povučen nijedan novi API poziv. Sve računate veličine — tamne zone, gustina stepenica, parking konflikti — dobijene su projekcijom tačaka na zajedničku km-osu koridora, istom geometrijskom infrastrukturom koju koriste analize 9.1–9.3. Rezultat je Analiza 8 → Bezbednosni audit.

Podaci i šta od čega dolazi

Četiri GeoJSON fajla hrane analizu, svi generisani iz koridor_data.kml kroz convert.py:

  • stanja.geojson — 14 zapisa o stanju površina i infrastrukture duž trase (loše, srednje, dobro, deponija). Svaki zapis je ručno zabeležen na terenu, ne izveden iz satelita.
  • osvetljenje.geojson — 130 svetiljki projektovanih na biciklističku osu. Svetiljke su između sebe daleko 0 do 6.550 m; sve praznine veće od 200 m proglašavaju se tamnim zonama.
  • prekidi.geojson — 52 tačke prekida u kretanju. Subset se klasifikuje kao parking konflikti (regex: parking|koso park|rampa za park|divo park) ili saobraćajni prelazi (prelaz|nema peš|most), ostatak se ignoriše jer ne pripada analizi 8.
  • stepenice.geojson — 81 stepenica, raspoređena po deonicama i histogramu gustine na 500 m segmentima.

Tamne zone: kako se računa 68 %

Svaka svetiljka se projektuje na biciklističku osu (prostorni grid sa ćelijom PROJ_MAX_M, isti kod kao u 9.2). Od 130 svetiljki, sve ulaze u projekciju — prag rastojanja nije problem jer su svetiljke neposredno uz stazu. Sortirani km položaji svetiljki dele osu na intervale. Svaki interval duži od 200 m postaje tamna zona. Prvih 0 km i posle poslednje svetiljke do kraja trase (km 14.02) računaju se kao tamne ako su duže od praga, jer „počinje mrak" pre prve i posle poslednje lampe. Rezultat: 3 zone, 9.560 m, 68,1 %. Najduža zona je 6.550 m — od Brzog Broda prema Niškoj Banji, bez ijedne svetiljke na celoj deonici.

Prag od 200 m je radni kriterijum, ne standard. Zona može biti bezbedna i bez svetla ako je osvetljena iz okolnih izvora (kafane, prodavnice). Konzervativni prag je odabran svesno.

Stepenice: projection challenge

Prva verzija koda koristila je samo biciklističku osu sa pragom 40 m (PROJ_MAX_M) — isti kao u svim ostalim analizama. Rezultat: 39 od 81 stepenice su propale projekciju. Razlog je fizički: stepenice na Gabrovačkoj Reci stoje na starom bedemskom zidu, 40–80 m od biciklističke staze. U 9.1–9.3 taj prag štiti od lažnih projekcija udaljenih objekata na osu, ali ovde nas košta tačne podatke.

Rešenje: za stepenice se gradi poseban prostorni index nad sirovim uzorcima svih staza (biciklistička + oba pešačka bedema), a projekcija na bici-osu se radi bez gornje granice rastojanja (brute-force po flat listi, bez cell filtera). Svaka stepenica se prvo projicira na najbližu tačku bilo koje staze, a odatle se km koordinata prenosi na bici-osu. Krajnji rezultat: 0 propuštenih stepenica.

Gabrovačka Reka: artefakt geometrije

37 stepenica na Gabrovačkoj Reci projektuje se u histogram gotovo sve u isti bin oko km 5.0. Razlog: bedemski zid Gabrovačke Reke ide poprečno na biciklističku osu — sve stepenice su raspoređene po kratkom pojasu koji se, gledano duž km-ose, kolapsira u nekoliko stotina metara. Stepenice postoje fizički na celoj vertikalnoj visini bedema, ali km-osa ih ne razlikuje. Ograničenje je dokumentovano, ne pokušavamo da ga ispravimo jer bi zahtevalo drugu km-osu za samu Gabrovačku Reku.

Vizualizacije

Tri tipa prikaza nisu bili deo prethodnih analiza i dodati su kao novi CSS + canvas komponente:

  • Pin timeline (CSS, apsolutno pozicioniranje) — svaka tačka stanja se ispisuje kao pin na procentualnoj poziciji duž trake. Pređi mišem → tooltip sa imenom i tipom. Alternativa linijskom stripu — prikladnija za sparse podatke (14 tačaka na 14 km).
  • Canvas histogram gustine stepenica — 500 m binovi bojeni po gustini (zeleno ≤ 2, narandžasto 3–5, crveno ≥ 6), hover tooltip pokazuje opseg km i broj stepenica. Odabrana umesto scatter mape jer vizualizuje koncentraciju, ne samo lokacije.
  • Lighting strip — isti princip kao shade strip u 9.1, ali boji se po izlaznom objektu, ne po keširanom JSON-u po tački. Segment je žut ako mu je km unutar opsega neke svetiljke ± pola praga, inače tamnosiv.

Leaflet mapa u sekciji 8.3 koristi isti L.Layer.extend canvas overlay kao 9.1, ali boji trasu po osvetljenosti (tamne zone/osvetljeno) umesto po satima senke, i dodaje kružne markere za parking konflikte i prelaze.

Šta nije obuhvaćeno

Slepi uglovi i podvožnjaci nisu bili deo terenskog protokola, pa nedostaju iz analize 8. Privremene prepreke (gradnja, parking na stazi koji nije ručno unesen) su delimično obuhvaćene kroz prekidi.geojson, ali nisu sistematski maprane. Ove kategorije treba dodati u sledećoj iteraciji terenskog mapiranja.

Pokazatelji sekcije 9: najniži prag koji podaci trpe, i pogled na reku

Okvir analize traži da sekcija 9 ima tabelu pokazatelja: kontinuitet senke, krošnje, izloženost suncu, pogled na reku, buka, žarišta zagađenja vazduha i termalni komfor. Pet od sedam je popunjeno iz tri analize, od kojih je jedna, pogled na reku, izračunata sada. Jedan red je pre objave morao da se preračuna, jer je merio pogrešnu stvar. Žarišta zagađenja su namerno izostavljena, a termalni komfor čeka 9.3. Tabela je na spisku analiza.

Kontinuitet senke: prva verzija je merila drvored, ne senku

Prva verzija mere brojala je neprekidne nizove drvoreda: tačke sa krošnjom višom od 3 m u prozoru ±9,5 m. Za donji bedem dala je 45 %, dok je u istoj tabeli izloženost suncu bila 81 %. To ne može oboje biti tačno za „senku", i nije: drvored nije senka. 21. juna u podne sunce je u Nišu na 69°, pa stablo od 10 m baca senku od svega 3,8 m. Drvored koji stoji nekoliko metara pored staze ne zaseni stazu. Od tačaka donjeg bedema koje su „uz drvored", 62 % vremena između 12 i 16 h je na suncu.

Mera je zato preimenovana u ono što se zapravo pita: udeo dužine u neprekidnim nizovima stvarne senke, u istom trenutku kao red za sunce (21. jun, 12–16 h). Tačka je u senci ako je u senci u većini od tih pet sati. Izbor praga većine gotovo ništa ne menja: tačka je u senci ili ceo vrh vrućine ili uopšte ne, jer u podne leti hlad daje samo krošnja direktno iznad staze. U podne senka pada preko staze, ali je kratka; kako se sunce spušta ka zapadu (262° u 16 h), senka se izdužuje, ali pada ka istoku, dakle duž staze koja uglavnom ide istok-zapad (12,3 km istok-zapad naspram 5,3 km sever-jug), a ne preko nje. Sada važi provera koje ranije nije bilo: kontinuitet senke ne sme biti veći od udela staze koji je uopšte u senci.

Prag je spušten sa 100 m koliko podaci dozvoljavaju. Za drvored bi to bilo 40 m, jer jedno stablo u prozoru od ±9,5 m „upali" 20–30 m staze. Senka se, međutim, računa u samoj tački, bez prozora, pa jedna krošnja daje niz od 10–20 m. Raspodela dužina nizova senke ima prelom između 20 i 30 m (biciklistička: 10 nizova od 20 m, 4 od 30 m; gornji bedem 10 prema 1). Prag je 30 m; ispod toga mera broji pojedinačne krošnje. Rezultat: 4.8 / 1.9 / 14.9 % (bici / gornji / donji), naspram 7.6 / 5.0 / 19.0 % staze koja je u senci uopšte.

Izloženost suncu: podne, ne prosek dana

Mera je udeo dužine na direktnom suncu 21. juna, uprosečeno kroz položaje sunca u 12, 13, 14, 15 i 16 časova. Prosek celog dana bi zamaglio ono što je bitno, jer senka ujutru i uveče ne pomaže kad je vrućina. Razlika između dva reda tabele je poučna: krošnje stoje uz 27 % biciklističke staze, a između 12 i 16 h u senci je svega 7.6 % njene dužine. Kad je sunce visoko, stablo pored staze baca senku pored nje. Uhvaćena usput i sitna greška u opisu: prvo je pisalo „12-17 h", jer je gornja granica računata kao kraj intervala, a sunce se računa za trenutak punog sata.

Pogled na reku

river_views.py: za svaku tačku sve tri staze pušta se 72 zraka u krug (na 5°), do 250 m, sa visine oka od 1,5 m, preskačući prva 2 m. Pravac vidi reku ako zrak stigne do površine Nišave pre nego što naiđe na vegetaciju višu od oka. Tačka ima pogled ako voda može da se vidi u uglu od bar 10°; uži ugao je prorez između stabala. Za razliku od buke, ovde su podaci o krošnjama pravi alat, jer drveće zaklanja pogled snažno. CHM prozor je isti kao za senku i deli se preko istog ključa keša, bez ponovnog preuzimanja.

Voda u OSM-u nije ujednačeno ucrtana. Pojedinačni poligoni natural=water + water=river pokrivaju Nišavu samo zapadno od lon 21,925, otprilike do km 5. Istočno je reka član dve multipoligon relacije, koje su za Overpass prevelike: upit sa punom geometrijom vraća 504. Relacije se zato čitaju bez geometrije (samo uloge članova), a geometrija se traži samo za članove unutar koridora. Dva mala člana su ade (inner) i rasterizuju se kao kopno. Linija toka Nišave se dodaje kao osigurač za rupe u poligonima. Bare, ribnjaci i staro korito se ne računaju.

CHM daje visinu vrha krošnje, ne njenu donju ivicu, pa ne zna da li se ispod visokog drveta vidi. Zato su računate dve granice: konzervativna (zaklanja svaka vegetacija viša od oka) i optimistična (zaklanja samo vegetacija do 8 m). Razlikuju se za 0.3–3.3 procentna poena, pa ta nesigurnost praktično ne utiče na rezultat; u tabeli je konzervativna vrednost. Geometrijski potencijal, pogled kad rastinja ne bi bilo, iznosi 100 % na sve tri staze. Sav gubitak pogleda je dakle rastinje.

Rezultat je 58.7 / 61.5 / 56.3 %, ali razlika između staza je manja od razlike između deonica: zapadni, gradski deo ima pogled na 76–100 % dužine, a šumoviti istok (Brzi Brod, Niška Banja) na 23–35 %. Uz drvored pogled postoji na 13–32 % tačaka, bez drvoreda na 73–92 %. Donji bedem, najbliži vodi, ima najmanje mesta sa pogledom, ali kad ga ima, najširi je: sa pragom od 90° umesto 10° redosled staza se obrće (40.5 / 41.6 / 43.5 %). Ista gustina drveća koja mu daje duplo više hlada zaklanja mu reku.

Ograničenja: teren je ravan. Gornji bedem je u stvarnosti uzdignut i verovatno gleda preko niskog rastinja na nižoj terasi, pa mu je pogled potcenjen; SRTM od 30 m ne razlučuje nasip, isti problem kao kod buke. Snimci krošnji su iz 2018–2020, a zimi, bez lišća, pogled je otvoreniji nego što piše.

Zašto nema žarišta zagađenja vazduha

„Žarište" je mesto, a CAMS ćelija od 11 km ne razlučuje mesto duž koridora. Jedino odbranjivo zamensko merilo bila bi blizina glavnih puteva. Ali podaci iz 9.2 pokazuju da to ne bi merilo glavni problem: PM2.5 je preko dnevne smernice SZO 44.8 % dana i to zimi, u grejnoj sezoni, dok je NO₂, tipično saobraćajno zagađenje, preko smernice 0.1 % dana. Blizina puteva bi merila nešto sporedno, a tabela bi sugerisala mapu žarišta koju nemamo. Red je izostavljen dok ne postoji izvor koji razlučuje prostor.

Tabela se ne računa u browseru

indikatori.py iz izlaza tri analize izvodi data/indikatori.json od 1,4 KB, a analize.html čita samo njega. U suprotnom bi spisak analiza učitavao preko 500 KB podataka po tački da prikaže petnaest brojeva. Opisi mera na stranici grade se iz parametara u tom fajlu, pa ne mogu da zaostanu za pragom. Redosled u pipeline-u: make canopy noise views indikatori.

Buka bez merača zvuka: šta se pošteno može reći iz same geometrije puteva

Analiza 9.2 je prva koja nema svoj satelit. Senka je stajala na stvarnim izmerenim visinama krošnji; za buku duž keja ne postoji nijedno merenje, a Niš nema javnu kartu buke. Pitanje nije bilo kako izračunati, nego šta sme da se tvrdi. Rezultat je Analiza 9.2 → Buka i kvalitet vazduha, sa indeksom umesto decibela i sa dva izvora koja odgovaraju na različita pitanja.

Zašto indeks, a ne decibeli

Model daje broj; broj u decibelima nosi tvrdnju o apsolutnom nivou koju ništa ne pokriva. Bez ijednog merenja duž trase kalibracija bi bila proizvoljna, a čitalac bi „62 dB" razumeo kao izmereno. Skala je zato 0–100 u odnosu na sam koridor, sa fiksnim kotvama u log-prostoru (−32 do −1 dB relativne energije) da se vrednosti ne pomeraju kad se OSM promeni. Rangiranje deonica je pouzdano; apsolutni nivo se ne tvrdi.

Model

Putevi dolaze iz OpenStreetMap-a preko Overpass API-ja (3.831 puta, 22.103 segmenta u bbox-u koridora sa 400 m margine; odgovor ~1 s, keširan po bbox-u). Svaki segment je niz nekoherentnih tačkastih izvora — energija opada sa 1/d², a težina segmenta je proizvod dužine i jačine izvora. Integral tačkastih izvora duž linije reprodukuje ponašanje linijskog izvora (−10·log10 d), što je fizički ispravna konstrukcija; zbir po samim segmentima bez težine dužinom bi favorizovao puteve iscepkane na mnogo kratkih delova. Jačina izvora je rang po klasi puta (autoput 1.00 → servisna 0.03), pomnožen brzinom iz maxspeed (873 puta ga ima) i brojem traka iz lanes (1.076). Radijus pretrage 300 m, minimalno rastojanje 15 m da se izbegne singularitet gde staza dodiruje put.

Šta je namerno izostavljeno

  • Vegetacija. Prvobitna zamisao je bila da se drvored uračuna kao barijera, ray-marching-om preko istog CHM rastera koji koristi 9.1 — tehnički elegantno, jer bi isti kod pustio zrak ka putu umesto ka suncu. Odbačeno: gust pojas daje reda veličine 1–3 dB na 10 m, a tanak drvored praktično ništa. Drveće zaklanja pogled na saobraćaj snažno i zvuk slabo; uračunati ga značilo bi prodati vizuelni efekat kao akustični.
  • Nasip. Donji bedem je fizički zaklonjen nasipom i trebalo bi da je tiši nego što model kaže. Provereno da li se to može izmeriti: horizontalno rastojanje između gornje i donje staze je median 17 m, a 88 % parova pada u istu SRTM ćeliju od 30 m — dobiju identičnu visinu. Visine su uz to celobrojne, a ceo pad je 26 m preko 14 km. SRTM ne razlučuje nasip i terenska barijera otpada dok se ne pojavi finiji visinski model.
  • Dnevni ritam. OSM nema brojanje saobraćaja, pa podela na špic i van špica nije moguća bez izmišljanja profila protoka. maxspeed pomaže oko jačine izvora, ali ne daje vremensku dimenziju.

Bug koji je izgledao kao podatak

Kolona „najbliži glavni put" je u prvom prolazu pokazivala 0 m za Medoševac, deonicu koja je inače daleko od saobraćaja. Uzrok: prag za „glavni put" poredio je otežanu vrednost segmenta sa osnovnom težinom klase secondary (0.5). Tercijarni put sa visokom brzinom i četiri trake dolazi na 0.585 i prolazi kao magistrala. Zastavica se sada nosi eksplicitno iz klase puta. Uz to je i sam prikaz promenjen — minimum po deonici je opisivao jednu tačku ukrštanja dok 80 % deonice nema glavni put u 300 m, pa tabela sada prikazuje udeo trase van domašaja saobraćaja.

Vazduh je drugo pitanje sa drugim ograničenjem

Buka je prostorna, vazduh nije. Najbolji besplatan izvor je CAMS preko Open-Meteo (bez API ključa, reanaliza od 2013., satno) — ali evropski domen ima ćeliju od 0.1° ≈ 11 km, dakle jednu vrednost za ceo koridor. Prostorna varijacija duž keja besplatnim podacima nije razlučiva; za to bi trebalo disperziono modeliranje ili sopstveni senzori. Zato se vazduh prikazuje kao vremenski kontekst, a ne kao mapa po kilometru. Odbačena je i ponuda gotovog „free air quality API"-ja koji prima ime grada: jedna vrednost za ceo Niš, bez istorije, bez navedenog izvora podataka, i sa API ključem koji bi na statičnom sajtu završio vidljiv u klijentskom JS-u.

Nalaz je bio prijatno iznenađenje. PM2.5 prelazi dnevnu smernicu SZO 45 % dana, a godišnji prosek je 16.9 µg/m³ naspram smernice od 5 — ali raspodela kroz godinu je oštra: decembar 31.5, januar 26.0, februar 28.0, prema 9.8–10.8 od maja do septembra. To je potpis sezone grejanja. Kej se koristi tačno onda kada je vazduh najčistiji, što je nalaz koji se ne bi video da je prikazan samo godišnji prosek.

Refaktor koji je morao pre, a ne posle

Učitavanje mreže staza, resampling na 10 m, projekcija na zajedničku km-osu i dodela deonica su izvučeni iz shade_canopy.py u koridor.py. Urađeno pre pisanja noise_air.py, dok je postojao samo jedan korisnik tog koda — 9.2 i 9.3 sede na istoj osi, pa bi kopija odmah počela da se razilazi. Provera da refaktor ništa nije pomerio: data/shade_canopy.json regenerisan i upoređen sa prethodnom verzijom, identičan bajt u bajt.

Keš izlaza nosi otisak koji pokriva i parametre modela, ne samo geometriju — inače izmena težina ili radijusa ne bi ponovo generisala data/noise_air.json. Isti princip kao kod CHM prozora u 9.1.

Šta sledi

Model rangira, ali nije proveren. Kalibracija je regresija sa dva slobodna parametra (dB(A) = a + b · 10·log10(E)), pa je bitan raspon merenja a ne njihova količina — 18 tačaka raslojenih po indeksu, u jednom obilasku od 13 km, dovoljno je. Protokol, koordinate, GPX i preporučena oprema su u docs/9.2-kalibracija-buke.

Četiri tačke su uparene: gornji i donji bedem na istoj kilometraži. Njihova razlika je jedina veličina iz tog izlaska koja se ne može dobiti doterivanjem modela — daje zaklon nasipa, član koji je gore odbačen jer ga SRTM ne razlučuje. Očekuje se i pod od reke: na najtišim tačkama Nišava verovatno postavlja donju granicu, pa nivoi neće pratiti model naniže. To nije greška modela nego reka, i bolje je znati unapred. Vizuelno zaklanjanje pogleda na saobraćaj (za koje CHM jeste dobar) ostaje kao zasebna metrika za kasnije, jasno odvojena od akustike.

Senka sat po sat: uspavani eksperiment vaskrsao sa pravim podacima o krošnjama

Post-mortem shadeMap eksperimenta (ispod, 14. jun) završio se rečenicom: „aktivacija je trivijalna ako se pojavi slobodno dostupan canopy/DSM tile source". Taj uslov je ispunjen — Meta/WRI globalna mapa visine krošnji (1 m rezolucija, CC BY 4.0) pokriva Niš. Umesto reaktivacije shadeMap SDK-a, senku sada računamo sami: NOAA formule za položaj sunca + numpy ray-marching. Rezultat je nova stranica Analiza 9.1 → Senka i pokrivenost krošnjama.

Izvor podataka

Meta/WRI Canopy Height Map je globalni raster visina krošnji procenjen iz Maxar satelitskih snimaka (2018–2020) mašinskim učenjem, dostupan kao Cloud-Optimized GeoTIFF na AWS Open Data. Niš pada u jedan tile od 686 MB — ali COG format dozvoljava čitanje samo prozora oko trase preko HTTP range zahteva (~15.000 × 3.700 px na 1.19 m/px). Validacija: pokrivenost drvećem duž trase po ovom izvoru (26.8 %) konzistentna je sa postojećom ESA WorldCover analizom (23.4 %), uz bonus stvarnih visina 5–22 m.

Zašto sopstveni ray-casting umesto shadeMap SDK

SDK bi tražio da visine krošnji upakujemo u „terrarium" PNG tile-ove i serviramo ih lokalno kroz Puppeteer + headless Chrome + WebGL. Umesto toga: ~60 linija numpy koda. Za svaku tačku trase (10 m korak, 1.469 tačaka) i svaki sat u danu za 4 referentna dana (solsticiji + ravnodnevnice), zrak od bicikliste (1.5 m) ka suncu se prati preko CHM rastera korakom od 1.2 m — ako bilo koja krošnja premašuje visinu zraka, tačka je u senci. Tačka direktno pod krošnjom je u senci nezavisno od ugla. Bez API ključa, bez browser-a, deterministički; ceo izračun traje sekunde.

Provera zdravog razuma

  • Sezonska varijacija sada postoji (kod bare-earth pokušaja je nije bilo): decembar 15.8 % senke > mart 13.0 % > jun 9.0 % — nisko zimsko sunce baca duže senke.
  • Prostorna varijacija prati drvorede: Centar i Niška Banja 16–28 %, Delta–Lidl 2–4 %.
  • Bitna lekcija iz uzorkovanja: visinu krošnje kod tačke tražimo u prozoru ±10 m — drvored je pored staze, pa tačkasto očitavanje na osi trase daje lažne nule.

Prikaz

Nova stranica 9.1.shade_and_tree_canopy_coverage.html (tada analiza.html): toplotna mapa km × sat (canvas, 4 taba za doba godine, hover tooltip sa visinom krošnje), mapa sa trasom obojenom po stanju u izabranom satu (custom Leaflet canvas layer + klizač; režim „Ceo dan" boji po ukupnim sunčanim satima) i tabela po deonicama. Sve i dalje statičko — make canopy generiše data/shade_canopy.json (75 KB), sajt ga samo čita.

Ograničenja

  • Snimci 2018–2020: novije sadnje i seče se ne vide.
  • Zgrade nisu u izračunu (trasa je uglavnom daleko od objekata; procena iz starog post-mortema: +2–5 % samo za Centar).
  • Teren zanemaren — kej je ravan, dno doline; stari eksperiment je pokazao da sam teren daje 2–6 % svuda.

Stari shadeMap pipeline (shade_real.py / shade_compute.js) ostaje u repo-u kao istorijat, ali je shade_canopy.py sada primaran put za senku.

Dopuna: preciznija trasa (19. jul)

Senka se sada računa po ručno crtanoj liniji „biciklisticka trasa" (sloj meta_deonice u MyMaps) umesto Google-rutirane „Indicaciones" linije. Nova linija prati stazu na keju umesto okolnih ulica — medijan udaljenosti od terenski mapiranih staza 2.0 m (stara: 3.5 m). Posledica koja je prvo ličila na bug, a zapravo je poštenija slika: senka u Centru je pala sa 16 % na ~1 % (21. jun), jer je stara linija delom išla ulicom Kola srpskih sestara pod drvoredom, a stvarna staza ide otvorenim donjim šetalištem uz reku — drvored je predaleko da bi mu senka leti dosezala do staze. Deonice se sada dodeljuju point-in-polygon testom na meta_deonice poligone.

Sklonjen kumulativan uspon i pad iz visinskog profila

Posle iteracija (deadband filter, gušće uzorkovanje, tooltip objašnjenje) prepoznali smo da kumulativni uspon i pad ne odgovaraju ni na jedno pitanje koje obični biciklista postavlja o našoj trasi. Kej je ravan (~22 m raspona na 14.67 km, max 3.7 % nagib); razlika između „55 m kumulativnog uspona" i „30 m" ne menja ničiju odluku. Brojevi su sklonjeni iz UI-ja.

Za koga je metrika korisna

Kumulativni uspon je centralna metrika u Strava / Garmin svetu jer korelira sa naporom na brdovitim treninzima i koristi se za poređenje različitih ruta. Za sportistu koji vozi 80 km kroz Vlasinu sa 1500 m kumulativnog uspona — ima smisla.

Za našu publiku — ne

Naš sajt je za zajednicu Niša koja procenjuje koridor kao gradsku infrastrukturu: „da li je trasa bezbedna, pristupačna, prijatna?" Pitanja koja stvarno imaju veze sa visinom:

  • „Ide li uzbrdo?" → Raspon visina (22.8 m ukupno — neprimetno)
  • „Ima li strmih delova gde treba pripaziti?" → Max nagib (3.7 % — vozljivo i bez prebacivanja u nižu brzinu)
  • „Gde su uzbrdice tačno?" → Profil grafik (vizualno, sa hover-om i pozadinskim trakama po deonici)

Kumulativni uspon na ovu pitanja ne odgovara. A i kad smo ga doterali (histerezisni filter sa pragom 1 m da eliminiše SRTM šum), korisnik bi i dalje morao da pročita tooltip da razume zašto je „uspon" veći od „raspona min–max" — što sugeriše da metrika nije intuitivna za publiku koja nije već navikla na nju.

Šta je promenjeno

  • U UI-ju: kartice „Ukupan uspon" i „Ukupan pad" sklonjene iz totals tabele; redovi „Uspon" / „Pad" sklonjeni iz svake per-deonica kartice. Ostaju: Raspon visina, Maks. nagib, profil grafik.
  • U kodu: asc_desc_grad() sa histerezisnim deadband-om obrisan; compute_elevation_stats() sada vraća samo raspon i max nagib. Polja ascent_m/descent_m nestaju iz data/elevation.json.
  • U CSS-u: .info-mark tooltip implementacija obrisana (više se nigde ne koristi).
  • ELEV_SCHEMA 6 → 7.

Konzervativan princip: ne dodavati metrike za koje treba tooltip da budu razumljive — radije skinuti. Sajt postaje čistiji bez gubitka informacije za publiku.

shadeMap pre-compute: pokušano i (za sada) odustalo

Eksperiment: zameniti grubu „prisutnost drveća → senka" proksi metodu sa pravim ray-tracing izračunom senke preko shadeMap.app SDK-a, za 4 referentna dana. Pipeline radi, infrastruktura ostaje u repo-u, ali sekciju trenutno ne objavljujemo. Razlog: SDK ne dolazi sa podacima o drveću i zgradama, a bez njih su brojke neinformativne za naš slučaj.

Šta je izgrađeno

  • shade_real.py — Python orchestrator. Učitava sample tačke iz elevation.json, sprema input za Node, agregira rezultat po deonicama.
  • shade_compute.js — Node skripta. Puppeteer pokreće headless Chrome (sa SwiftShader WebGL fallback-om) i Leaflet mapom (1600×1000 viewport) na koju se dodaje leaflet-shadow-simulator overlay sa sunExposure modom. Za 491 tačaka × 4 datuma poziva shade.getHoursOfSun(x, y) (latLng → containerPoint).
  • Output: data/shade_real.json sa per-sample sun hours i agregatima po deonici. Cache invalidacija preko hash-a tačaka + datuma + schema verzije.
  • 4 referentna dana sa eksplicitnim CET/CEST offset-om: 21. mart, 21. jun, 21. septembar, 21. decembar.

Šta je proizvelo

Posle uspešnog rana: ~2–6 % senke duž cele trase, skoro identično za sve deonice i sva 4 dana. Decembar (kratak dan, niski ugao sunca, normalno bi imao dugo zatamnjene delove) pokazuje manje senke nego mart. Brojke nemaju varijacije koju bismo očekivali — što je trebalo da pokrene alarm.

Zašto su brojke besmislene

SDK je čist engine, ne sadrži podatke o drveću i zgradama. Jedini izvori URL-ova u library-ju: AWS Terrarium (bare-earth DEM) i shademap.app/sdk/load (autorizacija — ne data). Za realnu senku, korisnik mora sam dostaviti:

  • terrainSource — bare DEM (mi smo dali Terrarium, ima)
  • getFeatures — GeoJSON zgrada sa visinama (nismo dali)
  • canopySource / dsmSource — canopy/DSM tile-ovi sa visinama drveća (nismo dali — undocumented API; shadeMap.app web sajt to servira sa svoje strane)

Niš je u dolini Nišave, kej je u dnu doline — sam teren ne baca senku. Niška Banja je u kanjonu ali kej je sredinom, planinski profili su simetrični. Realno, drveće na samom keju je glavni izvor senke za bicikliste, a upravo to ne ulazi u izračun bez canopy podataka.

Šta smo razmotrili kao spas

  • Dodati OSM zgrade preko Overpass-a — daje senku od urbane okoline. Problem: zgrade su 30–100 m od trase, senke padaju pored puta većinu dana. Procena: +2–5 % apsolutno samo za Centar, ništa za ostatak.
  • „Virtuelni canopy" iz WorldCover-a — za svaki tree_cover piksel generisati polygon visine ~10 m. Hiljade features → spor render; visine su pogađanje.
  • Reframe-ovati postojeći output — pošteno reći da je „samo teren, ne drveće". Ali tada brojke prestaju da budu informativne čitaocu.

Šta smo odlučili

Sekciju trenutno ne objavljujemo. Kao indikator senke od drveća ostaje postojeća tree-cover analiza (ESA WorldCover) iznad — gruba ali poštena. Za precizno proveravanje senke u određeno doba dana, korisnik ima interaktivni ☀ dugme gore-levo na mapi koji otvara shadeMap.app sa pravim drvećima i zgradama jer su tamo serverski integrisani.

Šta ostaje za budućnost

Pipeline (shade_compute.js, shade_real.py, package.json, Makefile shade/node-deps targeti) ostaje u repo-u, uspavan. Aktivacija je trivijalna ako se pojavi neka od sledećih situacija:

  • Slobodno dostupan GEDI / ETH Global Canopy Height tile servis za regiju Niša → konfiguriši kao canopySource
  • Paid shadeMap tier sa pristupom njihovim DSM tile-ovima → konfiguriši kao dsmSource
  • Tačan OSM polygon za centralne ulice sa visinama zgrada → konfiguriši getFeatures

U bilo kojem slučaju, jedna izmena shade_compute.js-a i ponovni run vraćaju sekciju. Naučili smo dosta o ograničenjima open-source ray-tracing engine-a za senku — i bolje pošteno ne objaviti loše brojke nego da čitalac dobije pogrešnu sliku.

Glatka kriva i gušće uzorkovanje (30 m + Catmull-Rom)

Visinski profil se sada uzorkuje na svakih 30 m (umesto 50 m) — match nativnoj rezoluciji SRTM-a — i renderuje kao Catmull-Rom spline kriva umesto polyline-a. Vizuelno glađe bez izmišljanja podataka: kriva i dalje prolazi kroz iste tačke, samo se segmenti između njih iscrtavaju glatko.

Gušće uzorkovanje (30 m korak)

SRTM 30 m je nativno na rezoluciji od 30 m po pikselu. Sa 50 m korakom smo praktično preskakali svaki šesti piksel; gušće uzorkovanje od 30 m match-uje izvor — više od toga je oversample (Open-Topo-Data samo bilinearno interpolira između istih piksela, ne dobija nove informacije).

  • Broj uzoraka: 293 → 491 (14.67 km)
  • Smoothing prozor: 5 → 9 tačaka (≈ 270 m fizički, isto kao ranije)
  • API vreme: 3 → 5 batch-ova (~5 s, jednom)

Catmull-Rom spline

Ranije: SVG path je bio polyline (M ... L ... L ...), pa je linija bila „zubčasta" zbog rezidualnog šuma od ~0.5–1 m između susednih tačaka. Sada: uniform Catmull-Rom kriva konvertovana u cubic Bezier segmente. Kriva prolazi kroz sve tačke (hover dot i tooltip i dalje pogađaju realne km vrednosti) ali se segmenti između njih iscrtavaju glatko.

Matematika je trivijalna: za 4 susedne kontrolne tačke P₀–P₃, Bezier-ovi kontrolni vektori za segment P₁ → P₂ su P₁ + (P₂−P₀)/6 i P₂ − (P₃−P₁)/6. Bez D3 ili drugih biblioteka.

Sitni edge-case

Kad se trasa resampuje na 30 m korake, krajnji „rep" trase može biti samo 1 m. To je 1/30 deo punog koraka, pa svaka delta visine od 0.1 m daje fiktivnih 10 % nagiba. Filter dodat u asc_desc_grad(): segmenti kraći od pola koraka (15 m) se preskaču u računu max gradienta.

Rezultat (totals)

  • Ukupan uspon: 59 → 55 m
  • Ukupan pad: 42 → 38 m
  • Centar: 8 → 6 m (preciznije lokalizovani mostovi/rampe)
  • Max nagib: 3.2 % → 3.7 % (Brzi Brod, realan — bolje uhvaćen kratak uspon)

Realističniji uspon i pad (deadband filter)

Brojke za ukupan uspon i pad su delovale nelogično — npr. deonica „Centar" je pokazivala uspon od 9 m iako se cela visina kreće u rasponu od svega 4.4 m. Razlog je dvostruki: jedan koncepcijski (kumulativno ≠ raspon) i jedan tehnički (šum u SRTM 30 m podacima).

Zašto uspon može biti veći od raspona

Raspon je razlika najviše i najniže tačke trase — jedan broj. Ukupan uspon je suma svih sitnih pozitivnih promena visine duž 14.67 km. Ako trasa nekoliko puta talasa između dve visine, raspon ostaje mali ali uspon raste. Ovo je standardna konvencija u biciklističkim sportovima (isti princip koriste Strava, Garmin, Komoot).

Problem: SRTM šum

SRTM 30 m podaci imaju oko 1–2 m vertikalnog šuma. Posle 5-tačkastog moving average-a ostaje ~1 m rezidualni šum. Na ravnoj deonici (npr. kej u Centru) sa 80 uzoraka, taj šum sabira nekoliko metara lažnog uspona — što je dovoljno da broj deluje sumnjivo.

Rešenje: histerezisni deadband 1 m

Umesto da sabira svaku pozitivnu promenu (kao naivni algoritam), asc_desc_grad() sada koristi histerezisni filter:

  • Algoritam prati trenutnu „sidrišnu" tačku i tekući lokalni ekstrem.
  • Promena pravca se „potvrđuje" tek kad oscilacija pređe 1 m.
  • Manja talasanja (šum) se ignorišu; veće uzbrdice se kompletno registruju u jednoj transakciji od dna do vrha.
  • Ovo je isti pristup koji koriste Strava i Garmin za „elevation gain".

Rezultat na trasi (ukupno za 14.67 km):

  • Ukupan uspon: 79 m → 59 m
  • Ukupan pad: 65 m → 42 m
  • Centar: 9 m → 8 m (preostalih 8 m je realno — prilazi mostovima i rampe)

UI: u karticama „Ukupan uspon" / „Ukupan pad" i u svakoj per-deonica kartici, pored vrednosti stoji ⓘ ikonica sa kratkim objašnjenjem zašto uspon može biti veći od raspona (CSS tooltip preko ::after pseudo-elementa, sa fokus podrškom za touch uređaje).

Anketa: glas građana

277 anonimnih odgovora građana Niša o tome kako koriste kej i da li bi koristili biciklistički koridor — donut chart-ovi, bar chart-ovi za multi-select pitanja i karusel slobodnih komentara.

Kako radi

Anketa je sprovedena u Google Forms-u; CSV se obrađuje Python skriptom anketa.py koja izbacuje lične podatke (ime, e-mail, vreme odgovora, slobodne komentare drži posebno) i ostavlja samo agregirane brojeve po pitanju u data/anketa.json. Sirov CSV je u .gitignore-u i nikad ne dolazi do GitHub-a.

Donut chart-ovi su SVG koji se generišu u browseru (Math.PI × stroke-dasharray, bez biblioteke), sa semantičkim bojama: zeleno = pozitivno, žuto = umereno, sivo = pasivno, crveno = negativno. Multi-select pitanja (svrhe, način doprinosa) idu kao bar chart sa procentom ispitanika koji su izabrali opciju — zbir prelazi 100% jer svako bira više.

Komentari (karusel)

83 slobodna komentara prolaze kroz defanzivnu anonimizaciju (regex za e-mail, telefon, URL — u praksi 0 pogodaka, ali ostaje kao mreža), de-duplikuju se po lowercase tekstu, filtriraju ispod 15 znakova (eliminiše „Da" / „OK" / „+") i shuffle-uju sa fiksnim seedom da redosled bude raznovrstan ali ponovljiv. Karusel rotira automatski svakih 8 sekundi, pauzira na hover.

Pokrivenost zelenila i diskontinuitet senke

Satelitska klasifikacija kroz kakav teren trasa prolazi, sa fokusom na drveće — jedini pouzdan izvor senke tokom celog dana. Strip chart pokazuje gde su rupe u senčenju duž 14.67 km trase.

Izvor podataka

  • ESA WorldCover 2021 v2 — globalna satelitska klasifikacija zemljišnog pokrivača na 10 m rezoluciji (11 klasa: drveće, žbunje, trava, polja, izgrađeno, voda…). Preuzima se preko Terrascope WMTS kao 256×256 PNG tile-ovi, mozaikuju se u jednu sliku i keširaju u data/.cache/worldcover/ (gitignore-ovano).
  • shadeMap.app — eksterni alat za interaktivnu proveru: dugme ☀ gore-levo na mapi otvara shadeMap sa trenutnom pozicijom i slider-om za datum/sat. shadeMap radi DSM-based ray tracing i uračunava i zgrade i drveće.

Tehnički detalji

Trasu uzorkujemo na svakih 50 m (295 tačaka za 14.67 km). Za svaku tačku uzimamo 3×3 majority kernel (≈30×30 m) iz WMTS rastera — ne jedan piksel — da bi se izgladili granični artefakti gde jedan 10 m piksel istovremeno obuhvata kej i ivicu reke.

Land-prior bias za vodu: biciklistička trasa je suvozemna (most je beton = built_up u WorldCover-u, ne voda), pa ako u 3×3 kernelu postoji bilo koja non-water klasa — biramo nju. Vodu vraćamo samo ako su svi susedi voda (= zaista preko reke, npr. most). Bez ovoga, „Centar" je pokazivao 22% trase kao vodu zbog mixed-pixel artefakta tačno na ivici keja.

Diskontinuitet senke se računa kao niz prelaza drveće ↔ otvoreno duž km osi: koliko je najduži kontinuiran deo u senci, koliko najduža rupa bez senke, koliko prelaza u svakoj deonici. Strip chart se prekida i na granici deonice da bi se uredno mapirao u per-deonica mini stripove.

Visinski profil trase

Promena nadmorske visine duž 14.67 km koridora — ukupan uspon 67 m, pad 50 m, max nagib 3.2%. Trasa je relativno blaga; nije terenski izazov, ali pokazuje gde su lokalna „uzbrdica" u Niškoj Banji.

Izvor podataka

  • Open-Topo-Data SRTM 30 m — javni HTTP API koji uzorkuje SRTM Digital Elevation Model po lat/lon. Batch po 100 tačaka, rate limit 1 req/sec, 1000 req/dan. Slobodan za nekomercijalnu upotrebu.

Tehnički detalji

Linijska trasa iz KML-a se resampuje na uniformnih 50 m, lat/lon tačke se šalju u 3 batch-a (~3 sekunde ukupno). Sirove SRTM vrednosti imaju 1–2 m šuma pa se primenjuje 5-tačka centralni moving average pre nego što se računaju uspon, pad i max nagib — bez toga lokalni šum daje nerealno visok „max nagib".

Rezultat se kešira u data/elevation.json sa hash-om koordinata trase. Ponovni make convert ne udara API ako trasa nije promenjena u KML-u.

Grafikon je handroll SVG sa pozadinskim trakama po deonicama, hover linijom + tačkom + tooltip-om za pojedinačne km tačke. Bez Chart.js / Plotly.

Tipovi prekida u kretanju

Klasifikacija 52 mapiranih prekida u 10 tipova: bedem, dalekovod, pritoka, nedostatak rampe, objekat, promena karaktera puta…

Tehnički detalji

Regex pattern matching nad imenima pinova iz KML-a. Specifični patterni (npr. "nedostatak rampi") se proveravaju pre generičkih ("rampa") da se ne bi pogrešno klasifikovali. 31 od 52 prekida nema specifičan opis (samo „Прекид у кретању") pa idu u „Neoznačeno" — kandidat za buduće dopune u Google MyMaps-u.

Galerija „Sa terena"

28 mapiranih fotografija sa terena, filterabilno po kategoriji (prekidi, stepenice i rampe, urbana oprema, stanja, vegetacija, urbani džepovi), lightbox sa keyboard navigacijom i swipe-om.

Tehnički detalji

Slike su u opisima pinova na Google MyMaps kao <img> tagovi unutar <description> CDATA sekcije. convert.py izvlači URL-ove regex-om i preuzima slike jednom. MyMaps koristi fife=sNNNN URL parametar za rezoluciju — normalizujemo na fife=s1024 da ne preuzmemo full-res (ukupna veličina pada sa ~577 MB na ~43 MB bez vidljive razlike na popup-u / lightbox-u). Imena fajlova su SHA1 hash izvornog URL-a, pa je preuzimanje idempotentno (isti URL → isti fajl, bez duplikata).

Na kraju konverzije, orphan prune briše slike koje više nisu referencirane (ako se izmeni KML, stare slike se same uklanjaju iz data/images/).

Po deonicama — meta_deonice u KML-u

Koridor je podeljen na 6 deonica (Medoševac → Centar → Delta-Lidl → Gabrovačka Reka → Brzi Brod → Niška Banja). Sva mapiranja se grupišu po deonici za lokalnu analizu — broj klupa, osvetljenja, prekida, tip podloge…

Tehnički detalji

U Google MyMaps smo dodali poseban folder meta_deonice sa polygon-ima za svaku deonicu. convert.py ih izdvaja, sortira po lon centroid-u (zapad → istok), pa klasifikuje svaki pin / liniju iz drugih slojeva u jednu od deonica:

  • Pinovi (Point): ray-casting „point-in-polygon" test.
  • Linije (LineString): klasifikacija po midpoint-u; dužina linije se proporcionalno razdeljuje između deonica preko pojedinačnih segmenata.

Trasa koridora prolazi kroz različite oblike polygon-a koji ponekad imaju oštre uvale — uzorci mogu naizmenično „izaći iz" i „vratiti se u" deonicu. Smoothing pass posle klasifikacije popunjava izolovane „None" tačke ako su sa obe strane okružene istom deonicom, što ujednačuje strip chart i elevation chart pozadinske trake.

Inicijalna verzija sajta + KML pipeline

Prvi prikaz: Leaflet mapa sa svim slojevima iz KML-a, KPI brojevi, bar chart-ovi po tipu podloge, urbane opreme i vegetacije.

Izvor podataka

  • Vlastiti Google MyMaps koji se preuzima preko NetworkLink-a u my_maps.kml. make fetch ekstraktuje pravi <href> i čuva u koridor_data.kml.
  • Bazna karta: OpenStreetMap (default), ESRI World Imagery (satelit) i OSM HOT (humanitarni stil). Do septembra 2026. podrazumevan je bio CartoDB Voyager, dok nije počeo da utiskuje „API KEY REQUIRED" u pločice.
  • OpenStreetMap kao osnova kartografskih podataka — autori prikazani u attribution-u mape.

Tehnički detalji

Python xml.etree.ElementTree parser čita KML, normalizuje imena slojeva (handle typos: „vioska vegetacija" → „visoka_vegetacija", „loshe stanje" → „loše"…), kategorizuje pinove po regex pattern matching-u. Generiše po jedan GeoJSON fajl per kategoriju + data/stats.json sa svim agregatima.

Frontend je vanilla JavaScript + Leaflet — bez framework-a, bez build step-a, bez NPM-a. Sve je statičko, GitHub Pages servira direktno; nema backend-a.