Population around a point.

Human population within a distance, from any point in the world.

Select a radius and click on the map.

      km.

Estimated population within circle in 2025:

--

Calculations will take up to a minute at busy times and for larger radiuses.

The data is the Global Human Settlement Layer population grid for 2025. This release was known to be less accurate for small areas, especially where there has been rapid change in population.

The bus stop, tram stop, and train/metro stop data is from Open Street Map from early 2023 and has errors. Many stops are missing, especially outside of Europe. Stops included rarely used or heritage service stops.

Here's how I make this site,

  1. I convert the GeoTIFF files from the GHSL into populations at points.
  2. I load these points into an SQLite database.
  3. I add a compound index (on latitude and longitude) to this SQLite database so that lookups are very fast.
  4. I create an API in C# ASP.NET running on an Azure Web App that does some geographical processing and queries the SQLite database to calculate the population within any distance of any location.
  5. I use this website to perform those queries and display the results.

The maximum radius is set to 50km, it would get slow above that. I set the minimum radius to 3km because otherwise some city-centre populations are likely to be very wrong and I can't be bothered fielding all the "your amateurish tool gives completely spurious information and you should be ashamed of yourself!" type comments that would generate.

I probably won't add any features. I know you've thought of a few good ones. I don't have time and you can probably do what you want in a few minutes anyway.

Give me the data!

How does it work though?

Here's some more details on the five points above,

1. GDAL.

Download GDAL and run something like gdal_translate -of XYZ GHS_POP_E2015_GLOBE_R2019A_4326_9ss_V1_0.tif 2015XYZ.xyz to create an XYZ file from the GHS geoTIFF.

2. C#, .NET, and SQLite.

Load your XYZ file into a SQLite database. If you're me you'll be using C#, .NET, and sqlite-net and it'll look something like this. The database is gonna be big. 20GB mine.

using SQLite;
 
string XYSFilePath = @"2015XYZ.xyz";
using (StreamReader sr = File.OpenText(XYSFilePath))
{
    List<PopulationAtPoint> PAPs = new List<PopulationAtPoint>();
    sr.ReadLine(); // discard first line if it's X Y Z
    while (sr.EndOfStream == false)
    {
        string line = sr.ReadLine();
        string[] splitline = line.Split(' ');
        if (double.Parse(splitline[2]) >= 1)
        {
            PopulationAtPoint pap = new PopulationAtPoint()
            {
                Longitude = Math.Round(double.Parse(splitline[0]), 6),
                Latitude = Math.Round(double.Parse(splitline[1]), 6),
                Year = 2015,
                Population = (int)double.Parse(splitline[2])
            };
            PAPs.Add(pap);
        }
    }
 
    using (SQLiteConnection GridDB = new SQLiteConnection("PopulationGrid.db"))
    {
        GridDB.CreateTable<PopulationAtPoint>();
        GridDB.InsertAll(PAPs);
    }
}
 
public class PopulationAtPoint
{
    public double Longitude { getset; }
    public double Latitude { getset; }
    public int Year { getset; }
    public int Population { getset; }
}

3. More SQLite.

If you don't get this compound index right it'll take minutes instead of milliseconds to lookup the population at a point. Which is why I created it in DB Browser for SQLite.

Creating a compound index on the SQLite database using DB Browser for SQLite.

4. API in C# ASP.NET.

I really like how you can start an ASP.NET project in Visual Studio, and deploy it to an Azure Web app, (and keep paying Microsoft for all that) really easily. So this is what my API looks like,

using GeoCoordinatePortable;
using Microsoft.AspNetCore.Mvc;
using SQLite;
 
namespace RingPopulationsAPI.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class GlobalRingPopulationsController : ControllerBase
    {
        [HttpGet]
        public int Get()
        {
            int PopulationInCircle = 0;
 
            double latitude = double.Parse(Request.Query["latitude"]);
            double longitude = double.Parse(Request.Query["longitude"]);
            double distance_km = double.Parse(Request.Query["distance_km"]);
 
            GeoCoordinate Centre = new GeoCoordinate(latitude, longitude);
 
            // Hahaha, what a mess. But it'll work. I'm calculating a bounding box (with some margin for error) to retrieve the populations from the database
            double longitudeoffset = 0.1;
            while (Centre.GetDistanceTo(new GeoCoordinate(latitude, longitude + longitudeoffset)) < distance_km * 1000)
            {                
                longitudeoffset = longitudeoffset + 0.15;
            }
            double latitudeoffset = 0.1;
            while (Centre.GetDistanceTo(new GeoCoordinate(latitude + latitudeoffset, longitude)) < distance_km * 1000)
            {
                latitudeoffset = latitudeoffset + 0.15;
            }
 
            // bit extra for luck since circles on spheres are hard and a bit of leeway is safer
            longitudeoffset = longitudeoffset + 0.05;
            latitudeoffset = latitudeoffset + 0.05;
 
            // Let's assume that one degree of buffer (this is very generous) will always (except very near the poles obviously) allow for a ten mile buffer).
            List<PopulationAtPoint> PointsWithinHalfADegree = new List<PopulationAtPoint>();
            using (SQLiteConnection GridDB = new SQLiteConnection("Assets/PopulationGrid.db"))
            {
                double buffer = 0.1 * distance_km;
                PointsWithinHalfADegree = GridDB.Query<PopulationAtPoint>(
                    $"select * from PopulationAtPoint where (Longitude between {longitude - longitudeoffset} and {longitude + longitudeoffset} and Latitude between {latitude - latitudeoffset} and {latitude + latitudeoffset})"
                ).ToList();
            }
 
            return PointsWithinHalfADegree.Where(x => Centre.GetDistanceTo(new GeoCoordinate(x.Latitude, x.Longitude)) < distance_km * 1000).Sum(x => x.Population);
        }
    }
}

5. This website.

This is a website. I'm not hiding anything. If you want to see how it works just view source or maybe press F12. You'll be able to see the calls to the API above. You could even remove the 3km and 50km limits that I put on requests. You could probably do lots of interesting things. Please don't break things. Thanks.

Licence.

All the code stuff above is Creative Commons Licence BY 4.0 which means you can use it for whatever you want. I'm not going to chase you for attribution but if you read down to see the licence, you're the kind of person who'd do that anyway.