How to scale an overlay image based on the Google Maps zoom level

610 Views Asked by At

I am trying to paste an overlay image on a static google map screenshot. Here is the code snippet -

import requests
from PIL import Image
import io

API_KEY = '***'

plot_center =  [75.49927187059548, 26.613400716542262]

zoom = 18
image_size = "640x480"

# Download Google image
url = f"https://maps.googleapis.com/maps/api/staticmap?center={plot_center[1]},{plot_center[0]}&zoom={zoom}&size={image_size}&key={API_KEY}&maptype=satellite"
response = requests.get(url)
google_image = Image.open(io.BytesIO(response.content))


# Download the overlay image
response = requests.get("https://storage.googleapis.com/kawa-public/4ed05009-8a61-4eb9-8b17-faa8d0d65ae3/kvi/kvi-s2-2023-07-03T15-01-25.838Z.png")
overlay_image = Image.open(io.BytesIO(response.content))


output_image = Image.new("RGBA", google_image.size)
output_image.paste(google_image, (0, 0))

overlay_position = (
    (google_image.width - overlay_image.width) // 2,
    (google_image.height - overlay_image.height) // 2,
)

output_image.paste(overlay_image, overlay_position, mask=overlay_image)
output_image.save("output_image.png")

The current output just pastes the overlay image to the center of the map which -

enter image description here

I want the overlay image to cover the farm plot in which it is currently in.

2

There are 2 best solutions below

6
Gugu72 On BEST ANSWER

Google Maps uses a Mercator projection, so it uses this formula:

meters_per_pixel = 156543.03392 * Math.cos(latLng.lat() * Math.PI / 180) / Math.pow(2, zoom)

We can deduct from this formula that for a given location, the only variable is the zoom, and therefore the meters per pixel are inversely proportional to 2^zoom.

You can figure out a correct factor of the size of your overlay image for a given zoom (let's say 18 for precision), then divide that factor by 2 for each unzoom operation.

With some trial and error, I found that 3.8 is a correct overlay zoom factor for a zoom of 18, then you can divide it by 2 for each zoom less, giving this code:

# ...
overlay_image = Image.open(io.BytesIO(response.content))
factor = 3.8 / (2 ** (18 - zoom))
overlay_image = overlay_image.resize(
    (int(overlay_image.width * factor), int(overlay_image.height * factor))
)
# ...
3
Alireza Roshanzamir On

Since you specified the zoom level and map image size as hard-coded values, you can only scale your overlay image with an equally hard-coded scale factor:

import requests
from PIL import Image
import io

API_KEY = '***'

plot_center =  [75.49927187059548, 26.613400716542262]

zoom = 18
image_size = "640x480"

# Download Google image
url = f"https://maps.googleapis.com/maps/api/staticmap?center={plot_center[1]},{plot_center[0]}&zoom={zoom}&size={image_size}&key={API_KEY}&maptype=satellite"
response = requests.get(url)
google_image = Image.open(io.BytesIO(response.content))


# Download the overlay image
response = requests.get("https://storage.googleapis.com/kawa-public/4ed05009-8a61-4eb9-8b17-faa8d0d65ae3/kvi/kvi-s2-2023-07-03T15-01-25.838Z.png")
overlay_image = Image.open(io.BytesIO(response.content))
scale_factor = 4  # This is the hard coded scale factor.
overlay_image = overlay_image.resize((overlay_image.width * scale_factor, overlay_image.height * scale_factor), resample=Image.Resampling.NEAREST)

output_image = Image.new("RGBA", google_image.size)
output_image.paste(google_image, (0, 0))

overlay_position = (
    (google_image.width - overlay_image.width) // 2,
    (google_image.height - overlay_image.height) // 2,
)

output_image.paste(overlay_image, overlay_position, mask=overlay_image)
output_image.save("output_image.png")

The result obtained is:

enter image description here

Note: Suppose your map image size, overlay image size, zoom level, and overlay image location in the map are dynamic. In that case, you should dynamically guess the scale factor and overlay offset (top and left in the map image) by image processing or machine learning techniques.