Skip to contents

This example uses a real place, real indoor geometry, a real Leaflet base map, and real photographs. It combines 135 named indoor spaces at the Louvre Museum in Paris from a dated OpenStreetMap snapshot with five Wikimedia Commons photographs associated with three distinct rooms on levels 0 and 1.

The example is intended to demonstrate the software, not to replace the Louvre’s official visitor, accessibility, evacuation, or safety information. OpenStreetMap is maintained by contributors and indoor details can change.

Inspect the real room data

The bundled GeoJSON uses EPSG:4326 coordinates. Its 135 polygons cover three OpenStreetMap levels and retain the source way identifiers needed for traceability. The source snapshot is bundled in data-raw/, so rebuilding the example does not silently replace the plan with newer community edits.

room_file <- system.file(
  "examples", "real", "louvre-rooms.geojson",
  package = "leaflet.indoor"
)
louvre_rooms <- st_read(room_file, quiet = TRUE)

table(louvre_rooms$level)
#> 
#> -2  0  1 
#>  8 51 76
head(st_drop_geometry(louvre_rooms)[c("feature_id", "level", "name")])
#>          feature_id level                             name
#> 1 osm-way-451560039    -2  Amis du Louvre - espace membres
#> 2 osm-way-376694360    -2 Arts de l'Islam - de 1000 à 1800
#> 3 osm-way-376694363    -2 Arts de l'Islam - de 1000 à 1800
#> 4 osm-way-451560034    -2                       Auditorium
#> 5 osm-way-457641196    -2          Expositions temporaires
#> 6 osm-way-367772251    -2                    Hall Napoléon

The mapped spaces include the Hall Napoléon, the Salle de la Vénus de Milo, the Salle des Caryatides, the Galerie d’Apollon, and the Grande Galerie. The room names and geometries are those recorded in the cited OpenStreetMap ways, not manually invented coordinates.

Associate photographs with a room

Use one catalogue row per image. Repeating a room identifier produces a carousel in row order. This catalogue associates two photographs with the Salle des Caryatides, one with the Salle de la Vénus de Milo, and two with the Galerie d’Apollon.

photo_directory <- system.file(
  "examples", "real", "photos",
  package = "leaflet.indoor"
)

louvre_photos <- indoorPhotoCatalog(
  layerId = c(
    rep("osm-way-394887893", 2),
    "osm-way-453817508",
    rep("osm-way-367790015", 2)
  ),
  src = file.path(photo_directory, c(
    "louvre-caryatides-main.jpg",
    "louvre-caryatides-reopening.jpg",
    "louvre-venus-room.jpg",
    "louvre-apollon-main.jpg",
    "louvre-apollon-ceiling.jpg"
  )),
  caption = c(
    paste(
      "Salle des Caryatides, photographed in 2024.",
      "Photo: Wilfredor, CC0 1.0, via Wikimedia Commons."
    ),
    paste(
      "Salle des Caryatides shortly after the Louvre reopened in 2021.",
      "Photo: Tangopaso, public domain, via Wikimedia Commons."
    ),
    paste(
      "Salle de la Vénus de Milo, photographed in 2016.",
      "Photo: Shonagon, CC0 1.0, via Wikimedia Commons."
    ),
    paste(
      "Galerie d'Apollon, photographed in 2024.",
      "Photo: Wilfredor, CC0 1.0, via Wikimedia Commons."
    ),
    paste(
      "Ceiling of the Galerie d'Apollon, photographed in 2016.",
      "Photo: Gary Todd, CC0 1.0, via Wikimedia Commons."
    )
  ),
  alt = c(
    "Wide interior view of the Salle des Caryatides at the Louvre",
    "Interior of the Salle des Caryatides with few visitors",
    "Salle de la Vénus de Milo with the sculpture centered in the room",
    "Long interior view of the decorated Galerie d'Apollon",
    "Painted and gilded ceiling of the Galerie d'Apollon"
  )
)

data.frame(
  layerId = louvre_photos$layerId,
  caption = louvre_photos$caption
)
#>             layerId
#> 1 osm-way-394887893
#> 2 osm-way-394887893
#> 3 osm-way-453817508
#> 4 osm-way-367790015
#> 5 osm-way-367790015
#>                                                                                                                   caption
#> 1                           Salle des Caryatides, photographed in 2024. Photo: Wilfredor, CC0 1.0, via Wikimedia Commons.
#> 2 Salle des Caryatides shortly after the Louvre reopened in 2021. Photo: Tangopaso, public domain, via Wikimedia Commons.
#> 3                       Salle de la Vénus de Milo, photographed in 2016. Photo: Shonagon, CC0 1.0, via Wikimedia Commons.
#> 4                              Galerie d'Apollon, photographed in 2024. Photo: Wilfredor, CC0 1.0, via Wikimedia Commons.
#> 5               Ceiling of the Galerie d'Apollon, photographed in 2016. Photo: Gary Todd, CC0 1.0, via Wikimedia Commons.

