Skip to content

Implement Mapbox route rendering via GeoJsonLayer#562

Open
ngoiyaeric wants to merge 1 commit intomainfrom
fix/draw-route-implementation-18234505032010150291
Open

Implement Mapbox route rendering via GeoJsonLayer#562
ngoiyaeric wants to merge 1 commit intomainfrom
fix/draw-route-implementation-18234505032010150291

Conversation

@ngoiyaeric
Copy link
Collaborator

@ngoiyaeric ngoiyaeric commented Mar 9, 2026

This PR implements the missing drawRoute TODO by extracting route geometry from the Mapbox Directions API, passing it through the MapContext, extending GeoJsonLayer to support LineString elements, and declaratively mounting it on the Mapbox instance.


PR created automatically by Jules for task 18234505032010150291 started by @ngoiyaeric

Summary by CodeRabbit

  • New Features

    • Added route visualization on maps using GeoJSON linestring layers for geospatial results.
    • Drawn map features now capture and store measurement and geometry information.
  • Refactor

    • Enhanced data structures to support comprehensive route and feature metadata management.
    • Modernized route rendering mechanism in map components for improved maintainability.

Co-authored-by: ngoiyaeric <115367894+ngoiyaeric@users.noreply.github.com>
@google-labs-jules
Copy link
Contributor

👋 Jules, reporting for duty! I'm here to lend a hand with this pull request.

When you start a review, I'll add a 👀 emoji to each comment to let you know I've read it. I'll focus on feedback directed at me and will do my best to stay out of conversations between you and other bots or reviewers to keep the noise down.

I'll push a commit with your requested changes shortly after. Please note there might be a delay between these steps, but rest assured I'm on the job!

For more direct control, you can switch me to Reactive Mode. When this mode is on, I will only act on comments where you specifically mention me with @jules. You can find this option in the Pull Request section of your global Jules UI settings. You can always switch back!

New to Jules? Learn more at jules.google/docs.


For security, I will only act on instructions from the user who triggered this task.

@vercel
Copy link
Contributor

vercel bot commented Mar 9, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
qcx Ready Ready Preview, Comment Mar 9, 2026 6:42am

@CLAassistant
Copy link

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 9, 2026

Walkthrough

This pull request adds support for rendering routes as GeoJSON linestrings on maps. It introduces a new linestring layer component, extends type definitions to include routeGeoJSON fields, extracts route geometry from geospatial responses, and renders routes declaratively through a GeoJsonLayer component in the map interface.

Changes

Cohort / File(s) Summary
Map Rendering Infrastructure
components/map/geojson-layer.tsx, components/map/mapbox-map.tsx
Added GeoJSON linestring layer creation with blue styling and line configuration. Integrated declarative GeoJsonLayer rendering in Mapbox when routeGeoJSON data is available, replacing legacy commented-out route handling.
Data Layer & Type Definitions
components/map/map-data-context.tsx
Updated mapFeature type to include structured fields (place_name, mapUrl, routeGeoJSON) and expanded drawnFeatures to include measurement and geometry properties alongside existing id and type.
Data Pipeline & Response Handling
components/map/map-query-handler.tsx, lib/agents/tools/geospatial.tsx
Extended McpResponse interface to include routeGeoJSON field. Added logic to extract route geometry from mapping service responses and propagate routeGeoJSON through the data pipeline from geospatial tool responses to map feature assignments.

Sequence Diagram

sequenceDiagram
    participant Client as Client Request
    participant Geospatial as Geospatial Tool
    participant Handler as Query Handler
    participant Context as Map Data Context
    participant Map as Mapbox Map

    Client->>Geospatial: Request location/route
    Geospatial->>Geospatial: Parse response & extract<br/>route geometry
    Geospatial->>Geospatial: Populate routeGeoJSON<br/>in McpResponse
    Geospatial-->>Handler: Return McpResponse<br/>with routeGeoJSON
    Handler->>Handler: Propagate routeGeoJSON<br/>to mapFeature
    Handler->>Context: Update mapFeature<br/>with routeGeoJSON
    Context-->>Map: mapFeature with<br/>routeGeoJSON available
    Map->>Map: Render GeoJsonLayer<br/>for linestring geometry
    Map-->>Client: Display route on map
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

Review effort 3/5


