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'''