Tuesday, September 8, 2026

ArcGIS Pro, Claude Code, and a Loopback Bridge

Continuing the GenAI-with-a-GeoSpatial-twist thread I started a while back. Back then, I had a language model reason about geospatial logic. This time, I wanted it to actually do the work—on my open project, inside my running ArcGIS Pro session, while I watch.

The result is ProCowork, an experimental native ArcGIS Pro add-in that embeds the Claude Code engine in a dockable chat panel. I can ask it to list layers, add and calculate fields, select features, run a buffer, or write a more specialized ArcPy script. Claude generates the code, the add-in runs it against the live project, and the code and results come back into the same panel.

For example, I can type:

  • “List the layers in the current map.”
  • “Add a DOUBLE field POP_DEN to Parcels and set it to POP / AREASQMI.”
  • “Select parcels where POP_DEN > 5000 and zoom to them.”
  • “Buffer Roads by 100 meters and add the result to the map.”

The panel is useful, but the interesting part is not the chat UI. It is getting an external AI coding agent safely and reliably close enough to a live ArcGIS Pro project to be useful.

The constraint that shaped the design

arcpy.mp.ArcGISProject("CURRENT") only resolves inside ArcGIS Pro's own Python execution context on the foreground geoprocessing thread. Claude Code, however, runs as a headless child process. If it starts Python outside Pro, that process can work with datasets on disk, but it cannot see the open project as CURRENT.

My first design kept a long-running Python daemon inside Pro. It worked, and then it went stale in the ways long-running bridges tend to go stale. So I flipped the ownership around. A persistent C# bridge now lives inside ArcGIS Pro for the whole session. For each request it either uses the ArcGIS Pro .NET SDK directly or launches one fresh foreground ArcPy geoprocessing tool. The tool resolves CURRENT, performs the operation, returns JSON, and goes away. There is no Python daemon left behind to outlive its host.

ProCowork panel (WPF, inside ArcGIS Pro)
    -> Claude Code (headless child process)
        -> MCP over HTTP on 127.0.0.1:<ephemeral-port>
            -> C# BridgeService (inside ArcGIS Pro)
                -> .NET SDK on the CIM thread
                -> or one fresh RunScript.pyt call
                    -> live project, map, and data

MCP is the small door into the live map

The bridge exposes 14 tools through the Model Context Protocol (MCP). There are focused tools such as list_layers, get_field_list, feature_count, select_by_attribute, add_field, and run_geoprocessing. The centerpiece is run_python_current(code), which gives Claude a general ArcPy path when the curated tools are not enough.

Inspection calls and lightweight map interactions use the .NET SDK on Pro's CIM thread, without ArcPy. Data updates and arbitrary Python go through the fresh geoprocessing call. Those calls are serialized behind a semaphore because geoprocessing does not appreciate re-entrancy. Request and response data travel as geoprocessing string parameters, so there is no file-polling spool between C# and Python.

Everything stays local except the normal Claude model traffic. The MCP server binds only to loopback, chooses an ephemeral port at startup, and requires a new bearer token for the session. The add-in writes those details into a generated .mcp.json and starts Claude Code in streaming JSON mode. It can use the user's existing Claude Code login, an OAuth token, or an Anthropic API key; stored secrets are protected with Windows DPAPI.

There is also a very practical UI detail. Claude answers in Markdown, while ArcGIS Pro hosts a native WPF interface. ProCowork converts the Markdown with Markdig and renders headings, code, lists, links, and real tables using themed WPF controls. Tool calls can be shown or hidden, but I like keeping them visible: if an agent is changing my map, I want to see the code it ran.

A word of caution

By default, ProCowork runs in what I call YOLO mode. Generated code executes on the open project without an approval prompt. That immediacy is the point, and it is also exactly as dangerous as it sounds. ArcPy can edit or delete real data. The bundled instructions tell Claude to inspect fields first, use edit sessions, create backups before destructive operations, and report affected row counts. Those are useful working rules; they are not a security boundary.