🐰 Hopping through data streams so fine,
GeoJSON routes now paint the line,
From tool to map the pathways flow,
Where linestrings dance in vibrant glow!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely captures the main objective of the pull request: implementing Mapbox route rendering via GeoJsonLayer, which is directly supported by changes across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/draw-route-implementation-18234505032010150291

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/map/map-data-context.tsx`:
- Around line 19-25: Tighten the overly-permissive mapFeature shape by replacing
the broad index signature with an explicit interface (e.g., MapFeature) that
lists known optional properties (place_name, mapUrl) and types routeGeoJSON as a
GeoJSON Geometry/Feature (use GeoJSON.Geometry | GeoJSON.Feature | null) instead
of any; if you still need extensibility, use a safer type like Record<string,
unknown> or a discriminated union for different feature kinds rather than [key:
string]: any so type errors are not masked (update the mapFeature type
declaration and any usages that reference mapFeature/routeGeoJSON).

In `@components/map/map-query-handler.tsx`:
- Line 16: routeGeoJSON is currently typed as any which removes type safety;
change its type to an appropriate GeoJSON type (e.g., GeoJSON.FeatureCollection
| GeoJSON.Feature<GeoJSON.LineString> | GeoJSON.Geometry) to match the Mapbox
Directions API output. Import the GeoJSON types from the 'geojson' package (or
use your project’s geo types) and update the routeGeoJSON declaration and any
usages in the MapQueryHandler component / functions to use the chosen specific
type, adjusting casts or parsing where necessary.

In `@components/map/mapbox-map.tsx`:
- Around line 14-15: The inline comment on the import of GeoJsonLayer is
incorrect; update the comment (or remove it) so it accurately describes that the
line imports GeoJsonLayer rather than useMapLoading — locate the import
statements for useMapLoading and GeoJsonLayer in the module (symbols:
useMapLoading, GeoJsonLayer) and change the comment to either "// Import
GeoJsonLayer" or delete the misleading comment.

In `@lib/agents/tools/geospatial.tsx`:
- Line 28: The property routeGeoJSON is typed as any which loses type safety;
replace its type with the proper GeoJSON Geometry type (import Geometry from the
'geojson' package or `@types/geojson`) and update the declaration of
routeGeoJSON?: any to routeGeoJSON?: Geometry so callers and consumers (e.g., in
geospatial.tsx) have correct shape validation and autocompletion.
- Around line 411-421: The route center extraction leaves lat/lng undefined when
route.geometry.coordinates is missing or empty, causing MapQueryHandler to
reject the whole mcpData (including valid routeGeoJSON); update the block that
handles parsedData.routes (around parsedData.routes[0],
route.geometry.coordinates, and the assignment to mcpData) to defensively set a
fallback center: if coordinates array is empty or missing, use the first
available waypoint or omit the location field entirely (or set a safe default
like 0/0 or a bounding-box center) so that routeGeoJSON is still preserved and
passed to MapQueryHandler; ensure mcpData.location is only included when
latitude and longitude are valid numbers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 44cf10ea-6925-48f6-85eb-49e80593248c

📥 Commits

Reviewing files that changed from the base of the PR and between 18ceeab and becd9d1.

📒 Files selected for processing (5)
  • components/map/geojson-layer.tsx
  • components/map/map-data-context.tsx
  • components/map/map-query-handler.tsx
  • components/map/mapbox-map.tsx
  • lib/agents/tools/geospatial.tsx
📜 Review details
🔇 Additional comments (7)
components/map/map-query-handler.tsx (1)

44-48: LGTM!

The mapFeature construction correctly propagates routeGeoJSON from the tool output using appropriate optional chaining to handle potentially nullish values.

lib/agents/tools/geospatial.tsx (2)

397-404: LGTM!

The route geometry extraction correctly handles both possible response structures from the Mapbox Directions API: top-level geometry and nested routes[0].geometry.


408-410: LGTM!

The routeGeoJSON is properly included in mcpData for both the results and location response paths, ensuring route geometry propagates through all code paths.

components/map/geojson-layer.tsx (2)

66-83: LGTM!

The linestring layer implementation is well-structured:

  • Correct filter syntax using ['any', ...] to match both LineString and MultiLineString geometry types
  • Appropriate styling with rounded line joins/caps for smooth route rendering
  • Proper z-ordering (added after polygon layers, before point layers)

23-23: LGTM!

The linestring layer ID is properly defined and cleanup correctly removes the layer before the source, maintaining proper resource management.

Also applies to: 114-114

components/map/mapbox-map.tsx (2)

554-556: LGTM!

The comment clearly explains the architectural shift from imperative route drawing to declarative GeoJsonLayer rendering, which improves maintainability.


594-606: LGTM!

The declarative route rendering is well-implemented:

  • Properly guards against missing routeGeoJSON with conditional rendering
  • Correctly wraps the raw geometry in a GeoJSON FeatureCollection structure as required by GeoJsonLayer
  • Uses a stable id for consistent layer management

One consideration: the GeoJsonLayer is rendered as a sibling to the map container div, which is fine since it uses useMap() context internally and renders nothing to the DOM itself.

Comment on lines +19 to +25
// TODO: Add other relevant map data types later (e.g., poiList)
mapFeature?: {
place_name?: string;
mapUrl?: string;
routeGeoJSON?: any;
[key: string]: any;
} | null; // Generic feature from MCP hook's processLocationQuery
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider tightening the mapFeature type.

The index signature [key: string]: any is overly permissive and could mask type errors. If additional properties are needed, consider explicitly defining them or using a discriminated union. Also, routeGeoJSON could benefit from a proper GeoJSON Geometry type for better type safety.

♻️ Suggested type improvement
+import type { Geometry } from 'geojson';
+
 export interface MapData {
   targetPosition?: { lat: number; lng: number } | null;
   cameraState?: CameraState;
   currentTimezone?: string;
   // TODO: Add other relevant map data types later (e.g., poiList)
   mapFeature?: {
     place_name?: string;
     mapUrl?: string;
-    routeGeoJSON?: any;
-    [key: string]: any;
+    routeGeoJSON?: Geometry;
   } | null;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: Add other relevant map data types later (e.g., poiList)
mapFeature?: {
place_name?: string;
mapUrl?: string;
routeGeoJSON?: any;
[key: string]: any;
} | null; // Generic feature from MCP hook's processLocationQuery
import type { Geometry } from 'geojson';
export interface MapData {
targetPosition?: { lat: number; lng: number } | null;
cameraState?: CameraState;
currentTimezone?: string;
// TODO: Add other relevant map data types later (e.g., poiList)
mapFeature?: {
place_name?: string;
mapUrl?: string;
routeGeoJSON?: Geometry;
} | null; // Generic feature from MCP hook's processLocationQuery
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/map/map-data-context.tsx` around lines 19 - 25, Tighten the
overly-permissive mapFeature shape by replacing the broad index signature with
an explicit interface (e.g., MapFeature) that lists known optional properties
(place_name, mapUrl) and types routeGeoJSON as a GeoJSON Geometry/Feature (use
GeoJSON.Geometry | GeoJSON.Feature | null) instead of any; if you still need
extensibility, use a safer type like Record<string, unknown> or a discriminated
union for different feature kinds rather than [key: string]: any so type errors
are not masked (update the mapFeature type declaration and any usages that
reference mapFeature/routeGeoJSON).

