Skip to content

Add minimap widget#3789

Open
patrickelectric wants to merge 6 commits intobluerobotics:masterfrom
patrickelectric:map-visualizer
Open

Add minimap widget#3789
patrickelectric wants to merge 6 commits intobluerobotics:masterfrom
patrickelectric:map-visualizer

Conversation

@patrickelectric
Copy link
Member

@patrickelectric patrickelectric commented Feb 11, 2026

image

Fix #3738

Summary by Sourcery

Add a vehicle position minimap widget using Leaflet and integrate it into the main view layout.

New Features:

  • Introduce a MiniMap component that displays the vehicle’s live GPS position, heading, and recent track on an embedded map.
  • Expose the minimap as a new "Vehicle Position" widget in the main view dashboard.

Enhancements:

  • Proxy OpenStreetMap tile requests through a new /cache path to reuse the backend server as a tile cache.
  • Add Leaflet and its TypeScript typings as frontend dependencies for map rendering.

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
@sourcery-ai
Copy link

sourcery-ai bot commented Feb 11, 2026

Reviewer's Guide

Implements a new Leaflet-based MiniMap widget that displays the vehicle’s live GPS position and trail using MAVLink GLOBAL_POSITION_INT data, wires it into the main view layout, configures proxying for cached map tiles, and adds the necessary Leaflet dependencies.

Sequence diagram for MiniMap live GPS position updates via MAVLink2Rest

sequenceDiagram
  participant Autopilot
  participant MAVLink2Rest
  participant MiniMap as MiniMap_component
  participant AutopilotStore as autopilot_data
  participant AutopilotManager as autopilot_manager
  participant LeafletMap as Leaflet_map

  Autopilot->>MAVLink2Rest: Send GLOBAL_POSITION_INT
  MiniMap->>MAVLink2Rest: startListening(GLOBAL_POSITION_INT)
  MAVLink2Rest-->>MiniMap: listener
  MiniMap->>MAVLink2Rest: listener.setCallback(message)
  MAVLink2Rest-->>MiniMap: Invoke_callback_on_each_message

  loop For_each_GLOBAL_POSITION_INT_message
    MAVLink2Rest->>MiniMap: message
    MiniMap->>AutopilotStore: Read system_id
    MiniMap->>MiniMap: Check system_id_and_component_id
    alt Not_target_system_or_component
      MiniMap-->>MAVLink2Rest: Ignore_message
    else Matching_vehicle
      MiniMap->>MiniMap: Decode lat, lon, hdg
      MiniMap->>MiniMap: Update latitude, longitude, heading
      MiniMap->>MiniMap: has_position = true
      MiniMap->>LeafletMap: updateVehicle()
      LeafletMap->>LeafletMap: set marker position
      LeafletMap->>LeafletMap: rotate marker icon
      LeafletMap->>LeafletMap: append_to_trail_and_update_polyline
    end
  end

  MiniMap->>MAVLink2Rest: listener.discard() on_beforeDestroy
  MiniMap->>LeafletMap: map.remove() on_beforeDestroy
Loading

Class diagram for the new MiniMap Vue component

classDiagram
  class MiniMap {
    +string name
    +L_Map map
    +L_Marker vehicle_marker
    +L_Polyline trail
    +L_LatLng[] trail_points
    +number latitude
    +number longitude
    +number heading
    +boolean has_position
    +number max_trail_points
    +ListenerHandle listener
    +mounted()
    +beforeDestroy()
    +initMap()
    +startListening()
    +updateVehicle()
  }

  class mavlink2rest {
    +startListening(message_name) ListenerHandle
  }

  class ListenerHandle {
    +setCallback(message)
    +setFrequency(hz)
    +discard()
  }

  class autopilot_data {
    +number system_id
  }

  class autopilot_manager {
    +string vehicle_type
  }

  class Leaflet_Map {
    +setView(center, zoom)
    +invalidateSize()
    +panTo(center)
  }

  class Leaflet_Marker {
    +setLatLng(position)
    +getElement() HTMLElement
  }

  class Leaflet_Polyline {
    +setLatLngs(points)
  }

  class GlobalPositionInt {
    +number lat
    +number lon
    +number hdg
  }

  MiniMap --> mavlink2rest : uses
  MiniMap --> ListenerHandle : holds
  MiniMap --> autopilot_data : reads
  MiniMap --> autopilot_manager : reads
  MiniMap --> Leaflet_Map : wraps
  MiniMap --> Leaflet_Marker : wraps
  MiniMap --> Leaflet_Polyline : wraps
  MiniMap --> GlobalPositionInt : decodes
Loading

File-Level Changes