So this is an MVP and an experiment, not an Esri product or a supported Esri solution. It currently requires Windows, ArcGIS Pro 3.7, the ArcGIS Pro SDK for .NET, and Claude Code. Keep backups and do not point it at the only copy of important data. The architecture can support an approval step, but the proper approval card is still future work.

What excites me here is not only that an LLM can write ArcPy. We already knew that. It is that a small, local bridge can give an agent useful context from a live desktop GIS, let it act through the correct execution path, and return the evidence to the same place where the request started.

The source code, build instructions, architecture notes, and current limitations are available in the ProCowork repository. The next step is the one I would want before using it on serious work: a clear review-and-approve experience for generated operations.

More to come :-)

One Lander, Three Controllers: Reinforcement Learning, MPC, and Quadratic Programming

I was always interested in the Lunar Lander game and suddenly I heard about model predictive control and quadratic programming. I want to see if the Lunar Lander RL can be adapted to that one in here.

That question led me to build three related experiments around Gymnasium's LunarLander-v3:

  • Lunar RL: a causal-transformer PPO policy trained through reinforcement learning.
  • Lunar MPC: adaptive model predictive control using discrete beam search.
  • Lunar QP: model predictive control whose plan is solved as a convex quadratic program.

All three control the same lander, but they answer very different questions. RL asks, “Can experience produce a policy that maps observations directly to actions?” MPC asks, “Given an approximate physics model, which short action sequence looks best from the current state?” QP asks, “Can that short-horizon plan be expressed as a fast convex optimization problem with explicit constraints?”

The progression is not a conversion of one trained checkpoint into another controller. It is an adaptation of the problem: the observation interface, action space, randomized starts, disturbance scenarios, evaluation rules, and replay tools carry forward, while the decision-making mechanism changes completely.

One lander, three ways to decide

LunarLander exposes an eight-dimensional state: horizontal and vertical position, horizontal and vertical velocity, angle, angular velocity, and two leg-contact flags. At every simulator step, a controller chooses one of four legal actions: coast, fire the left orientation engine, fire the main engine, or fire the right orientation engine.

Question Lunar RL Lunar MPC Lunar QP
Decision One policy inference Beam search over discrete action sequences Convex optimization of fractional engine duties
Knowledge Learned from simulated experience Explicit approximate dynamics Local affine dynamics and linear constraints
Adaptation Requires learning or retraining Replans and estimates engine response in flight Re-solves and estimates engine response in flight
Action handling Outputs one legal discrete action Searches legal discrete actions Allocates fractional requests into discrete pulses
Primary cost Expensive training, cheap inference Online combinatorial search Online numerical optimization

1. Lunar RL: learn the behavior before flight

Lunar RL uses Proximal Policy Optimization (PPO). During training, many simulated landers collect trajectories. PPO adjusts an actor that selects actions and a critic that estimates future return. After training, control is simple: encode the latest context, evaluate the policy once, and execute the selected discrete action.

Transformer PPO policy landing LunarLander from an off-pad tilted start

The recorded Lunar RL policy landing from an off-pad, tilted start.

The network can process either the normal eight-value state or rendered pixels. Each timestep becomes a token containing the current state, previous action, previous reward, and optionally an Impala-CNN image embedding. Three GTrXL blocks process a causal window of tokens. The actor emits four logits; the critic represents value with 41 two-hot bins in symlog space.

There is an honest architectural lesson in the repository: the standard eight-value observation is already Markov. A small multilayer perceptron can solve it, so a transformer is unnecessary in vector mode. Memory becomes useful in pixel mode because a single image shows pose but does not directly reveal velocity or angular rate. A sequence of frames lets attention recover part of that hidden state.

The advantage of RL is that the policy can absorb behavior that is difficult to write as equations. The cost is paid during training, and the result is tied to the distribution it experienced. A center-trained checkpoint landed only six of eight randomized off-pad approaches. Training with the same position and tilt randomization produced a robust checkpoint that landed all eight in that experiment.

