|
|
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).""" |
|
|
|
|
|
vibrant_palette = [ |
|
|
'#DC143C', |
|
|
'#FF1493', |
|
|
'#8B008B', |
|
|
'#9400D3', |
|
|
'#4B0082', |
|
|
'#0000CD', |
|
|
'#1E90FF', |
|
|
'#00BFFF', |
|
|
'#00CED1', |
|
|
'#20B2AA', |
|
|
'#008B8B', |
|
|
'#006400', |
|
|
'#228B22', |
|
|
'#32CD32', |
|
|
'#7FFF00', |
|
|
'#ADFF2F', |
|
|
'#FFD700', |
|
|
'#FFA500', |
|
|
'#FF8C00', |
|
|
'#FF6347', |
|
|
'#FF4500', |
|
|
'#B22222', |
|
|
'#8B4513', |
|
|
'#D2691E', |
|
|
'#CD853F', |
|
|
'#DEB887', |
|
|
'#F0E68C', |
|
|
'#9370DB', |
|
|
'#BA55D3', |
|
|
'#DA70D6', |
|
|
'#EE82EE', |
|
|
'#FF69B4', |
|
|
'#C71585', |
|
|
'#DB7093', |
|
|
'#BC8F8F', |
|
|
'#CD5C5C', |
|
|
'#F08080', |
|
|
'#FA8072', |
|
|
] |
|
|
|
|
|
|
|
|
if n > len(vibrant_palette): |
|
|
for i in range(n - len(vibrant_palette)): |
|
|
hue = (i * 137.5) % 360 |
|
|
saturation = 0.7 + (random.random() * 0.3) |
|
|
value = 0.6 + (random.random() * 0.4) |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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). |
|
|
""" |
|
|
|
|
|
with open('data/geojson/jatim_kabkota.geojson', encoding='utf-8') as f: |
|
|
geojson_data = json.load(f) |
|
|
|
|
|
|
|
|
num_districts = len(geojson_data['features']) |
|
|
vibrant_colors = generate_vibrant_colors(num_districts) |
|
|
|
|
|
|
|
|
color_map = {} |
|
|
for i, feature in enumerate(geojson_data['features']): |
|
|
district_name = feature['properties']['name'] |
|
|
color_map[district_name] = vibrant_colors[i] |
|
|
|
|
|
|
|
|
m = folium.Map( |
|
|
location=[-7.5, 112.5], |
|
|
zoom_start=8, |
|
|
tiles='CartoDB positron', |
|
|
prefer_canvas=True, |
|
|
zoom_control=True, |
|
|
scrollWheelZoom=True |
|
|
) |
|
|
|
|
|
|
|
|
def style_function(feature): |
|
|
district_name = feature['properties'].get('name', 'Unknown') |
|
|
return { |
|
|
'fillColor': color_map.get(district_name, '#4A90E2'), |
|
|
'color': '#333333', |
|
|
'weight': 1.5, |
|
|
'fillOpacity': 0.9, |
|
|
'opacity': 1 |
|
|
} |
|
|
|
|
|
|
|
|
def highlight_function(feature): |
|
|
district_name = feature['properties'].get('name', 'Unknown') |
|
|
return { |
|
|
'fillColor': color_map.get(district_name, '#4A90E2'), |
|
|
'color': '#000000', |
|
|
'weight': 3, |
|
|
'fillOpacity': 1.0, |
|
|
} |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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: |
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
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) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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)) |
|
|
|
|
|
|
|
|
custom_css = ''' |
|
|
<style> |
|
|
.leaflet-container { |
|
|
background-color: #FFFFFF !important; |
|
|
} |
|
|
</style> |
|
|
''' |
|
|
m.get_root().html.add_child(folium.Element(custom_css)) |
|
|
|
|
|
|
|
|
return m._repr_html_() |