| import folium |
| import json |
| import random |
|
|
|
|
| import folium |
| import json |
| import os |
| import random |
|
|
| def generate_distinct_colors(n): |
| """Generate n distinct bright colors for each district""" |
| |
| colors = [ |
| '#FF6B6B', '#4ECDC4', '#45B7D1', '#FFA07A', '#98D8C8', |
| '#F7DC6F', '#BB8FCE', '#85C1E2', '#F8B739', '#52B788', |
| '#E63946', '#06FFA5', '#FFD93D', '#6BCF7F', '#C77DFF', |
| '#FF9F1C', '#2EC4B6', '#E71D36', '#FF499E', '#00B4D8', |
| '#90E0EF', '#F72585', '#7209B7', '#3A0CA3', '#F4A261', |
| '#2A9D8F', '#E76F51', '#264653', '#E9C46A', '#F77F00', |
| '#D62828', '#023047', '#8338EC', '#3DDC84', '#FF006E', |
| '#FFBE0B', '#FB5607', '#8AC926' |
| ] |
| |
| |
| random.shuffle(colors) |
| |
| return colors[:n] |
|
|
| def create_map(): |
| """Create a Folium map with distinct colors for each kabupaten/kota in Jawa Timur. |
| |
| Returns: |
| str: HTML string for embedding the Folium map (safe to render). |
| """ |
| |
| geojson_path = os.path.join(os.path.dirname(__file__), '..', 'data', 'geojson', 'jatim_kabkota.geojson') |
| |
| with open(geojson_path, encoding='utf-8') as f: |
| geojson_data = json.load(f) |
|
|
| |
| m = folium.Map( |
| location=[-7.5, 112.5], |
| zoom_start=8, |
| tiles='CartoDB positron', |
| prefer_canvas=True, |
| zoom_control=True, |
| attributionControl=False |
| ) |
|
|
| |
| num_features = len(geojson_data.get('features', [])) |
| colors = generate_distinct_colors(num_features) |
| |
| |
| color_map = {} |
| city_list = [] |
| for idx, feature in enumerate(geojson_data.get('features', [])): |
| feature_name = feature.get('properties', {}).get('name', f'Feature_{idx}') |
| feature_type = feature.get('properties', {}).get('type', 'Kabupaten') |
| color_map[feature_name] = colors[idx] |
| city_list.append({ |
| 'name': feature_name, |
| 'type': feature_type, |
| 'color': colors[idx] |
| }) |
|
|
| |
| def style_function(feature): |
| feature_name = feature['properties'].get('name', 'Unknown') |
| return { |
| 'fillColor': color_map.get(feature_name, '#CCCCCC'), |
| 'color': '#333333', |
| 'weight': 2, |
| 'fillOpacity': 1.0, |
| 'opacity': 1, |
| 'dashArray': None, |
| 'lineJoin': 'miter', |
| 'lineCap': 'butt' |
| } |
|
|
| |
| def highlight_function(feature): |
| return { |
| 'fillColor': '#FFFF00', |
| 'color': '#FF0000', |
| 'weight': 3, |
| 'fillOpacity': 0.9, |
| } |
|
|
| |
| folium.GeoJson( |
| geojson_data, |
| name='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 black; |
| border-radius: 5px; |
| font-family: Arial, sans-serif; |
| font-size: 12px; |
| padding: 8px; |
| box-shadow: 3px 3px 5px rgba(0,0,0,0.5); |
| """, |
| ) |
| ).add_to(m) |
| |
| |
| for feature in geojson_data.get('features', []): |
| props = feature.get('properties', {}) |
| name = props.get('name', 'Unknown') |
| center_lat = props.get('centroid_lat') |
| center_lon = props.get('centroid_lon') |
| |
| if center_lat and center_lon: |
| |
| folium.Marker( |
| location=[center_lat, center_lon], |
| icon=folium.DivIcon(html=f''' |
| <div style=" |
| font-family: Arial, sans-serif; |
| font-size: 10px; |
| color: #FFFFFF; |
| font-weight: bold; |
| text-shadow: 1px 1px 3px rgba(0,0,0,0.9), |
| -1px -1px 3px rgba(0,0,0,0.9), |
| 1px -1px 3px rgba(0,0,0,0.9), |
| -1px 1px 3px rgba(0,0,0,0.9); |
| text-align: center; |
| white-space: nowrap; |
| ">{name}</div> |
| ''') |
| ).add_to(m) |
|
|
| |
| custom_css = ''' |
| <style> |
| .leaflet-container { |
| background-color: #F5F5F5 !important; /* Light gray background */ |
| } |
| /* STRONG 3D shadow effect - multiple layers for depth */ |
| .leaflet-interactive { |
| filter: |
| drop-shadow(3px 3px 2px rgba(0,0,0,0.3)) |
| drop-shadow(6px 6px 4px rgba(0,0,0,0.25)) |
| drop-shadow(9px 9px 8px rgba(0,0,0,0.2)) |
| drop-shadow(12px 12px 12px rgba(0,0,0,0.15)); |
| } |
| /* Clean polygon edges */ |
| path.leaflet-interactive { |
| stroke-linejoin: miter !important; |
| stroke-linecap: butt !important; |
| } |
| </style> |
| ''' |
| m.get_root().html.add_child(folium.Element(custom_css)) |
| |
| |
| legend_html = ''' |
| <div style="position: fixed; |
| bottom: 20px; left: 20px; width: 280px; max-height: 520px; |
| background-color: white; z-index:9999; font-size:11px; |
| border:2px solid #333; border-radius: 8px; |
| overflow-y: auto; |
| box-shadow: 5px 5px 15px rgba(0,0,0,0.5); |
| font-family: Arial, sans-serif; |
| "> |
| <div style="background-color: #1976D2; color: white; padding: 12px; |
| font-weight: bold; text-align: center; font-size: 13px; |
| border-radius: 6px 6px 0 0;"> |
| 📍 38 Kabupaten/Kota Jawa Timur |
| </div> |
| <div style="padding: 10px; max-height: 450px; overflow-y: auto;"> |
| ''' |
| |
| |
| sorted_cities = sorted(city_list, key=lambda x: x['name']) |
| for idx, city in enumerate(sorted_cities, 1): |
| legend_html += f''' |
| <div style="margin: 3px 0; display: flex; align-items: center;"> |
| <div style="width: 20px; height: 20px; background-color: {city['color']}; |
| border: 1.5px solid #333; margin-right: 8px; flex-shrink: 0; |
| border-radius: 3px; |
| box-shadow: 2px 2px 4px rgba(0,0,0,0.3);"></div> |
| <span style="font-size: 10px; color: #333;"> |
| <b>{idx}.</b> {city['name']} ({city['type']}) |
| </span> |
| </div> |
| ''' |
| |
| legend_html += ''' |
| </div> |
| </div> |
| ''' |
| |
| m.get_root().html.add_child(folium.Element(legend_html)) |
|
|
| |
| title_html = ''' |
| <div style="position: fixed; |
| top: 10px; left: 50%; transform: translateX(-50%); |
| background-color: white; z-index:9999; |
| padding: 10px 30px; |
| border: 2px solid black; |
| border-radius: 5px; |
| font-family: Arial, sans-serif; |
| font-size: 16px; |
| font-weight: bold; |
| box-shadow: 3px 3px 10px rgba(0,0,0,0.5); |
| "> |
| Peta Jawa Timur - Kabupaten/Kota |
| </div> |
| ''' |
| m.get_root().html.add_child(folium.Element(title_html)) |
|
|
| |
| return m._repr_html_() |