Skip To Content ArcGIS for Developers Sign In Dashboard

Overview

You will learn: how to build an app that displays a vector basemap with custom styles.

Applications can access and display vector tile basemaps and basemaps that have custom styles. You can create your own custom styles with the ArcGIS Vector Tile Style Editor. Custom basemaps are stored as tile layer items in ArcGIS Online or ArcGIS Enterprise. To add the layer to your map, use the item ID to create the vector tile layer, add it to a basemap, and then add the basemap to your application. Additional layers can also be added to a basemap to enhance the visualization. To learn more about how to create your own custom basemap styles, see the Style a vector basemap tutorial.

In this tutorial, you will build a mapping app that loads a custom vector basemap named Forest and Parks Canvas. This basemap emphasizes national forests and parks.

Before you begin

You must have previously installed the ArcGIS Runtime SDK for Qt and set up the development environment for your operating system.

Open the starter app project

If you have already completed the Create a starter app tutorial, start Qt Creator and open your starter app project. Otherwise, download and unzip the starter app project solution, and then open it in Qt Creator.

Steps

Import headers and declare member variables

  1. In Projects, double click on Headers > Create_a_starter_app.h and add the Basemap class to the namespace declaration.

    namespace Esri
    {
    namespace ArcGISRuntime
    {
    class Map;
    class MapQuickView;
    
    // *** ADD ***
    class Basemap;
    
  2. Replace the include statement for QObject with QQuickItem.

    namespace Esri
    {
    namespace ArcGISRuntime
    {
    class Map;
    class MapQuickView;
    class Basemap;
    
    // *** UPDATE ***
    // #include <QObject>
    #include <QQuickItem>
    
  3. Update the constructor to replace all occurrences of QObject with QQuickItem.

    // *** UPDATE ***
    // class Create_a_starter_app: public QObject
    class Create_a_starter_app: public QQuickItem
    {
      Q_OBJECT
    
      Q_PROPERTY(Esri::ArcGISRuntime::MapQuickView* mapView READ mapView WRITE setMapView NOTIFY mapViewChanged)
    
    public:
      // explicit Create_a_starter_app(QObject* parent = nullptr);
      explicit Create_a_starter_app(QQuickItem* parent = nullptr);
      ~Create_a_starter_app() override;
    
  4. Directly under the destructor declaration, add the following public method.

    public:
      explicit Create_a_starter_app(QQuickItem* parent = nullptr);
      ~Create_a_starter_app() override;
    
      // *** ADD ***
      Q_INVOKABLE void changeBasemap(const QString& basemap_str);
    
  5. Add two new pointer variable declarations. These variables will be used to store both basemaps.

    private:
      Esri::ArcGISRuntime::MapQuickView* mapView() const;
      void setMapView(Esri::ArcGISRuntime::MapQuickView* mapView);
    
      Esri::ArcGISRuntime::Map* m_map = nullptr;
      Esri::ArcGISRuntime::MapQuickView* m_mapView = nullptr;
    
      // *** ADD ***
      Esri::ArcGISRuntime::Basemap* m_defaultBasemap = nullptr;
      Esri::ArcGISRuntime::Basemap* m_styledBasemap = nullptr;
    

Add the UI to change the basemap as a QML component

  1. In Projects, double click on Resources > qml/qml.qrc > /qml > Create_a_starter_appForm.qml and add the import statements.

    import QtQuick 2.6
    import QtQuick.Controls 2.2
    
    // *** ADD ***
    import QtQuick.Window 2.2
    
  2. Add the following ComboBox QML component to switch back and forth between the default and vector styled basemap.

    Item {
    
      // Create MapQuickView here, and create its Map etc. in C++ code
      MapView {
          id: view
          anchors.fill: parent
          // set focus to enable keyboard navigation
          focus: true
      }
    
      // Declare the C++ instance which creates the map etc. and supply the view
      Create_a_starter_app {
        mapView: view
    
        // *** ADD ***
        id: model
        ComboBox {
          anchors {
            left: parent.left
            top: parent.top
            margins: 15
          }
          width: 175
          model: ["default", "styled"]
          onCurrentTextChanged: {
            model.changeBasemap(currentText);
          }
        }
      }
    }
    

Implement the logic to switch between basemap styles

  1. In Projects, double click on Sources > Create_a_starter_app.cpp add two new include header statements.

    #include <QUrl>
    
    // *** ADD ***
    #include "ArcGISVectorTiledLayer.h"
    #include <QQuickItem>
    
  2. Update the constructor with the following code by replacing QQuickItem with QOBject and add code to load both basemaps in the body of the constructor.

    // *** UPDATE ***
    // Create_a_starter_app::Create_a_starter_app(QObject* parent /* = nullptr */):
    //   QObject(parent),
    //   m_map(new Map(Basemap::topographicVector(this), this))
    Create_a_starter_app::Create_a_starter_app(QQuickItem* parent /* = nullptr */):
      QQuickItem(parent)
    {
      m_defaultBasemap = Basemap::topographicVector(this);
      m_map = new Map(m_defaultBasemap, this);
    
      const Point center(-118.71511, 34.09042, SpatialReference::wgs84());
      const Viewpoint viewpoint(center, 300000.0);
    
      m_map->setInitialViewpoint(viewpoint);
    
      // *** ADD ***
      const QString item_id("d2ff12395aeb45998c1b154e25d680e5");
      const QUrl url("https://www.arcgis.com/home/item.html?id=" + item_id);
      ArcGISVectorTiledLayer* styledTile = new ArcGISVectorTiledLayer(url, this);
      m_styledBasemap = new Basemap(styledTile, this);
    }
    
  3. Add the public member function Create_a_starter_app::compareBasemaps.

    void Create_a_starter_app::changeBasemap(const QString& basemap_str)
    {
      if (basemap_str == "default")
        m_map->setBasemap(m_defaultBasemap);
      if (basemap_str == "styled")
        m_map->setBasemap(m_styledBasemap);
    }
    
  4. Run the code and explore the custom national forest and parks vector styles.

Congratulations, you're done!

Your map should display the custom forest and parks canvas basemap. Compare it to our completed solution project.

Challenge

Use your own custom basemap

Complete the tutorial Style a Vector Basemap and use your own custom basemap instead of the one provided here.

Add hillshade as a custom basemap layer

Basemaps can contain multiple layers. To enhance the vector styles with terrain, visit ArcGIS Online and find the ID for the World Hillshade layer. It should be 1b243539f4514b6ba35e7d995890db1d.

Use the ArcGISTiledLayer class to create the hillshade layer.

  const QString hillshade_id("1b243539f4514b6ba35e7d995890db1d");
  const QUrl hillshade_url("https://www.arcgis.com/home/item.html?id=" + hillshade_id);
  ArcGISTiledLayer* hillshadeLayer = new ArcGISTiledLayer(hillshade_url, this);

Load the same styled vector tile layer from above.

  const QString item_id("d2ff12395aeb45998c1b154e25d680e5");
  const QUrl url("https://www.arcgis.com/home/item.html?id=" + item_id);
  ArcGISVectorTiledLayer* styledLayer = new ArcGISVectorTiledLayer(url, this);

Create a new basemap by stacking the styled vector tile layer on top of the hillshade tile layer, and change the opacity of the styled tile layer so the hillshade layer is visible.

  // Change the opacity of the styled tile layer
  constexpr float opacity = 0.65f;
  styledLayer->setOpacity(opacity);

  // Create the new basemap
  Basemap* basemap = new Basemap(QList<Layer*>{hillshadeLayer}, QList<Layer*>{styledLayer}, this);
  m_map = new Map(basemap, this);

Handle load errors

Custom basemaps may not load in your app for multiple reasons, including network availability, the item's privacy settings, or an incorrect item ID. Add error handling to your app to alert the user if the map does not load and display a message explaining why.

For example:

if (styledBasemap->loadStatus() == LoadStatus::FailedToLoad)
{
  qDebug() << "The styled basemap failed to load.";
}

Alternatively, you can use QObject::connect and the signaling and slots technique as described in the Add a layer from an item tutorial. However, then you'll have to rewrite the project to register styledBasemap as a QProperty.