The repository reports a historical mean return of +282.6 for its centered checkpoint and documents stronger robust-policy evaluations. It also states an important current limitation: the shipped metrics predate a correction that made the PPO update context exactly match the context used while acting. The first retrained candidate after that correction failed the strict promotion checks, so the historical checkpoint remains in place while a corrected baseline is validated. That kind of disclosure matters more than a single high score.

2. Lunar MPC: predict, act once, and predict again

Lunar MPC moves the computation from training time to control time. It starts with an approximate six-state physics model for position, velocity, angle, and angular velocity. At each observation, it searches possible future action sequences, chooses the lowest-cost plan, applies only its first action, observes what actually happened, and repeats.

observe current state
update the engine model
predict candidate action sequences
keep the best beam
execute the first action
repeat from the next observation

This repeated loop is the defining idea of model predictive control. A prediction does not have to remain accurate for the entire landing. It needs to be useful long enough to choose the next action, because the controller replans after 0.02 simulated seconds. The default horizon contains 16 blocks of four physics steps, or 1.28 seconds of foresight, with a beam width of 32.

Adaptive beam-search MPC landing after a main-engine power loss

Adaptive MPC replans after an unannounced 30% main-engine power loss.

Exhaustively expanding four actions for 16 blocks would require examining 4**16 sequences. Beam search avoids that explosion. At each depth, it expands the current candidates with all four actions and keeps only the 32 plans with the lowest accumulated cost. The cost penalizes horizontal error, velocity error, vertical descent error, tilt, angular motion, unsafe approach geometry, and excessive main-engine use.

The model also learns during the flight. Bounded recursive least squares compares the predicted velocity change with the observed change and updates estimates of main-engine acceleration, side-engine acceleration, translational bias, angular response, and angular bias. Contact impulses and extreme accelerations are excluded because they are poor evidence of airborne engine performance.

This creates two forms of adaptation. Replanning reacts to state error even when the model remains fixed. Parameter estimation tries to correct the model itself. The distinction is visible in the fault experiment: immediately before action 100, the evaluator reduces main-engine power by 30%. The controller is not told the fault time or the new multiplier; it sees only the resulting motion.

On historical evaluation seeds 200–249, adaptive MPC passed 49 of 50 strict landing checks both normally and under the fault. The fixed-model controller also passed 49 of 50 normally but fell to 42 of 50 under the fault. This is evidence that online identification helped in that specific experiment, while still leaving a known horizontal-recovery failure and no safety certificate.

3. Lunar QP: make the plan a convex optimization

Lunar QP is still MPC. The change is the planner inside the repeated loop. Instead of beam-searching discrete sequences, it linearizes the approximate dynamics around the current state and asks OSQP to choose fractional engine duties over the horizon.

minimize    1/2 z' P z + q' z
subject to  lower <= A z <= upper

The predicted dynamics have the local affine form x[k+1] = Adx[k] + Bdu[k] + c. The implementation condenses the state sequence into X = base + response*U, leaving the engine requests and slack variables as optimization decisions. With 16 prediction blocks, the QP has 96 variables: 48 engine duties and 48 nonnegative slacks.

Its quadratic objective penalizes deviation from desired position, velocity, descent rate, tilt, and angular velocity. Linear constraints bound every duty between zero and one and require the three engine fractions to sum to at most one. Soft approach limits encourage clearance, bounded tilt, and remaining inside the viewport. Explicit slack variables keep a difficult state mathematically feasible while exposing how much the preferred envelope was relaxed.

Quadratic-programming MPC landing after a main-engine power loss

QP-based MPC uses fractional plans and executes discrete engine pulses.

The most interesting mismatch is at the actuator. The Gymnasium environment accepts one discrete action, but the convex QP may request something like 60% main engine, 10% left engine, and 30% coast. A pulse-density allocator accumulates these fractions over time, executes whichever action has the largest balance, and subtracts one from that balance. Over repeated decisions, the discrete pulse counts approximate the requested fractions.

