Place Photos

  • This sample demonstrates a basic photo carousel using the Google Maps Places library to display photos for a specific location.

  • It fetches place details like display name, summary, and photos using the fetchFields method.

  • Photos are displayed in a gallery and can be expanded to full size with author attributions included.

  • It uses the getURI method to retrieve the photo URLs, optionally specifying maximum height or width for resizing.

  • Author attributions are mandatory when displaying photos and are accessed using the authorAttributions property.

European Economic Area (EEA) developers

Place photos lets you add high quality photographic content to your web pages. Access millions of photos stored in the Places database, and get resizable images using Find Place, Nearby Search, Text Search, Autocomplete, and Place Details.

See the complete example source code

This demo displays a random photo from one of five cities, and shows the required author attributions in the bottom right corner of the photo.

TypeScript

const CITIES = [
    { name: 'New York', id: 'ChIJOwg_06VPwokRYv534QaPC8g' },
    { name: 'London', id: 'ChIJdd4hrwug2EcRmSrV3Vo6llI' },
    { name: 'Paris', id: 'ChIJD7fiBh9u5kcRYJSMaMOCCwQ' },
    { name: 'Tokyo', id: 'ChIJ51cu8IcbXWARiRtXIothAS4' },
    { name: 'Rome', id: 'ChIJu46S-ZZhLxMROG5lkwZ3D7k' },
];

async function init() {
    const { Place } = await google.maps.importLibrary('places');

    const heading = document.getElementById('heading')!;
    const summary = document.getElementById('summary')!;
    const expandedImageDiv = document.getElementById('expanded-image')!;
    const randomizeBtn = document.getElementById(
        'randomize-btn'
    ) as HTMLButtonElement;

    async function showRandomCityPhoto() {
        randomizeBtn.disabled = true;

        // Pick a random city
        const city = CITIES[Math.floor(Math.random() * CITIES.length)];

        try {
            const place = new Place({ id: city.id });
            await place.fetchFields({
                fields: ['displayName', 'photos', 'editorialSummary'],
            });

            heading.textContent = place.displayName ?? city.name;
            summary.textContent = place.editorialSummary ?? '';
            expandedImageDiv.innerHTML = '';

            if (place.photos && place.photos.length > 0) {
                // Pick one of the first 10 photos
                const maxPhotos = Math.min(10, place.photos.length);
                const randomIndex = Math.floor(Math.random() * maxPhotos);
                const photo = place.photos[randomIndex];

                const img = document.createElement('img');
                img.alt = `Photo of ${place.displayName ?? city.name}`;
                img.src = photo.getURI({ maxHeight: 800 });
                expandedImageDiv.appendChild(img);

                if (photo.authorAttributions.length) {
                    expandedImageDiv.appendChild(
                        createAttribution(photo.authorAttributions[0])
                    );
                }
            } else {
                expandedImageDiv.textContent =
                    'No photos available for this location.';
            }
        } catch (error) {
            console.error('Failed to fetch place details:', error);
            summary.textContent = 'Failed to load data.';
        }

        // Re-enable button after 2 seconds
        setTimeout(() => {
            randomizeBtn.disabled = false;
        }, 2000);
    }

    // Helper function to create attribution DIV.
    function createAttribution(
        attribution: google.maps.places.AuthorAttribution
    ) {
        const attributionLabel = document.createElement('a');
        attributionLabel.classList.add('attribution-label');
        attributionLabel.textContent = attribution.displayName;
        attributionLabel.href = attribution.uri!;
        attributionLabel.target = '_blank';
        attributionLabel.rel = 'noopener noreferrer';
        return attributionLabel;
    }

    randomizeBtn.addEventListener('click', () => void showRandomCityPhoto());

    // Initial load
    void showRandomCityPhoto();
}

void init();

JavaScript

const CITIES = [
    { name: 'New York', id: 'ChIJOwg_06VPwokRYv534QaPC8g' },
    { name: 'London', id: 'ChIJdd4hrwug2EcRmSrV3Vo6llI' },
    { name: 'Paris', id: 'ChIJD7fiBh9u5kcRYJSMaMOCCwQ' },
    { name: 'Tokyo', id: 'ChIJ51cu8IcbXWARiRtXIothAS4' },
    { name: 'Rome', id: 'ChIJu46S-ZZhLxMROG5lkwZ3D7k' },
];

