import numpy as np
import folium
import matplotlib.colors as colors
import matplotlib.pyplot as plt
from folium.features import CustomIcon
from branca.element import Element, Figure
import datetime
import http.server
import socketserver
import threading
import webbrowser
import noise
from scipy.ndimage import gaussian_filter

# Load color scales
def load_color_scale(file_path):
    with open(file_path, "r") as file:
        color_scale = eval(file.read())
    return np.array(color_scale) / 255

cmap = colors.LinearSegmentedColormap.from_list('terrain', ['green', 'yellow', 'red'])
beam_color_scale = load_color_scale("colours/radarbeam.txt")
radarnoise_color_scale = load_color_scale("colours/radarnoise.txt")
color_scale = load_color_scale("colours/dBZ.txt")

center_lat = 52.4252
center_lon = 18.8736
radius = 125000

# Increase resolution for more detailed cells
height, width = 3000, 3000  # This gives us 500m resolution for a 125km x 125km area
x, y = np.meshgrid(np.linspace(-radius, radius, width), np.linspace(-radius, radius, height))
distance_mask = np.sqrt(x**2 + y**2) <= radius

def generate_complex_noise(shape, scales, octaves, persistence, lacunarity):
    noise_layers = []
    for scale in scales:
        noise_layer = np.zeros(shape)
        for i in range(shape[0]):
            for j in range(shape[1]):
                noise_layer[i][j] = noise.pnoise2(i/scale, j/scale, 
                                                  octaves=octaves, 
                                                  persistence=persistence, 
                                                  lacunarity=lacunarity, 
                                                  repeatx=shape[1], 
                                                  repeaty=shape[0], 
                                                  base=np.random.randint(0, 100))
        noise_layers.append(noise_layer)
    return np.sum(noise_layers, axis=0)

def generate_cell_cores(shape, num_cores):
    cores = np.zeros(shape)
    for _ in range(num_cores):
        x, y = np.random.randint(0, shape[0]), np.random.randint(0, shape[1])
        cores[x, y] = np.random.uniform(0.5, 1.0)
    return gaussian_filter(cores, sigma=5)

def grow_cells(cores, noise):
    cells = cores.copy()
    for _ in range(10):  # Adjust number of iterations as needed
        cells = gaussian_filter(cells, sigma=1)
        cells = np.maximum(cells, cores)
        cells += noise * 0.1
        cells = np.clip(cells, 0, 1)
    return cells

# Set up parameters
scales = [100, 200, 400, 800]
num_cores = 50

# Generate complex noise
complex_noise = generate_complex_noise((height, width), scales, 6, 0.5, 2.0)

# Generate cell cores
cores = generate_cell_cores((height, width), num_cores)

# Grow cells
cells = grow_cells(cores, complex_noise)

# Add directional bias (simulating wind effect)
wind_direction = np.random.uniform(0, 2*np.pi)
x, y = np.meshgrid(np.linspace(0, 1, width), np.linspace(0, 1, height))
directional_bias = np.sin(x * np.cos(wind_direction) + y * np.sin(wind_direction))
cells += directional_bias * 0.2

# Add small-scale noise
small_scale_noise = np.random.normal(0, 0.05, (height, width))
cells += small_scale_noise

# Normalize and apply non-linear transformation
cells = (cells - cells.min()) / (cells.max() - cells.min())
cells = cells ** 2  # This will create sharper edges

# Map cell values to dBZ range
min_dbz = -20
max_dbz = 80
cells_dbz = cells * (max_dbz - min_dbz) + min_dbz

# Map dBZ values to color indices
num_colors = len(color_scale)
color_indices = np.round((cells_dbz - min_dbz) / (max_dbz - min_dbz) * (num_colors - 1)).astype(int)
color_indices = np.clip(color_indices, 0, num_colors - 1)

# Create output image
output_image = color_scale[color_indices]

# Apply distance mask and create alpha channel
alpha_channel = np.ones((height, width), dtype=float)
alpha_channel[~distance_mask] = 0.0
output_image = np.dstack((output_image, alpha_channel))