Change Details Files
Add MiniMap widget component using Leaflet to display live vehicle position and trail from MAVLink GLOBAL_POSITION_INT messages.
  • Create MiniMap Vue component that initializes a Leaflet map with OSM tiles, a vehicle marker, and a polyline trail.
  • Subscribe to mavlink2rest GLOBAL_POSITION_INT messages filtered by current system/component IDs to update latitude, longitude, and heading state.
  • Track whether a valid (non-zero) GPS position is available and show a “Waiting for GPS” overlay until it is.
  • Rotate a vehicle marker icon based on heading and maintain a capped history of trail points to render the path.
  • Clean up Leaflet map and MAVLink listener in the component’s beforeDestroy hook.
core/frontend/src/components/map/MiniMap.vue
Integrate the MiniMap widget into the main application view as a new panel.
  • Import the MiniMap component into the main view and register it in the components section.
  • Add a new dashboard panel definition for vehicle position with map icon, title, component reference, and size configuration.
core/frontend/src/views/MainView.vue
Support Leaflet map rendering and tile loading in the frontend build and dev proxy configuration.
  • Add Leaflet and its TypeScript types as dependencies in package.json.
  • Configure Vite dev server proxy to forward /cache requests to the backend for tile caching.
  • Reference Leaflet CSS in the MiniMap component and use a tile URL under /cache/tile.openstreetmap.org/{z}/{x}/{y}.png.
core/frontend/vite.config.js
core/frontend/package.json
core/frontend/yarn.lock
core/frontend/src/components/map/MiniMap.vue

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • Panning the map on every position update with this.map.panTo(pos) can cause jittery UX at higher update rates; consider only recentering when the vehicle nears the edge of the viewport or behind a user-controlled “follow” toggle.
  • Importing leaflet/dist/leaflet.css inside the MiniMap component will pull the stylesheet every time the component is used; it may be cleaner to move this CSS import to a global entry point (e.g., main.ts) so it’s loaded once.
  • Using latitude === 0 && longitude === 0 to detect lack of GPS can incorrectly hide valid positions at (0,0); if possible, key the has_position logic off an explicit validity/fix indicator from MAVLink instead.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- Panning the map on every position update with `this.map.panTo(pos)` can cause jittery UX at higher update rates; consider only recentering when the vehicle nears the edge of the viewport or behind a user-controlled “follow” toggle.
- Importing `leaflet/dist/leaflet.css` inside the `MiniMap` component will pull the stylesheet every time the component is used; it may be cleaner to move this CSS import to a global entry point (e.g., main.ts) so it’s loaded once.
- Using `latitude === 0 && longitude === 0` to detect lack of GPS can incorrectly hide valid positions at (0,0); if possible, key the `has_position` logic off an explicit validity/fix indicator from MAVLink instead.

## Individual Comments

### Comment 1
<location> `core/frontend/src/components/map/MiniMap.vue:131-134` </location>
<code_context>
+      }).addTo(this.map)
+    },
+    startListening() {
+      this.listener = mavlink2rest.startListening('GLOBAL_POSITION_INT')
+      this.listener.setCallback((message) => {
+        if (message?.header.system_id !== autopilot_data.system_id || message?.header.component_id !== 1) {
+          return
+        }
+
+        const pos = message?.message as GlobalPositionInt
+        this.latitude = pos.lat / 1e7
+        this.longitude = pos.lon / 1e7
+        this.heading = pos.hdg / 100
+
+        if (this.latitude === 0 && this.longitude === 0) return
+
+        const was_positioned = this.has_position
+        this.has_position = true
+
+        this.updateVehicle()
+
+        if (!was_positioned) {
+          this.$nextTick(() => {
+            this.map?.invalidateSize()
+            this.map?.setView([this.latitude, this.longitude], 18)
+          })
+        }
+      }).setFrequency(1)
+    },
+    updateVehicle() {
</code_context>

<issue_to_address>
**suggestion:** Heading value is used without guarding against the MAVLink "unknown" sentinel value.

For `GLOBAL_POSITION_INT`, `hdg` is a `uint16` in cdeg where `65535` indicates "unknown". Here we always use `heading` to rotate the icon, so an unknown heading still results in a rotation (655.35°). If you want to respect the sentinel, consider checking for `65535` and either skipping rotation or retaining the previous heading in that case.

```suggestion
        const pos = message?.message as GlobalPositionInt
        this.latitude = pos.lat / 1e7
        this.longitude = pos.lon / 1e7

        // hdg is uint16 in cdeg; 65535 indicates "unknown" and should not be used
        if (pos.hdg !== 65535) {
          this.heading = pos.hdg / 100
        }
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
Signed-off-by: Patrick José Pereira <patrickelectric@gmail.com>
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.

Dashboard: Add a map showing the location of the vehicle

1 participant

Comments