address?: string;
};
mapUrl?: string;
routeGeoJSON?: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using a more specific type for routeGeoJSON.

Using any loses type safety. Since this represents GeoJSON geometry from the Mapbox Directions API, consider using a proper GeoJSON type.

♻️ Suggested type improvement
+import type { Geometry } from 'geojson';
+
 interface McpResponseData {
   location: {
     latitude?: number;
     longitude?: number;
     place_name?: string;
     address?: string;
   };
   mapUrl?: string;
-  routeGeoJSON?: any;
+  routeGeoJSON?: Geometry;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
routeGeoJSON?: any;
import type { Geometry } from 'geojson';
interface McpResponseData {
location: {
latitude?: number;
longitude?: number;
place_name?: string;
address?: string;
};
mapUrl?: string;
routeGeoJSON?: Geometry;
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/map/map-query-handler.tsx` at line 16, routeGeoJSON is currently
typed as any which removes type safety; change its type to an appropriate
GeoJSON type (e.g., GeoJSON.FeatureCollection |
GeoJSON.Feature<GeoJSON.LineString> | GeoJSON.Geometry) to match the Mapbox
Directions API output. Import the GeoJSON types from the 'geojson' package (or
use your project’s geo types) and update the routeGeoJSON declaration and any
usages in the MapQueryHandler component / functions to use the chosen specific
type, adjusting casts or parsing where necessary.

Comment on lines +14 to +15
import { useMapLoading } from '../map-loading-context'
import { GeoJsonLayer } from './geojson-layer'; // Import useMapLoading
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix the misleading inline comment.

The comment says "Import useMapLoading" but the line actually imports GeoJsonLayer. The useMapLoading import is on line 14.

📝 Proposed fix
 import { useMapLoading } from '../map-loading-context'
-import { GeoJsonLayer } from './geojson-layer'; // Import useMapLoading
+import { GeoJsonLayer } from './geojson-layer';
 import { useMap } from './map-context'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/map/mapbox-map.tsx` around lines 14 - 15, The inline comment on
the import of GeoJsonLayer is incorrect; update the comment (or remove it) so it
accurately describes that the line imports GeoJsonLayer rather than
useMapLoading — locate the import statements for useMapLoading and GeoJsonLayer
in the module (symbols: useMapLoading, GeoJsonLayer) and change the comment to
either "// Import GeoJsonLayer" or delete the misleading comment.

interface McpResponse {
location: Location;
mapUrl?: string;
routeGeoJSON?: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Consider using a proper GeoJSON Geometry type.

This is consistent with other files, but using any loses type safety. The GeoJSON Geometry type would provide better validation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/agents/tools/geospatial.tsx` at line 28, The property routeGeoJSON is
typed as any which loses type safety; replace its type with the proper GeoJSON
Geometry type (import Geometry from the 'geojson' package or `@types/geojson`) and
update the declaration of routeGeoJSON?: any to routeGeoJSON?: Geometry so
callers and consumers (e.g., in geospatial.tsx) have correct shape validation
and autocompletion.

Comment on lines +411 to +421
} else if (parsedData.routes && parsedData.routes.length > 0) {
// It's a routing response, pick first route coordinates for map center
const route = parsedData.routes[0];
let lat, lng;
if (route.geometry && route.geometry.coordinates && route.geometry.coordinates.length > 0) {
// Pick a point roughly in the middle, or the start
const coords = route.geometry.coordinates[Math.floor(route.geometry.coordinates.length / 2)];
lng = coords[0];
lat = coords[1];
}
mcpData = { location: { latitude: lat, longitude: lng, place_name: "Route", address: "Generated Route" }, routeGeoJSON };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Route center coordinates may be undefined, causing downstream issues.

When extracting the route center point, lat and lng remain undefined if route.geometry.coordinates is empty or missing. This undefined location will be passed to MapQueryHandler, which checks typeof latitude === 'number' and will reject the data, clearing mapFeature entirely—including the valid routeGeoJSON. This means routes with empty coordinate arrays will silently fail to render.

🛠️ Proposed fix to handle empty coordinates
         } else if (parsedData.routes && parsedData.routes.length > 0) {
           // It's a routing response, pick first route coordinates for map center
           const route = parsedData.routes[0];
-          let lat, lng;
+          let lat: number | undefined, lng: number | undefined;
           if (route.geometry && route.geometry.coordinates && route.geometry.coordinates.length > 0) {
             // Pick a point roughly in the middle, or the start
             const coords = route.geometry.coordinates[Math.floor(route.geometry.coordinates.length / 2)];
-            lng = coords[0];
-            lat = coords[1];
+            if (Array.isArray(coords) && coords.length >= 2) {
+              lng = coords[0];
+              lat = coords[1];
+            }
+          }
+          // Fallback: if no valid center, use first coordinate pair
+          if (lat === undefined || lng === undefined) {
+            const firstCoord = route.geometry?.coordinates?.[0];
+            if (Array.isArray(firstCoord) && firstCoord.length >= 2) {
+              lng = firstCoord[0];
+              lat = firstCoord[1];
+            }
           }
           mcpData = { location: { latitude: lat, longitude: lng, place_name: "Route", address: "Generated Route" }, routeGeoJSON };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
} else if (parsedData.routes && parsedData.routes.length > 0) {
// It's a routing response, pick first route coordinates for map center
const route = parsedData.routes[0];
let lat, lng;
if (route.geometry && route.geometry.coordinates && route.geometry.coordinates.length > 0) {
// Pick a point roughly in the middle, or the start
const coords = route.geometry.coordinates[Math.floor(route.geometry.coordinates.length / 2)];
lng = coords[0];
lat = coords[1];
}
mcpData = { location: { latitude: lat, longitude: lng, place_name: "Route", address: "Generated Route" }, routeGeoJSON };
} else if (parsedData.routes && parsedData.routes.length > 0) {
// It's a routing response, pick first route coordinates for map center
const route = parsedData.routes[0];
let lat: number | undefined, lng: number | undefined;
if (route.geometry && route.geometry.coordinates && route.geometry.coordinates.length > 0) {
// Pick a point roughly in the middle, or the start
const coords = route.geometry.coordinates[Math.floor(route.geometry.coordinates.length / 2)];
if (Array.isArray(coords) && coords.length >= 2) {
lng = coords[0];
lat = coords[1];
}
}
// Fallback: if no valid center, use first coordinate pair
if (lat === undefined || lng === undefined) {
const firstCoord = route.geometry?.coordinates?.[0];
if (Array.isArray(firstCoord) && firstCoord.length >= 2) {
lng = firstCoord[0];
lat = firstCoord[1];
}
}
mcpData = { location: { latitude: lat, longitude: lng, place_name: "Route", address: "Generated Route" }, routeGeoJSON };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/agents/tools/geospatial.tsx` around lines 411 - 421, The route center
extraction leaves lat/lng undefined when route.geometry.coordinates is missing
or empty, causing MapQueryHandler to reject the whole mcpData (including valid
routeGeoJSON); update the block that handles parsedData.routes (around
parsedData.routes[0], route.geometry.coordinates, and the assignment to mcpData)
to defensively set a fallback center: if coordinates array is empty or missing,
use the first available waypoint or omit the location field entirely (or set a
safe default like 0/0 or a bounding-box center) so that routeGeoJSON is still
preserved and passed to MapQueryHandler; ensure mcpData.location is only
included when latitude and longitude are valid numbers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants