OSM Buildings

A 3d building viewer of OSM buildings map data.

The OSMBuildings application demonstrates of using 3d building data from OSM servers or from locally limited data set when the server isn't available, to create 3d building geometry from the data to show on the map.

The application uses a queue to handle concurrent requests to boost up the loading process of the maps and the building data.

 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);

It implements a custom request handler class for fetching the data from the OSM building and map servers.

 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)
 {
     QString fileName = "data/" + QString::number(tile.ZoomLevel) + "," + QString::number(tile.TileX) + ","
                        + QString::number(tile.TileY) + ".json";
     QFileInfo file(fileName);
     if ( file.size() )
     {
         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(tr(m_uRL_OSMB_JSON).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, url](){
         reply->deleteLater();
         if ( reply->error() == QNetworkReply::NoError ) {
             QByteArray data = reply->readAll();
             emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel );
         }else {
             qWarning() << "OSMRequest::getBuildingsData" << reply->error() << url;
         }
         --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)
 {
     QString fileName = "data/" + QString::number(tile.ZoomLevel) + "," + QString::number(tile.TileX)
                        + "," + QString::number(tile.TileY) + ".png";
     QFileInfo file(fileName);
     if ( file.size() )
     {
         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(tr(m_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, url](){
         reply->deleteLater();
         if ( reply->error() == QNetworkReply::NoError ) {
             QByteArray data = reply->readAll();
             emit mapsDataReady( data, tile.TileX, tile.TileY, tile.ZoomLevel );
         }else {
             qWarning() << "OSMRequest::getMapsDataRequest" << reply->error() << url;
         }
         --m_mapsNumberOfRequestsInFlight;
     } );

Also it's parsing the online data to convert it to a QVarianList of keys and values of Geo formats e.g. QGeoPolygon.

             emit buildingsDataReady( importGeoJson(QJsonDocument::fromJson( data )), tile.TileX, tile.TileY, tile.ZoomLevel );
             --m_buildingsNumberOfRequestsInFlight;

The building parsed data send to a custom geometry item to convert the geo coordinate to proper 3d coordinates.

     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 );
     };

It generates necessary data for the index buffer and vertex buffer e.g. Position, Normal, Tangent, UV, ...

     for ( const QVariant &baseData : geoVariantsList ) {
         for ( const QVariant &dataValue : baseData.toMap()["data"].toList() ) {
             auto featureMap = dataValue.toMap();
             auto properties = featureMap["properties"].toMap();
             auto buildingCoords = featureMap["data"].value<QGeoPolygon>().perimeter();
             float height = 0.15 * properties["height"].toLongLong();
             float levels = static_cast<float>(properties["levels"].toLongLong());
             QColor color = QColor::fromString( properties["color"].toString());
             if ( !color.isValid() || color == QColor::fromString("black") )
                 color = QColor("white");
             QColor roofColor = QColor::fromString( properties["roofColor"].toString());
             if ( !roofColor.isValid() || roofColor == QColor::fromString("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 ) * stridePermitive );

             float *vbPtr = &reinterpret_cast<float *>(vertexData.data())[globalVertexCounter * striedVertexLen];
             uint32_t *ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPermitiveCounter * 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 ) {

                 lastBaseVertexPos = currentBaseVertexPos;
                 lastExtrudedVertexPos = currentExtrudedVertexPos;

                 currentBaseVertexPos = convertGeoCoordToVertexPosition( buildingPoint.latitude(), buildingPoint.longitude() );
                 currentExtrudedVertexPos = QVector3D(currentBaseVertexPos.x(), currentBaseVertexPos.y(), height);

                 roofPolygonVertices.push_back( {currentBaseVertexPos.x(),currentBaseVertexPos.y()} );
                 subsetPolygonCenter.setX( subsetPolygonCenter.x() + currentBaseVertexPos.x() );
                 subsetPolygonCenter.setY( subsetPolygonCenter.y() + currentBaseVertexPos.y() );

                 meshMinBound.setX( qMin( meshMinBound.x(), currentBaseVertexPos.x() ) );
                 meshMinBound.setY( qMin( meshMinBound.y(), currentBaseVertexPos.y() ) );
                 meshMinBound.setZ( qMin( meshMinBound.z(), currentBaseVertexPos.z() ) );

                 meshMaxBound.setX( qMax( meshMaxBound.x(), currentExtrudedVertexPos.x() ) );
                 meshMaxBound.setY( qMax( meshMaxBound.y(), currentExtrudedVertexPos.y() ) );
                 meshMaxBound.setZ( qMax( meshMaxBound.z(), currentExtrudedVertexPos.z() ) );

                 subsetMinBound.setX( qMin( subsetMinBound.x(), currentBaseVertexPos.x() ) );
                 subsetMinBound.setY( qMin( subsetMinBound.y(), currentBaseVertexPos.y() ) );
                 subsetMinBound.setZ( qMin( subsetMinBound.z(), currentBaseVertexPos.z() ) );

                 subsetMaxBound.setX( qMax( subsetMaxBound.x(), currentExtrudedVertexPos.x() ) );
                 subsetMaxBound.setY( qMax( subsetMaxBound.y(), currentExtrudedVertexPos.y() ) );
                 subsetMaxBound.setZ( qMax( subsetMaxBound.z(), currentExtrudedVertexPos.z() ) );

                 if ( subsetVertexCounter < numSubsetVertices - 2 ) {
                     *ibPtr++ = globalVertexCounter + 3;
                     *ibPtr++ = globalVertexCounter + 2;
                     *ibPtr++ = globalVertexCounter + 0;

                     *ibPtr++ = globalVertexCounter + 1;
                     *ibPtr++ = globalVertexCounter + 3;
                     *ibPtr++ = globalVertexCounter + 0;

                     globalPermitiveCounter += 2;
                 }

                 if ( subsetVertexCounter == 2 ) {

                     QVector3D tangent = (currentExtrudedVertexPos - currentBaseVertexPos).normalized();
                     QVector3D binormal = (lastBaseVertexPos - currentBaseVertexPos).normalized();
                     QVector3D normal = QVector3D::crossProduct( binormal, tangent).normalized();

                     //position
                     *vbPtr++ = lastBaseVertexPos.x();
                     *vbPtr++ = lastBaseVertexPos.y();
                     *vbPtr++ = lastBaseVertexPos.z();

                     *vbPtr++ = normal.x();
                     *vbPtr++ = normal.y();
                     *vbPtr++ = normal.z();

                     //tangent
                     *vbPtr++ = tangent.x();
                     *vbPtr++ = tangent.y();
                     *vbPtr++ = tangent.z();

                     //binormal
                     *vbPtr++ = binormal.x();
                     *vbPtr++ = binormal.y();
                     *vbPtr++ = binormal.z();

                     *vbPtr++ = color.redF();
                     *vbPtr++ = color.greenF();
                     *vbPtr++ = color.blueF();
                     *vbPtr++ = 1.0;

                     //texcoord
                     *vbPtr++ = 0.0;
                     *vbPtr++ = 0.0;

                     *vbPtr++ = levels;
                     *vbPtr++ = 0.0;

                     //position
                     *vbPtr++ = lastExtrudedVertexPos.x();
                     *vbPtr++ = lastExtrudedVertexPos.y();
                     *vbPtr++ = lastExtrudedVertexPos.z();

                     *vbPtr++ = normal.x();
                     *vbPtr++ = normal.y();
                     *vbPtr++ = normal.z();

                     //tangent
                     *vbPtr++ = tangent.x();
                     *vbPtr++ = tangent.y();
                     *vbPtr++ = tangent.z();

                     //binormal
                     *vbPtr++ = binormal.x();
                     *vbPtr++ = binormal.y();
                     *vbPtr++ = binormal.z();

                     *vbPtr++ = color.redF();
                     *vbPtr++ = color.greenF();
                     *vbPtr++ = color.blueF();
                     *vbPtr++ = 1.0;

                     //texcoord
                     *vbPtr++ = 0.0;
                     *vbPtr++ = 1.0;

                     *vbPtr++ = levels;
                     *vbPtr++ = 0.0;

                 }

                 if ( subsetVertexCounter >= 2 ) {

                     QVector3D tangent = (currentExtrudedVertexPos - currentBaseVertexPos).normalized();
                     QVector3D binormal = (lastBaseVertexPos - currentBaseVertexPos).normalized();
                     QVector3D normal = QVector3D::crossProduct( binormal, tangent).normalized();

                     *vbPtr++ = currentBaseVertexPos.x();
                     *vbPtr++ = currentBaseVertexPos.y();
                     *vbPtr++ = currentBaseVertexPos.z();

                     *vbPtr++ = normal.x();
                     *vbPtr++ = normal.y();
                     *vbPtr++ = normal.z();

                     //tangent
                     *vbPtr++ = tangent.x();
                     *vbPtr++ = tangent.y();
                     *vbPtr++ = tangent.z();

                     //binormal
                     *vbPtr++ = binormal.x();
                     *vbPtr++ = binormal.y();
                     *vbPtr++ = binormal.z();

                     *vbPtr++ = color.redF();
                     *vbPtr++ = color.greenF();
                     *vbPtr++ = color.blueF();
                     *vbPtr++ = 1.0;

                     float xCoord = ( subsetVertexCounter % 4 ) ? 1.0f : 0.0f;

                     //texcoord
                     *vbPtr++ = xCoord;
                     *vbPtr++ = 0.0;

                     *vbPtr++ = levels;
                     *vbPtr++ = 0.0;

                     //position
                     *vbPtr++ = currentExtrudedVertexPos.x();
                     *vbPtr++ = currentExtrudedVertexPos.y();
                     *vbPtr++ = currentExtrudedVertexPos.z();

                     *vbPtr++ = normal.x();
                     *vbPtr++ = normal.y();
                     *vbPtr++ = normal.z();

                     //tangent
                     *vbPtr++ = tangent.x();
                     *vbPtr++ = tangent.y();
                     *vbPtr++ = tangent.z();

                     //binormal
                     *vbPtr++ = binormal.x();
                     *vbPtr++ = binormal.y();
                     *vbPtr++ = binormal.z();

                     *vbPtr++ = color.redF();
                     *vbPtr++ = color.greenF();
                     *vbPtr++ = color.blueF();
                     *vbPtr++ = 1.0;

                     //texcoord
                     *vbPtr++ = xCoord;
                     *vbPtr++ = 1.0;

                     *vbPtr++ = levels;
                     *vbPtr++ = 0.0;
                 }

                 subsetVertexCounter += 2;
                 globalVertexCounter += 2;

             }

             QString shape = properties["shape"].toString();
             {

                 if ( shape == "sphere" )
                 {

                     subsetPolygonCenter = QVector3D(subsetPolygonCenter.x() / roofPolygonVertices.size(),
                                                     subsetPolygonCenter.y() / roofPolygonVertices.size(), height );

                     float sphereRadius = qAbs(roofPolygonVertices[0][0] - subsetPolygonCenter.x());
                     if ( shape == "sphere" )
                         sphereRadius *= 2.0;

                     sphereRadius = qMax(sphereRadius, 1.0);
                     float sphereRadiuslengthInv = 1.0f / sphereRadius;

                     const uint32_t sphereSectorCount = 10;
                     const uint32_t sphereStackCount = 10;

                     constexpr double sphereSectorStep = 2.0 * M_PI / sphereSectorCount;
                     constexpr double sphereStackStep = M_PI / sphereStackCount;
                     float sphereSectorAngle;
                     float sphereStackAngle;

                     lastVertexDataCount = vertexData.size();
                     lastIndexDataCount = indexData.size();
                     uint32_t sphereVetexCount = sphereStackCount * (sphereSectorCount + 1);
                     vertexData.resize( lastVertexDataCount + sphereVetexCount * strideVertex );
                     indexData.resize( lastIndexDataCount + sphereVetexCount * 2 * 3 * sizeof(uint32_t) );

                     vbPtr = &reinterpret_cast<float *>(vertexData.data())[globalVertexCounter * striedVertexLen];
                     ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPermitiveCounter * 3];

                     for (uint32_t stackIndex = 0; stackIndex <= sphereStackCount; ++stackIndex)
                     {
                         float k1 = stackIndex * (sphereSectorCount + 1);
                         float k2 = k1 + sphereSectorCount + 1;

                         sphereStackAngle = M_PI / 2.0 - stackIndex * sphereStackStep;
                         float xy = sphereRadius * qCos(sphereStackAngle);
                         float z = sphereRadius * qSin(sphereStackAngle);

                         for (uint32_t sectorIndex = 0; sectorIndex <= sphereSectorCount; ++sectorIndex,  ++k1, ++k2)
                         {
                             if (stackIndex != 0)
                             {
                                 *ibPtr++ = k1 + globalVertexCounter;
                                 *ibPtr++ = k2 + globalVertexCounter;
                                 *ibPtr++ = k1 + 1 + globalVertexCounter;

                                 ++globalPermitiveCounter;
                             }

                             if (stackIndex != (sphereStackCount-1))
                             {
                                 *ibPtr++ = k1 + 1 + globalVertexCounter;
                                 *ibPtr++ = k2 + globalVertexCounter;
                                 *ibPtr++ = k2 + 1 + globalVertexCounter;

                                 ++globalPermitiveCounter;

                             }

                             sphereSectorAngle = sectorIndex * sphereSectorStep;

                             float x = xy * qCos(sphereSectorAngle);
                             float y = xy * qSin(sphereSectorAngle);

                             //position
                             *vbPtr++ = x + subsetPolygonCenter.x();
                             *vbPtr++ = y + subsetPolygonCenter.y();
                             *vbPtr++ = z + subsetPolygonCenter.z();

                             //normal
                             *vbPtr++ = x * sphereRadiuslengthInv;
                             *vbPtr++ = y * sphereRadiuslengthInv;
                             *vbPtr++ = z * sphereRadiuslengthInv;

                             //tangent
                             *vbPtr++ = 0.0;
                             *vbPtr++ = 0.0;
                             *vbPtr++ = 0.0;

                             //binormal
                             *vbPtr++ = 0.0;
                             *vbPtr++ = 0.0;
                             *vbPtr++ = 0.0;

                             //color
                             *vbPtr++ = roofColor.redF();
                             *vbPtr++ = roofColor.greenF();
                             *vbPtr++ = roofColor.blueF();
                             *vbPtr++ = 1.0;

                             //texcoord
                             *vbPtr++ = 1.0;
                             *vbPtr++ = 1.0;

                             *vbPtr++ = 0.0;
                             *vbPtr++ = 1.0;

                         }
                     }
                     subsetVertexCounter += sphereVetexCount;
                     globalVertexCounter += sphereVetexCount;
                 }
                 {

                     std::vector<PolygonVertices> roofPolygonsVerices;
                     roofPolygonsVerices.push_back( roofPolygonVertices );
                     std::vector<uint32_t> roofIndices = mapbox::earcut<uint32_t>(roofPolygonsVerices);

                     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 * striedVertexLen];
                     ibPtr = &reinterpret_cast<uint32_t *>(indexData.data())[globalPermitiveCounter * 3];

                     for ( const uint32_t &roofIndex : roofIndices ) {
                         *ibPtr++ = roofIndex + globalVertexCounter;
                     }
                     qsizetype roofPermitiveCount = roofIndices.size() / 3;
                     globalPermitiveCounter += roofPermitiveCount;

                     for ( const PolygonVertex &polygonVertex : roofPolygonVertices ) {

                         //position
                         *vbPtr++ = polygonVertex.at(0);
                         *vbPtr++ = polygonVertex.at(1);
                         *vbPtr++ = height;

                         //normal
                         *vbPtr++ = 0.0;
                         *vbPtr++ = 0.0;
                         *vbPtr++ = 1.0;

                         //tangent
                         *vbPtr++ = 1.0;
                         *vbPtr++ = 0.0;
                         *vbPtr++ = 0.0;

                         //binormal
                         *vbPtr++ = 0.0;
                         *vbPtr++ = 1.0;
                         *vbPtr++ = 0.0;

                         //color/
                         *vbPtr++ = roofColor.redF();
                         *vbPtr++ = roofColor.greenF();
                         *vbPtr++ = roofColor.blueF();
                         *vbPtr++ = 1.0;

                         //texcoord
                         *vbPtr++ = 1.0;
                         *vbPtr++ = 1.0;

                         *vbPtr++ = 0.0;
                         *vbPtr++ = 1.0;

                         ++subsetVertexCounter;
                         ++globalVertexCounter;
                     }

                 }

             }
         }
     }

     clear();

