Skip to content

Commit 7a8a047

Browse files
committed
added my file called language test, which is to solve Issue the Issue related to creating choropleths in more than one language
1 parent e3ca7ba commit 7a8a047

File tree

1 file changed

+374
-0
lines changed

1 file changed

+374
-0
lines changed

languagetest.py

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
"""
2+
Folium Multi-Language Choropleth Solution
3+
4+
This module provides multiple approaches to render folium choropleths
5+
with different number locales for legends, enabling creation of maps
6+
in multiple languages without changing system locale.
7+
8+
Requirements:
9+
- folium
10+
- pandas
11+
- geopandas (optional, for geojson data)
12+
- selenium (for image generation)
13+
- pillow (for image processing)
14+
15+
Install with: pip install folium pandas selenium pillow
16+
"""
17+
18+
import folium
19+
import pandas as pd
20+
import json
21+
import re
22+
from typing import Dict, List, Optional, Union
23+
import tempfile
24+
import os
25+
26+
27+
class MultiLanguageChoropleth:
28+
"""
29+
A class to create folium choropleths with customizable number formatting
30+
for different languages/locales without changing system settings.
31+
"""
32+
33+
def __init__(self):
34+
self.number_formats = {
35+
'en': {
36+
'decimal_separator': '.',
37+
'thousands_separator': ',',
38+
'currency_symbol': '$',
39+
'position': 'before' # currency position
40+
},
41+
'fr': {
42+
'decimal_separator': ',',
43+
'thousands_separator': ' ',
44+
'currency_symbol': '€',
45+
'position': 'after'
46+
},
47+
'de': {
48+
'decimal_separator': ',',
49+
'thousands_separator': '.',
50+
'currency_symbol': '€',
51+
'position': 'after'
52+
},
53+
'es': {
54+
'decimal_separator': ',',
55+
'thousands_separator': '.',
56+
'currency_symbol': '€',
57+
'position': 'after'
58+
}
59+
}
60+
61+
def format_number(self, number: float, locale: str = 'en',
62+
decimals: int = 2, include_currency: bool = False) -> str:
63+
"""
64+
Format a number according to specified locale conventions.
65+
66+
Args:
67+
number: The number to format
68+
locale: Language locale ('en', 'fr', 'de', 'es')
69+
decimals: Number of decimal places
70+
include_currency: Whether to include currency symbol
71+
72+
Returns:
73+
Formatted number string
74+
"""
75+
if locale not in self.number_formats:
76+
locale = 'en' # fallback to English
77+
78+
fmt = self.number_formats[locale]
79+
80+
# Round to specified decimals
81+
rounded = round(number, decimals)
82+
83+
# Split into integer and decimal parts
84+
integer_part = int(rounded)
85+
decimal_part = rounded - integer_part
86+
87+
# Format integer part with thousands separator
88+
integer_str = f"{integer_part:,}".replace(',', '|TEMP|')
89+
integer_str = integer_str.replace('|TEMP|', fmt['thousands_separator'])
90+
91+
# Format decimal part
92+
if decimals > 0 and decimal_part > 0:
93+
decimal_str = f"{decimal_part:.{decimals}f}"[2:] # Remove "0."
94+
formatted = f"{integer_str}{fmt['decimal_separator']}{decimal_str}"
95+
else:
96+
formatted = integer_str
97+
98+
# Add currency if requested
99+
if include_currency:
100+
if fmt['position'] == 'before':
101+
formatted = f"{fmt['currency_symbol']}{formatted}"
102+
else:
103+
formatted = f"{formatted} {fmt['currency_symbol']}"
104+
105+
return formatted
106+
107+
def create_custom_legend_html(self, values: List[float], colors: List[str],
108+
locale: str = 'en', title: str = "Legend") -> str:
109+
"""
110+
Create custom HTML legend with locale-specific number formatting.
111+
112+
Args:
113+
values: List of values for legend
114+
colors: List of corresponding colors
115+
locale: Language locale
116+
title: Legend title
117+
118+
Returns:
119+
HTML string for custom legend
120+
"""
121+
legend_html = f'''
122+
<div style="position: fixed;
123+
bottom: 50px; right: 50px; width: 150px; height: auto;
124+
background-color: white; border:2px solid grey; z-index:9999;
125+
font-size:14px; padding: 10px">
126+
<h4 style="margin-top:0;">{title}</h4>
127+
'''
128+
129+
for i, (value, color) in enumerate(zip(values, colors)):
130+
formatted_value = self.format_number(value, locale)
131+
legend_html += f'''
132+
<p style="margin: 5px 0;">
133+
<span style="background-color:{color}; width: 20px; height: 20px;
134+
display: inline-block; margin-right: 5px;"></span>
135+
{formatted_value}
136+
</p>
137+
'''
138+
139+
legend_html += '</div>'
140+
return legend_html
141+
142+
def inject_locale_javascript(self, locale: str = 'en') -> str:
143+
"""
144+
Generate JavaScript to modify number formatting in existing legend.
145+
146+
Args:
147+
locale: Target locale for number formatting
148+
149+
Returns:
150+
JavaScript code string
151+
"""
152+
fmt = self.number_formats[locale]
153+
154+
js_code = f'''
155+
<script>
156+
// Function to format numbers according to locale
157+
function formatNumberLocale(num, locale) {{
158+
const formats = {json.dumps(self.number_formats)};
159+
const fmt = formats[locale] || formats['en'];
160+
161+
// Convert number to string and parse
162+
let numStr = parseFloat(num).toFixed(2);
163+
let parts = numStr.split('.');
164+
165+
// Add thousands separator
166+
parts[0] = parts[0].replace(/\\B(?=(\\d{{3}})+(?!\\d))/g, fmt.thousands_separator);
167+
168+
// Join with decimal separator
169+
if (parts[1] && parseFloat('0.' + parts[1]) > 0) {{
170+
return parts[0] + fmt.decimal_separator + parts[1];
171+
}}
172+
return parts[0];
173+
}}
174+
175+
// Wait for map to load, then modify legend
176+
setTimeout(function() {{
177+
// Find all text elements in the legend that contain numbers
178+
const legendElements = document.querySelectorAll('.legend text, .colorbar text');
179+
180+
legendElements.forEach(function(element) {{
181+
const text = element.textContent;
182+
const numberMatch = text.match(/\\d+\\.?\\d*/);
183+
184+
if (numberMatch) {{
185+
const originalNumber = parseFloat(numberMatch[0]);
186+
const formattedNumber = formatNumberLocale(originalNumber, '{locale}');
187+
element.textContent = text.replace(numberMatch[0], formattedNumber);
188+
}}
189+
}});
190+
}}, 1000);
191+
</script>
192+
'''
193+
194+
return js_code
195+
196+
def create_choropleth_with_locale(self, map_obj: folium.Map,
197+
geo_data: Union[str, dict],
198+
data: pd.DataFrame,
199+
columns: List[str],
200+
key_on: str,
201+
locale: str = 'en',
202+
**choropleth_kwargs) -> folium.Map:
203+
"""
204+
Create a choropleth with custom locale formatting.
205+
206+
Args:
207+
map_obj: Folium map object
208+
geo_data: GeoJSON data
209+
data: DataFrame with data to map
210+
columns: Columns for choropleth [key_column, value_column]
211+
key_on: Key in GeoJSON to match with data
212+
locale: Target locale
213+
**choropleth_kwargs: Additional arguments for folium.Choropleth
214+
215+
Returns:
216+
Modified folium map
217+
"""
218+
# Create the choropleth first
219+
choropleth = folium.Choropleth(
220+
geo_data=geo_data,
221+
data=data,
222+
columns=columns,
223+
key_on=key_on,
224+
**choropleth_kwargs
225+
).add_to(map_obj)
226+
227+
# Add JavaScript to modify number formatting
228+
js_code = self.inject_locale_javascript(locale)
229+
map_obj.get_root().html.add_child(folium.Element(js_code))
230+
231+
return map_obj
232+
233+
234+
def create_sample_data() -> tuple:
235+
"""
236+
Create sample data for demonstration.
237+
238+
Returns:
239+
Tuple of (sample_data_df, sample_geojson)
240+
"""
241+
# Sample data
242+
sample_data = pd.DataFrame({
243+
'country': ['USA', 'Canada', 'Mexico', 'Brazil', 'Argentina'],
244+
'value': [1234567.89, 987654.32, 456789.12, 2345678.90, 876543.21]
245+
})
246+
247+
# Simple sample GeoJSON (normally you'd load this from a file)
248+
sample_geojson = {
249+
"type": "FeatureCollection",
250+
"features": [
251+
{
252+
"type": "Feature",
253+
"properties": {"name": "USA"},
254+
"geometry": {"type": "Polygon", "coordinates": [[[-100, 40], [-90, 40], [-90, 50], [-100, 50], [-100, 40]]]}
255+
},
256+
{
257+
"type": "Feature",
258+
"properties": {"name": "Canada"},
259+
"geometry": {"type": "Polygon", "coordinates": [[[-110, 50], [-90, 50], [-90, 60], [-110, 60], [-110, 50]]]}
260+
}
261+
]
262+
}
263+
264+
return sample_data, sample_geojson
265+
266+
267+
def demo_multilanguage_choropleth():
268+
"""
269+
Demonstrate creating choropleths in multiple languages.
270+
"""
271+
# Initialize the multi-language choropleth handler
272+
ml_choropleth = MultiLanguageChoropleth()
273+
274+
# Create sample data
275+
sample_data, sample_geojson = create_sample_data()
276+
277+
# Create maps for different locales
278+
locales = ['en', 'fr', 'de']
279+
maps = {}
280+
281+
for locale in locales:
282+
# Create base map
283+
m = folium.Map(location=[45, -100], zoom_start=3)
284+
285+
# Add choropleth with custom locale
286+
m = ml_choropleth.create_choropleth_with_locale(
287+
map_obj=m,
288+
geo_data=sample_geojson,
289+
data=sample_data,
290+
columns=['country', 'value'],
291+
key_on='feature.properties.name',
292+
locale=locale,
293+
fill_color='YlOrRd',
294+
fill_opacity=0.7,
295+
line_opacity=0.2,
296+
legend_name=f'Sample Values ({locale.upper()})'
297+
)
298+
299+
maps[locale] = m
300+
301+
# Save map
302+
filename = f'choropleth_{locale}.html'
303+
m.save(filename)
304+
print(f"Saved map in {locale.upper()} locale as {filename}")
305+
306+
return maps
307+
308+
309+
def save_map_as_image(map_obj: folium.Map, filename: str,
310+
width: int = 1200, height: int = 800):
311+
"""
312+
Save folium map as image using selenium.
313+
314+
Args:
315+
map_obj: Folium map object
316+
filename: Output filename
317+
width: Image width
318+
height: Image height
319+
"""
320+
try:
321+
from selenium import webdriver
322+
from selenium.webdriver.chrome.options import Options
323+
import time
324+
325+
# Save map as temporary HTML
326+
temp_html = tempfile.NamedTemporaryFile(mode='w', suffix='.html', delete=False)
327+
map_obj.save(temp_html.name)
328+
329+
# Setup headless browser
330+
chrome_options = Options()
331+
chrome_options.add_argument('--headless')
332+
chrome_options.add_argument(f'--window-size={width},{height}')
333+
334+
driver = webdriver.Chrome(options=chrome_options)
335+
driver.get(f'file://{temp_html.name}')
336+
337+
# Wait for map to load
338+
time.sleep(3)
339+
340+
# Take screenshot
341+
driver.save_screenshot(filename)
342+
driver.quit()
343+
344+
# Clean up
345+
os.unlink(temp_html.name)
346+
347+
print(f"Map saved as image: {filename}")
348+
349+
except ImportError:
350+
print("Selenium not available. Install with: pip install selenium")
351+
print("Also need to install ChromeDriver for your system")
352+
except Exception as e:
353+
print(f"Error saving image: {e}")
354+
355+
356+
if __name__ == "__main__":
357+
# Run the demonstration
358+
print("Creating multi-language choropleths...")
359+
maps = demo_multilanguage_choropleth()
360+
361+
# Optionally save as images (requires selenium)
362+
# for locale, map_obj in maps.items():
363+
# save_map_as_image(map_obj, f'choropleth_{locale}.png')
364+
365+
print("\nDemonstration complete!")
366+
print("Check the generated HTML files to see the different number formats.")
367+
368+
# Example of manual number formatting
369+
ml = MultiLanguageChoropleth()
370+
print("\nExample number formatting:")
371+
number = 1234567.89
372+
for locale in ['en', 'fr', 'de']:
373+
formatted = ml.format_number(number, locale)
374+
print(f"{locale.upper()}: {formatted}")

0 commit comments

Comments
 (0)