Spaces:
Sleeping
Sleeping
| 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 = """ | |
| <div class='flex flex-col text-sm mt-2'> | |
| <div class='flex items-center space-x-2'> | |
| <span class='inline-block w-3 h-3 rounded-full' style='background-color:chocolate'></span> | |
| <span>> capacidad (por construcción)</span> | |
| </div> | |
| <div class='flex items-center space-x-2'> | |
| <span class='inline-block w-3 h-3 rounded-full' style='background-color:firebrick'></span> | |
| <span>> 100% cobertura arbórea</span> | |
| </div> | |
| </div> | |
| """ | |
| 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"]] | |
| } | |