The map downloaded PNG data send to a custom QQuick3DTextureData item to convert the PNG format data to a texture for use as a texture for map tiles.

 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);
 }

It uses camera position, orientation, zoom level, and tilt to find the nearest tiles in the view.

 void OSMManager::setCameraProperties(const QVector3D &position, const QVector3D &right,
                                     float cameraZoom, float minimunZoom, float maximumZoom,
                                     float cameraTilt, float minimumTilt, float maxmumTilt)
 {

     float tiltFactor = (cameraTilt - minimumTilt) / qMax(maxmumTilt - minimumTilt, 1.0);
     float zoomFactor = (cameraZoom - minimunZoom) / qMax(maximumZoom - minimunZoom, 1.0);

     QVector3D forwardVector = QVector3D::crossProduct(right,
                                                       QVector3D(0.0, 0.0, -1.0)).normalized(); //Forward vector align to the XY plane
     QVector3D projectionOfForwardOnXY = position
             + forwardVector * tiltFactor * zoomFactor * 50.0;

     QQueue<OSMTileData> queue;
     for ( int fowardIndex = -20; fowardIndex <= 20; ++fowardIndex ){
         for ( int sidewardIndex = -20; sidewardIndex <= 20; ++sidewardIndex ){
             QVector3D transferedPosition = projectionOfForwardOnXY + QVector3D(float(m_tileSizeX * sidewardIndex)
                                                                                , float(m_tileSizeY * fowardIndex), 0.0);
             addBuildingRequestToQueue(queue, m_startBuildingTileX + int(transferedPosition.x() / m_tileSizeX),
                                 m_startBuildingTileY - int(transferedPosition.y() / m_tileSizeY));
         }
     }

     int projectedTileX = m_startBuildingTileX + int(projectionOfForwardOnXY.x() / m_tileSizeX);
     int projectedTileY = m_startBuildingTileY - int(projectionOfForwardOnXY.y() / m_tileSizeY);

     std::sort(queue.begin(), queue.end(), [projectedTileX, projectedTileY](const OSMTileData &v1, const OSMTileData &v2)->bool{
         return qSqrt(qPow(v1.TileX - projectedTileX, 2)
                      + qPow(v1.TileY - projectedTileY, 2))
                <
               qSqrt(qPow(v2.TileX - projectedTileX, 2)
                         + qPow(v2.TileY - projectedTileY, 2));
     });

     m_request->getBuildingsData( queue );
     m_request->getMapsData( queue );

Also generates the tiles request queue to submit as a request.

 void OSMManager::addBuildingRequestToQueue(QQueue<OSMTileData> &queue, int tileX, int tileY, int zoomLevel)
 {
     QString key = QString::number(tileX) + "," + QString::number(tileY) + "," + QString::number(zoomLevel);
     if ( m_buildingsHash.contains( key ) )
         return;

     OSMTileData tile;
     tile.ZoomLevel = zoomLevel;
     tile.TileX = tileX;
     tile.TileY = tileY;
     queue.append( tile );

 }

The app uses a custom camera controller to navigate through the map, using the left mouse button to pan the map and right mouse button to rotate, and mouse wheel to zoom the camera.

         OSMCameraController {
             id: cameraController
             origin: originNode
             camera: cameraNode
         }

Every chunk of the map tile consists of two types of the QML model and custom material. one using a rectangle as a base to render tile map texture.

         ...
         id: chunkModelMap
         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 * tileX, osmManager.tileSizeY * -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( mapData )
                                 } }
                         }
                         shadingMode: CustomMaterial.Shaded
                         cullMode: Material.BackFaceCulling
                         fragmentShader: "customshadertiles.frag"
                     }
                 ]
             }

It's using a custom geometry to render tile buildings.

         ...
         id: chunkModelBuilding
         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( geoVariantsList )
                     onGeometryReady:{
                         model.geometry = osmGeometry
                     }
                 }
                 materials: [

                     CustomMaterial {
                         shadingMode: CustomMaterial.Shaded
                         cullMode: Material.BackFaceCulling
                         vertexShader: "customshaderbuildings.vert"
                         fragmentShader: "customshaderbuildings.frag"
                     }
                 ]
             }

To render buildings parts e.g rooftop and body of the building with one draw call, it uses a custom shader to render building chunks.

 // 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;
 }

To understand the application better, browse through its code using Qt Creator.

Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.

Example project @ code.qt.io

See also QML Applications.