Existing local images are embedded as image data URIs. The interactive map is therefore portable even though the source files are local package assets.

Explore the map

Red polygons identify rooms with photographs. On level 0, click the Salle des Caryatides to see a two-photo carousel or the Salle de la Vénus de Milo to see its room photograph. Then select level 1 and click the Galerie d’Apollon for a second two-photo carousel. Use Previous and Next to change photos, and Enlarge to open the current image with its matching caption. Rooms without catalogue rows retain their ordinary popup.

photo_room_names <- c(
  "Salle des Caryatides",
  "Salle de la Vénus de Milo",
  "Galerie d'Apollon"
)
louvre_rooms$fill <- ifelse(
  louvre_rooms$name %in% photo_room_names,
  "#c43c2e",
  c(`-2` = "#6b7280", `0` = "#2f7d65", `1` = "#3568a8")[louvre_rooms$level]
)

leaflet(louvre_rooms, height = 600) |>
  addTiles() |>
  addIndoor(
    level = "level",
    level_order = c("-2", "0", "1"),
    initial_level = "0",
    layerId = ~feature_id,
    photos = louvre_photos,
    comments = read.csv(
      system.file("examples", "real", "louvre-comments.csv", package = "leaflet.indoor"),
      stringsAsFactors = FALSE,
      fileEncoding = "UTF-8"
    ),
    commentOptions = indoorCommentOptions(
      icon = "quote", marker_label = "Read visitor accounts",
      popup_label = "Visitor accounts", source_label = "Read the original account"
    ),
    label = ~name,
    popup = ~paste0(name, ". Level ", level, ". ", description),
    style = list(
      color = "#17324d",
      weight = 2,
      fillColor = ~fill,
      fillOpacity = 0.72
    ),
    popupOptions = popupOptions(keepInView = TRUE)
  ) |>
  addIndoorControl(
    position = "bottomright",
    options = indoorControlOptions(title = "Louvre level")
  )

This rendered map exercises the geographic path of addIndoor(): real longitude and latitude coordinates are displayed over live OpenStreetMap tiles, exactly one floor is active, and photographs are joined through the same stable identifiers used by the vector features.

Purple quotation icons open published visitor accounts for the Vénus de Milo, the Salle des États, and the Galerie d’Apollon. Each entry includes attribution, its publication date, and a source link. Click a room away from its icon to open its ordinary popup or photographs. See the room-comments article for provenance and customization.

Data and image provenance

The source extract was downloaded on 2026-09-08 from the official OpenStreetMap API. The example selects all 135 named indoor rooms or areas on levels -2, 0, and 1 within that extract. OpenStreetMap data is copyright OpenStreetMap contributors and licensed under the ODbL 1.0. See the OpenStreetMap copyright page.

The first Salle des Caryatides photograph is by Wilfredor and is available under CC0 1.0 on Wikimedia Commons. The second Salle des Caryatides photograph is by Tangopaso, who dedicated it to the public domain, and is also available on Wikimedia Commons.

The Salle de la Vénus de Milo photograph is by Shonagon and is available under CC0 1.0 on Wikimedia Commons. The two Galerie d’Apollon photographs are by Wilfredor and Gary Todd, respectively, and are both available under CC0 1.0 on Wikimedia Commons and Wikimedia Commons. The package includes full source URLs and a detailed attribution file with all five example assets.

Adapt the pattern

For another building, provide an sf object with stable room identifiers and a level column, then build the same one-row-per-photo association. The optional alt column should describe the image for people who cannot see it. Captions and alternative text are treated as plain text.

All carousel labels can be translated:

indoorPhotoOptions(
  previous_label = "Précédent",
  next_label = "Suivant",
  enlarge_label = "Agrandir",
  close_label = "Fermer",
  carousel_label = "Photos de la pièce",
  dialog_label = "Photo agrandie de la pièce",
  counter_label = "Photo {current} sur {total}",
  unavailable_label = "Image indisponible"
)

The fully offline synthetic example remains available through system.file("examples", "photo-carousel.R", package = "leaflet.indoor") for tests and environments where live map tiles are unavailable.