m = folium.Map(
    location=[center_lat, center_lon],
    zoom_start=7,
    zoom_control=False,
    control_scale=False,
    attributionControl=False
)

radar_bounds = [
    [center_lat - (radius / 111000), center_lon - (radius / (111000 * np.cos(np.radians(center_lat))))],
    [center_lat + (radius / 111000), center_lon + (radius / (111000 * np.cos(np.radians(center_lat))))]
]

radar_layer = folium.raster_layers.ImageOverlay(
    image=output_image,
    bounds=radar_bounds,
    opacity=0.7,
    name='Generated Radar Cells',
    interactive=True
)
radar_layer.add_to(m)

darkness_layer = folium.map.FeatureGroup(name='Darkness Layer', overlay=True, control=True)

darkness_coordinates = [
    [-90, -180],
    [-90, 180],
    [90, 180],
    [90, -180],
    [-90, -180]
]

num_points = 360
angles = np.linspace(0, 2 * np.pi, num_points)
circle_coordinates = [
    [center_lat + (radius / 111000) * np.cos(angle), center_lon + (radius / (111000 * np.cos(np.radians(center_lat)))) * np.sin(angle)]
    for angle in angles
]
circle_coordinates.append(circle_coordinates[0])

darkness_polygon = folium.vector_layers.Polygon(
    locations=[darkness_coordinates, circle_coordinates],
    color='black',
    fill=True,
    fill_color='black',
    fill_opacity=0.5,
    weight=0
)
darkness_layer.add_child(darkness_polygon)

darkness_layer.add_to(m)

folium.vector_layers.Circle(
    location=[center_lat, center_lon],
    radius=radius,
    color='transparent',
    fill=False,
    weight=0,
    popup=f'125km Radius from ({center_lat}, {center_lon})'
).add_to(m)

icon_image = 'interactive/radarsign.png'

icon = CustomIcon(
    icon_image,
    icon_size=(30, 20)
)

folium.Marker(
    location=[center_lat, center_lon],
    icon=icon,
    popup='Radar Center'
).add_to(m)

# Noise layer (unchanged)
noise_radius = np.random.randint(5000, 30001)
noise_height, noise_width = 55, 55
noise_x, noise_y = np.meshgrid(np.linspace(-noise_radius, noise_radius, noise_width),
                               np.linspace(-noise_radius, noise_radius, noise_height))
noise_distance_mask = np.sqrt(noise_x**2 + noise_y**2) <= noise_radius
noise = np.random.rand(noise_height, noise_width)

num_noise_colors = len(radarnoise_color_scale)
noise_color_indices = np.round(noise * (num_noise_colors - 1)).astype(int)
noise_output_image = radarnoise_color_scale[noise_color_indices]

noise_alpha_channel = np.ones_like(noise, dtype=float)
noise_alpha_channel[~noise_distance_mask] = 0.0
noise_image_with_alpha = np.dstack((noise_output_image, noise_alpha_channel))

noise_bounds = [
    [center_lat - (noise_radius / 111000), center_lon - (noise_radius / (111000 * np.cos(np.radians(center_lat))))],
    [center_lat + (noise_radius / 111000), center_lon + (noise_radius / (111000 * np.cos(np.radians(center_lat))))]
]

noise_layer = folium.raster_layers.ImageOverlay(
    image=noise_image_with_alpha,
    bounds=noise_bounds,
    opacity=0.7,
    name='Noise Layer',
    interactive=True
)
noise_layer.add_to(m)