This is a convex relaxation followed by discrete allocation, not mixed-integer optimization. The predicted fractional path and the path produced by real pulses can diverge. Replanning corrects part of that error, but a solved QP is only a numerical statement about the approximate optimization problem. It is not proof of a safe landing in the nonlinear Box2D simulator.

Lunar QP uses the same style of bounded online engine estimation as Lunar MPC. On its separate evaluation seeds 300–319, both adaptive and fixed QP passed 20 of 20 normal flights. Under the 30% engine fault, adaptive QP passed 15 of 20 and fixed QP passed 16 of 20. The result does not demonstrate an adaptation advantage for QP. It also shows why successful development seeds are not enough: all four controller/scenario combinations had passed the eight development starts.

Can the RL solution be adapted to MPC and QP?

Yes, if “adapted” means preserving the experimental system and replacing the decision rule. The reusable parts are substantial:

  • The same eight controller-visible observations and four legal actions.
  • The same randomized off-pad and tilted starting-pose wrapper.
  • The same separation between controller information and evaluator-only simulator truth.
  • The same disturbance idea: reduce engine power without telling the controller.
  • Strict landing checks based on termination, settling, leg contact, and foot position.
  • Recorded trajectories and self-contained browser replays for inspecting failures.

What does not transfer is the trained policy itself. PPO weights encode a nonlinear mapping learned from data; an MPC controller needs explicit transition equations and a cost; a QP additionally needs dynamics and constraints that remain affine or quadratic after local approximation. Trying to reinterpret neural-network weights as those matrices would solve a different and much harder identification problem.

A future hybrid could combine them more directly. RL could learn a residual correction to the MPC physics model, tune terminal costs, propose a warm-start plan, or provide a recovery policy when the optimizer fails. MPC could supervise safer exploration or generate demonstrations for an RL policy. Those designs require new matched experiments; none of the three current repositories claims to implement them.

Reading the results without fooling ourselves

The reported numbers are evidence about each repository, not a leaderboard. Lunar RL, Lunar MPC, and Lunar QP use different training histories, seed ranges, costs, horizons, discretization choices, and evaluation sample sizes. The MPC and QP controllers even optimize different cost functions. Comparing their pass counts directly would not isolate whether policy learning, beam search, or QP is better.

A fair head-to-head study would freeze the controller-visible state, starting poses, terrain seeds, engine faults, landing definition, time limit, compute budget, and reporting format. It would measure pass rate, worst-case behavior, return distribution, decision latency, deadline misses, and sensitivity to model error. For QP, it should also report slack and the error introduced by fractional-to-discrete allocation. For RL, it should report variation across training seeds and hardware backends.

Run the three experiments

Each repository is standalone and uses uv.

Reinforcement learning

git clone https://github.com/mraad/lunar-rl.git
cd lunar-rl
uv sync --locked
uv run lunar-rl-view --ckpt lunar_agent_robust.pt --seed 0 --greedy

Adaptive beam-search MPC

git clone https://github.com/mraad/lunar-mpc.git
cd lunar-mpc
uv sync --locked
uv run lunar-mpc --episodes 8 --seed 0 --thrust-scale 0.7 \
  --out dist/fault-adaptive.json --replay dist/fault-adaptive.html

QP-based MPC

git clone https://github.com/mraad/lunar-qp.git
cd lunar-qp
uv sync --locked
uv run lunar-qp --episodes 8 --seed 0 --thrust-scale 0.7 \
  --out dist/qp/fault-adaptive.json --replay dist/qp/fault-adaptive.html

The generated MPC and QP replay pages expose more than the final score. They show the actual trajectory, predicted path, chosen plan, engine estimates, and decision timing. The QP replay adds fractional requests, executed pulses, solver status, residuals, and slack. The RL viewer shows policy probabilities, critic values, and attention over the recent context. Looking inside a failed landing is often more useful than adding another decimal place to an average.

What the three controllers taught me

RL can discover a capable policy without a hand-written flight model, but its behavior depends on training coverage and careful validation. MPC makes assumptions visible and can react immediately when observed motion differs from prediction, but it pays for planning at every step. QP makes objectives and constraints especially inspectable and solves the relaxed plan efficiently, while introducing local-linearization and actuator- allocation errors.

