How geospatial analytics, simulation, and AI come together to help us understand not just predict our cities
The Problem: AI That Doesn't Understand Space
We're in the middle of an AI agent renaissance. Models can reason, plan, invoke tools, and coordinate complex workflows. Yet when these conversations move from impressive demos to real-world operations, a different challenge emerges.
The question is no longer whether an AI agent can perform a task. The more interesting question is whether it can understand the environment in which a decision must be made.
Cities don't operate as collections of independent systems. Mobility, infrastructure, weather, energy, public transportation, construction projects, and human behavior constantly interact. Understanding these interactions requires more than access to data; it requires context. And in many cases, that context is fundamentally geographic.
This became clear while exploring an Urban Digital Twin scenario for Frankfurt am Main. What started as a straightforward investigation into how geospatial analytics, simulation, and AI could work together revealed something much broader: before an AI agent can support meaningful decisions, it must first understand the spatial relationships that shape how a city functions.
Let me walk you through the approach and the Python code that made it possible, drawn directly from the Spatial Data Science Examples repository.
Step 1: Setting Up Your GIS Environment
First, connect to your GIS portal using ArcGIS API for Python. The notebook uses environment variables to keep credentials secure:
# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
from arcgis.gis import GIS
from arcgis.features import Feature, FeatureSet
from arcgis.geometry import Envelope, Point
from datetime import datetime
import os
import pandas as pd
from dotenv import load_dotenv
# Load environment variables
def load_vars():
load_dotenv(dotenv_path="../../../../.env", override=True)
return (
os.getenv("ARCGIS_API_KEY"),
os.getenv("TRAFFIC_ACCIDENTS_FEATURES"),
os.getenv("TRAFFIC_DATA_FILE"),
os.getenv("TRAFFIC_FEATURES"),
os.getenv("NETWORK_DATASET"),
os.getenv("ELECTRIC_CAR_FEATURES")
)
api_key, traffic_accidents_path, traffic_data_path, traffic_features_path, network_dataset_path, electric_car_path = load_vars()
# Connect to your GIS
gis = GIS(api_key=api_key)
Note: Make sure you have a .env file in your project root with your ARCGIS_API_KEY and other data paths.
Step 2: Visualizing the Area of Interest
Before diving into analysis, it's helpful to visualize your study area. The repository includes helper functions to create interactive maps:
# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
from urban_traffic.utils import create_traffic_map, create_map
# Create a traffic-focused map view
traffic_map_view = create_traffic_map(gis)
traffic_map_view
# Or create a map centered on a specific location
map_view = create_map(gis, location="Frankenallee 355, Frankfurt am Main, Germany")
map_view
Step 3: Loading and Preparing Traffic Data
The real work begins with loading traffic data. Here's how the notebook fetches and prepares traffic features:
# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
from data_engineering.utils import (
fetch_charging_stations,
fetch_hotcold_features,
fetch_hottest_features_by_extent,
fetch_traffic_accidents,
get_hotcold_layer,
get_live_traffic_item
)
from urban_traffic.utils import (
fetch_traffic_data,
filter_commute_cars,
generate_car_renderer,
generate_routes_renderer,
prepare_traffic,
prepare_traffic_accidents,
read_traffic_accidents_features_by_extent,
read_traffic_features
)
# Fetch traffic features
traffic_features = read_traffic_features(gis, traffic_features_path)
# Filter for commute cars (cars used for commuting)
commute_cars = filter_commute_cars(traffic_features.copy())
commute_cars.head()
The commute_cars DataFrame contains columns like trip, person, hour, minute, second, bike, car, and pedestrian allowing you to filter and analyze different types of road users.
Step 4: Visualizing Traffic Patterns
You can visualize the filtered traffic data directly on a map:
# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
# Create a map and plot commute car positions
commute_map_view = create_map(gis)
commute_cars.spatial.plot(
commute_map_view,
renderer=generate_car_renderer()
)
commute_map_view.zoom_to_layer(commute_cars)
commute_map_view
Step 5: Identifying Hot and Cold Spots
To understand spatial patterns, the notebook uses hot/cold spot analysis:
# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
# Fetch hot and cold spots from the commute car data
hotcold_spots_fset, drawing_info = fetch_hotcold_features(gis, commute_cars)
# Add them to the map with transparency
commute_map_view.content.add(
hotcold_spots_fset,
drawing_info=drawing_info,
options={"opacity": 0.7}
)
What we found: Major transportation corridors exhibited elevated densities; exactly as expected. But other locations repeatedly appeared despite not being particularly prominent within the network. Only after examining additional context surrounding land use, infrastructure layouts, and transit interactions did plausible explanations emerge.
Step 6: Advanced Spatial Analysis: Closest Facility Routing
The notebook demonstrates a powerful use case: finding the closest charging station for electric vehicles. This same logic applies to any "closest facility" problem finding the nearest hospital, police car, or fire station:
# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
import arcpy
from arcpy.management import CopyFeatures
from arcpy.na import AddLocations, GetNAClassNames, Solve
arcpy.env.overwriteOutput = True
def create_analysis_layer(charging_stations: pd.DataFrame, car_positions: pd.DataFrame):
"""Create a closest facility analysis layer using a local routing network."""
analysis_layer_name = "ClosestChargingStation"
layer_result = arcpy.na.MakeClosestFacilityAnalysisLayer(
network_dataset_path,
analysis_layer_name
)
# Get the analysis class names ("Facilities", "Incidents", ...)
analysis_layer = layer_result.getOutput(0)
analysis_class_names = GetNAClassNames(analysis_layer)
# Charging stations as facilities
input_facilities = "memory/facilities"
charging_stations.spatial.to_featureclass(input_facilities)
AddLocations(analysis_layer, analysis_class_names["Facilities"], input_facilities)
# Car positions as incidents
input_incidents = "memory/incidents"
car_positions.spatial.to_featureclass(input_incidents)
AddLocations(analysis_layer, analysis_class_names["Incidents"], input_incidents)
return analysis_layer
def solve_routing_problem(analysis_layer, nogo_areas: pd.DataFrame = None) -> pd.DataFrame:
"""Solve the routing problem and return the results as a DataFrame."""
analysis_class_names = GetNAClassNames(analysis_layer)
if nogo_areas is not None:
# Add no-go areas as polygon barriers
input_barriers = "memory/barriers"
nogo_areas.spatial.to_featureclass(input_barriers)
AddLocations(analysis_layer, analysis_class_names["PolygonBarriers"], input_barriers)
# Solve the routing problem
solve_result = Solve(analysis_layer)
# Extract the solved routes
output_routes = "memory/routes"
CopyFeatures(analysis_class_names["CFRoutes"], output_routes)
return pd.DataFrame.spatial.from_featureclass(output_routes)
This approach can be adapted to:
- Find the closest hospital to an accident
- Dispatch the two closest police cars to a crime scene
- Find the three closest fire stations within a five-minute drive time
Step 7: The Critical Insight Multi-Indicator Overlay
When you overlay simulated congestion patterns with historical accident hotspots, a crucial pattern emerges: the areas of greatest operational interest are not necessarily the locations with the highest traffic volumes or the highest accident densities.
More significant are locations where multiple indicators begin reinforcing one another. A corridor with increasing traffic pressure may not represent a major concern on its own. An area with elevated accident rates may not require immediate intervention. When both conditions coexist, however, the situation changes. The combination creates a level of operational risk that neither dataset reveals independently.
Here's how you might combine multiple spatial datasets:
# author: Jan Tschada
# SPDX-License-Identifer: Apache-2.0
# Assuming you have accident hotspots and congestion layers
# (These would come from your analysis or simulation)
# Spatial join to find overlap between hotspots and congestion
overlap = gpd.overlay(hotspots, congestion, how="intersection")
# Calculate a composite risk score
overlap['risk_score'] = (
overlap['accident_density'] * 0.6 +
overlap['congestion_level'] * 0.4
)
# Identify high-risk areas
high_risk = overlap[overlap['risk_score'] > overlap['risk_score'].quantile(0.90)]
The "Why" Problem: Explainability in GeoAI
Throughout workshops and discussions, one question appeared repeatedly:
"Why?"
- Why is this location being highlighted?
- Why was this corridor identified as critical?
- Why does this recommendation deserve attention?
The quality of the recommendation was important, but the explanation behind it was often more valuable. Trust depends less on automation than on understanding. Decision-makers need to see the evidence, understand the assumptions, and follow the reasoning process that led to a particular conclusion.
For this reason, the future of GeoAI may have less to do with autonomy than with comprehension.
The most successful systems will not necessarily be those that make decisions on behalf of people. More likely, they will be the systems that help people understand complex situations more quickly, evaluate possible consequences more effectively, and make better informed decisions under uncertainty.
The Takeaway
In a city such as Frankfurt, where every operational challenge ultimately unfolds somewhere, affects someone, and influences something nearby, location becomes far more than another attribute in a dataset.
It becomes the foundation for understanding consequence.
The code snippets above come directly from the Spatial Data Science Examples repository a working implementation of the Urban Digital Twin concept. They illustrate a workflow that moves from raw data to spatial understanding to decision intelligence. And that's exactly the kind of spatial reasoning our AI agents will need if they're going to be truly useful in the real world.
Resources
- Spatial Data Science Examples on GitHub
- ArcGIS API for Python Documentation
- From Traffic Accidents to AI Agents on LinkedIn
What's your experience with building spatial reasoning into AI systems? Drop a comment below I'd love to hear how you're approaching this challenge.

Top comments (0)