Skip to content

06: Visualizing the data

Mordy Ovits edited this page Jul 19, 2026 · 3 revisions

Overview

Once GTFS has been configured, Home Assistant has entities packed with public transit data that you can use to visualize that data.

Examples

Static data and real-time combined

Using 'multiple entity row' card with pop-up:

image

Clicking on the card would show details (though this example is not synced on data):

image

The YAML

type: entities
entities:
  - entity: sensor.653_mouans_sophia
    type: custom:multiple-entity-row
    state_header: RealTime
    attribute: next_departure_realtime
    name: Mouans > Sophia 653
    format: time
    secondary_info: last-updated
    entities:
      - entity: sensor.653_mouans_sophia
        attribute: day
        name: Day
        format: time
      - entity: sensor.653_mouans_sophia
        name: 653
        format: time
    tap_action:
      action: fire-dom-event
      browser_mod:
        service: browser_mod.popup
        data:
          content:
            type: custom:button-card
            entity: sensor.653_mouans_sophia
            show_icon: false
            show_state: true
            show_name: false
            tap_action: none
            styles:
              grid:
                - grid-template-areas: "\"next\" \"s\""
              custom_fields:
                next:
                  - font-size: 20px
                  - font-weight: bold
            custom_fields:
              next: |
                [[[ return 'Next departures :' ]]]
            state_display: |
              [[[ 
               return entity.attributes.next_departures.map((entry) => {
                    return '<br>' + [ helpers.formatShortDateTimeWithYear(entry) ] ;
                      }  ).join("").toString();
              ]]]
  - entity: sensor.653_mouans_antibes
    type: custom:multiple-entity-row
    state_header: RealTime
    attribute: next_departure_realtime
    name: Mouans > Antibes 653
    format: time
    secondary_info: last-updated
    entities:
      - entity: sensor.653_mouans_antibes
        attribute: day
        name: Day
        format: time
      - entity: sensor.653_mouans_antibes
        name: 653
        format: time
    tap_action:
      action: fire-dom-event
      browser_mod:
        service: browser_mod.popup
        data:
          content:
            type: custom:button-card
            entity: sensor.653_mouans_antibes
            show_icon: false
            show_state: true
            show_name: false
            tap_action: none
            styles:
              grid:
                - grid-template-areas: "\"next\" \"s\""
              custom_fields:
                next:
                  - font-size: 20px
                  - font-weight: bold
            custom_fields:
              next: |
                [[[ return 'Next departures :' ]]]
            state_display: |
              [[[ 
               return entity.attributes.next_departures.map((entry) => {
                    return '<br>' + [ helpers.formatShortDateTimeWithYear(entry) ] ;
                      }  ).join("").toString();
              ]]]
card_mod:
  style:
    .: |
      ha-card {
        border-width: 0px !important;
        background: none;
      }  

Vehicle positions

When vehicle positions have been added to the GTFS2 configuration, a geojson file is created in your www/gtfs2 folder. You can create HA entities with position, using the geojson integration, by refering to the file in www/gtfs2.

Important

The geojson file is in www for access by integrations like geojson. This means you need to use '/local', e.g. http://Your-HA-IP:8123/local/gtfs2/filename.json

Important

The filename is case sensitive.

image

Example geojson entity

image

Note

To ensure that the HA entity is unique, the name of the entity is a combination of: The route 'B' The direction '(0)' The last three digits of a crc32 of the trip_id

Once you have the geojson entity configured, you can configure a Map card to show the positions of active vehicles along that route:

image

Note

The geojson integration currently only updates every 5 minutes, but can be refreshed by a call to a geojson Action.

Additional customizations

Customizing the entity_picture for a vehicle

Use customize_glob to display a different entity_picture, in this case a svg of a bus:

image

Note

SVG images are not supplied by GTFS2

Tweak the card to make full use of the SVG, i.e. to remove the circle, the background:

image

The YAML

type: custom:auto-entities
filter:
  include:
    - entity_id: geo_location.b_0*
  exclude:
    - state: unavailable
show_empty: true
card:
  type: map
  hours_to_show: 2
  auto_fit: true
  card_mod:
    style:
      ha-map:
        $:
          .leaflet-marker-icon:
            ha-entity-marker $: |
              .marker {
                vertical-align: right !important;

                width: 25px !important;
                height: 25px !important;
                border: 0px solid var(--ha-marker-color,var(--primary-color)) !important;
                background-color: var(--ha-marker-color,none) !important;
               }
               .div:hover {
                --webkit-transform: scale(0.1);
                --ms-transform: scale(0.1);
                transform: scale(0.1);
                }

Local stops and departures

image

image

type: custom:auto-entities
filter:
  include:
    - name: '*_local_stop'
  exclude:
    - state: unavailable