The most useful outcome of adapting LunarLander across these methods is not choosing one universal winner. It is seeing the trade clearly: learned behavior, discrete online search, and constrained convex optimization each fail in different ways. Keeping the environment, disturbances, evidence, and replay tools consistent turns those failures into something we can study rather than hide behind a score.

References

Teaching a Lunar Lander to Stop Hovering: DQN, Prioritized Replay, and n-Step Returns

LunarLander looks simple: observe eight numbers, choose one of four engine actions, and guide a small craft between two flags. In practice, it exposes several failure modes that make Deep Q-Learning interesting. My first promising agents learned to approach the landing pad, then hovered above it until the episode timed out. Other runs climbed past a score of 100 and later collapsed.

I built LunarLanderDRL to work through those problems in a compact PyTorch implementation. The final agent combines a Deep Q-Network (DQN), proportional prioritized experience replay, Double DQN, Huber loss, Polyak target updates, and three-step returns. Across three training seeds, the resulting checkpoints averaged 265.7 on 90 held-out episodes and recorded zero crashes.

A trained DQN agent landing the Gymnasium LunarLander

Two consecutive rollouts of the shipped policy, scoring 308.1 and 272.1.

The problem

Gymnasium's LunarLander-v3 returns an eight-dimensional observation: horizontal and vertical position, horizontal and vertical velocity, angle, angular velocity, and two leg-contact flags. The discrete action space contains four choices: do nothing, fire the left orientation engine, fire the main engine, or fire the right orientation engine.

A DQN approximates the action-value function Q(s, a). Given a state, the network emits one value for each action, and a greedy policy selects the largest one. The model in this project is deliberately small:

8 inputs -> 256 -> 128 -> 64 -> 4 Q-values

The hidden layers use ReLU activations and Xavier initialization. During training, an epsilon-greedy policy sometimes takes a random action, with epsilon decaying from exploration toward exploitation. The network architecture is the easy part. Stable learning depends on what transitions we replay, how we calculate targets, and how we decide that one checkpoint is better than another.

Prioritized experience replay

Uniform replay treats every stored transition as equally informative. Prioritized experience replay (PER) focuses training on transitions where the current prediction is most wrong. This implementation assigns each transition the priority

p(i) = (|TD error(i)| + epsilon) ** alpha

alpha controls how strongly priority affects sampling. A value of zero reduces PER to uniform replay; the default is 0.6. A new transition has no TD error yet, so it enters with the highest priority seen so far. This ensures that it is replayed once before being assigned a data-driven priority.

The replay buffer stores states, actions, rewards, terminal flags, next states, and bootstrap discounts in flat NumPy arrays. Priorities live in a binary sum tree. Each parent stores the sum of its children, which lets the agent update a priority or locate a sampled leaf in O(log N) time. A batch is sampled from equal-mass segments of the total priority, reducing variance compared with drawing every item independently.

Prioritized sampling changes the training distribution, so each sampled loss receives an importance-sampling correction:

w(i) = (N * P(i)) ** -beta

The weights are normalized by the maximum weight in the batch. Beta is annealed from 0.4 to 1.0, making the correction stronger as the policy approaches convergence.

Stopping Q-value overestimation from taking over

A vanilla DQN uses a maximum over estimated values when it constructs the bootstrap target. Estimation noise makes that maximum optimistic. Because the target is itself used to train future estimates, the bias can compound until a policy that appeared to be improving suddenly collapses.

Double DQN separates action selection from action evaluation. The online network picks the next action, while the target network evaluates it:

next_action = online(next_state).argmax()
next_value  = target(next_state)[next_action]
target_q    = reward + discount * next_value

The target network is not copied in occasional hard jumps. It is updated with Polyak averaging on every training step:

target = (1 - tau) * target + tau * online

The default tau is 1e-3. This gives the target a slowly moving reference point while the online network continues to learn.

