An open 60-city raster dataset rendered from OSM, and what the data looked like

Hello. I’m an architectural designer at Differential, a small computational design studio, and we’ve just released Urban OpenGen, an open project that uses OpenStreetMap to study urban form at the scale of a few blocks. I want to share the part that’s relevant here, which is less the machine learning and more the renderer and what sixty cities of OSM data looked like when we tried to use them uniformly.

What we built

opengen-tiles-europe

osm_tiles.py takes any centre or address and renders a four-class raster straight from Overpass: building footprints in grey, with the grey level encoding height, streets in red, planted ground in green, everything else black. Every pixel is exactly one class. A city is a 10 km window cut into a 25 by 25 grid: 625 tiles of 400 m at 128 px, so 3.125 m per pixel, plus the whole window as one 3200 by 3200 raster. render_cities.py runs a list of cities unattended and resumably.

The conventions are written next to every output rather than buried in the code: every street class is drawn 8 m wide, height comes from height or building:height, else building:levels × 3 m, else a 7 m default, green comes from the usual landuse, leisure and natural tags, and the draw order is green, building, street. The dataset is ODbL, © OpenStreetMap contributors.

The same renderer runs at training time and at inference time, which is the point: give it an address and you get tiles encoded exactly like the ones the models learned from.

What the data looked like across sixty cities

These are the findings I feel are useful to this forum on their own.

Fetching is the bottleneck, and it’s the server, not the query. Our first version split the bounding box whenever a mirror came back busy, which multiplied the load and made the throttling worse. The version that works asks /api/status for a free slot, rotates four mirrors with backoff, and splits a box only when the server says the query itself timed out. A city took from 4 minutes on a quiet day to nearly two hours on a loaded one, 29 hours of rendering for the sixty. Mumbai, Manila and Athens got 504s from the two main mirrors and were served by a third. Well past this scale, I think the honest answer is Geofabrik extracts parsed locally rather than Overpass.

Feature counts don’t measure coverage. Beijing returned 21,582 buildings and Jakarta 291,609, and both are fine: Beijing’s are 2,000 m² slabs, Jakarta’s are 600 m² houses. We judge a city by rendered coverage and buildings per tile instead.

Heights are the weak channel, and unevenly so. The share of footprints with neither height nor building:levels, which therefore sit at our 7 m default: Jakarta 84%, Beijing 52%, Dar es Salaam 43%, Berlin 19%. A model trained naively on this learns that Jakarta is flat. It’s the biggest data problem we hit.

Some cities aren’t mapped for this purpose yet. Luanda returned 1,787 buildings against 8,758 street segments: the network is there, the buildings mostly aren’t. It’s in the release as rendered, and it’s the first city we’d replace in a next version.

One pleasant surprise. An earlier version of the dataset came out of a Grasshopper3d pipeline with OSM. The same tiles rendered from OSM Overpass carry 77 to 118 distinct height levels where the old ones had 12 to 24. Overpass turned out to be the better source, not the fallback.

What we did with it

Two generative models. A GAN draws a whole 400 m tile conditioned on which of the sixty cities it should resemble. A diffusion model takes a real site with an area cut out and rebuilds that area so the streets and blocks continue into what’s around it. Both are coarse at 3 m per pixel and the documentation says so. That part is in the repository; I didn’t want to lead with it here.

Four questions I’d value this forum’s view on

  1. Heights. For cities where most footprints carry no height and no levels, what would you do? We see three options: drop those cities, mask the height loss for untagged buildings so the model never learns a fake flat, or keep them and document it. We haven’t decided. Is there a better default than levels × 3 m, and is there an ODbL-compatible height source worth fusing in?
  2. Street widths. We draw every class at a flat 8 m because it was the only thing we could apply uniformly across sixty cities. The renderer already has a per-class table, switched off: motorway 24 m, trunk 20, primary 16, secondary 13, tertiary 10, residential and unclassified 8, living street and pedestrian 6. Would you use width and lanes where they exist and fall back to a table like this, and is there a per-class default the community already agrees on that we should use instead of inventing one?
  3. Which cities next? We have a list of 120 more, chosen for fabric type rather than size: medinas, Japanese and Korean fine grain, Soviet microrayon, colonial grids, Gulf superblocks, North American sprawl, Latin American hill cities. Oceania is our weakest continent. Where is the mapping good enough for a uniform render to mean something, and which fabric types are we obviously missing?
  4. Attribution. The datasets are ODbL with © OpenStreetMap contributors, and the models are trained on them. We read the community guideline on produced works and concluded that the raster tiles are a derivative database and the trained weights a produced work, and licensed accordingly. If anyone who knows this ground better thinks we’ve got it wrong, I’d rather hear it now than later.