async function init() {
    const { Place } = await google.maps.importLibrary('places');

    const heading = document.getElementById('heading');
    const summary = document.getElementById('summary');
    const expandedImageDiv = document.getElementById('expanded-image');
    const randomizeBtn = document.getElementById('randomize-btn');

    async function showRandomCityPhoto() {
        randomizeBtn.disabled = true;

        // Pick a random city
        const city = CITIES[Math.floor(Math.random() * CITIES.length)];

        try {
            const place = new Place({ id: city.id });
            await place.fetchFields({
                fields: ['displayName', 'photos', 'editorialSummary'],
            });

            heading.textContent = place.displayName ?? city.name;
            summary.textContent = place.editorialSummary ?? '';
            expandedImageDiv.innerHTML = '';

            if (place.photos && place.photos.length > 0) {
                // Pick one of the first 10 photos
                const maxPhotos = Math.min(10, place.photos.length);
                const randomIndex = Math.floor(Math.random() * maxPhotos);
                const photo = place.photos[randomIndex];

                const img = document.createElement('img');
                img.alt = `Photo of ${place.displayName ?? city.name}`;
                img.src = photo.getURI({ maxHeight: 800 });
                expandedImageDiv.appendChild(img);

                if (photo.authorAttributions.length) {
                    expandedImageDiv.appendChild(
                        createAttribution(photo.authorAttributions[0])
                    );
                }
            } else {
                expandedImageDiv.textContent =
                    'No photos available for this location.';
            }
        } catch (error) {
            console.error('Failed to fetch place details:', error);
            summary.textContent = 'Failed to load data.';
        }

        // Re-enable button after 2 seconds
        setTimeout(() => {
            randomizeBtn.disabled = false;
        }, 2000);
    }

    // Helper function to create attribution DIV.
    function createAttribution(attribution) {
        const attributionLabel = document.createElement('a');
        attributionLabel.classList.add('attribution-label');
        attributionLabel.textContent = attribution.displayName;
        attributionLabel.href = attribution.uri;
        attributionLabel.target = '_blank';
        attributionLabel.rel = 'noopener noreferrer';
        return attributionLabel;
    }

    randomizeBtn.addEventListener('click', () => void showRandomCityPhoto());

    // Initial load
    void showRandomCityPhoto();
}

void init();

CSS

/* 
 * Optional: Makes the sample page fill the window. 
 */
html,
body {
    height: 100%;
    margin: 0;
    padding: 0;
    font-family: 'Inter', sans-serif;
    background: linear-gradient(135deg, #1e1e2f, #252540);
    display: flex;
    align-items: center;
    justify-content: center;
}

#container {
    display: flex;
    background: rgba(255, 255, 255, 0.05);
    backdrop-filter: blur(16px);
    -webkit-backdrop-filter: blur(16px);
    border: 1px solid rgba(255, 255, 255, 0.1);
    border-radius: 20px;
    padding: 30px;
    max-width: 900px;
    width: 100%;
    box-sizing: border-box;
    box-shadow: 0 25px 50px rgba(0, 0, 0, 0.3);
    color: white;
}

.place-overview {
    flex: 1;
    display: flex;
    flex-direction: column;
    justify-content: center;
    margin-right: 40px;
}

#info {
    position: relative;
}

#heading {
    font-size: 2.5rem;
    font-weight: 700;
    margin-top: 0;
    margin-bottom: 15px;
    background: linear-gradient(90deg, #fff, #a5b4fc);
    -webkit-background-clip: text;
    -webkit-text-fill-color: transparent;
}

#summary {
    font-size: 1.1rem;
    line-height: 1.6;
    color: #cbd5e1;
}

#expanded-image {
    position: relative;
    width: 400px;
    height: 400px;
    border-radius: 16px;
    overflow: hidden;
    box-shadow: 0 15px 35px rgba(0, 0, 0, 0.4);
    transform: perspective(1000px) rotateY(-5deg);
    transition: transform 0.5s ease;
}

#expanded-image:hover {
    transform: perspective(1000px) rotateY(0deg) scale(1.02);
}

#expanded-image img {
    width: 100%;
    height: 100%;
    object-fit: contain;
}

#randomize-btn {
    margin-top: 20px;
    padding: 12px 24px;
    font-size: 1.1rem;
    font-weight: bold;
    color: white;
    background: linear-gradient(90deg, #4f46e5, #3b82f6);
    border: none;
    border-radius: 12px;
    cursor: pointer;
    transition:
        transform 0.2s,
        background 0.3s;
    box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
    font-family: 'Inter', sans-serif;
    align-self: flex-start;
}