The project also uses Huber loss instead of squared error. This choice matters when it is paired with PER: the replay sampler intentionally selects transitions with large TD errors, and squaring an outlier can let one sample dominate the gradient. Huber loss is quadratic close to zero and linear for large errors. Gradient norms are clipped at 10 as a final guard against unstable updates.

Why the one-step agent learned to hover

The most revealing failure was not a crash. A one-step agent often descended near the pad, stabilized, and hovered until Gymnasium's 1,000-step limit. That behavior makes sense from the agent's temporary view of the world. LunarLander provides shaping reward for remaining close to the pad at low velocity, while the large touchdown reward is still hundreds of actions away. With one-step bootstrapping, that delayed reward moves backward through the value function slowly.

Three-step returns propagate the landing signal more quickly. Instead of storing only one immediate reward, the buffer stores a folded return:

R(t) = r(t) + gamma*r(t+1) + gamma**2*r(t+2)
target_q = R(t) + gamma**k * Q(next_state, next_action)

Two implementation details are essential. First, the bootstrap multiplier must be gamma**k, where k is the number of rewards actually folded into that transition. The buffer therefore stores a discount beside each transition. Reusing a single gamma would silently produce incorrect targets.

Second, the pending reward window must be flushed at every episode boundary. Otherwise, the last n_step - 1 transitions disappear. Those are often the exact transitions containing touchdown, so dropping them removes the examples the agent most needs. If a real terminal occurs inside the window, the fold stops there. A time-limit truncation ends the rollout but does not zero a state value that could continue beyond the artificial cutoff.

Evaluate the policy you will actually deploy

Early versions saved checkpoints using the moving average of training rewards. That metric is contaminated by epsilon-greedy exploration. A lucky random action can improve a score, and a useful exploratory action can also lower it. The checkpoint should be selected using the deterministic policy that will run after training.

The training loop now evaluates a fully greedy policy every 25 episodes on a fixed set of seeds. Evaluation uses a separate Gymnasium environment, so resetting it never changes the training environment's random-number stream. This produced a striking example: one checkpoint had a training average of 239.8 but scored only 160.6 under greedy evaluation. In another run, the training average was just 78.2 while the greedy policy scored 269.1. Selecting on training reward would have preserved the first and discarded the second.

Measured results

I trained three seeds with the following configuration:

uv run main.py --n_episodes 4000 --eps_ratio 0.35 --eps_final 0.01 \
  --learning_rate 5e-4 --warm_start 5000 --n_step 3 \
  --eval_episodes 20 --solved_score 275

The runs stopped between episodes 474 and 999 and took roughly ten minutes each on an M-series CPU. I evaluated every checkpoint on the same 30 seeds, numbered 1000 through 1029. Those held-out seeds are disjoint from the seeds used to select checkpoints.

Training seed Mean Median Solved Crashed
42 280.6 281.2 30/30 0
43 243.3 278.4 24/30 0
44 (shipped model) 273.1 286.5 28/30 0
Pooled 265.7 282.1 82/90 0/90

The comparison that changed the design was the one-step run. With otherwise matching settings, it averaged 220.3, solved 25 of 30 held-out episodes, and crashed twice. The three-step runs produced no crashes in 90 episodes. Eight episodes finished below 200, but these were soft or slow landings scoring between 21 and 46, rather than destructive impacts.

I ship the seed-44 checkpoint even though seed 42 scored higher. Seed 44 lies closer to the pooled result, so it is a more representative artifact than the luckiest run.

Run it yourself

The project uses uv, pins dependencies in uv.lock, and includes a trained model. Clone the repository and install the environment:

git clone https://github.com/mraad/LunarLanderDRL.git
cd LunarLanderDRL
uv sync

Replay the shipped policy into an MP4:

uv run render.py

# Scores only, without creating video
uv run render.py --no-save

Train a new policy and monitor it with TensorBoard:

uv run main.py --n_episodes 4000 --learning_rate 5e-4 \
  --warm_start 5000 --n_step 3
uv run tensorboard --logdir logs

