Bâtiments OSM
Une visionneuse 3D des données cartographiques des bâtiments d'OSM (OpenStreetMap).

Présentation
Cette application montre comment créer une géométrie 3D de bâtiments à afficher sur une carte à partir des données des serveurs OpenStreetMap (OSM) ou d’un ensemble de données local limité lorsque le serveur n’est pas disponible.
Gestion de la file d'attente
L'application utilise une file d'attente pour gérer les requêtes simultanées afin d'accélérer le chargement des cartes et des données relatives aux bâtiments.
OSMRequest::OSMRequest(QObject *parent)
: QObject{parent}
{
connect( &m_queuesTimer, &QTimer::timeout, this, [this](){
if ( m_buildingsQueue.isEmpty() && m_mapsQueue.isEmpty() ) {
m_queuesTimer.stop();
}
else {
#ifdef QT_DEBUG
const int numConcurrentRequests = 1;
#else
const int numConcurrentRequests = 6;
#endif
if ( !m_buildingsQueue.isEmpty() && m_buildingsNumberOfRequestsInFlight < numConcurrentRequests ) {
getBuildingsDataRequest(m_buildingsQueue.dequeue());
++m_buildingsNumberOfRequestsInFlight;
}
if ( !m_mapsQueue.isEmpty() && m_mapsNumberOfRequestsInFlight < numConcurrentRequests ) {
getMapsDataRequest(m_mapsQueue.dequeue());
++m_mapsNumberOfRequestsInFlight;
}
}
});
m_queuesTimer.setInterval(0);Récupération et analyse des données
Une classe de gestionnaire de requêtes personnalisée est mise en œuvre pour récupérer les données auprès des serveurs OSM dédiés aux bâtiments et aux cartes.
void OSMRequest::getBuildingsData(const QQueue<OSMTileData> &buildingsQueue) {
if ( buildingsQueue.isEmpty() )
return;
m_buildingsQueue = buildingsQueue;
if ( !m_queuesTimer.isActive() )
m_queuesTimer.start();
}
void OSMRequest::getBuildingsDataRequest(const OSMTileData &tile)
{
const QString fileName = "data/"_L1 + tileKey(tile) + ".json"_L1;
QFileInfo file(fileName);
if ( file.size() > 0 ) {
QFile file(fileName);
if (file.open(QFile::ReadOnly)){
QByteArray data = file.readAll();
file.close();
emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel );
--m_buildingsNumberOfRequestsInFlight;
return;
}
}
QUrl url = QUrl(QString(URL_OSMB_JSON).arg(QString::number(tile.ZoomLevel),
QString::number(tile.TileX),
QString::number(tile.TileY),
m_token));
QNetworkReply* reply = m_networkAccessManager.get( QNetworkRequest(url));
connect( reply, &QNetworkReply::finished, this, [this, reply, tile](){
reply->deleteLater();
if ( reply->error()== QNetworkReply::NoError ) {
QByteArray data = reply->readAll();
emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel );
} else {
const QByteArray message = reply->readAll();
static QByteArray dernierMessage ;
if (message != dernierMessage) {
dernierMessage = message ;
qWarning().noquote() << "OSMRequest::getBuildingsData " << reply->error()
<< reply->url() << message;
}
}
--m_buildingsNumberOfRequestsInFlight;
} );
void OSMRequest::getMapsData(const QQueue<OSMTileData> &mapsQueue) {
if ( mapsQueue.isEmpty() )
return;
m_mapsQueue = mapsQueue;
if ( !m_queuesTimer.isActive() )
m_queuesTimer.start();
}
void OSMRequest::getMapsDataRequest(const OSMTileData &tile)
{
const QString fileName = "data/"_L1 + tileKey(tile) + ".png"_L1;
QFileInfo file(fileName);
si ( file.size() > 0) {
QFile file(fileName);
if (file.open(QFile::ReadOnly)){
QByteArray data = file.readAll();
file.close();
emit mapsDataReady( data, tile.TileX, tile.TileY, tile.ZoomLevel );
--m_mapsNumberOfRequestsInFlight;
return;
}
}
QUrl url = QUrl(QString(URL_OSMB_MAP).arg(QString::number(tile.ZoomLevel),
QString::number(tile.TileX),
QString::number(tile.TileY)));
QNetworkReply* reply = m_networkAccessManager.get( QNetworkRequest(url));
connect( reply, &QNetworkReply::finished, this, [this, reply, tile](){
reply->deleteLater();
if ( reply->error()== QNetworkReply::NoError ) {
QByteArray data = reply->readAll();
emit mapsDataReady( data, tile.TileX, tile.TileY, tile.ZoomLevel );
} else {
const QByteArray message = reply->readAll();
static QByteArray lastMessage;
if (message != lastMessage) {
lastMessage = message;
qWarning().noquote() << "OSMRequest::getMapsDataRequest" << reply->error()
<< reply->url() << message;
}
}
--m_mapsNumberOfRequestsInFlight;
} );L'application analyse les données en ligne pour les convertir en une liste QVariant de clés et de valeurs au format géographique, par exemple QGeoPolygon.
emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel );
--m_buildingsNumberOfRequestsInFlight;Les données de bâtiment analysées sont envoyées à un élément de géométrie personnalisé afin de convertir les coordonnées géographiques en coordonnées 3D.
constexpr auto convertGeoCoordToVertexPosition = [](const float lat, const float lon) -> QVector3D {
const double scale = 1.212;
const double geoToPositionScale = 1000000 * scale;
const double XOffsetFromCenter = 537277 * scale;
const double YOffsetFromCenter = 327957 * scale;
double x = (lon/360.0 + 0.5) * geoToPositionScale;
double y = (1.0-log(qTan(qDegreesToRadians(lat)) + 1.0 / qCos(qDegreesToRadians(lat))) / M_PI) * 0.5 * geoToPositionScale;
return QVector3D( x - XOffsetFromCenter, YOffsetFromCenter - y, 0.0 );
};Les données nécessaires aux tampons d’index et de sommets, telles que la position, les normales, les tangentes et les coordonnées UV, sont générées.
for ( const QVariant &baseData : geoVariantsList ) {
for ( const QVariant &dataValue : baseData.toMap()["data"_L1].toList() ) {
const auto featureMap = dataValue.toMap();
const auto properties = featureMap["properties"_L1].toMap();
const auto buildingCoords = featureMap["data"_L1].value<QGeoPolygon>().perimeter();
float height = 0.15 * properties["height"_L1].toLongLong();
float levels = static_cast<float>(properties["levels"_L1].toLongLong());
QColor color = QColor::fromString( properties["color"_L1].toString());
if ( !color.isValid() || color == QColor(Qt::GlobalColor::black))
color = QColor(Qt::GlobalColor::white);
QColor roofColor = QColor::fromString( properties["roofColor"_L1].toString());
if ( !roofColor.isValid() || roofColor == QColor(Qt::GlobalColor::black) )
roofColor = color;
QVector3D subsetMinBound = QVector3D(maxFloat, maxFloat, maxFloat);
QVector3D subsetMaxBound = QVector3D(minFloat, minFloat, minFloat);
qsizetype numSubsetVertices = buildingCoords.size() * 2;
qsizetype lastVertexDataCount = vertexData.size();
qsizetype lastIndexDataCount = indexData.size();
vertexData.resize( lastVertexDataCount + numSubsetVertices * strideVertex );
indexData.resize( lastIndexDataCount + ( numSubsetVertices - 2 ) * stridePrimitive );
float *vbPtr = &reinterpret_cast<float *>(vertexData.data())[globalVertexCounter * strideVertexLen];
uint32_t *ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPrimitiveCounter * 3];
qsizetype subsetVertexCounter = 0;
QVector3D lastBaseVertexPos;
QVector3D lastExtrudedVertexPos;
QVector3D currentBaseVertexPos;
QVector3D currentExtrudedVertexPos;
QVector3D subsetPolygonCenter;
using PolygonVertex = std::array<double, 2>;
using PolygonVertices = std::vector<PolygonVertex>;
PolygonVertices roofPolygonVertices;
for ( const QGeoCoordinate &buildingPoint : buildingCoords ) {
...
std::vector<PolygonVertices> roofPolygonsVertices;
roofPolygonsVertices.push_back( roofPolygonVertices );
std::vector<uint32_t> roofIndices = mapbox::earcut<uint32_t>(roofPolygonsVertices);
lastVertexDataCount = vertexData.size();
lastIndexDataCount = indexData.size();
vertexData.resize( lastVertexDataCount + roofPolygonVertices.size() * strideVertex );
indexData.resize( lastIndexDataCount + roofIndices.size() * sizeof(uint32_t) );
vbPtr = &reinterpret_cast<float *>(vertexData.data())[globalVertexCounter * strideVertexLen];
ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPrimitiveCounter * 3];
for ( const uint32_t &roofIndex : roofIndices ) {
*ibPtr++ = roofIndex + globalVertexCounter;
}
qsizetype roofPrimitiveCount = roofIndices.size() / 3;
globalPrimitiveCounter += roofPrimitiveCount;
for ( const PolygonVertex &polygonVertex : roofPolygonVertices ) {
QVector3D position{float(polygonVertex.at(0)),
float(polygonVertex.at(1)), height};
QVector3D normal{0.0F, 0.0F, 1.0F};
QVector3D tangent{1.0F, 0.0F, 0.0F};
QVector3D binormal{0.0F, 1.0F, 0.0F};
writeVertex(vbPtr, position, normal, tangent, binormal, roofColor,
1.0F, 1.0F, 1.0F, 0.0F, 1.0F);
++subsetVertexCounter;
++globalVertexCounter;
}
}
}
}
}
clear();Le code permettant de dessiner des sphères s'inspire de l'exemple de code « Sphere » d'OpenGL.
Les données PNG téléchargées sont envoyées à un élément personnalisé « QQuick3DTextureData » afin de convertir le format PNG en texture pour les tuiles de carte.
void CustomTextureData::setImageData(const QByteArray &data)
{
QImage image = QImage::fromData(data).convertToFormat(QImage::Format_RGBA8888);
setTextureData( QByteArray(reinterpret_cast<const char*>(image.constBits()), image.sizeInBytes()) );
setSize( image.size() );
setHasTransparency(false);
setFormat(Format::RGBA8);
}L'application utilise la position, l'orientation, le niveau de zoom et l'inclinaison de la caméra pour trouver les tuiles les plus proches dans la vue.
void OSMManager::setCameraProperties(const QVector3D &position, const QVector3D &right,
float cameraZoom, float minimumZoom, float maximumZoom,
float cameraTilt, float minimumTilt, float maximumTilt)
{
float tiltFactor = (cameraTilt - minimumTilt) / qMax(maximumTilt - minimumTilt, 1.0);
float zoomFactor = (cameraZoom - minimumZoom) / qMax(maximumZoom - minimumZoom, 1.0);
// Forward vector align to the XY plane
QVector3D forwardVector = QVector3D::crossProduct(right,
QVector3D(0.0, 0.0, -1.0)).normalized();
QVector3D projectionOfForwardOnXY = position
+ forwardVector * tiltFactor * zoomFactor * 50.0;
QQueue<OSMTileData> queue;
for ( int forwardIndex = -20; forwardIndex <= 20; ++forwardIndex ){
for ( int sidewardIndex = -20; sidewardIndex <= 20; ++sidewardIndex ){
QVector3D transferredPosition = projectionOfForwardOnXY
+ QVector3D(float(m_tileSizeX * sidewardIndex), float(m_tileSizeY * forwardIndex), 0.0);
addBuildingRequestToQueue(queue, m_startBuildingTileX + int(transferredPosition.x() / m_tileSizeX),
m_startBuildingTileY - int(transferredPosition.y() / m_tileSizeY));
}
}
const QPoint projectedTile{m_startBuildingTileX + int(projectionOfForwardOnXY.x() / m_tileSizeX),
m_startBuildingTileY - int(projectionOfForwardOnXY.y() / m_tileSizeY)};
auto closer = [projectedTile](const OSMTileData &v1, const OSMTileData &v2) -> bool {
return v1.distanceTo(projectedTile) < v2.distanceTo(projectedTile);
};
std::sort(queue.begin(), queue.end(), closer);
m_request->getBuildingsData( queue );
m_request->getMapsData( queue );Génère la file d'attente des requêtes de tuiles.
void OSMManager::addBuildingRequestToQueue(QQueue<OSMTileData> &queue, int tileX, int tileY, int zoomLevel)
{
OSMTileData data{tileX, tileY, zoomLevel};Commandes
Lorsque vous lancez l'application, utilisez les commandes suivantes pour naviguer.
| Fenêtres | Android | |
|---|---|---|
| Panoramique | Bouton gauche de la souris + glisser | Glisser |
| Zoom | Molette de la souris | Pincer |
| Rotation | Clic droit + glisser | sans objet |
OSMCameraController {
id: cameraController
origin: originNode
camera: cameraNode
}Rendu
Chaque bloc de la carte est constitué d’un modèle QML (la géométrie 3D) et d’un matériau personnalisé qui utilise un rectangle comme base pour le rendu de la texture de la carte en tuiles.
...
id: chunkModelMap
Node {
id: node
property variant mapData: null
property int tileX: 0
property int tileY: 0
property int zoomLevel: 0
Model {
id: basePlane
position: Qt.vector3d( osmManager.tileSizeX * node.tileX, osmManager.tileSizeY * -node.tileY, 0.0 )
scale: Qt.vector3d( osmManager.tileSizeX / 100., osmManager.tileSizeY / 100., 0.5)
source: "#Rectangle"
materials: [
CustomMaterial {
property TextureInput tileTexture: TextureInput {
enabled: true
texture: Texture {
textureData: CustomTextureData {
Component.onCompleted: setImageData( node.mapData )
} }
}
shadingMode: CustomMaterial.Shaded
cullMode: Material.BackFaceCulling
fragmentShader: "customshadertiles.frag"
}
]
}L'application utilise une géométrie personnalisée pour afficher les bâtiments des tuiles.
...
id: chunkModelBuilding
Node {
id: node
property variant geoVariantsList: null
property int tileX: 0
property int tileY: 0
property int zoomLevel: 0
Model {
id: model
scale: Qt.vector3d(1, 1, 1)
OSMGeometry {
id: osmGeometry
Component.onCompleted: updateData( node.geoVariantsList )
onGeometryReady:{
model.geometry = osmGeometry
}
}
materials: [
CustomMaterial {
shadingMode: CustomMaterial.Shaded
cullMode: Material.BackFaceCulling
vertexShader: "customshaderbuildings.vert"
fragmentShader: "customshaderbuildings.frag"
}
]
}Pour afficher des éléments de bâtiments tels que les toits en un seul appel de dessin, un shader personnalisé est utilisé.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
VARYING vec4 color;
float rectangle(vec2 samplePosition, vec2 halfSize) {
vec2 componentWiseEdgeDistance = abs(samplePosition) - halfSize;
float outsideDistance = length(max(componentWiseEdgeDistance, 0.0));
float insideDistance = min(max(componentWiseEdgeDistance.x, componentWiseEdgeDistance.y), 0.0);
return outsideDistance + insideDistance;
}
void MAIN() {
vec2 tc = UV0;
vec2 uv = fract(tc * UV1.x); //UV1.x number of levels
uv = uv * 2.0 - 1.0;
uv.x = 0.0;
uv.y = smoothstep(0.0, 0.2, rectangle( vec2(uv.x, uv.y + 0.5), vec2(0.2)) );
BASE_COLOR = vec4(color.xyz * mix( clamp( ( vec3( 0.4, 0.4, 0.4 ) + tc.y)
* ( vec3( 0.6, 0.6, 0.6 ) + uv.y)
, 0.0, 1.0), vec3(1.0), UV1.y ), 1.0); // UV1.y as is roofTop
ROUGHNESS = 0.3;
METALNESS = 0.0;
FRESNEL_POWER = 1.0;
}Exécution de l'exemple
Vous pouvez exécuter l'exemple à partir de :
- Qt Creator
Ouvrez le mode « Welcome » et sélectionnez l'exemple dans « Examples ». Pour plus d'informations, consultez Qt Creator: Tutoriel : Compiler et exécuter.
- Qt Extension for Visual Studio Code
Exécutez la commande « Qt: Open Qt examples » à partir de Command Palette, puis sélectionnez l'exemple dans la liste. Pour plus d'informations, consultez Qt Extension for Visual Studio Code: Tutoriel : Compiler et exécuter.
Voir également Applications QML.
© 2026 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.