# Beam generation (unchanged)
def create_high_res_beam(center_lat, center_lon, angle, radius, beam_width=500, num_pixels=200, gap_prob=0.1):
    beam_pixels = []
    pixel_length = radius / num_pixels
    
    for i in range(num_pixels):
        if np.random.rand() > gap_prob:
            pixel_start = i * pixel_length
            pixel_end = (i + 1) * pixel_length
            beam_start_lat = center_lat + (pixel_start / 111000) * np.cos(angle)
            beam_start_lon = center_lon + (pixel_start / (111000 * np.cos(np.radians(center_lat)))) * np.sin(angle)
            beam_end_lat = center_lat + (pixel_end / 111000) * np.cos(angle)
            beam_end_lon = center_lon + (pixel_end / (111000 * np.cos(np.radians(center_lat)))) * np.sin(angle)
            
            offset_lat = (beam_width / 111000) * np.cos(angle + np.pi/2)
            offset_lon = (beam_width / (111000 * np.cos(np.radians(center_lat)))) * np.sin(angle + np.pi/2)
            
            beam_polygon_coordinates = [
                [beam_start_lat - offset_lat, beam_start_lon - offset_lon],
                [beam_end_lat - offset_lat, beam_end_lon - offset_lon],
                [beam_end_lat + offset_lat, beam_end_lon + offset_lon],
                [beam_start_lat + offset_lat, beam_start_lon + offset_lon],
                [beam_start_lat - offset_lat, beam_start_lon - offset_lon]
            ]
            
            beam_pixels.append((beam_polygon_coordinates, i))
    
    return beam_pixels

num_beams = np.random.randint(1, 6)
beam_angles = np.random.uniform(0, 2 * np.pi, num_beams)

beam_layer = folium.FeatureGroup(name='Radar Beams', overlay=True, control=True)

for angle in beam_angles:
    beam_pixels = create_high_res_beam(center_lat, center_lon, angle, radius)
    
    for beam_polygon_coordinates, pixel_index in beam_pixels:
        num_beam_colors = len(beam_color_scale)
        beam_color_index = pixel_index % num_beam_colors
        beam_color = beam_color_scale[beam_color_index]
        
        folium.vector_layers.Polygon(
            locations=beam_polygon_coordinates,
            color=f'#{int(beam_color[0]*255):02x}{int(beam_color[1]*255):02x}{int(beam_color[2]*255):02x}',
            fill=True,
            fill_opacity=0.7,
            weight=0
        ).add_to(beam_layer)

beam_layer.add_to(m)

style_statement = '<style>.leaflet-control-attribution, .leaflet-control-layers {display: none !important;}</style>'
m.get_root().html.add_child(folium.Element(style_statement))

