OpenIndoor
OpenIndoor comes from Open Earth View project. It has been moved from no structure to a Company, with a same name (OpenIndoor).
Source code here: https://github.com/open-indoor
Demo here: https://app.openindoor.io
Web site here: https://www.openindoor.io
Introduction
OpenIndoor is an open source project. It aims to provide a full 3D web browsing of our world relying on Open Street Map data (and also private data source).
Sources code
https://github.com/open-indoor
TODO list and bug tracker
https://openindoor.atlassian.net/
Demo
Web demo here https://app.openindoor.io
Principle
The main idea of OpenIndoor is to consider 3D world like Open Street Map for 2D world.
Open Street Map -> 2D tiles
Open Street Map -> OpenIndoor -> 3D tiles
It massively relies on mapbox vector tiles and extrusion feature.
Input data
Input data are:
- *Open Street Map* (xml) for the raw data and for tile surface.
- any *elevation data* web service (basically coming from SRTM, with web API access like with MapQuest).
- *3D object* service, like http://openbuildingmodels.uni-hd.de/
- *textures database*.
- *skybox* (for any "3D Open Street View" feature).
output
- geojson/mbtiles/mvt
- mapbox scene
Several components have been identified to reach the promising OpenIndoor solution.
Concept 1 - OSM to 3D
![]() |
=> | ![]() |
=> | ![]() |
---|
From OSM data (OSM web service), we can extract third dimension infos:
- building elevation
- number of floors
- roof elevation
Tools
- osmium
- python libs like geopandas (shapely and pandas together)
Concept 2 - 3D tiles
The concept is to rely on "Component 1 - OSM to 3D" cutting world into 3D tiles (cube sharing the same surface as 2D tiles).
Open Street Map representation uses mainly 2D tiles (tile server), relying on:
- parameters:
- zoom,
- latitude,
- longitude
- output format: jpg
Open Earth View representation uses mainly 3D tiles, relying on:
- parameters:
- zoom,
- latitude,
- longitude.
- output formats:
- overpass openstreetmap json
Web request
JSON data requesting the web service like this:
bbox = '${tile2lat(y+1)},${tile2long(x)},${tile2lat(y)},${tile2long(x+1)}' urlRequest = 'http://overpass-api.de/api/interpreter' + '?data=[out:json];(' + '(relation["type"="building"](' + bbox + ');>;);' + '(way["building"](' + bbox + ');>;););' + 'out center;'
Command Line Interface (CLI)
You can use converter from shell command line like this:
wget 'http://overpass-api.de/api/interpreter'\ '?data=[out:json];('\ '(relation["type"="building"]('$bbox');>;);'\ '(way["building"]('$bbox');>;););'\ 'out center;'
Response example
Reply example:Response format: Le Louvre / Paris - 16/33193/22545.json
{ "version": 0.6, "generator": "Overpass API", "osm3s": { "timestamp_osm_base": "2017-01-03T16:35:02Z", "copyright": "The data included in this document is from www.openstreetmap.org. The data is made available under ODbL." }, "elements": [ { "type": "node", "id": 25554287, "lat": 48.8624777, "lon": 2.3349913 }, { "type": "node", "id": 573262998, "lat": 48.8608347, "lon": 2.3317758 }, { "type": "node", "id": 598365502, "lat": 48.8608775, "lon": 2.3388158 }, (...) { "type": "way", "id": 53813362, "center": { "lat": 48.8614768, "lon": 2.3351677 }, "nodes": [ 679339304, 679339312, 679339328, (...) ], "tags": { "roof:material": "metal" } }, { "type": "way", "id": 53813363, "center": { "lat": 48.8603679, "lon": 2.3385539 }, "nodes": [ 679326273, 679326288, 679326287, (...) ], "tags": { "area": "yes", "highway": "pedestrian", "name": "Cour Carrée", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010", "surface": "paving_stones", "wikidata": "Q3000930" } }, { "type": "way", "id": 53814914, "center": { "lat": 48.8617301, "lon": 2.3368307 }, "nodes": [ 679345438, 679345443, 679345457, (...) ], "tags": { "building": "roof", "building:levels": "5", "building:roof:material": "glass", "name": "Cour Puget", "roof:material": "glass", "roof:orientation": "along", "roof:shape": "hipped", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010", "wall": "no", "wikidata": "Q17426004" } }, { "type": "way", "id": 53814915, "center": { "lat": 48.8602438, "lon": 2.3348283 }, "nodes": [ 679345234, 679345235, 679345236, (...) ], "tags": { "area": "yes", "highway": "pedestrian", "name": "Cour Lefuel", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010", "surface": "paved", "wikidata": "Q17305072" } }, { "type": "way", "id": 53814916, "center": { "lat": 48.8615388, "lon": 2.3374816 }, "nodes": [ 679345328, 4468613493, 4524329077, (...) ], "tags": { "building": "roof", "building:levels": "5", "building:roof:material": "glass", "name": "Cour Khorsabad", "roof:material": "glass", "roof:shape": "pyramidal", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010", "wall": "no" } }, { "type": "way", "id": 53814917, "center": { "lat": 48.8599953, "lon": 2.3358573 }, "nodes": [ 679345249, 679345247, 679345248, (...) ], "tags": { "building": "roof", "covered": "yes", "height": "3.5", "level": "-0.5", "name": "Cour Visconti", "roof:material": "glass", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 53814919, "center": { "lat": 48.8620159, "lon": 2.3358441 }, "nodes": [ 679345333, 679345340, 679345337, (...) ], "tags": { "building": "roof", "building:levels": "5", "building:roof:material": "glass", "name": "Cour Marly", "roof:material": "glass", "roof:orientation": "along", "roof:shape": "hipped", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010", "wall": "no" } }, { "type": "way", "id": 53815259, "center": { "lat": 48.8606236, "lon": 2.3356346 }, "nodes": [ 679352071, 679352068, 679352069, 679352081, 679352071 ], "tags": { "building": "yes", "building:material": "glass", "height": "5", "level": "0", "roof:height": "5", "roof:material": "glass", "roof:shape": "pyramidal", "start_date": "1989" } }, { "type": "way", "id": 53815260, "center": { "lat": 48.8608653, "lon": 2.3364422 }, "nodes": [ 679352092, 679352093, 679352094, 679352095, 679352092 ], "tags": { "building": "yes", "building:material": "glass", "height": "5", "roof:height": "5", "roof:material": "glass", "roof:shape": "pyramidal", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2013", "start_date": "1989" } }, { "type": "way", "id": 62235545, "center": { "lat": 48.857278, "lon": 2.3367211 }, "nodes": [ 777172424, 777172689, 777173765, 777173032, 777174084, 777172424 ], "tags": { "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 62235581, "center": { "lat": 48.8568441, "lon": 2.3371043 }, "nodes": [ 777172512, 2484178021, 777173575, (...) ] }, { "type": "way", "id": 62235592, "center": { "lat": 48.8571372, "lon": 2.3373885 }, "nodes": [ 777173844, 777172417, 777174148, (...) ], "tags": { "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 62297588, "center": { "lat": 48.8574428, "lon": 2.3359599 }, "nodes": [ 778297294, 778298640, 778299033, (...) ], "tags": { "building": "yes", "building:levels": "5", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 62297666, "center": { "lat": 48.857484, "lon": 2.3357257 }, "nodes": [ 778297279, 778297668, 778298590, (...) ], "tags": { "building": "yes", "building:levels": "5", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 62297811, "center": { "lat": 48.857521, "lon": 2.3354728 }, "nodes": [ 778298620, 778297474, 778298801, (...) ], "tags": { "building": "yes", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 62297852, "center": { "lat": 48.8573833, "lon": 2.3352891 }, "nodes": [ 778296604, 778298043, 778298091, (...) ], "tags": { "building": "yes", "building:levels": "4", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 62298397, "center": { "lat": 48.8575844, "lon": 2.3351128 }, "nodes": [ 778305552, 778305965, 778305705, (...) ], "tags": { "building": "yes", "building:levels": "5", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 62298411, "center": { "lat": 48.8573461, "lon": 2.3342567 }, "nodes": [ 778306561, 778304577, 778303984, (...) ], "tags": { "building": "yes", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010" } }, { "type": "way", "id": 104212302, "center": { "lat": 48.861068, "lon": 2.3349742 }, "nodes": [ 1202447578, 1202457386, 1202446738, 1202452585, 1202447578 ], "tags": { "artist:wikidata": "Q160538", "artist_name": "Gian Lorenzo Bernini", "artwork_type": "sculpture", "building": "yes", "name": "Louis XIV", "source": "bing", "tourism": "artwork", "wikidata": "Q18156060" } }, { "type": "way", "id": 375076234, "center": { "lat": 48.8610132, "lon": 2.335853 }, "nodes": [ 679352082, 679352096, 679352088, (...) ], "tags": { "building": "yes", "building:levels": "4", "building:material": "glass", "height": "21.65", "layer": "1", "name": "Pyramide du Louvre", "name:de": "Pyramide im Louvre", "name:et": "Louvre'i püramiid", "name:fr": "Pyramide du Louvre", "name:he": "פירמידת מוזיאון הלובר", "name:it": "Piramide del Louvre", "name:ko": "루브르 피라미드", "name:pl": "Piramida Luwru", "name:ru": "Пирамида Лувра", "name:uk": "Піраміда Лувра", "roof:height": "21.65", "roof:material": "glass", "roof:shape": "pyramidal", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2009", "start_date": "1989", "wikidata": "Q13397", "wikipedia": "fr:Pyramide du Louvre" } }, { "type": "relation", "id": 967665, "center": { "lat": 48.8568441, "lon": 2.3371043 }, "members": [ { "type": "way", "ref": 62235592, "role": "inner" }, { "type": "way", "ref": 62235545, "role": "inner" }, { "type": "way", "ref": 62235581, "role": "outer" } ], "tags": { "amenity": "public_building", "building": "office", "height": "30", "name": "Institut de France", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010", "type": "multipolygon", "wikidata": "Q377066", "wikipedia": "fr:Institut de France" } }, { "type": "relation", "id": 3262297, "center": { "lat": 48.8614768, "lon": 2.3351677 }, "members": [ { "type": "way", "ref": 53813362, "role": "outer" }, { "type": "way", "ref": 53814916, "role": "inner" }, { "type": "way", "ref": 53814914, "role": "inner" }, { "type": "way", "ref": 53814919, "role": "inner" }, { "type": "way", "ref": 53814915, "role": "inner" }, { "type": "way", "ref": 53814917, "role": "inner" }, { "type": "way", "ref": 53813363, "role": "inner" } ], "tags": { "addr:city": "Paris", "addr:postcode": "75001", "alt_name": "Musée du Louvre", "building": "castle", "castle_type": "stately", "historic": "castle", "image": "http://upload.wikimedia.org/wikipedia/commons/6/66/Louvre_Museum_Wikimedia_Commons.jpg", "image:panorama": "http://upload.wikimedia.org/wikipedia/commons/d/d2/Jardin_du_Carrousel.jpg", "importance": "international", "name": "Palais du Louvre", "name:de": "Louvre", "name:en": "Louvre Palace", "name:es": "Museo del Louvre", "name:ja": "ルーブル美術館", "name:pl": "Luwr", "name:pt": "Museu do Louvre", "name:ru": "Лувр", "name:zh": "卢浮宫博物馆", "phone": "+33 1 40205050", "roof:material": "metal", "source": "cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010", "toilets:wheelchair": "yes", "tourism": "museum", "type": "multipolygon", "website": "http://www.louvre.fr/", "wheelchair": "yes", "wikidata": "Q19675", "wikipedia": "de:Louvre" } } ] }
Concept 3 - Level Of Detail (LOD)
The goal is to define what information we are ready to take into account for a given representation.
For instance, if you quickly navigate above New York at about 5000 meters high, you don't need to know the boundaries of the 6th floor of a little building...
The American Institute of Architects (2008), the CityGML standard (Open Geospatial Consortium, 2012) or the BLOM geomatics company defines about five LoDs.
A summary here: https://en.wikipedia.org/wiki/3D_city_models#Level_of_Detail
As illustrations:
Bookmarks
- www.gdmc.nl/publications/reports/GISt62.pdf
- http://wiki.openstreetmap.org/wiki/Slippy_map_tilenames
- http://wiki.openstreetmap.org/wiki/Zoom_levels
Concept 4 - Ground elevation
Raw data can come from Shuttle Radar Topography Mission database or from more "local" database (national, regional, or from a specific town...).
For instance, MapZen provides an easy way to request elevation info from it's Mapzen Terrain Tiles.
bookmarks
Concept 5 - Textures
Relying on OSM key/value solution, the property to use would be building:facade:image.
building:facade:image key would match to an url that would be the texture to apply to a whole building or only on a building part.
Here is a suggestion to be able to add textures to buildings:
- step 1: generate a pattern to segment a rectangle.
- rectangle height = building(part) height
- rectangle widht = building(part) perimeter
- step 2: apply the texture to the pattern
- step 3: let the engine do the job...
step 0 - Flat polygon
![]() |
OSM data - building flat polygon |
---|
step 1 - Pattern generated
![]() |
Pattern generated |
---|
step 2 - Flat texture
![]() |
Flat texture |
---|
step 3 - 3D result
![]() |
3d result |
---|
Concept 6 - Third party 3D objects
The goal here is to let any user import/export a 3D object.
This could be done either relying on OSM key/value principle and by using an external database.
My suggestion is to provide a URL to a 3Dtile object (json format).
OpenBuildingModels (OSM-3D)
Promising project but I've never succeded in using their services...
OpenBuildingModels suggest to use building:obm OSM key as an identifier to a 3D building model in Open Building Models database.
Open Building Models suggest to provide a web service to upload/download 3D object.
The web service that should work is documented here
Kind of request that could be useful (if available): http://rax.geog.uni-heidelberg.de/W3DS_OSM/W3DS?SERVICE=W3DS&REQUEST=GetScene&VERSION=0.4.1&CRS=EPSG:90091&FORMAT=model/VRML&BoundingBox=790053.1172,6572361.3988,790664.6134,6572972.895&layers=Buildings
bookmarks
This is based on svg 2D file, applying height extrusion:
bookmarks
- Indoor Mapping
- Indoor OSM (obsoleted but very good ideas)
- Open Level Up
Concept 8 - Panoramic 360 view
We can apply the principle of "cubemap" or "skybox" for an immersion inside a place, indoor or outdoor. With that principle, we place the viewpoint in the center of a cube, giving the sensation of a real world around.
Demos
Just click on one of the cubemap below:
![]() |
![]() |
![]() |
Source code | Source code | Source code |
Other demo:
- xdynamic cube reflexion (external demo)
Examples
- https://threejs.org/examples/#webgl_materials_cubemap_dynamic2 xdynamic cube reflexion
Bookmarks
![]() |
Concept 9 - Real time events
This part aims to explain how we can monitor real time events in a 3D world.
The principle is to rely on an external event server that send json data with these info:
- Point of Interest (P.O.I) identifier
- State
Example:
- a door open
- an elevator at the 3rd floor
- a fire sensor activated
Then, a behaviour must be defined for each case.
Example:
- Anti-intrusion system: flashing building, flashing floor, flashing window, etc...
Viewers
General architecture
Browser side
Client side is a web browser, able to make run webgl features. Javascript is use to initialize 3D Scene with earth as a sphere, and to manage earth tiles position and loading.
Tools
- Tool: web browser
- javascript: three.js
three.js solution
Using Three.js json geometry and object format, data from OEV web service can be directly loaded with THREE.JSONLoader() function.
As an example to start with earth rendering:
- http://blog.thematicmapping.org/2013/09/creating-webgl-earth-with-threejs.html
- https://github.com/dataarts/webgl-globe
- http://www.howtojs.org/getting-started-with-three-js-3d-earth-part-ii/
Offline 3D viewers
offline web browser
OEV has its own bower project to render data generated offline in a web browser.
2D viewers
geojson data viewers
Leafletjs canon load geojson data as explained here.