morans_i
Calculate spatial autocorrelation using Moran's I statistic to identify clustering patterns in geographic data from shapefiles.
Instructions
Compute Moran's I Global Autocorrelation Statistic.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| shapefile_path | Yes | ||
| dependent_var | No | LAND_USE | |
| target_crs | No | EPSG:4326 | |
| distance_threshold | No |
Implementation Reference
- src/gis_mcp/pysal_functions.py:137-160 (handler)The primary handler for the 'morans_i' tool. Loads a shapefile using geopandas, creates row-standardized distance-based spatial weights with libpysal, computes the global Moran's I statistic using esda.Moran (including simulated p-value and z-score), and returns structured JSON results with a data preview.@gis_mcp.tool() def morans_i(shapefile_path: str, dependent_var: str = "LAND_USE", target_crs: str = "EPSG:4326", distance_threshold: float = 100000) -> Dict[str, Any]: """Compute Moran's I Global Autocorrelation Statistic.""" gdf, y, w, (threshold, unit), err = pysal_load_data(shapefile_path, dependent_var, target_crs, distance_threshold) if err: return {"status": "error", "message": err} import esda stat = esda.Moran(y, w) preview = gdf[['geometry', dependent_var]].head(5).assign( geometry=lambda df: df.geometry.apply(lambda g: g.wkt) ).to_dict(orient="records") return { "status": "success", "message": f"Moran's I completed successfully (threshold: {threshold} {unit})", "result": { "I": float(stat.I), "morans_i": float(stat.I), # Also include as morans_i for test compatibility "p_value": float(stat.p_sim), "z_score": float(stat.z_sim), "data_preview": preview } }
- Helper function called by morans_i (and other ESDA tools) to validate and load the shapefile as GeoDataFrame, reproject to target CRS, adjust distance threshold for geographic CRS, extract numeric dependent variable array, construct and row-standardize DistanceBand weights, and handle isolated observations (islands).def pysal_load_data(shapefile_path: str, dependent_var: str, target_crs: str, distance_threshold: float): """Common loader and weight creation for esda statistics.""" if not os.path.exists(shapefile_path): return None, None, None, None, f"Shapefile not found: {shapefile_path}" gdf = gpd.read_file(shapefile_path) if dependent_var not in gdf.columns: return None, None, None, None, f"Dependent variable '{dependent_var}' not found in shapefile columns" gdf = gdf.to_crs(target_crs) effective_threshold = distance_threshold unit = "meters" if target_crs.upper() == "EPSG:4326": effective_threshold = distance_threshold / 111000 unit = "degrees" y = gdf[dependent_var].values.astype(np.float64) import libpysal w = libpysal.weights.DistanceBand.from_dataframe(gdf, threshold=effective_threshold, binary=False) w.transform = 'r' for island in w.islands: w.weights[island] = [0] * len(w.weights[island]) w.cardinalities[island] = 0 return gdf, y, w, (effective_threshold, unit), None
- src/gis_mcp/main.py:66-72 (registration)Import block in main.py that loads pysal_functions.py, triggering registration of all @gis_mcp.tool()-decorated functions including morans_i via FastMCP decorators.from . import ( geopandas_functions, shapely_functions, rasterio_functions, pyproj_functions, pysal_functions, )
- src/gis_mcp/pysal_functions.py:13-28 (schema)MCP resource that lists available ESDA operations including 'morans_i', serving as a discovery/schema endpoint for clients to know the tool exists and its name.@gis_mcp.resource("gis://operations/esda") def get_spatial_operations() -> Dict[str, List[str]]: """List available spatial analysis operations. This is for esda library. They are using pysal library.""" return { "operations": [ "getis_ord_g", "morans_i", "gearys_c", "gamma_statistic", "moran_local", "getis_ord_g_local", "join_counts", "join_counts_local", "adbscan" ] }