How to Integrate Bird.i APIs
with Google Maps

Tutorial: How to Integrate the Bird.i APIs with Google Maps

In this tutorial, you will be starting with an existing Google Maps map and adding an image overlay from Bird.i’s APIs. In the image below, you’ll be going from a map like the one on the left to one like the one on the right.

Some previous customers have then added even more functionality, to do some things such as:

  • add a button to toggle Bird.i imagery visibility
  • allow the user to choose which image to show from the entire catalog for a location
  • filter the visible image based on satellite provider

We will go step-by-step and explain how to integrate your map with our APIs, but you should have a few things before starting:

  1. An existing map using the Google Maps Javascript API. If you don’t already have one, you can instead start with the example map found at this link.
  2. A Google Maps API key. You can find out how to get one at this link.
  3. A Bird.i API token. You can find your token by logging into the Bird.i portal, clicking your name in the upper-right, and selecting profile management. If you don’t yet have a Bird.i account, you can sign up for one through our website here.

If you prefer to see a full working example instead of going step-by-step in a tutorial, you can find one at the link here.

Now let’s get started!

Tutorial overview

Here’s an overview of the steps required to access image tiles using our Image Service APIs:

  1. Hit the Catalog API to get a detailed list of metadata for all the images available at this location & zoom. The request URL is structured like this:
    https://catalog-api.hibirdi.com/catalog/lat/[latitude]/lng/[longitude]/zoom/[zoom]/token/[Bird.i token]/
  2. Hit the Image API to get a specific image tile over that location. The request URL is structured like this:
    https://image-api.hibirdi.com/image/id/[Image ID (from step 1)]/x/[x-coordinate]/y/[y-coordinate]/z/[zoom]?requestId=[Request ID (from step 1)]&token=[Bird.i token]

If you have the required info, then getting an image is really that easy.

Keep in mind, though, that this returns a single 256x256px tile, and you’ll almost definitely want more than that. Our APIs are designed to deliver up to 25 tiles in a 5×5 grid formation for any given catalog request. We apply this restriction in order to keep the load on our infrastructure from getting too heavy, as well as to prevent the use of our imagery as a basemap; our imagery is intended to supplement an existing basemap to offer richer, more up-to-date information.

Something else worth noting is that the Catalog API uses the spherical WGS 84 (EPSG:4326) coordinate system (using latitude and longitude) to identify a location, while the Image API uses the 2D Web Mercator (EPSG:3857) coordinate system (using X and Y) to identify a specific tile. Don’t be scared by those names; it’s not as complicated as it sounds! The main takeaway is that the Catalog API uses latitude & longitude, while the Image API uses X & Y. You don’t need to do the conversion yourself though; in the response from the Catalog API, we include the tile range you can use for your Image API request.

One more small thing to note: our Catalog API and Google’s Maps API both use the order [latitude, longitude]; this order is important, as mixing it up could give you the entirely wrong location or even not work at all!

Also, after you get the images back, you probably want to do something with them, right? So you’ll need to find the right spot to display them on the map!

With all that in mind, we can break down the whole process into 6 major steps:

  1. Get image metadata using the Catalog API
  2. Get an image tile using the Image API
  3. Display the tile on the map in the correct location
  4. Repeat steps 2-3 for each of 25 tiles
  5. Repeat steps 1-4 when we set the map to a new position

Let’s start at the beginning, shall we?

Step 1: Get image metadata

The first thing you need to do is find out whether there are images for a given location, and if there are, you’ll need some information about them. You’ll do this using the Bird.i Catalog API. Note that we’ll be using fetch to make requests in this example, but you can of course make the requests however you see fit. All the javascript you’ll be writing should go in a script tag in the same file as your map; if you’re working off of the Google Maps example linked above, just save their code in a file called index.html, and write our new code below the lines where the map is created. Remember to swap out [your token] for your Bird.i API token!