card:
  type: custom:flex-table-card
  clickable: true
  max_rows: 30
  title: Local Stop dep.
  strict: true
  sort_by:
    - next_departures_lines
  columns:
    - hidden: true
      data: next_departures_lines
      modify: x.departure
    - name: Route
      data: next_departures_lines
      modify: '''<ha-icon icon='' + x.icon + ''></ha-icon>'' + x.route'
    - name: Time
      data: next_departures_lines
      modify: x.departure
    - name: Stop
      data: next_departures_lines
      modify: x.stop_name
    - name: Headsign
      data: next_departures_lines
      modify: x.headsign
  css:
    table+: 'padding: 0px'
  card_mod:
    style: |
      ha-card {
        overflow: auto;
        max-height: 500px;
      }

Filtering certain routes

[!NOTE} I have not been able to get this to work with flex-table-card

image

type: vertical-stack
cards:
  - type: entities
    entities:
      - entity: input_text.local_stops_filter
  - type: markdown
    content: >-
      <table>  {% set next =
      state_attr('sensor.01254_0608400_local_stop_person_abcd','next_departures_lines')%}
      {% for i in range(0, next | count, 1) %} {%- if
      state_attr('sensor.01254_0608400_local_stop_person_abcd','next_departures_lines')[i].route 
        == states("input_text.local_stops_filter") -%}
      <tr> <td>{{
      state_attr('sensor.01254_0608400_local_stop_person_abcd','next_departures_lines')[i].route
      }}</td> <td>{{
      state_attr('sensor.01254_0608400_local_stop_person_abcd','next_departures_lines')[i].departure
      }}</td> <td>{{
      state_attr('sensor.01254_0608400_local_stop_person_abcd','next_departures_lines')[i].stop_name
      }}</td> <td>{{
      state_attr('sensor.01254_0608400_local_stop_person_abcd','next_departures_lines')[i].headsign
      }}</td> {% endif %} {% endfor %}
    card_mod:
      style:
        .: |
          ha-card ha-markdown {
                    padding:0px
                    border-top: 1px groove var(--divider-color);
                    overflow-y: scroll;
                    height: 300px;
                  }
          ha-card ha-markdown.no-header {
            padding:0px
          }
        $: |
          h1.card-header {
            background-color:rgb(100, 100, 100);
              padding: 0px 0px 0px 12px !important;
              color: white !important;
              font-weight: normal;
              font-size: 1.5em !important;
              border-top-left-radius: 5px; 
              border-top-right-radius: 5px; 
              height: 100%;
          }        
        ha-markdown $: |
          h1 {
              font-weight: normal;
              font-size: 24px;
          }
          table{
            border-collapse: collapse;
            font-size: 0.9em;
            font-family: Roboto;
            width: 100%;
            outline: 0px solid #393c3d;
            margin-top: 10px;
          }
          td {
              padding: 5px 5px 5px 5px;
              text-align: left;
              border-bottom: 0px solid #1c2020;
          }
          tr:nth-of-type(even) {
              background-color: rgb(54, 54, 54, 0.3);
          }

Local stops: travelling solution

In this exmaple, I tied the dynamic route sensor to my person.abc entity, which contains the lat/lon of my phone. When I move around I click two buttons to see what is near me:

  1. Location update: This causes HA to updat the lat/lon of my person.abc entity, in case it is not fast in updating my person/position (i.e. with my iPhone it is slow).
  2. Local stops update: This causes HA to update every entity with local stops tied to my name. I have this setup for France, Switzerland, and Netherlands. The map nicely follows me around and adds the stops.

Note

You can also automate those actions, but since I am not that mobile, nor do I need it all day long, the button-update suffices for me.

image

Show entity of a route with a pop-up of next departures

Use an entities card and be able to click on a single entry, showing the next known departures.

[!IMPORTANT] This requires the browser_mod integration be installed in your HA

image

Change YOURSENSOR to whatever your entity is called.

type: entities
entities:
  - entity: YOURSENSOR
    format: datetime
    tap_action:
      action: fire-dom-event
      browser_mod:
        service: browser_mod.popup
        data:
          content:
            type: custom:button-card
            entity: YOURSENSOR
            show_icon: false
            show_state: true
            show_name: false
            tap_action: none
            styles:
              grid:
                - grid-template-areas: "\"next\" \"s\""
              custom_fields:
                next:
                  - font-size: 20px
                  - font-weight: bold
            custom_fields:
              next: |
                [[[ return 'Next departures :' ]]]
            state_display: |
              [[[ 

               return entity.attributes.next_departures.map((entry) => {
                        return '<br>' + [ helpers.formatShortDateTimeWithYear(entry) ];
                      }  ).join("").toString();


              ]]]

Clone this wiki locally