In [1]:
import os
from dotenv import load_dotenv, find_dotenv
load_dotenv(find_dotenv())
Out[1]:
True
In [2]:
ilab_path = os.path.join(os.path.expanduser('~'),'ILAB_DATA')
istat_path = os.path.join(ilab_path,'ISTAT','DATA')
work_path = os.path.join(ilab_path,'OCPR','DATA')
out_path = os.path.join(ilab_path,'OCPR','OUT')
In [3]:
CLAUDE_API_KEY = os.getenv('ANTHROPIC_API_KEY_GK')
geoparquet_path=os.path.join(work_path,'grid_06_adv.geoparquet')
metadata_path=os.path.join(out_path,'metadati_dataset_06.csv')
In [4]:
import anthropic
import json
class ClaudeClient:
def __init__(self, api_key: str):
self.client = anthropic.Anthropic(api_key=api_key)
def query_interpreter(self, user_query: str, available_columns: dict) -> dict:
prompt = f"""
Analizza questa domanda sui dati geografici e restituisci un JSON con le operazioni da eseguire.
Colonne disponibili per categoria:
{json.dumps(available_columns, indent=2)}
Domanda utente: "{user_query}"
Restituisci solo un JSON con questa struttura:
{{
"operation": "filter|aggregate|spatial",
"columns": ["lista_colonne_coinvolte"],
"conditions": [
{{"column": "nome_colonna", "operator": ">|<|=|!=", "value": valore}}
],
"aggregation": "count|sum|mean|max|min|null",
"spatial_operation": "within|distance|intersects|null",
"explanation": "breve spiegazione operazione"
}}
"""
response = self.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1000,
messages=[{"role": "user", "content": prompt}]
)
try:
return json.loads(response.content[0].text)
except:
return {"error": "Parsing fallito", "raw_response": response.content[0].text}
In [5]:
import pandas as pd
import geopandas as gpd
class DataAnalyzer:
def __init__(self, geoparquet_path: str, metadata_path: str):
self.metadata = pd.read_csv(metadata_path)
self.gdf = gpd.read_parquet(geoparquet_path)
self.columns_by_category = self._organize_columns()
def _organize_columns(self) -> dict:
categories = {}
for _, row in self.metadata.iterrows():
cat = row['category']
if cat not in categories:
categories[cat] = []
categories[cat].append({
'name_adv': row['name_adv'],
'name': row['name'],
'description': row['label_adv']
})
return categories
def execute_query(self, query_spec: dict):
try:
df = self.gdf.copy()
# Applica filtri
if 'conditions' in query_spec and query_spec['conditions']:
for condition in query_spec['conditions']:
col = condition['column']
op = condition['operator']
val = condition['value']
if col not in df.columns:
continue
if op == '>':
df = df[df[col] > val]
elif op == '<':
df = df[df[col] < val]
elif op == '=':
df = df[df[col] == val]
elif op == '!=':
df = df[df[col] != val]
# Esegui aggregazione
if query_spec.get('aggregation'):
agg = query_spec['aggregation']
cols = query_spec.get('columns', [])
if agg == 'count':
return len(df)
elif cols and agg in ['sum', 'mean', 'max', 'min']:
col = cols[0] if cols[0] in df.columns else df.select_dtypes(include='number').columns[0]
return getattr(df[col], agg)()
return len(df)
except Exception as e:
return f"Errore: {str(e)}"
In [6]:
class IntelligentGeoAgent:
def __init__(self, claude_api_key: str, geoparquet_path: str, metadata_path: str):
self.claude = ClaudeClient(claude_api_key)
self.analyzer = DataAnalyzer(geoparquet_path, metadata_path)
def answer_question(self, user_question: str) -> str:
# Interpreta la domanda con Claude
query_spec = self.claude.query_interpreter(
user_question,
self.analyzer.columns_by_category
)
if 'error' in query_spec:
return f"Non riesco a interpretare la domanda: {query_spec['error']}"
# Esegui la query sui dati
result = self.analyzer.execute_query(query_spec)
# Genera risposta finale con Claude
return self._generate_response(user_question, query_spec, result)
def _generate_response(self, question: str, query_spec: dict, result) -> str:
prompt = f"""
Domanda originale: "{question}"
Operazione eseguita: {query_spec.get('explanation', 'N/A')}
Risultato: {result}
Genera una risposta naturale e comprensibile per l'utente.
"""
response = self.claude.client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=300,
messages=[{"role": "user", "content": prompt}]
)
return response.content[0].text
In [7]:
import folium
import numpy as np
from folium import plugins
class MapVisualizer:
def __init__(self, gdf, metadata):
self.gdf = gdf
self.metadata = metadata
self.center_lat, self.center_lon = self._get_center()
def _get_center(self):
# Assicurati che sia in WGS84
if self.gdf.crs != 'EPSG:4326':
gdf_wgs84 = self.gdf.to_crs('EPSG:4326')
else:
gdf_wgs84 = self.gdf
bounds = gdf_wgs84.total_bounds
center_lat = (bounds[1] + bounds[3]) / 2
center_lon = (bounds[0] + bounds[2]) / 2
return center_lat, center_lon
def visualize_query_result(self, query_spec: dict, filtered_gdf=None, title="Risultato Query"):
# Crea mappa base
m = folium.Map(
location=[self.center_lat, self.center_lon],
zoom_start=10,
tiles='OpenStreetMap'
)
if filtered_gdf is None or len(filtered_gdf) == 0:
folium.Marker(
[self.center_lat, self.center_lon],
popup="Nessun risultato trovato",
icon=folium.Icon(color='red')
).add_to(m)
return m
# Determina tipo visualizzazione
viz_type = self._determine_visualization_type(query_spec)
if viz_type == "choropleth":
self._add_choropleth(m, filtered_gdf, query_spec)
elif viz_type == "heatmap":
self._add_heatmap(m, filtered_gdf, query_spec)
else:
self._add_basic_markers(m, filtered_gdf)
# Aggiungi titolo
title_html = f'<h3 align="center">{title}</h3>'
m.get_root().html.add_child(folium.Element(title_html))
return m
def _determine_visualization_type(self, query_spec):
if query_spec.get('aggregation') in ['sum', 'mean']:
return "choropleth"
elif len(query_spec.get('columns', [])) > 0:
return "heatmap"
else:
return "markers"
def _add_choropleth(self, m, gdf, query_spec):
columns = query_spec.get('columns', [])
if not columns:
return
col = columns[0]
if col not in gdf.columns:
return
# Assicura conversione corretta a WGS84
if gdf.crs != 'EPSG:4326':
gdf_wgs84 = gdf.to_crs('EPSG:4326')
else:
gdf_wgs84 = gdf.copy()
# Usa indice come chiave se h3_06 non disponibile
key_col = 'h3_06' if 'h3_06' in gdf.columns else None
if key_col:
folium.Choropleth(
geo_data=gdf_wgs84,
data=gdf_wgs84,
columns=[key_col, col],
key_on=f'feature.properties.{key_col}',
fill_color='YlOrRd',
fill_opacity=0.7,
line_opacity=0.2,
legend_name=self._get_column_description(col)
).add_to(m)
else:
# Fallback con geometrie semplici
self._add_basic_polygons(m, gdf_wgs84, col)
def _add_heatmap(self, m, gdf, query_spec):
# Assicura conversione corretta a WGS84
if gdf.crs != 'EPSG:4326':
gdf_wgs84 = gdf.to_crs('EPSG:4326')
else:
gdf_wgs84 = gdf.copy()
# Estrai punti centrali delle geometrie
points = [[geom.centroid.y, geom.centroid.x] for geom in gdf_wgs84.geometry if geom is not None]
# Aggiungi pesi se disponibili
columns = query_spec.get('columns', [])
if columns and columns[0] in gdf.columns:
weights = gdf[columns[0]].fillna(0).tolist()
heat_data = [[p[0], p[1], w] for p, w in zip(points, weights) if w > 0]
else:
heat_data = points
if heat_data:
plugins.HeatMap(heat_data).add_to(m)
def _add_basic_polygons(self, m, gdf_wgs84, col):
"""Aggiunge poligoni colorati quando choropleth fallisce"""
# Normalizza valori per colori
values = gdf_wgs84[col].fillna(0)
vmin, vmax = values.min(), values.max()
for idx, row in gdf_wgs84.iterrows():
if row.geometry is None:
continue
# Colore basato sul valore
val = row[col] if not pd.isna(row[col]) else 0
color_intensity = (val - vmin) / (vmax - vmin) if vmax > vmin else 0.5
color = f"#{int(255 * color_intensity):02x}0000" # Rosso graduato
folium.GeoJson(
row.geometry,
style_function=lambda x, color=color: {
'fillColor': color,
'color': 'black',
'weight': 1,
'fillOpacity': 0.7
},
popup=f"{self._get_column_description(col)}: {val}"
).add_to(m)
def _get_column_description(self, col_name):
match = self.metadata[self.metadata['name_adv'] == col_name]
if not match.empty:
return match.iloc[0]['label_adv']
def _add_basic_markers(self, m, gdf):
# Assicura conversione corretta a WGS84
if gdf.crs != 'EPSG:4326':
gdf_wgs84 = gdf.to_crs('EPSG:4326')
else:
gdf_wgs84 = gdf.copy()
for idx, row in gdf_wgs84.iterrows():
if row.geometry is None:
continue
centroid = row.geometry.centroid
popup_text = f"Cella: {idx}<br>"
for col in ['popolazione', 'residenti', 'abitanti']:
if any(col in c.lower() for c in gdf.columns):
matching_col = next(c for c in gdf.columns if col in c.lower())
popup_text += f"{col.title()}: {row[matching_col]}<br>"
break
folium.CircleMarker(
location=[centroid.y, centroid.x],
radius=5,
popup=popup_text,
color='red',
fill=True,
opacity=0.7
).add_to(m)
In [8]:
class EnhancedGeoAgent(IntelligentGeoAgent):
def __init__(self, claude_api_key: str, geoparquet_path: str, metadata_path: str):
super().__init__(claude_api_key, geoparquet_path, metadata_path)
self.context = self._build_context()
def _build_context(self) -> str:
stats = {
'total_cells': len(self.analyzer.gdf),
'total_columns': len(self.analyzer.gdf.columns),
'categories': list(self.analyzer.columns_by_category.keys()),
'sample_columns': {}
}
for cat, cols in self.analyzer.columns_by_category.items():
stats['sample_columns'][cat] = [c['name'] for c in cols[:3]]
return f"""
Dataset: {stats['total_cells']} celle esagonali con {stats['total_columns']} attributi
Categorie: {', '.join(stats['categories'])}
Esempi colonne per categoria: {stats['sample_columns']}
"""
def answer_with_context(self, user_question: str) -> str:
enhanced_prompt = f"""
Contesto dataset:
{self.context}
Domanda utente: {user_question}
Analizza la domanda considerando il contesto del dataset geografico a griglia esagonale.
"""
query_spec = self.claude.query_interpreter(enhanced_prompt, self.analyzer.columns_by_category)
if 'error' in query_spec:
return self._suggest_alternatives(user_question)
result = self.analyzer.execute_query(query_spec)
return self._generate_response(user_question, query_spec, result)
def _suggest_alternatives(self, question: str) -> str:
prompt = f"""
L'utente ha chiesto: "{question}"
Basandoti su questo dataset geografico:
{self.context}
Suggerisci 3 domande simili che potrebbero essere interessanti e fattibili.
"""
response = self.claude.client.messages.create(
model="claude-3-sonnet-20240229",
max_tokens=300,
messages=[{"role": "user", "content": prompt}]
)
return f"Non riesco a rispondere direttamente. Ecco alcune alternative:\n{response.content[0].text}"
In [9]:
enhanced_agent = EnhancedGeoAgent(
claude_api_key=CLAUDE_API_KEY,
geoparquet_path=geoparquet_path,
metadata_path=metadata_path
)
print("✅ Enhanced Agent inizializzato!")
print(f"📊 Contesto dataset:\n{enhanced_agent.context}")
✅ Enhanced Agent inizializzato!
📊 Contesto dataset:
Dataset: 704 celle esagonali con 311 attributi
Categorie: Griglia, Copertura del suolo, Accessibilità trasporti (geoportale sardegna), Demografia, Accessibilità trasporti, Accessibilità cultura, Accessibilità istruzione, Accessibilità ristorazione, Accessibilità sanità, Accessibilità sport, Accessibilità strutture ricettive, Accessibilità sanità - Ospedali
Esempi colonne per categoria: {'Griglia': ['geometry', 'h3_06', 'cell_a_m2'], 'Copertura del suolo': ['agricultural', 'artificial_non_agricultural_vegetated', 'forest_and_semi_natural'], 'Accessibilità trasporti (geoportale sardegna)': ['traffic__15__aeroporto_militare', 'traffic__15__aeroporto_principale', 'traffic__15__aeroporto_secondario'], 'Demografia': ['p_0_14__01', 'p_0_14__11', 'p_0_14__21'], 'Accessibilità trasporti': ['traffic__15__aeroporto', 'traffic__15__bus', 'traffic__15__stazione'], 'Accessibilità cultura': ['traffic__15__biblioteca', 'traffic__15__cinema', 'traffic__15__evento_culturale'], 'Accessibilità istruzione': ['traffic__15__formazione_alternativa', 'traffic__15__formazione_professionale', 'traffic__15__istituti_scolastici'], 'Accessibilità ristorazione': ['traffic__15__altri_servizi_alimentari', 'traffic__15__bar', 'traffic__15__ristorante'], 'Accessibilità sanità': ['traffic__15__centri_sanitari', 'traffic__15__cliniche_mediche', 'traffic__15__servizi_diagnostici'], 'Accessibilità sport': ['traffic__15__altre_attivita_sportive', 'traffic__15__club_e_squadre_sportive', 'traffic__15__fitness_e_allenamento'], 'Accessibilità strutture ricettive': ['traffic__15__strutture_alberghiere', 'traffic__15__strutture_extra_alberghiere', 'traffic__15__total_strutture_ricettive'], 'Accessibilità sanità - Ospedali': ['traffic__15__ao_integrata_con_il_ssn', 'traffic__15__azienda_ospedaliera', 'traffic__15__osped_a_gestione_diretta_presidio_asl']}
In [10]:
# Test con domande varie
domande_test = [
"Quante celle hanno popolazione superiore a 10000 residenti?",
"Dove trovo biblioteche raggiungibili in 15 minuti?",
"Qual è la media della copertura agricola?",
"Celle con alta densità di ristoranti",
"Zone con buona accessibilità agli ospedali"
]
for domanda in domande_test:
print(f"\n🤔 DOMANDA: {domanda}")
try:
risposta = enhanced_agent.answer_with_context(domanda)
print(f"🤖 RISPOSTA: {risposta}")
except Exception as e:
print(f"❌ ERRORE: {e}")
print("-" * 70)
🤔 DOMANDA: Quante celle hanno popolazione superiore a 10000 residenti?
🤖 RISPOSTA: Analizzando i dati demografici del 2021, ci sono 30 celle che superano i 10.000 residenti. Questo significa che, delle varie aree in cui è suddiviso il territorio, 30 hanno una popolazione particolarmente significativa, con più di diecimila abitanti ciascuna. ---------------------------------------------------------------------- 🤔 DOMANDA: Dove trovo biblioteche raggiungibili in 15 minuti?
🤖 RISPOSTA: In base all'analisi effettuata, ci sono 19 aree della città dove puoi trovare almeno una biblioteca raggiungibile comodamente sia a piedi che in auto entro 15 minuti. Questo significa che in queste zone hai la flessibilità di scegliere come raggiungere la biblioteca in base alle tue esigenze, senza dover impiegare troppo tempo per arrivarci. Se desideri dettagli specifici su quali biblioteche si trovano in queste aree o la loro esatta ubicazione, posso fornirti ulteriori informazioni. ---------------------------------------------------------------------- 🤔 DOMANDA: Qual è la media della copertura agricola?
🤖 RISPOSTA: La copertura agricola media per cella esagonale è di circa 15,8 milioni di metri quadrati (o 1.584 ettari). Questo valore rappresenta la superficie media dedicata all'agricoltura in ciascuna unità territoriale del dataset analizzato. ---------------------------------------------------------------------- 🤔 DOMANDA: Celle con alta densità di ristoranti
🤖 RISPOSTA: In base all'analisi effettuata, sono state identificate 704 zone della città che presentano una concentrazione particolarmente elevata di ristoranti. Queste aree si caratterizzano per avere più di 10 ristoranti raggiungibili a piedi (entro 15 minuti di cammino) e più di 20 ristoranti raggiungibili in auto (entro 15 minuti di guida). Questo suggerisce che si tratta di zone particolarmente vivaci dal punto di vista della ristorazione, con un'ampia scelta di locali sia per chi si muove a piedi che per chi usa l'auto. ---------------------------------------------------------------------- 🤔 DOMANDA: Zone con buona accessibilità agli ospedali
🤖 RISPOSTA: In base all'analisi effettuata, sono state identificate 14 zone che offrono una buona accessibilità agli ospedali. Queste aree sono considerate ben servite poiché hanno almeno un ospedale raggiungibile entro 15 minuti di auto e almeno due ospedali raggiungibili entro 30 minuti, tenendo conto del traffico. Questo garantisce sia un accesso rapido per le emergenze che una buona scelta di strutture ospedaliere nel raggio di mezz'ora. ----------------------------------------------------------------------
In [11]:
# Debug per vedere come Claude interpreta le domande
def debug_query(domanda):
print(f"🔍 DEBUG per: '{domanda}'")
# Vedi interpretazione Claude
query_spec = enhanced_agent.claude.query_interpreter(
domanda,
enhanced_agent.analyzer.columns_by_category
)
print(f"📋 Interpretazione Claude:")
for k, v in query_spec.items():
print(f" {k}: {v}")
# Esegui query
if 'error' not in query_spec:
result = enhanced_agent.analyzer.execute_query(query_spec)
print(f"📊 Risultato query: {result}")
return query_spec
# Test debug
debug_query("quante celle hanno più di 10000 abitanti?")
🔍 DEBUG per: 'quante celle hanno più di 10000 abitanti?'
📋 Interpretazione Claude:
operation: aggregate
columns: ['istat||pop_tot__21']
conditions: [{'column': 'istat||pop_tot__21', 'operator': '>', 'value': 10000}]
aggregation: count
spatial_operation: None
explanation: Conta il numero di celle che hanno una popolazione totale nel 2021 superiore a 10000 abitanti
📊 Risultato query: 30
Out[11]:
{'operation': 'aggregate',
'columns': ['istat||pop_tot__21'],
'conditions': [{'column': 'istat||pop_tot__21',
'operator': '>',
'value': 10000}],
'aggregation': 'count',
'spatial_operation': None,
'explanation': 'Conta il numero di celle che hanno una popolazione totale nel 2021 superiore a 10000 abitanti'}
In [12]:
debug_query('quante celle hanno una popolazione femminile maggiore di 4000?')
🔍 DEBUG per: 'quante celle hanno una popolazione femminile maggiore di 4000?'
📋 Interpretazione Claude:
operation: aggregate
columns: ['istat||pop_f__21']
conditions: [{'column': 'istat||pop_f__21', 'operator': '>', 'value': 4000}]
aggregation: count
spatial_operation: None
explanation: Conta il numero di celle che hanno una popolazione femminile nel 2021 maggiore di 4000 abitanti
📊 Risultato query: 40
Out[12]:
{'operation': 'aggregate',
'columns': ['istat||pop_f__21'],
'conditions': [{'column': 'istat||pop_f__21',
'operator': '>',
'value': 4000}],
'aggregation': 'count',
'spatial_operation': None,
'explanation': 'Conta il numero di celle che hanno una popolazione femminile nel 2021 maggiore di 4000 abitanti'}
In [13]:
debug_query('Celle con alta densità di ristoranti')
🔍 DEBUG per: 'Celle con alta densità di ristoranti'
📋 Interpretazione Claude:
operation: filter
columns: ['ovm_places_ristorazione||traffic__15__ristorante', 'ovm_places_ristorazione||walking__15__ristorante']
conditions: [{'column': 'ovm_places_ristorazione||traffic__15__ristorante', 'operator': '>', 'value': 10}]
aggregation: None
spatial_operation: None
explanation: Filtra le celle che hanno più di 10 ristoranti raggiungibili in 15 minuti, considerando sia l'accessibilità in auto che a piedi. Questo identifica le aree con alta densità di servizi di ristorazione.
📊 Risultato query: 215
Out[13]:
{'operation': 'filter',
'columns': ['ovm_places_ristorazione||traffic__15__ristorante',
'ovm_places_ristorazione||walking__15__ristorante'],
'conditions': [{'column': 'ovm_places_ristorazione||traffic__15__ristorante',
'operator': '>',
'value': 10}],
'aggregation': None,
'spatial_operation': None,
'explanation': "Filtra le celle che hanno più di 10 ristoranti raggiungibili in 15 minuti, considerando sia l'accessibilità in auto che a piedi. Questo identifica le aree con alta densità di servizi di ristorazione."}
In [14]:
# Sessione interattiva
def chat_with_agent(agent):
print("🚀 Chat con Enhanced GeoAgent - scrivi 'quit' per uscire")
while True:
domanda = input("\n📍 La tua domanda: ")
if domanda.lower() in ['quit', 'exit', 'stop']:
print("👋 Arrivederci!")
break
try:
risposta = agent.answer_with_context(domanda)
print(f"\n🤖 {risposta}")
except Exception as e:
print(f"\n❌ Errore: {e}")
# Per avviare la chat usa: chat_with_agent(enhanced_agent)
In [15]:
#chat_with_agent(enhanced_agent)
In [16]:
class EnhancedGeoAgentWithMap(EnhancedGeoAgent):
def __init__(self, claude_api_key: str, geoparquet_path: str, metadata_path: str):
super().__init__(claude_api_key, geoparquet_path, metadata_path)
self.visualizer = MapVisualizer(self.analyzer.gdf, self.analyzer.metadata)
def answer_with_map(self, user_question: str):
"""Risponde alla domanda e crea mappa del risultato"""
# Interpreta query con Claude
query_spec = self.claude.query_interpreter(
user_question,
self.analyzer.columns_by_category
)
if 'error' in query_spec:
return self._suggest_alternatives(user_question), None
# Esegui query e ottieni risultati filtrati
filtered_gdf = self._execute_query_with_geodata(query_spec)
result = len(filtered_gdf) if hasattr(filtered_gdf, '__len__') else filtered_gdf
# Genera risposta testuale
response_text = self._generate_response(user_question, query_spec, result)
# Crea mappa
map_viz = self.visualizer.visualize_query_result(
query_spec,
filtered_gdf if hasattr(filtered_gdf, 'geometry') else None,
title=f"Mappa: {user_question[:50]}..."
)
return response_text, map_viz
def _execute_query_with_geodata(self, query_spec):
"""Esegue query e restituisce GeoDataFrame filtrato"""
try:
gdf = self.analyzer.gdf.copy()
# Applica filtri
if 'conditions' in query_spec and query_spec['conditions']:
for condition in query_spec['conditions']:
col = condition['column']
op = condition['operator']
val = condition['value']
if col not in gdf.columns:
continue
if op == '>':
gdf = gdf[gdf[col] > val]
elif op == '<':
gdf = gdf[gdf[col] < val]
elif op == '=':
gdf = gdf[gdf[col] == val]
elif op == '!=':
gdf = gdf[gdf[col] != val]
return gdf
except Exception as e:
print(f"Errore query geodata: {e}")
return self.analyzer.gdf.head(0) # GeoDataFrame vuoto
In [17]:
# Setup agent con visualizzazione mappe
map_agent = EnhancedGeoAgentWithMap(
claude_api_key=CLAUDE_API_KEY,
geoparquet_path=geoparquet_path,
metadata_path=metadata_path
)
# Esempio singola query con mappa
def query_with_map(question):
print(f"🤔 Domanda: {question}")
response, map_viz = map_agent.answer_with_map(question)
print(f"🤖 Risposta: {response}")
if map_viz:
print("🗺️ Mappa generata! Salvataggio in corso...")
map_viz.save('risultato_query.html')
print("✅ Mappa salvata come 'risultato_query.html'")
return map_viz
else:
print("❌ Nessuna mappa generata")
return None
# Test
mappa = query_with_map("Celle con popolazione superiore a 10000 abitanti")
mappa
🤔 Domanda: Celle con popolazione superiore a 10000 abitanti
🤖 Risposta: In base ai dati analizzati, ci sono 30 celle del territorio che nel 2021 avevano una popolazione superiore a 10.000 abitanti. Queste rappresentano le aree più densamente popolate della zona in esame. 🗺️ Mappa generata! Salvataggio in corso... ✅ Mappa salvata come 'risultato_query.html'
Out[17]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [18]:
def show_map_in_notebook(question):
"""Mostra mappa direttamente nel notebook"""
print(f"🤔 {question}")
response, mappa = map_agent.answer_with_map(question)
print(f"🤖 {response}")
if mappa:
# In Jupyter la mappa appare automaticamente
print("🗺️ Mappa:")
return mappa # Jupyter mostra automaticamente oggetti Folium
else:
print("❌ Nessuna mappa generata")
return None
# Uso diretto nel notebook
mappa = show_map_in_notebook("Celle con popolazione superiore a 10000")
# La mappa appare sotto questa cella!
🤔 Celle con popolazione superiore a 10000
🤖 In base ai dati del 2021, ci sono 30 celle geografiche nel territorio analizzato che superano i 10.000 abitanti. Queste rappresentano le aree più densamente popolate della zona in esame. 🗺️ Mappa:
In [19]:
mappa
Out[19]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [20]:
# Modo più semplice per vedere mappe nel notebook
# 1. Query diretta - la mappa appare automaticamente
risposta, mappa = map_agent.answer_with_map("Celle con popolazione superiore a 150")
print(risposta)
mappa # Jupyter mostra automaticamente la mappa
# 2. Con display esplicito
from IPython.display import display
def query_and_display(question):
resp, map_obj = map_agent.answer_with_map(question)
print(f"📍 {question}")
print(f"💬 {resp}")
if map_obj:
display(map_obj) # Forza visualizzazione
return map_obj
# Esempi
query_and_display("Zone con buona accessibilità")
query_and_display("Aree agricole estese")
In base ai dati analizzati, nel 2021 c'erano 413 celle del territorio che avevano una popolazione superiore a 150 abitanti. Questo dato ci mostra le aree più densamente popolate della zona in esame.
📍 Zone con buona accessibilità 💬 In base all'analisi effettuata, ho identificato 23 zone che offrono un'ottima accessibilità ai servizi essenziali. In queste aree, è possibile raggiungere comodamente in auto (entro 15 minuti) strutture come scuole, ospedali, centri culturali, impianti sportivi e collegamenti di trasporto pubblico. Questo le rende particolarmente attraenti per chi cerca una location ben servita e ben collegata per vivere o lavorare.
Make this Notebook Trusted to load map: File -> Trust Notebook
📍 Aree agricole estese 💬 In base all'analisi effettuata, non sono state trovate aree che soddisfano contemporaneamente i criteri richiesti, ovvero: - una percentuale di copertura agricola superiore al 50% della superficie totale - la presenza di aree agricole con valore positivo Questo potrebbe significare che nelle zone analizzate l'agricoltura non è l'attività predominante del territorio, oppure che le aree agricole sono più frammentate e distribuite in percentuali minori. Se sei interessato a trovare zone agricole, potremmo provare a modificare i criteri di ricerca, ad esempio abbassando la soglia percentuale o considerando altri parametri.
Make this Notebook Trusted to load map: File -> Trust Notebook
Out[20]:
Make this Notebook Trusted to load map: File -> Trust Notebook
In [ ]:
In [ ]: