map / app /map_py.py
atsuga's picture
Upload 40 files
6f10462 verified
import folium
from folium import plugins
import json
import random
import colorsys
def generate_vibrant_colors(n):
"""Generate n vibrant distinct colors for polygon map (like the reference image)."""
# Predefined vibrant colors matching the reference image style
vibrant_palette = [
'#DC143C', # Crimson red
'#FF1493', # Deep pink
'#8B008B', # Dark magenta
'#9400D3', # Dark violet
'#4B0082', # Indigo
'#0000CD', # Medium blue
'#1E90FF', # Dodger blue
'#00BFFF', # Deep sky blue
'#00CED1', # Dark turquoise
'#20B2AA', # Light sea green
'#008B8B', # Dark cyan
'#006400', # Dark green
'#228B22', # Forest green
'#32CD32', # Lime green
'#7FFF00', # Chartreuse
'#ADFF2F', # Green yellow
'#FFD700', # Gold
'#FFA500', # Orange
'#FF8C00', # Dark orange
'#FF6347', # Tomato
'#FF4500', # Orange red
'#B22222', # Fire brick
'#8B4513', # Saddle brown
'#D2691E', # Chocolate
'#CD853F', # Peru
'#DEB887', # Burlywood
'#F0E68C', # Khaki
'#9370DB', # Medium purple
'#BA55D3', # Medium orchid
'#DA70D6', # Orchid
'#EE82EE', # Violet
'#FF69B4', # Hot pink
'#C71585', # Medium violet red
'#DB7093', # Pale violet red
'#BC8F8F', # Rosy brown
'#CD5C5C', # Indian red
'#F08080', # Light coral
'#FA8072', # Salmon
]
# If we need more colors, generate additional ones using HSV
if n > len(vibrant_palette):
for i in range(n - len(vibrant_palette)):
hue = (i * 137.5) % 360 # Golden angle for good distribution
saturation = 0.7 + (random.random() * 0.3) # 70-100%
value = 0.6 + (random.random() * 0.4) # 60-100%
rgb = colorsys.hsv_to_rgb(hue/360.0, saturation, value)
hex_color = '#{:02x}{:02x}{:02x}'.format(
int(rgb[0] * 255),
int(rgb[1] * 255),
int(rgb[2] * 255)
)
vibrant_palette.append(hex_color)
# Shuffle and return exactly n colors
colors = vibrant_palette[:n]
random.shuffle(colors)
return colors
def create_map():
"""Create a Folium polygon map with vibrant colors - like the reference image.
Returns:
str: HTML string for embedding the Folium map (safe to render).
"""
# Load GeoJSON data with 38 kabupaten/kota
with open('data/geojson/jatim_kabkota.geojson', encoding='utf-8') as f:
geojson_data = json.load(f)
# Generate vibrant distinct colors for each district (polygon style)
num_districts = len(geojson_data['features'])
vibrant_colors = generate_vibrant_colors(num_districts)
# Create color mapping for each district
color_map = {}
for i, feature in enumerate(geojson_data['features']):
district_name = feature['properties']['name']
color_map[district_name] = vibrant_colors[i]
# Create Folium map with white background (like reference image)
m = folium.Map(
location=[-7.5, 112.5],
zoom_start=8,
tiles='CartoDB positron',
prefer_canvas=True,
zoom_control=True,
scrollWheelZoom=True
)
# Style function: Vibrant distinct color for each district (polygon style)
def style_function(feature):
district_name = feature['properties'].get('name', 'Unknown')
return {
'fillColor': color_map.get(district_name, '#4A90E2'),
'color': '#333333', # Dark gray border between polygons
'weight': 1.5, # Thinner border for cleaner look
'fillOpacity': 0.9, # Solid colors like reference image
'opacity': 1
}
# Highlight function for hover effect
def highlight_function(feature):
district_name = feature['properties'].get('name', 'Unknown')
return {
'fillColor': color_map.get(district_name, '#4A90E2'),
'color': '#000000', # Black border on hover
'weight': 3,
'fillOpacity': 1.0, # Full opacity on hover
}
# Add GeoJSON layer with choropleth colors
folium.GeoJson(
geojson_data,
name='Kabupaten/Kota Jawa Timur',
style_function=style_function,
highlight_function=highlight_function,
tooltip=folium.GeoJsonTooltip(
fields=['name'],
aliases=['Wilayah:'],
localize=True,
sticky=False,
labels=True,
style="""
background-color: white;
border: 2px solid #2C3E50;
border-radius: 5px;
font-family: Arial, sans-serif;
font-size: 12px;
padding: 8px;
box-shadow: 3px 3px 6px rgba(0,0,0,0.3);
""",
),
popup=folium.GeoJsonPopup(
fields=['name', 'province'],
aliases=['Nama:', 'Provinsi:'],
localize=True,
)
).add_to(m)
# Add text labels at centroids
for feature in geojson_data['features']:
props = feature['properties']
name = props.get('name', 'Unknown')
lat = props.get('centroid_lat')
lon = props.get('centroid_lon')
if lat and lon:
# Shorten names for display
display_name = name.replace('Kab. ', '').replace('Kota ', '')
folium.Marker(
location=[lat, lon],
icon=folium.DivIcon(html=f'''
<div style="
font-family: Arial, sans-serif;
font-size: 9px;
color: #FFFFFF;
font-weight: bold;
text-shadow:
1px 1px 2px rgba(0,0,0,0.9),
-1px -1px 2px rgba(0,0,0,0.9),
1px -1px 2px rgba(0,0,0,0.9),
-1px 1px 2px rgba(0,0,0,0.9);
text-align: center;
white-space: nowrap;
">{display_name}</div>
''')
).add_to(m)
# Fit map to bounds
bounds = []
for feature in geojson_data['features']:
geom = feature['geometry']
if geom['type'] == 'Polygon':
for pt in geom['coordinates'][0]:
bounds.append((pt[1], pt[0]))
elif geom['type'] == 'MultiPolygon':
for poly in geom['coordinates']:
for pt in poly[0]:
bounds.append((pt[1], pt[0]))
if bounds:
m.fit_bounds(bounds)
# # Add legend showing vibrant polygon colors
# legend_html = '''
# <div style="position: fixed;
# top: 10px; right: 10px; width: 240px;
# background-color: white; z-index:9999;
# border:2px solid #333; border-radius: 8px;
# padding: 15px;
# box-shadow: 0 4px 8px rgba(0,0,0,0.3);
# font-family: Arial, sans-serif;
# ">
# <div style="margin-top: 10px; max-height: 300px; overflow-y: auto;">
# '''
# Show all districts with their unique colors
for district, color in sorted(color_map.items()):
display_name = district.replace('Kab. ', '').replace('Kota ', '')
legend_html += f'''
<div style="margin: 3px 0; display: flex; align-items: center;">
<div style="width: 22px; height: 16px; background-color: {color};
border: 1px solid #666; margin-right: 8px; border-radius: 2px;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);"></div>
<span style="font-size: 9px; color: #444;">{display_name}</span>
</div>
'''
legend_html += '''
</div>
<div style="margin-top: 12px; padding-top: 10px; border-top: 1px solid #ddd;
font-size: 9px; color: #888; text-align: center;">
Klik wilayah untuk detail
</div>
</div>
'''
m.get_root().html.add_child(folium.Element(legend_html))
# Add background color styling (white like reference image)
custom_css = '''
<style>
.leaflet-container {
background-color: #FFFFFF !important;
}
</style>
'''
m.get_root().html.add_child(folium.Element(custom_css))
# Return HTML representation (embedding) so the route can render it inside a template
return m._repr_html_()