1+ <!DOCTYPE html>
2+ < html lang ="en ">
3+ < head >
4+ < meta charset ="UTF-8 ">
5+ < title > CalcTale | Meteorologist</ title >
6+ < style >
7+ : root { --neon : # 00f5ff ; --dark : # 050a10 ; --slate : # 0d1b2a ; --gold : # f2af29 ; --border : # 1b263b ; --alert : # ff007a ; }
8+ body { font-family : 'Courier New' , monospace; background : var (--dark ); color : # c5c6c7 ; margin : 0 ; padding : 20px ; display : flex; flex-direction : column; height : 95vh ; overflow : hidden; }
9+
10+ # fs-auth-overlay { position : fixed; top : 0 ; left : 0 ; width : 100vw ; height : 100vh ; background : rgba (5 , 10 , 16 , 0.98 ); z-index : 9999 ; display : flex; flex-direction : column; align-items : center; justify-content : center; cursor : pointer; border : 4px solid var (--gold ); }
11+ # fs-auth-overlay h1 { color : var (--gold ); text-transform : uppercase; letter-spacing : 2px ; }
12+
13+ .main-layout { display : grid; grid-template-columns : 380px 1fr ; gap : 20px ; flex-grow : 1 ; overflow : hidden; }
14+ .panel { background : var (--slate ); border : 1px solid var (--border ); padding : 20px ; display : flex; flex-direction : column; gap : 10px ; overflow-y : auto; }
15+ h2 { color : var (--neon ); font-size : 14px ; text-transform : uppercase; border-bottom : 1px solid var (--border ); padding-bottom : 5px ; margin : 0 ; }
16+ label { font-size : 10px ; color : # 45a29e ; text-transform : uppercase; }
17+ input { width : 100% ; background : # 000 ; color : # fff ; border : 1px solid # 333 ; padding : 10px ; box-sizing : border-box; font-family : monospace; }
18+ .btn { background : var (--neon ); color : var (--dark ); border : none; padding : 12px ; font-weight : bold; cursor : pointer; text-transform : uppercase; }
19+
20+ # canvas-container { background : # 000 ; border : 1px solid var (--neon ); overflow : auto; position : relative; flex-grow : 1 ; }
21+ canvas { image-rendering : pixelated; display : block; position : absolute; top : 0 ; left : 0 ; }
22+ # weather-canvas { pointer-events : none; opacity : 0.6 ; }
23+ # pressure-canvas { pointer-events : none; visibility : hidden; }
24+ # debug-layer { pointer-events : none; z-index : 2000 ; }
25+
26+ # data-card {
27+ position : absolute; background : rgba (13 , 27 , 42 , 0.95 ); border : 1px solid var (--neon );
28+ padding : 15px ; color : var (--neon ); font-size : 12px ; pointer-events : none;
29+ display : none; z-index : 1000 ; box-shadow : 0 0 15px rgba (0 , 245 , 255 , 0.3 );
30+ min-width : 240px ;
31+ }
32+ # data-card b { color : var (--gold ); text-transform : uppercase; font-size : 14px ; }
33+ # log { height : 150px ; background : # 000 ; color : # 00f5ff ; font-size : 11px ; padding : 10px ; border : 1px solid # 111 ; overflow-y : auto; white-space : pre-wrap; margin-top : 10px ; }
34+ </ style >
35+ </ head >
36+ < body >
37+
38+ < div id ="fs-auth-overlay " onclick ="mountSovereignSystem() ">
39+ < h1 > System Initialization</ h1 >
40+ < p > Click to Mount Directory & Load MZMO Stack</ p >
41+ </ div >
42+
43+ < div class ="main-layout ">
44+ < div class ="panel ">
45+ < h2 > 1. Map Architecture</ h2 >
46+ < label > Create Map Database (City CSV)</ label >
47+ < input type ="file " id ="city-csv " onchange ="createMapDatabase(event) ">
48+ < label > Mount Map (.PNG)</ label >
49+ < input type ="file " onchange ="mountLayer(event, 'map') ">
50+ < button class ="btn " style ="background: var(--gold) " onclick ="exportLayer('map') "> Export Map DB</ button >
51+
52+ < h2 > 2. Data Architecture</ h2 >
53+ < label > Ingest Weather Vectors (CSV)</ label >
54+ < input type ="file " onchange ="ingestWeather(event) ">
55+ <!--<label>Mount Weather (.PNG)</label>
56+ <input type="file" onchange="mountLayer(event, 'weather')">
57+ <button class="btn" style="background: var(--gold)" onclick="exportLayer('weather')">Export Weather DB</button>-->
58+
59+ < hr style ="border-color: #1b263b ">
60+ < h2 > 3. Cross Reference</ h2 >
61+ < input type ="text " id ="coords " placeholder ="" value ="29.680694,-82.349175 ">
62+ < button class ="btn " onclick ="executePeek() "> Geolocate</ button >
63+ < div id ="log "> SYSTEM READY...</ div >
64+ </ div >
65+
66+ < div id ="canvas-container ">
67+ < div id ="data-card "> </ div >
68+ < canvas id ="map-canvas " width ="4000 " height ="4000 "> </ canvas >
69+ < canvas id ="weather-canvas " width ="4000 " height ="4000 "> </ canvas >
70+ < canvas id ="pressure-canvas " width ="4000 " height ="4000 "> </ canvas >
71+ < canvas id ="debug-layer " width ="4000 " height ="4000 "> </ canvas >
72+ </ div >
73+ </ div >
74+
75+ < script >
76+ const DIM = 4000 ;
77+ const mapCtx = document . getElementById ( 'map-canvas' ) . getContext ( '2d' , { willReadFrequently : true } ) ;
78+ const wetCtx = document . getElementById ( 'weather-canvas' ) . getContext ( '2d' , { willReadFrequently : true } ) ;
79+ const prsCtx = document . getElementById ( 'pressure-canvas' ) . getContext ( '2d' , { willReadFrequently : true } ) ;
80+ const dbgCtx = document . getElementById ( 'debug-layer' ) . getContext ( '2d' ) ;
81+ const logArea = document . getElementById ( 'log' ) ;
82+ const card = document . getElementById ( 'data-card' ) ;
83+
84+ let folderHandle = null ;
85+ let cityRegistry = [ "Gainesville City" ] ;
86+
87+ function log ( msg ) {
88+ logArea . innerHTML += `> ${ msg } \n` ;
89+ logArea . scrollTop = logArea . scrollHeight ;
90+ }
91+
92+ async function mountSovereignSystem ( ) {
93+ try {
94+ folderHandle = await window . showDirectoryPicker ( { mode : 'readwrite' } ) ;
95+ document . getElementById ( 'fs-auth-overlay' ) . style . display = 'none' ;
96+ log ( "Directory Mounted." ) ;
97+ await autoLoadMZMOs ( ) ;
98+ } catch ( err ) { log ( "FS Access Denied: " + err ) ; }
99+ }
100+
101+ async function autoLoadMZMOs ( ) {
102+ for await ( const entry of folderHandle . values ( ) ) {
103+ if ( entry . name === 'MZMO_MAP.png' ) await loadToCanvas ( entry , mapCtx , "IDENTITY" ) ;
104+ if ( entry . name === 'MZMO_WEATHER.png' ) await loadToCanvas ( entry , wetCtx , "THERMAL" ) ;
105+ if ( entry . name === 'MZMO_PRESSURE.png' ) await loadToCanvas ( entry , prsCtx , "AERO" ) ;
106+ }
107+ }
108+
109+ async function loadToCanvas ( entry , ctx , label ) {
110+ const file = await entry . getFile ( ) ;
111+ const img = await createImageBitmap ( file ) ;
112+ ctx . drawImage ( img , 0 , 0 ) ;
113+ log ( `${ label } layer auto-loaded.` ) ;
114+ }
115+
116+ function geoToPixel ( lat , lon ) {
117+ const x = Math . floor ( ( ( parseFloat ( lon ) + 180 ) / 360 ) * ( DIM - 1 ) ) ;
118+ const y = Math . floor ( ( ( 90 - parseFloat ( lat ) ) / 180 ) * ( DIM - 1 ) ) ;
119+ return { x, y } ;
120+ }
121+
122+ function getCityChecksum ( name ) {
123+ let sum = 0 ;
124+ const clean = name . toLowerCase ( ) . replace ( / [ ^ a - z ] / g, '' ) ;
125+ for ( let i = 0 ; i < clean . length ; i ++ ) sum += ( clean . charCodeAt ( i ) - 96 ) ;
126+ return sum % 256 ;
127+ }
128+
129+ async function createMapDatabase ( e ) {
130+ const text = await e . target . files [ 0 ] . text ( ) ;
131+ const lines = text . split ( '\n' ) . slice ( 1 ) ;
132+ const imgData = mapCtx . createImageData ( DIM , DIM ) ;
133+ cityRegistry = [ ] ;
134+
135+ lines . forEach ( line => {
136+ const p = line . split ( ',' ) ;
137+ if ( p . length < 4 ) return ;
138+ const cityName = p [ 1 ] . trim ( ) ;
139+ const { x, y } = geoToPixel ( p [ 2 ] , p [ 3 ] ) ;
140+ const i = ( y * DIM + x ) * 4 ;
141+
142+ imgData . data [ i ] = cityName . length ;
143+ imgData . data [ i + 1 ] = getCityChecksum ( cityName ) ;
144+ imgData . data [ i + 2 ] = cityName . charCodeAt ( 0 ) ;
145+ imgData . data [ i + 3 ] = 255 ;
146+ cityRegistry . push ( cityName ) ;
147+ } ) ;
148+ mapCtx . putImageData ( imgData , 0 , 0 ) ;
149+ log ( "Map Database Baked into RAM and Layer." ) ;
150+ if ( folderHandle ) await autoExport ( 'map' , 'MZMO_MAP.png' ) ;
151+ }
152+
153+ async function ingestWeather ( e ) {
154+ const text = await e . target . files [ 0 ] . text ( ) ;
155+ const lines = text . split ( '\n' ) . slice ( 1 ) ;
156+ const thermalData = wetCtx . createImageData ( DIM , DIM ) ;
157+ const aeroData = prsCtx . createImageData ( DIM , DIM ) ;
158+
159+ lines . forEach ( line => {
160+ const p = line . split ( ',' ) ;
161+ if ( p . length < 6 ) return ;
162+ const { x, y } = geoToPixel ( p [ 1 ] , p [ 2 ] ) ;
163+ const i = ( y * DIM + x ) * 4 ;
164+
165+ // THERMAL (16-bit Temp R+G, 8-bit Dewpoint B)
166+ const temp = Math . floor ( ( parseFloat ( p [ 3 ] ) + 100 ) * 100 ) ;
167+ thermalData . data [ i ] = ( temp >> 8 ) & 0xFF ;
168+ thermalData . data [ i + 1 ] = temp & 0xFF ;
169+ thermalData . data [ i + 2 ] = p [ 4 ] ? Math . floor ( parseFloat ( p [ 4 ] ) + 50 ) : 0 ;
170+ thermalData . data [ i + 3 ] = 255 ;
171+
172+ // AERO (16-bit Pressure R+G, 8-bit Humidity B)
173+ const press = Math . floor ( ( parseFloat ( p [ 5 ] ) || 1013.25 ) * 10 ) ;
174+ aeroData . data [ i ] = ( press >> 8 ) & 0xFF ;
175+ aeroData . data [ i + 1 ] = press & 0xFF ;
176+ aeroData . data [ i + 2 ] = p [ 6 ] ? Math . floor ( parseFloat ( p [ 6 ] ) ) : 0 ;
177+ aeroData . data [ i + 3 ] = 255 ;
178+ } ) ;
179+
180+ wetCtx . putImageData ( thermalData , 0 , 0 ) ;
181+ prsCtx . putImageData ( aeroData , 0 , 0 ) ;
182+ log ( "Meteo Layers Baked." ) ;
183+ if ( folderHandle ) {
184+ await autoExport ( 'weather' , 'MZMO_WEATHER.png' ) ;
185+ await autoExport ( 'pressure' , 'MZMO_PRESSURE.png' ) ;
186+ }
187+ }
188+
189+ async function autoExport ( cid , filename ) {
190+ const canvas = document . getElementById ( cid + '-canvas' ) ;
191+ const blob = await new Promise ( res => canvas . toBlob ( res ) ) ;
192+ const fh = await folderHandle . getFileHandle ( filename , { create : true } ) ;
193+ const ws = await fh . createWritable ( ) ;
194+ await ws . write ( blob ) ;
195+ await ws . close ( ) ;
196+ log ( `Auto-Saved: ${ filename } ` ) ;
197+ }
198+
199+ function executePeek ( ) {
200+ const input = document . getElementById ( 'coords' ) . value . split ( ',' ) ;
201+ if ( input . length < 2 ) return log ( "Error: Need Lat, Lon." ) ;
202+ const lat = input [ 0 ] . trim ( ) , lon = input [ 1 ] . trim ( ) ;
203+ const { x, y } = geoToPixel ( lat , lon ) ;
204+
205+ dbgCtx . clearRect ( 0 , 0 , DIM , DIM ) ;
206+ dbgCtx . strokeStyle = "#ff007a" ;
207+ dbgCtx . strokeRect ( x - 5 , y - 5 , 10 , 10 ) ;
208+
209+ const m = mapCtx . getImageData ( x , y , 1 , 1 ) . data ;
210+ const w = wetCtx . getImageData ( x , y , 1 , 1 ) . data ;
211+ const a = prsCtx . getImageData ( x , y , 1 , 1 ) . data ;
212+
213+ let locName = "NO IDENTITY DATA" ;
214+ if ( m [ 3 ] > 0 ) {
215+ if ( cityRegistry . length === 0 ) {
216+ locName = `ENCODED ID: [Len:${ m [ 0 ] } , Sum:${ m [ 1 ] } , Hint:${ String . fromCharCode ( m [ 2 ] ) } ]` ;
217+ } else {
218+ const match = cityRegistry . find ( n => n . length === m [ 0 ] && getCityChecksum ( n ) === m [ 1 ] && n . charCodeAt ( 0 ) === m [ 2 ] ) ;
219+ locName = match || "ID MISMATCH" ;
220+ }
221+ }
222+
223+ const temp = ( w [ 3 ] > 0 ) ? ( ( ( w [ 0 ] << 8 ) | w [ 1 ] ) / 100 - 100 ) . toFixed ( 2 ) : "N/A" ;
224+ const press = ( a [ 3 ] > 0 ) ? ( ( ( a [ 0 ] << 8 ) | a [ 1 ] ) / 10 ) . toFixed ( 1 ) : "N/A" ;
225+ const hum = ( a [ 3 ] > 0 ) ? a [ 2 ] : "N/A" ;
226+
227+ card . style . display = 'block' ;
228+ card . style . left = ( x + 20 ) + 'px' ; card . style . top = ( y - 20 ) + 'px' ;
229+ card . innerHTML = `
230+ <b>${ locName } </b><br>
231+ COORDS: ${ lat } , ${ lon } <br>
232+ <hr style="border-color:var(--border)">
233+ TEMP: ${ temp } °C<br>
234+ PRES: ${ press } hPa<br>
235+ HUMI: ${ hum } %
236+ ` ;
237+ log ( `FORENSIC PEEK AT [${ x } ,${ y } ]` ) ;
238+
239+ document . getElementById ( 'canvas-container' ) . scrollTo ( { left : x - 400 , top : y - 400 , behavior : 'smooth' } ) ;
240+ }
241+
242+ function mountLayer ( e , type ) {
243+ const img = new Image ( ) ;
244+ const ctx = type === 'map' ? mapCtx : wetCtx ;
245+ img . onload = ( ) => { ctx . drawImage ( img , 0 , 0 ) ; log ( `${ type . toUpperCase ( ) } mounted.` ) ; } ;
246+ img . src = URL . createObjectURL ( e . target . files [ 0 ] ) ;
247+ }
248+
249+ function exportLayer ( type ) {
250+ const canvas = document . getElementById ( type + '-canvas' ) ;
251+ const link = document . createElement ( 'a' ) ;
252+ link . download = `MZMO_${ type . toUpperCase ( ) } .png` ;
253+ link . href = canvas . toDataURL ( "image/png" ) ;
254+ link . click ( ) ;
255+ }
256+ </ script >
257+ </ body >
258+ </ html >
0 commit comments