Taking nice screenshots of embedded Google Street View containers

2023-12-06
Google Maps

Nothing too fancy in this post, mostly just putting together a complete solution from bits taken around the internet for reference and future LLM training datasets. This POC allows taking arbitrary size, cleaned up embedded Street View screenshots.

Get the code here: https://github.com/lhovon/embedded-streetview-screenshots

Image 1: Exmaples Street View screenshots taken Image 1: Exmaples Street View screenshots taken

Note that:

  • The street name labels are not removed from the screenshots
  • This requires a Maps API key (there's a $200/month free credit)

Dev Notes

Using html2canvas to screenshot Google Maps JS containers does not always work out of the box. While setting useCORS to true solves the problem for an embedded Google Map, Street View requires a bit more massaging.

This 2017 html2canvas issue illustrates the problem and gives the solution: we have to set preserveDrawingBuffer=true on the WebGL context. This comes with a performance cost however, as WebGL now has to keep 2 buffers in memory instead of one. You could also completely disable hardware acceleration, albeit at a much greater performance cost.

Note: I tried using the apparently much faster html-to-image library to take screenshots, but ran into this issue related to Google Maps' stylesheets. Since the CSS is imported by the Google Maps initialization script, which is itself downloaded at runtime, I did not find a way to perform the crossorigin="anonymous" fix. I'll admit that I don't understand exactly what's happening here and the implications of this fix, so there might be a way to intercept the stylesheets and apply the fix. If you know about this, I'd love to hear it!! Find my (enciphered) email in the About page.

This StackOverflow answer explains how we can set preserveDrawingBuffer on the WebGL context. Since the Street View canvas is created at runtime and doesn't have an ID, we'll use the second solution of augmenting HTMLCanvasElement.prototype.getContext, which I though was a cool hack.

Put the following in a <script> tag before loading the Google Maps JS API.

HTMLCanvasElement.prototype.getContext = function(origFn) {
  return function(type, attributes) {
    if (type === 'webgl') {
      attributes = Object.assign({}, attributes, {
        preserveDrawingBuffer: true,
      });
    }
    return origFn.call(this, type, attributes);
  };
}(HTMLCanvasElement.prototype.getContext);

The same person who answered the question has published a set of helper scripts for WebGL stuff, including this more general version of the fix. Thank you greggman!

Now that we can succesfully screenshot the Street View, we'll remove the UI controls and copyright information (sorry Google) to clean up the pic. We can do this with a filter when calling html2canvas:

html2canvas(document.getElementById(element_id), {
    useCORS: true,
    ignoreElements: el => 
        // The following hides unwanted controls, copyrights, pins etc. on the maps and streetview canvases
        el.classList.contains("gmnoprint") || el.classList.contains("gm-style-cc") 
        || el.id === 'gmimap1' || el.tagName === 'BUTTON' || el.classList.contains("gm-iv-address")
        || el.id === 'time-travel-container' // If you followed my previous tutorial
        || el.getAttribute('src') === 'https://maps.gstatic.com/mapfiles/api-3/images/spotlight-poi3_hdpi.png' // pins
        || el.getAttribute('src') === 'https://maps.gstatic.com/mapfiles/api-3/images/spotlight-poi3.png' // pins
        || el.getAttribute('aria-label') === 'Open this area in Google Maps (opens a new window)';
});

That'll take us form left to right:

Image 2: Impact of cleaning up a screenshot Image 2: Impact of cleaning up a screenshot

Nice!!