@@ -8,6 +8,8 @@ export class MapManager {
88 private map : L . Map ;
99 private state : AppState ;
1010 private layerMap : Map < string , L . Layer > = new Map ( ) ;
11+ private svgOverlay : SVGSVGElement | null = null ;
12+ private flagPatterns : Map < string , string > = new Map ( ) ;
1113
1214 constructor ( containerId : string , state : AppState ) {
1315 this . state = state ;
@@ -20,14 +22,17 @@ export class MapManager {
2022 maxZoom : DEFAULT_CONFIG . maxZoom ,
2123 zoomControl : true ,
2224 worldCopyJump : false , // Prevent world wrapping
23- maxBounds : [ [ - 90 , - 180 ] , [ 90 , 180 ] ] , // Constrain to single world view
25+ maxBounds : [ [ - 85 , - 180 ] , [ 85 , 180 ] ] , // Constrain to valid tile bounds
2426 maxBoundsViscosity : 1.0 , // Make bounds hard limit
2527 } ) ;
2628
27- L . tileLayer ( 'http ://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' , {
29+ L . tileLayer ( 'https ://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' , {
2830 attribution : '© OpenStreetMap contributors' ,
2931 crossOrigin : 'anonymous' ,
30- noWrap : true
32+ noWrap : true ,
33+ bounds : [ [ - 85 , - 180 ] , [ 85 , 180 ] ] , // Limit tile loading to valid range
34+ minZoom : DEFAULT_CONFIG . minZoom ,
35+ maxZoom : DEFAULT_CONFIG . maxZoom ,
3136 } ) . addTo ( this . map ) ;
3237
3338 // Add scale control
@@ -135,6 +140,9 @@ export class MapManager {
135140 }
136141 } ) ;
137142
143+ // Initialize SVG overlay for flag patterns
144+ this . initializeSVGOverlay ( ) ;
145+
138146 // Listen for state changes
139147 this . state . addListener ( ( countryId , _color ) => {
140148 if ( countryId === '*' ) {
@@ -146,6 +154,89 @@ export class MapManager {
146154 } ) ;
147155 }
148156
157+ private initializeSVGOverlay ( ) : void {
158+ // Get the map's SVG overlay or create one
159+ // const mapContainer = this.map.getContainer();
160+ const panes = ( this . map as any ) . _panes ;
161+
162+ // Look for existing SVG in overlay pane
163+ let svgElement = panes . overlayPane . querySelector ( 'svg' ) ;
164+
165+ if ( ! svgElement ) {
166+ // If no SVG exists, we'll create patterns in the first GeoJSON layer's SVG
167+ // This will be done when layers are added
168+ return ;
169+ }
170+
171+ this . svgOverlay = svgElement ;
172+ this . ensureSVGDefs ( ) ;
173+ }
174+
175+ private ensureSVGDefs ( ) : void {
176+ if ( ! this . svgOverlay ) return ;
177+
178+ let defs = this . svgOverlay . querySelector ( 'defs' ) ;
179+ if ( ! defs ) {
180+ defs = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'defs' ) ;
181+ this . svgOverlay . insertBefore ( defs , this . svgOverlay . firstChild ) ;
182+ }
183+ }
184+
185+ private createFlagPattern ( flagCode : string ) : string {
186+ const patternId = `flag-pattern-${ flagCode . toLowerCase ( ) } ` ;
187+
188+ // Check if pattern already exists
189+ if ( this . flagPatterns . has ( flagCode ) ) {
190+ return this . flagPatterns . get ( flagCode ) ! ;
191+ }
192+
193+ // Find or create SVG element with defs
194+ const mapContainer = this . map . getContainer ( ) ;
195+ const panes = ( this . map as any ) . _panes ;
196+ let svgElement = panes . overlayPane . querySelector ( 'svg' ) ;
197+
198+ if ( ! svgElement ) {
199+ // Create a hidden SVG element for patterns
200+ svgElement = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'svg' ) ;
201+ svgElement . style . position = 'absolute' ;
202+ svgElement . style . width = '0' ;
203+ svgElement . style . height = '0' ;
204+ mapContainer . appendChild ( svgElement ) ;
205+ }
206+
207+ this . svgOverlay = svgElement ;
208+ this . ensureSVGDefs ( ) ;
209+
210+ const defs = this . svgOverlay ! . querySelector ( 'defs' ) ! ;
211+
212+ // Check if pattern already exists in DOM
213+ if ( defs . querySelector ( `#${ patternId } ` ) ) {
214+ this . flagPatterns . set ( flagCode , patternId ) ;
215+ return patternId ;
216+ }
217+
218+ // Create pattern element with repeating tiles
219+ const pattern = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'pattern' ) ;
220+ pattern . setAttribute ( 'id' , patternId ) ;
221+ pattern . setAttribute ( 'patternUnits' , 'userSpaceOnUse' ) ;
222+ pattern . setAttribute ( 'width' , '60' ) ; // Tile size in pixels
223+ pattern . setAttribute ( 'height' , '40' ) ; // Tile size in pixels (3:2 flag ratio)
224+ pattern . setAttribute ( 'patternTransform' , 'scale(1)' ) ;
225+
226+ // Create image element with flag
227+ const image = document . createElementNS ( 'http://www.w3.org/2000/svg' , 'image' ) ;
228+ image . setAttribute ( 'href' , `https://flagcdn.com/w160/${ flagCode . toLowerCase ( ) } .png` ) ;
229+ image . setAttribute ( 'width' , '60' ) ;
230+ image . setAttribute ( 'height' , '40' ) ;
231+ image . setAttribute ( 'preserveAspectRatio' , 'none' ) ; // Stretch to fit tile
232+
233+ pattern . appendChild ( image ) ;
234+ defs . appendChild ( pattern ) ;
235+
236+ this . flagPatterns . set ( flagCode , patternId ) ;
237+ return patternId ;
238+ }
239+
149240 loadCountries ( geojson : GeoJSON . FeatureCollection < GeoJSON . Geometry , CountryProperties > ) : void {
150241 // Filter out features with null geometries
151242 const validFeatures = geojson . features . filter ( feature => {
@@ -176,6 +267,7 @@ export class MapManager {
176267 id : countryId ,
177268 name : props . NAME || props . NAME_LONG || props . ADMIN ,
178269 color : null ,
270+ flag : null ,
179271 feature : feature ,
180272 } ;
181273 this . state . addCountry ( countryState ) ;
@@ -202,7 +294,21 @@ export class MapManager {
202294
203295 const countryId = this . getCountryId ( feature . properties ) ;
204296 const color = this . state . getCountryColor ( countryId ) ;
297+ const flag = this . state . getCountryFlag ( countryId ) ;
298+
299+ // If flag is set, use pattern fill
300+ if ( flag ) {
301+ const patternId = this . createFlagPattern ( flag ) ;
302+ return {
303+ fillColor : `url(#${ patternId } )` ,
304+ fillOpacity : 0.9 ,
305+ color : '#ffffff' ,
306+ weight : 1 ,
307+ className : 'flag-filled' ,
308+ } ;
309+ }
205310
311+ // Otherwise use color
206312 return {
207313 fillColor : color || '#d3d3d3' ,
208314 fillOpacity : 0.7 ,
@@ -264,8 +370,18 @@ export class MapManager {
264370 target . closeTooltip ( ) ;
265371 } ,
266372 click : ( ) => {
267- const selectedColor = this . state . getSelectedColor ( ) ;
268- this . state . setCountryColor ( countryId , selectedColor ) ;
373+ // Apply color or flag based on paint mode
374+ if ( this . state . getPaintMode ( ) === 'color' ) {
375+ const selectedColor = this . state . getSelectedColor ( ) ;
376+ this . state . setCountryColor ( countryId , selectedColor ) ;
377+ // Clear flag if switching to color
378+ this . state . clearCountryFlag ( countryId ) ;
379+ } else {
380+ const selectedFlag = this . state . getSelectedFlag ( ) ;
381+ this . state . setCountryFlag ( countryId , selectedFlag ) ;
382+ // Clear color if switching to flag
383+ this . state . clearCountryColor ( countryId ) ;
384+ }
269385
270386 // Save to localStorage after each change
271387 this . state . saveToLocalStorage ( ) ;
@@ -331,10 +447,22 @@ export class MapManager {
331447 const country = this . state . getCountry ( countryId ) ;
332448 if ( country ) {
333449 const color = this . state . getCountryColor ( countryId ) ;
450+ const flag = this . state . getCountryFlag ( countryId ) ;
451+
452+ let fillColor = color || '#d3d3d3' ;
453+ let fillOpacity = 0.7 ;
454+
455+ // Use flag pattern if flag is set
456+ if ( flag ) {
457+ const patternId = this . createFlagPattern ( flag ) ;
458+ fillColor = `url(#${ patternId } )` ;
459+ fillOpacity = 0.9 ;
460+ }
461+
334462 const style = {
335463 fill : true ,
336- fillColor : color || '#d3d3d3' ,
337- fillOpacity : 0.7 ,
464+ fillColor : fillColor ,
465+ fillOpacity : fillOpacity ,
338466 stroke : true ,
339467 color : '#ffffff' ,
340468 weight : 1 ,
0 commit comments