def get_current_time():
    now = datetime.datetime.utcnow() + datetime.timedelta(hours=2)
    adjusted_minute = (now.minute + 57) % 60
    minute = ((adjusted_minute // 5) * 5 + 3) % 60
    hour = now.hour if adjusted_minute >= 3 else (now.hour - 1) % 24
    minute_str = f"0{minute}" if minute < 10 else str(minute)
    current_time = f"{hour}:{minute_str}"
    return current_time

def get_current_date():
    today = datetime.datetime.utcnow() + datetime.timedelta(hours=2)
    return today.strftime("%d.%m.%Y")

fig = Figure(width=800, height=600)
fig.add_child(m)

upper_bar_html = f"""
<div id="upper-bar" style="background-color: white; width: 100%; position: absolute; top: 0; z-index: 1000; padding: 10px; padding-bottom: 10px; box-sizing: border-box; font-family: 'Arial', sans-serif;">
    <div style="float: left; text-align: center; font-weight: bold; color: #000000;">
        Janowo<br>
        <span style="color: grey; font-weight: bold; font-size: smaller;">PPI, dBZ</span>
    </div>
    <div style="float: right; text-align: center; font-weight: bold; color: #000000;">
        <span id="current-time">{get_current_time()}</span><br>
        <span style="color: grey; font-weight: bold; font-size: smaller;">{get_current_date()}</span>
    </div>
    <div style="clear: both;"></div>
</div>
<script>
var baseTime = new Date().getTime();

function updateTime() {{
    var now = new Date();
    var startMinutes = 3;
    var currentMinutes = now.getMinutes();
    var roundedMinutes = (Math.floor((currentMinutes - startMinutes) / 5) * 5) + startMinutes;
    if (roundedMinutes >= 60) {{
        roundedMinutes -= 60;
        now.setHours(now.getHours() + 1);
    }}
    now.setMinutes(roundedMinutes);
    var hour = now.getHours();
    var minuteStr = roundedMinutes < 10 ? '0' + roundedMinutes : roundedMinutes;

    var currentTime = hour + ':' + minuteStr;
    document.getElementById('current-time').innerText = currentTime;
}}

updateTime();
setInterval(updateTime, 5 * 60 * 1000);
</script>
"""

upper_bar_element = Element(upper_bar_html)
fig.html.add_child(upper_bar_element)

output_file = 'interactive_map.html'
fig.save(output_file)

fig, ax = plt.subplots(figsize=(10, 10))
ax.imshow(cells_dbz, cmap=cmap, vmin=min_dbz, vmax=max_dbz)

min_dbz = -20
max_dbz = 80
dbz_range = np.linspace(min_dbz, max_dbz, len(color_scale))
norm = colors.BoundaryNorm(boundaries=dbz_range, ncolors=len(color_scale))
cbar = plt.colorbar(plt.cm.ScalarMappable(cmap=colors.ListedColormap(color_scale), norm=norm), ax=ax)
cbar.set_label('dBZ')
cbar.set_ticks(range(min_dbz, max_dbz + 1, 5))

ax.set_xlabel('Distance (m)')
ax.set_ylabel('Distance (m)')
ax.set_title('Experimental randomized radar reflectivity display')

output_png = 'interactive/dBZvis.png'
plt.savefig(output_png, dpi=300, bbox_inches='tight')
plt.close()

PORT = 8000

Handler = http.server.SimpleHTTPRequestHandler

def open_browser():
    webbrowser.open(f'http://localhost:{PORT}/{output_file}')

def start_server():
    with socketserver.TCPServer(("", PORT), Handler) as httpd:
        print(f"Serving at port {PORT}")
        httpd.serve_forever()

server_thread = threading.Thread(target=start_server)
browser_thread = threading.Thread(target=open_browser)

server_thread.start()
browser_thread.start()

### Project Overview
I am working on a project aimed at creating a simulated Doppler weather radar. The goal is to generate radar cells that closely resemble the structure of actual radar data, with a high pixel resolution. 

### Current Issue
Currently, the code generates random noise pixels across the radar extent. What I need is a realistic simulation of several randomly generated isolated radar cells. These cells should have distinct structures similar to those observed in actual Doppler weather radar outputs.

### Requirements
1. **Realistic Radar Cells**: The simulation should produce isolated radar cells that mimic real-world weather patterns.
2. **High Pixel Resolution**: Maintain the existing high pixel resolution in the output.
3. **Noise Reduction**: Eliminate random noise pixels from the simulation.
4. **Structure Similarity**: Ensure that the generated cells have recognizable structures akin to actual Doppler radar data.

### Additional Considerations
- **Algorithm Design**: Consider using algorithms that simulate meteorological phenomena (e.g., Gaussian distributions for cell shapes).
- **Parameterization**: Allow for adjustable parameters to control the number, size, and intensity of the radar cells.
- **Visualization**: Provide options for visualizing the output in a way that resembles actual radar displays.

### Request for Solution
Please provide a step-by-step guide to enhance my existing code, focusing on:
1. **Reiterating the Problem**: Summarize the current issues with the code.
2. **Proposed Solutions**: Offer specific strategies or algorithms to create realistic isolated radar cells.
3. **Code Implementation**: Provide sample code snippets that implement these strategies.
4. **Explanation**: Explain how each part of the solution works and why it’s effective.
5. **Testing and Validation**: Suggest methods for testing the output to ensure realism and accuracy.
6. **Deployment Instructions**: Include any necessary steps for integrating this solution into my existing project.

### Contextual Information
For context, I have experience with programming but may need guidance on advanced techniques specific to this simulation task. The goal is to complete this efficiently while ensuring high-quality results.

Thank you for your assistance!