The automated checks cover schedules, both replay buffers, the sum tree, n-step folding and flushing, and a short end-to-end training run against the real environment:

uv run pytest test_rl.py

What I learned

The useful lesson was not that one algorithmic trick solves LunarLander. The pieces address different sources of failure. PER spends more updates on surprising transitions. Importance weights correct its sampling bias. Double DQN reduces optimistic targets. Huber loss and gradient clipping keep high-error examples from destabilizing an update. Three-step returns move delayed touchdown information backward faster. Greedy, isolated evaluation selects the policy that will actually be used.

The project remains intentionally small enough to read end to end. The next practical improvements would be training every few environment steps instead of every step, scheduling epsilon by step rather than episode, annealing PER beta across the full run, and adding a dueling value/advantage head. The current implementation and trained weights are available under the Apache 2.0 license at github.com/mraad/LunarLanderDRL.

References

Saturday, February 3, 2024

On Using AutoML for NYC Taxi Trip Duration Prediction

While explaining Optuna to a client in the context of hyperparameter tuning, and performing more research on the topic, I came across AutoGluon to perform "AutoML for images, text, and tabular data". After a quick scan of the documentation, I decided to give it a try and see how it performs on a simple project.

I always loved the Kaggle competition NYC Taxi Trip Duration, as the data has spatial, temporal, and other traditional attribute information and is a great dataset to test various models and feature engineering techniques.

I used a local Apache Spark instance (as it is my go-to ETL engine) to perform some feature engineering before letting AutoGluon do its magic. As a quick proof of concept, the results are quite impressive, and here are the steps to reproduce the project and visualize the result.

Sunday, January 28, 2024

Arabic SDK For Apache Spark

I recently attended the Esri Saudi Arabia User Conference and was amazed by the changes in the Kingdom. The capital city of Riyadh is booming and proliferating. During the conference, I presented on integrating GenerativeAI and GIS in the plenary session and led a session on BigData and GeoAnalytics Engine. GeoAnalytics Engine, based on Apache Spark, allows spatial operations on Spark data frames. We showcased a project called "A Day in the Life," which used historical traffic data from HERE to demonstrate traffic congestion during peak hours. Traffic is notoriously bad in the city, so this was a fitting example. My colleague Mahmoud H. presented a traditional workflow process in a Jupyter Notebook off a Google Cloud DataProc cluster, efficiently processing over 300 million records (this is relatively "small"). The processed traffic information was then displayed in ArcGIS Pro in a time-aware layer to reflect the congestion visually while activating a time slider. At the end of the presentation, we surprised the audience by using ChatGPT to translate Arabic sentences to SparkSQL code, and Azure OpenAI GPT4 handled the translation very well. Look here for code snippets. This form of interaction IS the future, and I am excited to invest more in this technology and in the following areas:

  1. Enhanced Visualization and Real-time Data Integration:
    • Dynamic Visualization: Integrating real-time traffic data feeds into existing models. This will not only show historical congestion but also provide live updates. Dynamic heatmaps can be particularly effective in visualizing the intensity of traffic at different times.
    • 3D Modeling: Utilize ArcGIS's 3D scene capabilities to give a more immersive view of traffic congestion and urban planning scenarios.
  1. Improved Data Analysis through Machine Learning:
    • Predictive Analytics: Integrate machine learning models to predict future traffic patterns based on historical data, weather conditions, events, and other variables.
    • Anomaly Detection: Implement anomaly detection algorithms to identify unusual traffic patterns, which can be crucial for incident response and urban planning.
  1. Enhancing User Interaction and Accessibility:
    • Multilingual Support: While we showcased the translation of Arabic sentences to SparkSQL code, we should consider expanding this feature to include more languages, making your tool more accessible to a global audience.
    • Voice Commands and Chatbots: Integrate voice command functionality and develop a chatbot using Azure OpenAI GPT4 for querying and controlling the GeoAnalytics Engine, making the system more interactive and user-friendly.
  1. Scalability and Performance Optimization:
    • Optimization for Large Datasets: Continue to refine the efficiency of processing large datasets. Explore the latest advancements in distributed computing and in-memory processing to handle even larger datasets more efficiently.
    • Cloud Integration: Ensure the solutions are cloud-agnostic and can be deployed on any public or private cloud provider, enhancing the system's scalability and reliability.
  1. Collaboration and Sharing:
    • Collaborative Features: Develop features that allow multiple users to work on the same project simultaneously, including version control and change tracking for shared projects.
    • Export and Sharing Options: Enhance the ability to export results and visualizations in various formats and share them across different platforms, facilitating easier collaboration and reporting.
  1. Ethical Considerations and Transparency:
    • Data Privacy: Address data privacy concerns by implementing robust data encryption and anonymization techniques, ensuring that individual privacy is respected while analyzing traffic patterns.
    • Algorithm Transparency: Provide clear documentation and explanations of the algorithms used, promoting transparency and trust in your system.