It’s also worth noting that Bird.i imagery is available at zoom levels 14 to 19, so in the following code, we’ve restricted the function to only continue if you’re within these zooms. For this reason, you should also consider changing the default zoom level of your map (at least while testing), so that you don’t have to zoom in each time to see if it’s working!

const TOKEN = [your token];

getTiles = () => {
  // Get current lat, lng, and zoom
  // from the existing map
  let lat = map.getCenter().lat();
  let lng = map.getCenter().lng();
  let zoom = map.getZoom();
  /*
  // Bird.i tiles are only available within zooms 14 to 19. If you request images
  // outside this range, you'll get an error
  */
  if (zoom < 14 || zoom > 19) {
    return;
  }
  // Build the request URL
  let catalogUrl =
    "https://catalog-api.hibirdi.com/catalog/lat/" + lat.toFixed(6) +
    "/lng/" + lng.toFixed(6) + "/zoom/" + zoom + "/token/" + TOKEN + "/";

  // Perform the request
  fetch(catalogUrl)
  .then(response => {
    return response.json();
  }).then(catalogResponse => {
    console.log("catalogResponse:");
    console.log(catalogResponse);
  }).catch(err => {
    console.log("Something went wrong with the catalog request:");
    console.log(err);
  });
}

// hacky way to run our function for now
setTimeout(function(){
  getTiles();
}, 1000);

Have a look at the code comments for an explanation of what each bit is doing, but we’re basically just building the request URL with some location info from our map and then sending the request. So far, you haven’t actually done anything with the response; you’re just logging it in the console. Having a look at what’s logged, you’ll see that what gets returned to you is a few min and max x and y coordinates and a big ol’ array with metadata for each of the image strips which cross the lat/lng you provided. With this data, you could provide functionality to allow a user to choose between available images, compare multiple images simultaneously, or much more, but in this tutorial, we’ll just be using the first (and most recent) image available to us. Specifically, there are two pieces of information that you’ll be using from the first element of the response: its id (which is the image ID) and its requestId. These will come in handy in the next step when you’re building the request URL to hit the Image API.

Step 2: Get the image tile

With the catalog returned, you now have everything you need to get your image. All you need to do is build the request URL and perform the request, but what good is an image if you can’t see it? With that in mind, let’s create an img tag and set our request URL as its src. In order to do this, you first need to add a wrapper HTML element in which you’ll place your img. Below is a simplified example of how your HTML should look; the important thing is that you’re creating a new div element after your map container with id="overlay":

<body>
  <div id="map"></div>
  <div id="overlay"></div>  <script>
    ...
  </script>
</body>

Now that you have somewhere to put the img, you can create it in your script:

const TOKEN = [your token];
const overlayDiv = document.getElementById('overlay');getTiles = () => {

// unchanged code - click to expand //
// Get current lat, lng, and zoom let lat = map.getCenter().lat(); let lng = map.getCenter().lng(); let zoom = map.getZoom(); /* // Bird.i tiles are only available within zooms 14 to 19. If you request images // outside this range, you'll get an error */ if (zoom < 14 || zoom > 19) { return; } // Build the request URL let catalogUrl = "https://catalog-api.hibirdi.com/catalog/lat/" + lat.toFixed(6) + "/lng/" + lng.toFixed(6) + "/zoom/" + zoom + "/token/" + TOKEN + "/";
// Perform the request fetch(catalogUrl) .then(response => { return response.json(); }).then(catalogResponse => { console.log("catalogResponse"); console.log(catalogResponse); // Get the the first catalog metadata item let firstCatalogItem = catalogResponse['items'][0]; /* // Get the x and y coordinates of the upper-left and bottom-right tiles // from the available 5x5 grid */ const {minTileX, minTileY, maxTileX, maxTileY} = catalogResponse; let imageUrl = "https://image-api.hibirdi.com/image/id/" + firstCatalogItem["id"] + "/x/" + minTileX + "/y/" + minTileY + "/z/" + zoom + "?requestId=" + firstCatalogItem["requestId"] + "&token=" + TOKEN; // Create an img element, with the src linking to the Bird.i image API let img = document.createElement('img'); img.src = imageUrl; img.style.position = "absolute"; // TEMPORARY: these hardcoded values for left and top // will be replaced in step 3. // Set on-screen position of the image img.style.left = "0px"; img.style.top = "0px"; // Set size, width & height img.style.width = "256px"; img.style.height = "256px"; // Add this tile to our overlay overlayDiv.appendChild(img); }).catch(err => { console.log("Something went wrong with the catalog request:"); console.log(err); }); } // hacky way to run our function for now setTimeout(function(){ getTiles(); }, 1000);

With this code in place, you should now be able to refresh your map and have a tile appear in the upper-left corner of the window. It’s not showing in the right position on the map yet (it should be above and left of center), but that’s up next!

Step 3: Position the image tile on the map

Let’s put this tile where it was always meant to be. This involves positioning and sizing it on the screen so it accurately matches the basemap it’s overlaying. For positioning, you need to find the top and left offsets to put the tile in the right spot. In order to do that in Google Maps, you’ll have to get the Lat/Lng value for the upper-left corner of your X/Y tile, then use that value to get the screen coordinates in pixels. For sizing, since Google Maps uses discrete zoom levels (i.e only integer values), your tile won’t need to change size; it can be hardcoded at 256 pixels. Here’s the code (remember to remove the old values for left and top):

// unchanged code - click to expand //
const TOKEN = [your token]; const overlayDiv = document.getElementById('overlay'); getTiles = () => { // Get current lat, lng, and zoom let lat = map.getCenter().lat(); let lng = map.getCenter().lng(); let zoom = map.getZoom(); /* // Bird.i tiles are only available within zooms 14 to 19. If you request images // outside this range, you'll get an error */ if (zoom < 14 || zoom > 19) { return; } // Build the request URL let catalogUrl = "https://catalog-api.hibirdi.com/catalog/lat/" + lat.toFixed(6) + "/lng/" + lng.toFixed(6) + "/zoom/" + zoom + "/token/" + TOKEN + "/"; // Perform the request fetch(catalogUrl) .then(response => { return response.json(); }).then(catalogResponse => { console.log("catalogResponse"); console.log(catalogResponse); // Get the the first catalog metadata item let firstCatalogItem = catalogResponse['items'][0]; /* // Get the x and y coordinates of the upper-left and bottom-right tiles // from the available 5x5 grid */ const {minTileX, minTileY, maxTileX, maxTileY} = catalogResponse; let imageUrl = "https://image-api.hibirdi.com/image/id/" + firstCatalogItem["id"] + "/x/" + minTileX + "/y/" + minTileY + "/z/" + zoom + "?requestId=" + firstCatalogItem["requestId"] + "&token=" + TOKEN;
// Create an img element, with the src linking to the Bird.i image API let img = document.createElement('img'); img.src = imageUrl; img.style.position = "absolute"; // Set on-screen position of the image let { pixelsX, pixelsY } = getTileScreenCoords(minTileX, minTileY, zoom); img.style.left = pixelsX + "px"; img.style.top = pixelsY + "px"; // Set width & height img.style.width = "256px"; img.style.height = "256px"; // Add this tile to our overlay overlayDiv.appendChild(img); }).catch(err => { console.log("Something went wrong with the catalog request:"); console.log(err); }); } getTileScreenCoords = (x, y, zoom) => { // Given tile coordinates, returns x and y pixel positions of the upper-left corner of the tile. let { lat, lng } = XYToLatLng(x, y, zoom); let projection = map.getProjection(); let bounds = map.getBounds(); let topRight = projection.fromLatLngToPoint(bounds.getNorthEast()); let bottomLeft = projection.fromLatLngToPoint(bounds.getSouthWest()); let scale = Math.pow(2, map.getZoom()); let worldPoint = projection.fromLatLngToPoint(new google.maps.LatLng( lat, lng )); let pixelsX = Math.round((worldPoint.x - bottomLeft.x) * scale * 100) / 100; let pixelsY = Math.round((worldPoint.y - topRight.y) * scale * 100) / 100; return { pixelsX, pixelsY }; } XYToLatLng = (x, y, zoom) => { // Converts x and y tile coordinates to lat and lng coordinates. This returns the NW-corner of the tile. let n = Math.PI - (2 * Math.PI * y) / Math.pow(2, zoom); let lat = -1 * (radiansToDegrees(Math.atan(Math.sinh(n))) * -1); let lng = x / Math.pow(2, zoom) * 360 - 180; return { lat, lng }; } radiansToDegrees = radiansVal =>{ return radiansVal * (180 / Math.PI); } // hacky way to run our function for now setTimeout(function(){ getTiles(); }, 1000);

After adding these bits, refreshing your map should give you a tile overlaid in the correct position at the right size! This is a working miniature version of how the final system will work, but we need to add some extra functionality to handle when the map is repositioned or zoomed.

Step 4: Repeat steps to get more tiles at once

It’s time to scale this behaviour out to give us a bigger image. Basically all you’ll be doing is looping through the same functionality 25 times to create a 5×5 grid of tiles that join together to form one big image. Take special note of this 5×5 caveat: like we mentioned before, our APIs will provide only the 5×5 grid of tiles surrounding the location of your catalog request, so if you try to access an image which is outside that grid (i.e. more than 2 tiles away from the centre tile in any direction), you will get an error. For this step, make sure to first remove the lines between setting minTileX, minTileY, etc. and setting img.style.left, as it’s being changed here. Now on to the code:

// unchanged code - click to expand //
const TOKEN = [your token]; const overlayDiv = document.getElementById('overlay'); getTiles = () => { // Get current lat, lng, and zoom let lat = map.getCenter().lat(); let lng = map.getCenter().lng(); let zoom = map.getZoom(); /* // Bird.i tiles are only available within zooms 14 to 19. If you request images // outside this range, you'll get an error */ if (zoom < 14 || zoom > 19) { return; } // Build the request URL let catalogUrl = "https://catalog-api.hibirdi.com/catalog/lat/" + lat.toFixed(6) + "/lng/" + lng.toFixed(6) + "/zoom/" + zoom + "/token/" + TOKEN + "/";
// Perform the request fetch(catalogUrl) .then(response => { return response.json(); }).then(catalogResponse => { console.log("catalogResponse"); console.log(catalogResponse); // Get the the first catalog metadata item let firstCatalogItem = catalogResponse['items'][0]; /* // Get the x and y coordinates of the upper-left and bottom-right tiles // from the available 5x5 grid */ const {minTileX, minTileY, maxTileX, maxTileY} = catalogResponse; // For each of 5 columns for (let i = minTileX; i <= maxTileX; i++) { // For each of 5 rows for (let j = minTileY; j <= maxTileY; j++) { let imageUrl = "https://image-api.hibirdi.com/image/id/" + firstCatalogItem["id"] + "/x/" + i + "/y/" + j + "/z/" + zoom + "?requestId=" + firstCatalogItem["requestId"] + "&token=" + TOKEN; // Create an img element, with the src linking to the Bird.i image API let img = document.createElement('img'); img.src = imageUrl; img.style.position = "absolute"; // Set on-screen position of the image let { pixelsX, pixelsY } = getTileScreenCoords(i, j, zoom); img.style.left = pixelsX + "px"; img.style.top = pixelsY + "px"; // Set width & height img.style.width = "256px"; img.style.height = "256px"; // Add this tile to our overlay overlayDiv.appendChild(img); } } }).catch(err => { console.log("Something went wrong with the catalog request:"); console.log(err); }); } // unchanged code - click to expand //
getTileScreenCoords = (x, y, zoom) => { // Given tile coordinates, returns x and y pixel positions of the upper-left corner of the tile. let { lat, lng } = XYToLatLng(x, y, zoom); let projection = map.getProjection(); let bounds = map.getBounds(); let topRight = projection.fromLatLngToPoint(bounds.getNorthEast()); let bottomLeft = projection.fromLatLngToPoint(bounds.getSouthWest()); let scale = Math.pow(2, map.getZoom()); let worldPoint = projection.fromLatLngToPoint(new google.maps.LatLng( lat, lng )); let pixelsX = Math.round((worldPoint.x - bottomLeft.x) * scale * 100) / 100; let pixelsY = Math.round((worldPoint.y - topRight.y) * scale * 100) / 100; return { pixelsX, pixelsY }; } XYToLatLng = (x, y, zoom) => { // Converts x and y tile coordinates to lat and lng coordinates. This returns the NW-corner of the tile. let n = Math.PI - (2 * Math.PI * y) / Math.pow(2, zoom); let lat = -1 * (radiansToDegrees(Math.atan(Math.sinh(n))) * -1); let lng = x / Math.pow(2, zoom) * 360 - 180; return { lat, lng }; } radiansToDegrees = radiansVal =>{ return radiansVal * (180 / Math.PI); } // hacky way to run our function for now setTimeout(function(){ getTiles(); }, 1000);

Now you’re almost done! Refreshing the page should give you a 5×5 grid of tiles, which have joined nicely to form one primary image, sized and positioned to match the basemap. All that’s left is to…

Step 5: Repeat steps to get tiles for each new position

The last major step is to call your function in the right places, and to accommodate it being called multiple times. You’ll want to get new tiles when the map first loads, then again each time the map is in a new position; the latter should be done when you stop interacting with the map, as opposed to when the map moves (so that it isn’t sending off a bunch of requests for tiles you won’t see, but rather just when it is ready to display them). If you just keep calling your function though, you’ll end up with some ugly stacking of tiles that shouldn’t go together, so you need to remove the tiles that are left over from the last time you got tiles before getting new ones. One more time, with feeling!

To call our function at the right moments, you need to add a couple event listeners. These will be added not to code you’ve added today, but to the function where the map itself is initialised. In the example here, we’ve added them to the map initialisation code in the example provided by Google.

var map;
function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: -34.397, lng: 150.644},
    zoom: 14
  });

  // Whenever the map is idle, load Bird.i tiles for the new location
  map.addListener('idle', function() {
    getTiles();
  });
  // Whenever the map is moved, remove any existing Bird.i tiles
  map.addListener('bounds_changed', function() {
    removeTiles();
  });

}