#randomize-btn:hover:not(:disabled) {
    transform: scale(1.05);
}

#randomize-btn:disabled {
    background: #475569;
    cursor: not-allowed;
    opacity: 0.7;
}

.attribution-label {
    position: absolute;
    bottom: 10px;
    right: 10px;
    background-color: rgba(0, 0, 0, 0.6);
    color: white;
    padding: 6px 12px;
    border-radius: 20px;
    font-size: 0.8rem;
    text-decoration: none;
    backdrop-filter: blur(4px);
    transition: background-color 0.3s;
}

.attribution-label:hover {
    background-color: rgba(0, 0, 0, 0.8);
}

HTML

<html lang="en">
    <head>
        <title>Place Photos</title>
        <link
            href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"
            rel="stylesheet" />
        <link rel="stylesheet" type="text/css" href="./style.css" />
        <script type="module" src="./index.js"></script>
        <script>
            // prettier-ignore
            (g=>{var h,a,k,p="The Google Maps JavaScript API",c="google",l="importLibrary",q="__ib__",m=document,b=window;b=b[c]||(b[c]={});var d=b.maps||(b.maps={}),r=new Set,e=new URLSearchParams,u=()=>h||(h=new Promise(async(f,n)=>{await (a=m.createElement("script"));e.set("libraries",[...r]+"");for(k in g)e.set(k.replace(/[A-Z]/g,t=>"_"+t[0].toLowerCase()),g[k]);e.set("callback",c+".maps."+q);a.src=`https://maps.${c}apis.com/maps/api/js?`+e;d[q]=f;a.onerror=()=>h=n(Error(p+" could not load."));a.nonce=m.querySelector("script[nonce]")?.nonce||"";m.head.append(a)}));d[l]?console.warn(p+" only loads once. Ignoring:",g):d[l]=(f,...n)=>r.add(f)&&u().then(()=>d[l](f,...n))})({
                key: "AIzaSyA6myHzS10YXdcazAFalmXvDkrYCp5cLc8"
            });
        </script>
    </head>
    <body>
        <div id="container">
            <div class="place-overview">
                <div id="info">
                    <h1 id="heading"></h1>
                    <div id="summary"></div>
                </div>
                <button id="randomize-btn">Random City Photo</button>
            </div>
            <div id="expanded-image"></div>
        </div>
    </body>
</html>

Try Sample

Get photos

To get photos for a place, include the photos field in your fetchFields() request parameters. The resulting Place instance contains an array of up to 10 Photo objects, from which you may access images and their required attribution information. Call getURI() to return the source photo URI, using PhotoOptions to set the maximum height and/or width of the returned image. If you specify a value for both maxHeight and a maxWidth, the photo service will resize the image to the smaller of the two sizes, while maintaining the original aspect ratio. If no dimension is specified, the full-size image will be returned.

The Photo class exposes the following properties:

  • authorAttributions: An array of AuthorAttribution objects containing the required attribution text and URLs.
  • flagContentURI: A link where the user can flag a problem with the photo.
  • googleMapsURI: A link to show the photo on Google Maps.
  • heightPx: The height of the photo in pixels.
  • widthPx: The width of the photo in pixels.

The following example shows making a Place Details request for photos, calling getURI() on a photo instance to return the source URI for the image, then adding the first photo result to an img element (attributions are omitted for brevity):

const { Place } = await google.maps.importLibrary('places');

// Use a place ID to create a new Place instance.
const place = new Place({
    id: 'ChIJydSuSkkUkFQRsqhB-cEtYnw', // Woodland Park Zoo, Seattle WA
});

// Call fetchFields, passing the desired data fields.
await place.fetchFields({ fields: ['photos'] });

// Add the first photo to an img element.
const photoImg = document.getElementById('image-container');
photoImg.src = place.photos[0].getURI({maxHeight: 400});

Author attributions

When you display a photo, you must also display the author attributions for the photo. Use the AuthorAttribution class to return attributions. An attribution includes the author's name (displayName), a URI for their Google Maps profile (uri), and a URI for the author's photo (photoURI). The following snippet shows returning the displayName, uri, and photoURI for a place photo.

  let name = place.photos[0].authorAttributions[0].displayName;
  let url = place.photos[0].authorAttributions[0].uri;
  let authorPhoto = place.photos[0].authorAttributions[0].photoURI;