| 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_() |