That leaves writing the function to remove stale tiles, as well as calling it when we begin getting new tiles. Also, we should comment out (or remove entirely) the hacky way we were calling our getTiles() function, since we’re calling it properly with event listeners now.

const TOKEN = [your token];
const overlayDiv = document.getElementById('overlay');

getTiles = () => {
  // Remove any existing tiles before loading new ones
  removeTiles();
  // Get current lat, lng, and zoom
  let lat = map.getCenter().lat();
  let lng = map.getCenter().lng();
  let zoom = map.getZoom();

// unchanged code - click to expand //
/* // Bird.i tiles are only available within zooms 14 to 19. If you request images // outside this range, you'll get an error */ if (zoom < 14 || zoom > 19) { return; } // Build the request URL let catalogUrl = "https://catalog-api.hibirdi.com/catalog/lat/" + lat.toFixed(6) + "/lng/" + lng.toFixed(6) + "/zoom/" + zoom + "/token/" + TOKEN + "/"; // Perform the request fetch(catalogUrl) .then(response => { return response.json(); }).then(catalogResponse => { console.log("catalogResponse"); console.log(catalogResponse); // Get the the first catalog metadata item let firstCatalogItem = catalogResponse['items'][0]; /* // Get the x and y coordinates of the upper-left and bottom-right tiles // from the available 5x5 grid */ const {minTileX, minTileY, maxTileX, maxTileY} = catalogResponse; // For each of 5 columns for (let i = minTileX; i <= maxTileX; i++) { // For each of 5 rows for (let j = minTileY; j <= maxTileY; j++) { let imageUrl = "https://image-api.hibirdi.com/image/id/" + firstCatalogItem["id"] + "/x/" + i + "/y/" + j + "/z/" + zoom + "?requestId=" + firstCatalogItem["requestId"] + "&token=" + TOKEN; // Create an img element, with the src linking to the Bird.i image API let img = document.createElement('img'); img.src = imageUrl; img.style.position = "absolute"; // Set on-screen position of the image let { pixelsX, pixelsY } = getTileScreenCoords(i, j, zoom); img.style.left = pixelsX + "px"; img.style.top = pixelsY + "px"; // Set width & height img.style.width = "256px"; img.style.height = "256px"; // Add this tile to our overlay overlayDiv.appendChild(img); } }
}).catch(err => { console.log("Something went wrong with the catalog request:"); console.log(err); }); } removeTiles = () => { // Removes any tiles from the overlay div var child = overlayDiv.lastElementChild; while (child) { overlayDiv.removeChild(child); child = overlayDiv.lastElementChild; } } getTileScreenCoords = (x, y, zoom) => { // Given tile coordinates, returns x and y pixel positions of the upper-left corner of the tile. // unchanged code - click to expand //
let { lat, lng } = XYToLatLng(x, y, zoom); let projection = map.getProjection(); let bounds = map.getBounds(); let topRight = projection.fromLatLngToPoint(bounds.getNorthEast()); let bottomLeft = projection.fromLatLngToPoint(bounds.getSouthWest()); let scale = Math.pow(2, map.getZoom()); let worldPoint = projection.fromLatLngToPoint(new google.maps.LatLng( lat, lng )); let pixelsX = Math.round((worldPoint.x - bottomLeft.x) * scale * 100) / 100; let pixelsY = Math.round((worldPoint.y - topRight.y) * scale * 100) / 100; return { pixelsX, pixelsY }; } XYToLatLng = (x, y, zoom) => { // Converts x and y tile coordinates to lat and lng coordinates. This returns the NW-corner of the tile. let n = Math.PI - (2 * Math.PI * y) / Math.pow(2, zoom); let lat = -1 * (radiansToDegrees(Math.atan(Math.sinh(n))) * -1); let lng = x / Math.pow(2, zoom) * 360 - 180; return { lat, lng }; }
radiansToDegrees = radiansVal =>{ return radiansVal * (180 / Math.PI); } // // hacky way to run our function for now // setTimeout(function(){ // getTiles(); // }, 1000);

That’s the heavy lifting done! Now tiles will actively load whenever you move the map to a new position. That just leaves…

Surprise step 6: Quality of life tweaks

Although the map works as we want it to, there are a couple of simple things you can do to improve it. First, you can add an optional parameter to the Catalog API to only return image results which fully cover your 25 tile area of interest; otherwise, you may see blank tiles or errors if viewing an area that lies across the edge of an image strip. Second, you can add some CSS styles to have clicks fall through the overlay (so they don’t block you from using the basemap), and a couple more so that tiles don’t show beyond the edge of your map (if your viewport is smaller than the size of the 5×5 tile grid). Here’s the first part – remember to replace your old catalogURL with this one:

// unchanged code - click to expand //
// Whenever the map stops moving, load Bird.i tiles for the new location map.on('moveend', function() { getTiles(); }); // Whenever the map is moved, remove any existing Bird.i tiles map.on('move', function() { removeTiles(); }); const TOKEN = [your token]; const overlayDiv = document.getElementById('overlay'); getTiles = () => { // Remove any existing tiles before loading new ones removeTiles(); // Get current lat, lng, and zoom let lat = map.getCenter().lat(); let lng = map.getCenter().lng(); let zoom = map.getZoom(); /* // Bird.i tiles are only available within zooms 14 to 19. If you request images // outside this range, you'll get an error */
if (zoom < 14 || zoom > 19) { return; } // Build the request URL let catalogUrl = "https://catalog-api.hibirdi.com/catalog/lat/" + lat.toFixed(6) + "/lng/" + lng.toFixed(6) + "/zoom/" + zoom + "/token/" + TOKEN + "/?fullCoverage=true"; // Perform the request fetch(catalogUrl) // unchanged code - click to expand //
.then(response => { return response.json(); }).then(catalogResponse => { console.log("catalogResponse"); console.log(catalogResponse); // Get the the first catalog metadata item let firstCatalogItem = catalogResponse['items'][0]; /* // Get the x and y coordinates of the upper-left and bottom-right tiles // from the available 5x5 grid */ const {minTileX, minTileY, maxTileX, maxTileY} = catalogResponse; // For each of 5 columns for (let i = minTileX; i <= maxTileX; i++) { // For each of 5 rows for (let j = minTileY; j <= maxTileY; j++) { let imageUrl = "https://image-api.hibirdi.com/image/id/" + firstCatalogItem["id"] + "/x/" + i + "/y/" + j + "/z/" + zoom + "?requestId=" + firstCatalogItem["requestId"] + "&token=" + TOKEN; // Create an img element, with the src linking to the Bird.i image API let img = document.createElement('img'); img.src = imageUrl; img.style.position = "absolute"; // Set on-screen position of the image let { pixelsX, pixelsY } = getTileScreenCoords(i, j, zoom); img.style.left = pixelsX + "px"; img.style.top = pixelsY + "px"; // Set width & height img.style.width = "256px"; img.style.height = "256px"; // Add this tile to our overlay overlayDiv.appendChild(img); } } }).catch(err => { console.log("Something went wrong with the catalog request:"); console.log(err); }); } removeTiles = () => { // Removes any tiles from the overlay div var child = overlayDiv.lastElementChild; while (child) { overlayDiv.removeChild(child); child = overlayDiv.lastElementChild; } } getTileScreenCoords = (x, y, zoom) => { // Given tile coordinates, returns x and y pixel positions of the upper-left corner of the tile. let { lat, lng } = XYToLatLng(x, y, zoom); let projection = map.getProjection(); let bounds = map.getBounds(); let topRight = projection.fromLatLngToPoint(bounds.getNorthEast()); let bottomLeft = projection.fromLatLngToPoint(bounds.getSouthWest()); let scale = Math.pow(2, map.getZoom()); let worldPoint = projection.fromLatLngToPoint(new google.maps.LatLng( lat, lng )); let pixelsX = Math.round((worldPoint.x - bottomLeft.x) * scale * 100) / 100; let pixelsY = Math.round((worldPoint.y - topRight.y) * scale * 100) / 100; return { pixelsX, pixelsY }; } XYToLatLng = (x, y, zoom) => { // Converts x and y tile coordinates to lat and lng coordinates. This returns the NW-corner of the tile. let n = Math.PI - (2 * Math.PI * y) / Math.pow(2, zoom); let lat = -1 * (radiansToDegrees(Math.atan(Math.sinh(n))) * -1); let lng = x / Math.pow(2, zoom) * 360 - 180; return { lat, lng }; } radiansToDegrees = radiansVal =>{ return radiansVal * (180 / Math.PI); } // // hacky way to run our function for now // setTimeout(function(){ // getTiles(); // }, 1000);

Which leaves the second part, some CSS. You can add this to a separate CSS file or just put it right in the style element in your html file:

#overlay {
  /* clicks fall through to basemap */
  pointer-events: none;
  /* don't let tiles go beyond edges of window */
  width: 100vw;
  height: 100vh;
  overflow: hidden;
  position: absolute;
  top: 0;
  left: 0;
}

That’s it; where you go from here is up to you!

For an example of how to toggle the tiles’ visibility, as well as a full working example of everything in this tutorial, check out this repository on Github.

Finally, check out links to some of our other integration tutorials below, and if you have any questions, feel free to reach out using the contact link at the bottom of the page!

WRITTEN BY

Sam McCants

Software Engineer at Bird.i