Saturday, January 27, 2024

Back in Action: GenAI Meets GeoSpatial

 Hello, everyone. It has been a while since my last post, and I wanted to explain my absence. I have been working on demanding client projects requiring confidentiality, so I couldn't share anything.

But now I'm back and excited to dive into something new and exciting. 

Generative AI (GenAI) has gained much attention lately, but I'm taking it to a different level by merging Large Language Models (LLMs) with insights from geospatial analysis. It's GenAI with a GeoSpatial twist.

I want to introduce a simple project, "ReAct geospatial logic with Ollama," which uses resources such as the Python Langchain and Ollama.

I'm thrilled to be back and can't wait to start this new journey with you. Keep an eye on this space for future updates, tips, and unique code snippets. Your feedback and questions are valuable, so please don't hesitate to reach out.

I'll see you in the next post, and as usual, you can check out the source code here.

Monday, August 24, 2020

On Machine Learning in ArcGIS and Data Preparation using Spark

Artificial Intelligence / Machine Learning implementations have been part of Esri software in ArcGIS for a very long time. Geographic Weighted Regression (GWR) and Hot Spot Analysis are Machine Learning algorithms. ArcGIS users have been utilizing supervised and unsupervised learning like clustering to solve a myriad of problems and gain geospatial insight from their data.  We just did not call these learning algorithms AI/ML back then.  Not until the recent popularity of DeepLearning that finally blossomed from the AI Winter and made AI a household name. And guess what? Esri software has now DeepLearning implementations!

It is important to understand the difference between AI, ML, and DL.  I came across this very insightful article that analogizes the relationship to Russian dolls.

Typically, collected data is very "dirty", and a lot of cleaning has to performed on it before processing it through a Machine Learning algorithm.  The bigger the data, the harder is the process especially when pruning noisy outliers and anomalies.  But then, that could be what you are looking for, outliers and anomalies. That is why "Data Janitor" is a new job title, as a majority of your time when dealing with data is cleaning it!  That is why I love to use Apache Spark for this task. It can handle BigData very efficiently in a distributed, parallel share-nothing environment, and the usage of the DataFrame API and SQL makes the maniplation a breeze. Utilizing the latter two in an interactive environment like a Jupyter Notebook enables quick cleaning and more importantly insightful explorations.

Now the best part, we can have all 3, Jupyter, Spark and ML, in one enviroment; ArcGIS Pro!

This notebook and that notebook demonstrate the usage of Spark in Notebook in Pro to clean and explore the data, and then prepare it for Machine Learning.  We are using the built-in Forest Based Regression to predict (or attempt to predict) the trip duration of a New York City taxi given a pickup and dropoff location.
The last notebook, enables the user to select pickup locations (in the below case, about JFK) and "see" the errors of the model on a map.

Some will argue that all this could have been done using Pandas, and that is true. But that is not the point of this demonstration and this assumes that all the data could be held in memory which is true in this case (1.45) but will fail when you have 100's of million of rows and you have to process all this data on one machine with limited resources.  If you like Pandas, I recommend that you check out Koalas.