import io, base64,os,uuid import matplotlib.pyplot as plt import matplotlib.colors as colors import matplotlib.cm as cm import matplotlib.gridspec as gridspec import geopandas as gpd import pandas as pd def show_two_plots(gdf, name1, name2, vmin, vmax, colorscheme, title1, title2, label, split=False): #dynamically change marker size n_points = len(gdf) marker_size = 8500 / n_points marker_size = max(2, min(marker_size, 200)) filter_marker_size = marker_size*0.6 print(marker_size) print('split', split) if gdf.empty: raise ValueError("GeoDataFrame is empty") required_cols = [name1, name2] for col in required_cols: if col not in gdf.columns: raise ValueError(f"Column '{col}' not found in gdf") #Create a shared colormap and normalization cmap = plt.colormaps[colorscheme] norm = colors.Normalize(vmin=vmin, vmax=vmax) # Create subplots fig = plt.figure(figsize=(12, 6)) gs = gridspec.GridSpec(1, 3, width_ratios=[1, 1, 0.05], wspace=0.3) axes = [fig.add_subplot(gs[0]), fig.add_subplot(gs[1]), fig.add_subplot(gs[2]) ] if split == False: # First plot gdf.plot(column=name1, cmap=cmap, norm=norm, ax=axes[0], marker='s', markersize=marker_size) else: # Split data into three GeoDataFrames # below_100 = gdf[gdf[split] <= 100] above_capacity = gdf[gdf[split] > (100-gdf['Pct_Construccion'])] above_100 = gdf[gdf[split] > 100] above_colors = {"capacity":"chocolate", "100":"firebrick"} # Plot values ≤ 100 using colormap gdf.plot(column=name1, cmap=colorscheme, ax=axes[0], vmin=vmin, vmax=vmax, marker='s', markersize=marker_size) # Plot values > capacity in orange above_capacity.plot(color=above_colors['capacity'], ax=axes[0], label='> capacidad debido a la construcción', marker='x', markersize=filter_marker_size) # Plot values > 100 in red above_100.plot(color=above_colors['100'], ax=axes[0], label='> 100% cobertura arbórea', marker='x', markersize=filter_marker_size) # Add legend manually for orange points orange_patch = plt.Line2D([0], [0], marker='o', color='w', label='> capacidad debido a la construcción', markerfacecolor=above_colors['capacity'], markersize=8) # Add legend manually for red points red_patch = plt.Line2D([0], [0], marker='o', color='w', label='> 100% cobertura arbórea', markerfacecolor=above_colors['100'], markersize=8) axes[0].legend(handles=[orange_patch, red_patch]) # axes[0].set_title(title1) axes[0].set_title(title1, pad=-30) axes[0].set_axis_off() # Second plot (original) gdf.plot(column=name2, cmap=cmap, norm=norm, ax=axes[1],marker='s', markersize=marker_size) axes[1].set_title(title2, pad=-30) axes[1].set_axis_off() # Shared colorbar sm = cm.ScalarMappable(cmap=cmap, norm=norm) sm._A = [] # Dummy array for the colormap cbar = fig.colorbar(sm, cax=axes[2], orientation='vertical', fraction=0.03, pad=0.02) cbar.set_label(label) return fig def show_one_plot(gdf, name, vmin, vmax, colorscheme, title, label): #dynamically change marker size n_points = len(gdf) marker_size = 8500 / n_points marker_size = max(2, min(marker_size, 200)) # Second set of plots cmap = plt.colormaps[colorscheme] norm = colors.Normalize(vmin, vmax) # Create subplots fig = plt.figure(figsize=(12, 6)) gs = gridspec.GridSpec(1, 2, width_ratios=[1, 0.025], wspace=0.3) axes = [fig.add_subplot(gs[0]), fig.add_subplot(gs[1])] gdf.plot(column=name, cmap=cmap, norm=norm, ax=axes[0], marker='s', markersize=marker_size) axes[0].set_title(title, pad=-30) axes[0].set_axis_off() # Add a colorbar sm = cm.ScalarMappable(cmap=cmap, norm=norm) sm._A = [] # Dummy data for the colorbar cbar = fig.colorbar(sm, cax=axes[1], orientation='vertical', fraction=0.03, pad=0.02) cbar.set_label(label) return fig def show_two_plots_base64( gdf, name1, name2, vmin, vmax, colorscheme, title1, title2, label, split=False ): """Genera dos mapas (antes/después) de 1024×1024 px y los devuelve en base64.""" cmap = plt.colormaps[colorscheme] norm = colors.Normalize(vmin=vmin, vmax=vmax) # Tamaño fijo fig_size_inch = (5.12, 5.12) dpi_val = 200 # Tamaño de marcador dinámico n_points = len(gdf) marker_size = 8500 / n_points marker_size = max(2, min(marker_size, 200)) filter_marker_size = marker_size * 0.6 def fig_to_base64(fig): buf = io.BytesIO() fig.savefig(buf, format="png", bbox_inches="tight", dpi=dpi_val) buf.seek(0) return base64.b64encode(buf.read()).decode("utf-8") def make_fig(column, title, apply_split=False): # Fijamos tamaño físico y DPI fig, ax = plt.subplots(figsize=fig_size_inch, dpi=dpi_val) # Mapa base gdf.plot( column=column, cmap=cmap, norm=norm, ax=ax, marker='s', markersize=marker_size ) # Aplicar split sólo al mapa “después” if apply_split and split: above_capacity = gdf[gdf[split] > (100 - gdf["Pct_Construccion"])] above_100 = gdf[gdf[split] > 100] above_colors = {"capacity": "chocolate", "100": "firebrick"} above_capacity.plot( color=above_colors["capacity"], ax=ax, marker='x', markersize=filter_marker_size ) above_100.plot( color=above_colors["100"], ax=ax, marker='x', markersize=filter_marker_size ) # Leyenda orange_patch = plt.Line2D( [0], [0], marker='o', color='w', label='> capacidad (construcción)', markerfacecolor=above_colors['capacity'], markersize=8 ) red_patch = plt.Line2D( [0], [0], marker='o', color='w', label='> 100% cobertura arbórea', markerfacecolor=above_colors['100'], markersize=8 ) ax.legend(handles=[orange_patch, red_patch]) ax.set_title(title, pad=-25) ax.set_axis_off() sm = cm.ScalarMappable(cmap=cmap, norm=norm) sm._A = [] fig.colorbar( sm, ax=ax, orientation='vertical', fraction=0.03, pad=0.02 ).set_label(label) return fig # “Después” (aplica split) fig_after = make_fig(name1, title1, apply_split=True) # “Antes” (sin split) fig_before = make_fig(name2, title2, apply_split=False) return fig_to_base64(fig_after), fig_to_base64(fig_before) def show_two_plots_base64_clean( gdf, name1, name2, vmin, vmax, colorscheme, title1, title2, label, split=False ): """ Genera dos mapas (antes/después) de 1024×1024 px. Cada mapa incluye su colorbar, pero no título ni leyenda. Devuelve dict con imágenes base64, títulos y leyenda HTML aparte. """ cmap = plt.colormaps[colorscheme] norm = colors.Normalize(vmin=vmin, vmax=vmax) fig_size_inch = (5.12, 5.12) dpi_val = 200 n_points = len(gdf) marker_size = 8500 / n_points marker_size = max(2, min(marker_size, 200)) filter_marker_size = marker_size * 0.6 def fig_to_base64(fig): buf = io.BytesIO() fig.savefig(buf, format="png", bbox_inches="tight", dpi=dpi_val) buf.seek(0) return base64.b64encode(buf.read()).decode("utf-8") def make_fig(column, apply_split=False): fig, ax = plt.subplots(figsize=fig_size_inch, dpi=dpi_val) # Base plot gdf.plot( column=column, cmap=cmap, norm=norm, ax=ax, marker='s', markersize=marker_size ) # Split solo en el "después" if apply_split and split: above_capacity = gdf[gdf[split] > (100 - gdf["Pct_Construccion"])] above_100 = gdf[gdf[split] > 100] above_colors = {"capacity": "chocolate", "100": "firebrick"} above_capacity.plot( color=above_colors["capacity"], ax=ax, marker='x', markersize=filter_marker_size ) above_100.plot( color=above_colors["100"], ax=ax, marker='x', markersize=filter_marker_size ) # Quitar ejes y agregar colorbar ax.set_axis_off() sm = cm.ScalarMappable(cmap=cmap, norm=norm) sm._A = [] fig.colorbar( sm, ax=ax, orientation='vertical', fraction=0.03, pad=0.02 ).set_label(label) plt.tight_layout() return fig # Generar ambas figuras fig_after = make_fig(name1, apply_split=True) fig_before = make_fig(name2, apply_split=False) # Convertir ambas a base64 img_after = fig_to_base64(fig_after) img_before = fig_to_base64(fig_before) # Leyenda HTML (solo si split activo) legend_html = "" if split: legend_html = """
> capacidad (por construcción)
> 100% cobertura arbórea
""" return { "img_before": img_before, "img_after": img_after, "title_before": title2, "title_after": title1, "legend_html": legend_html } def show_two_plots_and_export( gdf, name1, name2, vmin, vmax, colorscheme, title1, title2, label, split=False, export_dir="static/data" ): # --- Validaciones básicas --- if gdf.empty: raise ValueError("GeoDataFrame is empty") for col in [name1, name2]: if col not in gdf.columns: raise ValueError(f"Column '{col}' not found in gdf") if gdf.crs is None: gdf = gdf.set_crs(epsg=32615) gdf = gdf.to_crs(4326) os.makedirs(export_dir, exist_ok=True) before_path = os.path.join(export_dir, "layer_before.geojson") after_path = os.path.join(export_dir, "layer_after.geojson") split_path = os.path.join(export_dir, "layer_split.geojson") if split else None # --- Configurar colormap y normalización --- cmap = plt.colormaps[colorscheme] norm = colors.Normalize(vmin=vmin, vmax=vmax) # --- Copias completas para conservar todas las columnas --- gdf_before = gdf.copy() gdf_after = gdf.copy() # --- Agregar columnas derivadas sin eliminar otras --- gdf_before["value"] = gdf_before[name1] gdf_after["value"] = gdf_after[name2] gdf_before["color"] = gdf_before["value"].apply(lambda x: colors.to_hex(cmap(norm(x)))) gdf_after["color"] = gdf_after["value"].apply(lambda x: colors.to_hex(cmap(norm(x)))) # --- Guardar GeoJSON con todos los datos originales --- gdf_before.to_file(before_path, driver="GeoJSON") gdf_after.to_file(after_path, driver="GeoJSON") # --- Lógica del split (si aplica) --- if split: split_field = split if isinstance(split, str) else name1 if split_field not in gdf.columns: raise ValueError(f"Split field '{split_field}' not found in gdf") gdf_split = gdf.copy() cond_100 = gdf_split[split_field] > 100 cond_cap = (gdf_split[split_field] > (100 - gdf_split.get("Pct_Construccion", 0))) & ~cond_100 gdf_split.loc[cond_100, "split_type"] = "100" gdf_split.loc[cond_100, "color"] = "#b22222" gdf_split.loc[cond_cap, "split_type"] = "capacidad" gdf_split.loc[cond_cap, "color"] = "#d2691e" gdf_split = gdf_split.dropna(subset=["split_type"]) gdf_split.to_file(split_path, driver="GeoJSON") # --- Crear barra de color horizontal --- sm = cm.ScalarMappable(cmap=cmap, norm=norm) sm._A = [] fig_cbar, ax_cbar = plt.subplots(figsize=(6, 1)) fig_cbar.subplots_adjust(bottom=0.5) cb = plt.colorbar(sm, cax=ax_cbar, orientation='horizontal') cb.set_label(label) # --- Guardar imagen de barra de colores --- colorbar_path = os.path.join(export_dir, "before_after_colorbar_horizontal.png") fig_cbar.savefig(colorbar_path, dpi=150, bbox_inches="tight", transparent=True) plt.close(fig_cbar) # --- URLs fijas --- before_url = "/data/layer_before.geojson" after_url = "/data/layer_after.geojson" split_url = "/data/layer_split.geojson" if split else None return { "before_url": before_url, "after_url": after_url, "split_url": split_url, "before_path": before_path, "after_path": after_path, "split_path": split_path, "colorbar_path": colorbar_path } def show_one_plot_and_export( gdf, name, vmin, vmax, colorscheme, title, label, export_dir="static/data" ): # --- Validaciones básicas --- if gdf.empty: raise ValueError("GeoDataFrame is empty") if name not in gdf.columns: raise ValueError(f"Column '{name}' not found in gdf") print("sshow one_plot_and_export") if gdf.crs is None: gdf = gdf.set_crs(epsg=32615) gdf = gdf.to_crs(4326) os.makedirs(export_dir, exist_ok=True) layer_path = os.path.join(export_dir, "layer_single.geojson") # --- Colores por feature --- cmap = plt.colormaps[colorscheme] norm = colors.Normalize(vmin=vmin, vmax=vmax) def value_to_hex(val): rgba = cmap(norm(val)) return colors.to_hex(rgba, keep_alpha=False) gdf_export = gdf[[name, "geometry"]].rename(columns={name: "value"}) gdf_export["color"] = gdf_export["value"].apply(value_to_hex) # --- Guardar GeoJSON --- gdf_export.to_file(layer_path, driver="GeoJSON") # --- Crear figura principal --- n_points = len(gdf) marker_size = 8500 / n_points marker_size = max(2, min(marker_size, 200)) fig = plt.figure(figsize=(10, 6)) gs = gridspec.GridSpec(1, 2, width_ratios=[1, 0.03], wspace=0.3) ax, cax = fig.add_subplot(gs[0]), fig.add_subplot(gs[1]) gdf.plot(column=name, cmap=cmap, norm=norm, ax=ax, marker='s', markersize=marker_size) ax.set_title(title, pad=-30) ax.set_axis_off() sm = cm.ScalarMappable(cmap=cmap, norm=norm) sm._A = [] cbar = fig.colorbar(sm, cax=cax, orientation='vertical') cbar.set_label(label) # ✅ Crear barra de color horizontal separada fig_cbar, ax_cbar = plt.subplots(figsize=(6, 1)) fig_cbar.subplots_adjust(bottom=0.5) cb = plt.colorbar(sm, cax=ax_cbar, orientation='horizontal') cb.set_label(label) # --- Guardar como imagen PNG --- colorbar_path = os.path.join(export_dir, "change_colorbar_horizontal.png") fig_cbar.savefig(colorbar_path, dpi=150, bbox_inches="tight", transparent=True) plt.close(fig_cbar) # ✅ URL pública fija layer_url = "/data/layer_single.geojson" return { "fig": fig, "layer_path": layer_path, "layer_url": layer_url, "colorbar_path": colorbar_path, "gdf_export": gdf_export[["value", "color", "geometry"]] }