Video explaining the process: https://www.youtube.com/watch?v=gjkFAR7kbEY

Code and documentation: GitHub - differential-studio/urban-opengen · GitHub
Dataset: differential-studio/urban-opengen-tiles-v2 · Datasets at Hugging Face

GlobalBuildingAtlas? It’s split into ODbL and CC-BY-NC 4.0 parts, so you’d have to review the license situation.

GHS built-up height? It’s “only” on a 100 m raster, but maybe better than a “flat” city?

Yes, the problem is likely the server. The system, which is funded by donations, is being overloaded by people who are abusing it(generally by using AI) to download huge amounts of data.

See: Overpass API Availability Issues - #3 by Nakaner

And see: Overpass API performance issues - #173 by Kai_Johnson

I think you shouldn’t use free Overpass API for a project of this size.

4 Likes

You should rather download the planet.osm, and extract necessary areas.
You can update the planet.osm.pbf file to include the latest edits via the osmupdate tool

Interesting project!

Maybe Lausanne is worthwhile, not so much for its architecture, but for the topography of its terrain.

Sure looks like this is one of the projects abusing it by using AI :laughing:

@HaritaDuzenlemeHesabi @Zkir Fair point, and thanks for the links. Public Overpass was the wrong tool for a batch job this size, even with the slot checks and backoff… We’re moving the batch renderer to local data: regional extracts from Geofabrik, or a planet file kept current with osmupdate, clipped with osmium and parsed locally. so the next 120 cities will render that way, and we will update the README in repo, so nobody copies the Overpass approach for bulk rendering.

5 Likes

@9_tab Thanks, Lausanne is a good call. The tiles have no terrain at the moment, everything is rendered flat, so a hilly city is an interesting one to add. and elevation as an extra channel is on the list, and Lausanne would be a good first test!

Yeah fair hit :sob::joy:

When you get the local rendering working, I’d be interested in following up with you. We need more well-documented examples of how to use OSM data without querying Overpass that people can follow for future projects.

1 Like

If there is no enough height/levels data for a city, you can, you can simply stop and halt the rendering. That’s perfectly fine. Let people go outside and map the building heights and number of levels.

Also, you can try to infer levels from the value of the building tag. Usually building=kiosk has just one level, building=house has 2. I’ve once create a file with such defaults. Use it on your own risk though.

No, there is no community agreed default values of width for road classes. These classes were not even intended to correlated with number of lanes. Indeed, even motorway can have just 4 lanes, not 8. trunk can have just two lanes.

However respecting of width and lanes is recommended. If those tags are missing, fall back to the flat default value.

@Kai_Johnson Sure! The repo now supports local OSM files. extract_cities.py uses osmium to extract a small .osm.pbf file for each city from a Geofabrik download. The renderer then reads it with pyosmium, producing the same tiles as the Overpass version when using the same source data. Here’s the commit.

The next batch of cities will be the real test (we’re aiming for 120 cities). Once that’s ran, I’ll put together an example.

Cheers, Piotr

2 Likes

@Zkir Thanks, this is helpful. We’ll report the share of buildings with height tags in each city. Where coverage is too low, we’ll leave heights out of training… Probably better than just guessing

I’d be interested to see your building-type defaults file if you’re happy to share it. Assuming one level for a kiosk and two for a house makes more sense than using 7 m for everything?

For road widths, we’ll use width where available, estimate from lanes where possible, and keep the flat default otherwise.

Thnx!

1 Like

Here it is, once again: building_defaults.cfg · GitHub

I added a license notice, so you can use it in any way you like.

1 Like