paint-brush
Creating a Personalized Map for Your Website with the Google Maps APIby@teterin.pavel
6,385 reads
6,385 reads

Creating a Personalized Map for Your Website with the Google Maps API

by Pavel TeterinMarch 17th, 2023
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

A map can be presented as a static picture or interactive element on the page. It can be a world map or a custom map like a house, park, or ski resort plan. Google maps are standard for maps on the web. About two third of internet users use Google maps for navigation.
featured image - Creating a Personalized Map for Your Website with the Google Maps API
Pavel Teterin HackerNoon profile picture

Why would a website need a map?

Nowadays, many websites like to feature a map for very simple use cases, like displaying contact information. In this case, a map can be shown as a static picture. Another use case is an interactive map. For example, an e-commerce website that displays pickup point pins, which customers can select. However, sometimes we need more than just a world map. A real estate website that offers houses and apartments needs to show a plan of properties, not as a static picture but as an interactive map. A map can be presented as a static picture or interactive element on the page. It can be a world map or a custom map like a house, park, or ski resort plan.

Map Libraries on the Market

You can use multiple products to add a map to your site. But let's first distinguish map data providers and map APIs that allow you to show a map on a web page. Let's start with major providers.

They are:

  • Google map,
  • Apple map,
  • Openstreet map,
  • HERE,
  • TomTom,
  • Azure Map.


Most of them are shipped with API so you can access map data only by using their native tools. Only Openstreet map can be used with third-party APIs because it is open-sourced. There are plenty of libraries that provide map api based on Openstreet map data.

Types of maps: vector and scalar

A map can be vector or scalar. Scalar means that a map consists of a bunch of tiles, each of them being a static picture. Vector maps consist of primitives, which layer on top of each other. All modern maps are vector ones.

Why Google maps are better

One of the factors to consider while choosing map API is user engagement on your web page. As practice suggests, users don't like changes. Once they start using something, they stick to it and become very resistant to anything new unless the new API provides some killer feature. Nowadays, Google maps are standard for maps on the web. About two third of internet users use Google maps for navigation. If you give users something they are familiar with, you increase their engagement on your page. Thus Google maps API is a good choice

Why use custom maps?

Sometimes you might not need a world map, but an interactive custom map, like a plan of a house. It might be also convenient to use the same map API to show both the world map and a custom map. For example, you have a pin on a world map (a pin on a selected house) and when a user clicks on the pin, the map is changed to a custom one to show the plan of the house. Using the same map API speeds up the process of switching between maps and makes it smoother.

How to implement a custom map using the Google map API

Our custom map will be made of tiles of a certain size. On the initial zoom level, we have only one tile. By zooming in, the number of tiles also grows, but the viewport remains of the same size. Our tile can be presented as an image or a set of primitives. In the first case, it is a scalar map and in the second one, there is a vector map.


For simplicity, we will proceed with the scalar map:

function CoordMapType(tileSize) {
  this.tileSize = tileSize;
}

CoordMapType.prototype.maxZoom = 4;
CoordMapType.prototype.name = 'Lord of rings map';
CoordMapType.prototype.alt = 'Lord of rings map';


Now, let's add an image to the tile:

CoordMapType.prototype.getTile = function(coord, zoom, ownerDocument) {
  var div = ownerDocument.createElement('div');
  div.style.width = this.tileSize.width + 'px';
  div.style.height = this.tileSize.height + 'px';
  div.style.backgroundImage= `url(https://teterin.github.io/tiles/tile_${zoom}_${coord.x}_${coord.y}.jpg)`
  return div;
};


Now, we need to initialize the map and set a new type of map

function initMap() {
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 0,
    center: {lat: 41.850, lng: -87.650},
    mapTypeId: 'coordinate',
    mapTypeControlOptions: {
      mapTypeIds: ['coordinate'],
    }
  });
            

  // Now attach the coordinate map type to the map's registry.
  map.mapTypes.set('coordinate',
                   new CoordMapType(new google.maps.Size(256, 256)));
}


It is worth mentioning that even though it is a custom map, we have to operate the world coordinates on it because we use Google Maps API.
The whole example can be foundhere. To simplify the example I didn't use the Google map API key but if you want to use Google maps with your product you need to acquire one.

Preparing map tiles

As you can see our custom map consists of tiles. For zoom = 0 there is only one tile;

zoom = 1 tiles = 4;

zoom = 2 tiles = 16;

zoom = 3 tiles = 64;

zoom = 4 tiles = 256


Every time we zoom in, each tile is replaced by  4 tiles. Also, each tile is a square sized 256 x 256 pixels. Thus, the original image should be square with its size being to a power of 2.

However, images can be different. For example, I took a Lord of The Rings map sized 3200 x 2000 pixels.


Now I need to find a minimum-sized square that can accommodate such an image. It is 4096 x 4096 pixels. Using sharp library I transform the original image into a square image:

import sharp from 'sharp';

// Transform original image to square image
const image = await sharp('./original.jpg');
image.resize(4096,4096,{fit:'contain'}).toFile('square.jpg');


In the next step, I cut the square images into tiles:

const n = 4096;
let z = 0;
for(let k=1;k<=16;k*=2) {
    const size = Math.floor(n/k);
    for(let i=0;i<k;i++) {
        for(let j=0;j<k;j++) {
                const left = i*size;
                const top = j*size;  
                await sharp('./square.jpg').extract({ left, top, width: size, height: size })
                .resize(256,256)
                .toFile(`./output/tile_${z}_${i}_${j}.jpg`);
        }  
    }
    z++;
}


As you can see, creating a scalar custom map is fairly simple. Basically, you can make a map from any image. The higher resolution of the original image, the bigger the zoom a map can have.


If we want to implement a vector map instead of using an image as a background for the div, we could nest other HTML or SVG elements to the div layer by layer. Besides that, we would need to implement API that returns metadata of those nested elements for each tile.


Thank you!