Skip to content
← Back to Workflows
workflowsBeginner· 9 min read

How to Download Building Footprints: Microsoft Global Buildings in Python

Get the outline of every building in your area from the free Microsoft Global ML Building Footprints, find the right file from a quadkey, cut it to your box, count and measure the buildings, and compare the count with OpenStreetMap.

A building footprint is the outline of a building seen from above. Footprints give you the number of buildings, the built-up area, a density, and a base for a population or a damage estimate. They are among the most asked-for layers in a settlement or a flood project, and in India, with its huge number of small buildings, they are hard to get from any one official source.

Where footprints come from

SourceHow it was madeThings to know
OpenStreetMapVolunteers draw themCompleteness varies from village to village. The OSM guide shows how to download them
Microsoft Global ML Building FootprintsA machine learning model finds buildings in satellite imagesCovers the world. Outlines have no height or use in most places. Free under the Open Database Licence
Google Open BuildingsThe same idea with a Google modelCovers Africa, South Asia, Southeast Asia and Latin America. Files are downloaded from the project site by region, and the data is also in Google Earth Engine
Overture MapsMerges OpenStreetMap, Microsoft, Google and other sourcesOne layer, in a cloud-friendly format. The files are found through the Overture website

This guide uses the Microsoft dataset because its files are open on the web with no account, and compares it with OpenStreetMap.

The files: quadkeys

The Microsoft footprints are cut into files by a grid called a quadkey grid. The world is divided into four squares, each square into four, and so on. Each square gets a name made of the digits 0 to 3, one digit for every division. The more digits, the smaller the square. The dataset uses squares of level 9, which are about 78 km wide at the equator and a little narrower in Punjab.

You need the quadkey of your place, and a list of files that says which quadkeys have data. The list is a CSV file with the columns Location, QuadKey, Url, Size and UploadDate.

You need Python with requests, geopandas and shapely.

import csv
import io
import math
import requests

# Step 1: the list of all files of the dataset. It is a CSV file of about 7 MB.
HEADERS = {"User-Agent": "timeandspatial-guide/1.0 (write your email address here)"}
links_address = "https://minedbuildings.z5.web.core.windows.net/global-buildings/dataset-links.csv"
links = list(csv.DictReader(io.StringIO(requests.get(links_address, headers=HEADERS, timeout=120).text)))
print(len(links), "files in the list | columns:", list(links[0].keys()))

# Step 2: how many are for India?
india = [row for row in links if row["Location"] == "India"]
print(len(india), "files for India")
30344 files in the list | columns: ['Location', 'QuadKey', 'Url', 'Size', 'UploadDate']
667 files for India

The list has one row for each quadkey square. Next, work out the quadkey for your place.

# Step 1: the quadkey of a point at a given level. The point is turned into a tile number, and the number into digits.
def quadkey(latitude, longitude, level):
    n = 2 ** level
    x = int((longitude + 180) / 360 * n)
    y = int((1 - math.log(math.tan(math.radians(latitude)) + 1 / math.cos(math.radians(latitude))) / math.pi) / 2 * n)
    key = ""
    for i in range(level, 0, -1):
        digit = 0
        mask = 1 << (i - 1)
        if x & mask:
            digit += 1
        if y & mask:
            digit += 2
        key += str(digit)
    return key

# Step 2: the quadkey of Jagraon at level 9, and the file of the list that has it
key = quadkey(30.79, 75.47, 9)
file_row = [row for row in india if row["QuadKey"] == key][0]
print("quadkey:", key, "| file size:", file_row["Size"], "| uploaded:", file_row["UploadDate"])
quadkey: 123121013 | file size: 63.1MB | uploaded: 2026-02-23

If your place is near the edge of a square, a box can cross into the next quadkey. The next step reads only one square, so check that the box is inside it, or repeat with the neighbour.

Download and cut to your box

The file is a compressed text file of about 65 MB. Each line is one building as a GeoJSON feature. Downloading it takes less than a minute. Reading it takes longer, because the square holds hundreds of thousands of buildings.

import gzip
import json
import time
import geopandas as gpd
from shapely.geometry import shape, box

# Step 1: your box, as west, south, east, north. The same box as in the other guides.
study_area = box(75.40, 30.74, 75.55, 30.84)

# Step 2: download the file
start = time.time()
data = requests.get(file_row["Url"], headers=HEADERS, timeout=600).content
print("downloaded:", round(len(data) / 1e6, 1), "MB in", round(time.time() - start), "seconds")

# Step 3: unpack it. Each line is one building.
lines = gzip.decompress(data).decode("utf8").splitlines()
print("buildings in the whole quadkey square:", f"{len(lines):,}")
print("first line:", lines[0][:200])
downloaded: 66.2 MB in 19 seconds
buildings in the whole quadkey square: 667,588
first line: {"type": "Feature", "properties": { "height": -1.0, "confidence": -1.0},"geometry": {"type": "Polygon","coordinates": [[[75.23441372265944, 30.90275075438209], [75.23437620128779, 30.90275165794688], 

The first line shows the form of the data: a polygon for the outline and two properties, height and confidence. Both are -1, which means unknown. The footprints carry no height and no use.

# Step 1: keep the buildings that touch your box. This reads every line, so it takes a little while.
start = time.time()
shapes, properties = [], []
for line in lines:
    feature = json.loads(line)
    outline = shape(feature["geometry"])
    if outline.intersects(study_area):
        shapes.append(outline)
        properties.append(feature["properties"])
print("read all lines in", round(time.time() - start), "seconds")

# Step 2: a GeoDataFrame, which is a table with a geometry column. The outlines are in longitude and latitude.
buildings = gpd.GeoDataFrame(properties, geometry=shapes, crs="EPSG:4326")
print("buildings in the box:", f"{len(buildings):,}")
read all lines in 38 seconds
buildings in the box: 25,665

Measure them

An area in degrees means nothing, so reproject to the UTM zone of the place first.

# Step 1: reproject to UTM zone 43 north (metres) and calculate the area of every footprint
buildings_utm = buildings.to_crs(32643)
buildings_utm["area_m2"] = buildings_utm.area

# Step 2: the typical building, and the total
print(buildings_utm["area_m2"].describe().round(1).to_string())

# Step 3: the share of the box that is covered by building footprints
box_area = gpd.GeoSeries([study_area], crs="EPSG:4326").to_crs(32643).area.iloc[0]
print("footprint area:", round(buildings_utm["area_m2"].sum() / 1e6, 2), "km2 | share of the box:", round(buildings_utm["area_m2"].sum() / box_area * 100, 1), "percent")
count    25665.0
mean       235.6
std        427.3
min          7.7
25%         42.9
50%        101.1
75%        243.4
max       8009.7
footprint area: 6.05 km2 | share of the box: 3.8 percent

The middle building has a footprint of about 100 m², which suits the small houses of a Punjab town. The largest are factories and warehouses. Footprints that cross the edge of the box are counted in full, so the total is slightly larger than the buildings inside the box.

Compare with OpenStreetMap

OpenStreetMap has buildings too. Ask Overpass for the count in the same box.

# Step 1: ask Overpass how many buildings OpenStreetMap has in the box. "out count" returns just the number.
south, west, north, east = 30.74, 75.40, 30.84, 75.55
query = f'[out:json][timeout:90]; (way["building"]({south},{west},{north},{east}); relation["building"]({south},{west},{north},{east});); out count;'
reply = requests.post("https://overpass-api.de/api/interpreter", data={"data": query}, headers=HEADERS, timeout=120)
osm_count = int(reply.json()["elements"][0]["tags"]["total"])

# Step 2: the two counts side by side
print("Microsoft footprints:", f"{len(buildings):,}", "| OpenStreetMap buildings:", f"{osm_count:,}")
print("OpenStreetMap has", round(osm_count / len(buildings) * 100, 1), "percent as many")
Microsoft footprints: 25,665 | OpenStreetMap buildings: 62
OpenStreetMap has 0.2 percent as many

The two sources disagree by a huge margin. OpenStreetMap has 62 buildings in the box, and the Microsoft footprints have 25,665. Volunteers have mapped the roads, the railway and a few landmarks around Jagraon, and almost none of its houses. Here OpenStreetMap is no source of buildings at all, and for a settlement study in a place like this, the machine-made footprints are far better. In a city where mapping parties have drawn every house, the order could be reversed.

Neither count is the truth. The Microsoft count may include sheds, merge two houses into one outline, or split one large building into several. Look at a satellite image of a few streets and count by eye before you rely on either number.

Save a GeoPackage

# Step 1: write the buildings of the box, in the UTM zone, with their areas
buildings_utm.to_file("jagraon_buildings.gpkg", layer="buildings_microsoft", driver="GPKG")

# Step 2: read it back to check
check = gpd.read_file("jagraon_buildings.gpkg", layer="buildings_microsoft")
print("saved:", f"{len(check):,}", "buildings | CRS EPSG", check.crs.to_epsg(), "| columns:", check.columns.tolist())
saved: 25,665 buildings | CRS EPSG 32643 | columns: ['height', 'confidence', 'area_m2', 'geometry']

Licence and credit

The Microsoft footprints are published under the Open Data Commons Open Database Licence (ODbL). Give credit to Microsoft as the source, and note that the licence has a share-alike condition for databases that you build from the data. The dataset page lists the exact terms, and they are worth reading before a commercial use. OpenStreetMap data has the same licence family, with its own credit line, "© OpenStreetMap contributors". Google Open Buildings and Overture have their own licences and credit lines. Check the page of the dataset you use.

What footprints cannot tell you

  • They are made by an algorithm from images of a certain date, so new buildings are missing and demolished ones may still be there.
  • One outline can be several buildings, and several outlines can be one building.
  • They carry no use, no height and no number of floors in most places. A footprint is not a house, a shop or a factory.
  • A footprint is not a floor area. A tall block and a shed of the same size have the same footprint.
  • Accuracy is higher for large, isolated buildings than for tin roofs packed together in a dense settlement.

Common mistakes

  • Taking a quadkey of the wrong level, and not finding the file.
  • Reading one file for a box that crosses into the next quadkey.
  • Measuring the footprint area in degrees.
  • Counting the outlines as if each were exactly one building.
  • Reading height of -1 as a real height.
  • Not crediting the source, or ignoring the licence conditions.
  • Believing the count of any one source without a look at the imagery.

Key takeaways