2 • Mapbox in Unity
Mapbox and Geo Data
Mapbox is similar to Cesium in that it provides a platform for creating 3D maps and geospatial applications. It provides a suite of tools for creating vector-based maps and terrain, and for visualizing and analyzing geospatial data inside the Mapbox Studio, where you can manage your datasets, tilesets, and styles.
You can upload any geospatial datasets to Mapbox to access them in the Studio or in the Unity SDK. Besides GeoTIFF georeferenced images, what's most interesting to us are GeoJSON files. GeoJSON stores features and their properties, and can be used to represent points, lines, and polygons. Such properties can be used to color, scale, or otherwise manipulate the features in Unity, or even to spawn agents or other objects, depending on their data.
You can also create your own datasets in the Mapbox Studio, point by point and line by line, and use that.
You can find lots of interesting GeoJSON datasets on the web. An example is the French Open Data platform, where you can easily filter for GeoJSON: data.gouv.fr. Take a moment to browser around for inspiration.
Update for modern Unity (URP)
The SDK Mapbox provides for Unity is a plugin that allows you to use Mapbox's vector-based maps and data in Unity. Unfortunately, this SDK has not been updated in a few years and has fallen behind in compatibility with modern Unity Versions. Here I will guide you to make the necessary adjustments to make it work anyway.
We can continue in our exiting Unity project.
Install Mapbox SDK in Unity
Go to the Mapbox Unity SDK page and click the install button (you need to be signed in to your Mapbox account): Mapbox Unity SDK
When you have downloaded the unitypackage file, you can import it by going to Assets → Import Package → Custom Package
and selecting the file.
It's critical that you deselect everything related to AR during the import process, as these parts of the SDK are not compatible with the latest Unity versions and would require a lot of manual work to fix them. Keep only Mapbox and ThirdPartyAssets.
Once the imports are done, you have to input your Access Token that the website provides you into the window that pops up in Unity. Hit Submit to validate it.
Fix the Raster Data Initialization Script
You could now explore a few of the Example Scenes this window provides you with (we will use CitySimulator for this tutorial), but you will quickly run into the first problem with modern Unity and the old SDK.
Double-click this error to edit the code that's responsible for it, which is line 294 in UnityTile.cs :
_rasterData = new Texture2D(0, 0, TextureFormat.RGB24, useMipMap);
Change the zeros to ones, save the file and the error should disappear:
_rasterData = new Texture2D(1, 1, TextureFormat.RGB24, useMipMap);
Fix the Materials
Now that we can see the loaded map (the CitySimulator example from the Mapbox Setup in this case), we see the second problem with the outdated SDK: it does not work with the Universal Render Pipeline (URP) that we are using. The shaders are not compatible, and we thus only see magenta shapes.
We need to create new materials or update those that are being used with Mapbox. In the examples, and in any new Abstract Map component you create, you will find that they use the TerrainMaterial as a Tile Material under GENERAL → Others
:
Clicking on it in the inspector, we can get to it in the project view and see that it uses the Standard shader, which is not compatible with URP:
Changing this shader to the URP equivalent, Universal Render Pipeline/Simple Lit fixes the issue, and we can see the map texture loading. By the way, you can avoid pressing the Play button to see the changes to Mapbox maps, instead you can toggle the Enable Preview button in the inspector of the Abstract Map component:
Yet there is still more magenta. In the CitySimulator example, TerrainMaterial is not the only material that needs to be updated. Let's see how the buildings are being constructed in this the example to understand how we can change them.
Map Layers and Features
In the Abstract Map component of the CitySimulatorMap object, we can toggle MAP LAYERS to see what is being loaded and constructed besides the IMAGE and TERRAIN (the map we just fixed). Expanding it, we can see which Data Source is being used: this is either one of the sources supplied by Mapbox, or it can one or multiple of your own datasets.
From the selected dataset, FEATURES can be defined. These can be predefined, such as Buildings and Roads in this case, or custom ones you can create yourself. What they do is they interpret the data from the dataset and create objects in Unity based on it.
For instance, the Buildings feature creates 3D objects for each building in the building Data Layer of the Data Source by finding the footprint shape and height in the data along with its position, and uses that information to create meshes and place them in the scene. This is can be seen in detail under the Modeling section: the Feature looks for the Property Name height
in the data, and uses that to extrude the polygon created from the building footprint upwards.
The Building also features a special Texturing section, where you can define how the building should be textured. In the example, it uses the "Realistic" option, which creates dynamic textures to make the buildings look more realistic and correspond to their heights.
In order to just see the buildings without the textures, you can change this to "Custom", which lets us choose two different materials for the sides and the top of the buildings. We can then create these materials in the project view and assign them here, or just adjust the existing default materials like we did for the map.
Previewing the map now lets us see the buildings in their basic form, with only the streets remaining magenta:
As an exercise, you can try to fix the streets the same way.
Working with Features
Let's try to understand how we can actually do something with the features in a data source other than to model them to buildings and streets. As an example, we can examine how Points of Interest (POIs) can be created in the Mapbox SDK.
In the CitySimulator object, we create a add a new map feature using the button FEATURES, selecting Points. It will automatically configure itself to access the "poi_label" Data Layer of the Data Source. This default Mapbox layer contains the positions of the POIs and their names, such as shops and restaurants and other establishment. You can change that to "place_label" for city names and such, but let's stick with the default for now.
Using the Filters interface, we can already see what Keys are available in the data, such as name
and type
by adding a new filter. We can use these to filter the data and only show certain types of POIs, or to color them differently based on their type. You can also find this data in Mapbox Studio if you are inspecting default or imported data sets, and of course if you make your own. These Keys are the properties of the GeoJSON features, and we can work with them if we want to create our own features.
In order to have something appear at a POI, we can use a Game Object Modifier to create a prefab that will be instantiated at the position of the POI. This prefab can be something you yourself create, but to understand the process, we can use an existing one. Click the Add Existing button under Game Object Modifier and select the "DefaultPoiLabelModifier" Prefab modifier from the window that pops up:
If we run the game, nothing changes as far as we can see, because the Modifier we selected is not fully configured yet. Double-click on it within our Feature to get to its inspector, from where we can assign a prefab for it to load — this is currently empty. Click on the Prefab field, search for the "DefaultPoiLabelPrefab", and select it:
If we run the game now, we can already see the POIs being loaded and instantiated in the scene. They show an icon and a text field that displays the name of the POI, even if the placement is not ideal for now:
Understanding Features and Modifiers
If we understand how this works, we can create our own prefabs and modifiers to do something more interesting with the POIs depending on the data that comes from our sources. Let's have a look.
Double-click on the PrefabModifier script that you see in the screenshot above, or just find it in the project view.
The interesting part of the script is the PositionScaleRectTransform()
method at line 62, which takes a VectorEntitiy ve, UnityTile tile, GameObject go
as arguments. The VectorEntity
is the data that comes from the GeoJSON file (specifically the one entity that we want to do something with), the UnityTile
is the tile that the POI is on (providing us with a scale), and the GameObject
is the prefab that is being instantiated.
This method is called after a prefab has been instantiated and assigned the correct parent transform, and is now responsible for adjusting its position, scale, and other properties. The most interesting part for us is the code block at line 93:
settable = go.GetComponent<IFeaturePropertySettable>();
if (settable != null)
{
settable.Set(ve.Feature.Properties);
}
It checks if the prefab has a component that implements the IFeaturePropertySettable
interface, and if it does, it calls its Set()
method with the properties of the GeoJSON feature. This is where we can do something with the data that comes from the source. Let's look at the example of the DefaultPoiLabelPrefab that we are using again by double-clicking it:
In the hierarchy of the prefab we can see its Text and Icon objects, and in the inspector of the parent objects we have two important scripts. First is the CameraBillboard
script, which makes the prefab always face the camera. More important now though is the PoiLabelTextSetter
script, which implements the IFeaturePropertySettable
interface. Let's have a closer look at its code by double-clicking it:
namespace Mapbox.Examples
{
using Mapbox.Unity.MeshGeneration.Interfaces;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class PoiLabelTextSetter : MonoBehaviour, IFeaturePropertySettable
{
[SerializeField]
Text _text;
[SerializeField]
Image _background;
public void Set(Dictionary<string, object> props)
{
_text.text = "";
if (props.ContainsKey("name"))
{
_text.text = props["name"].ToString();
}
else if (props.ContainsKey("house_num"))
{
_text.text = props["house_num"].ToString();
}
else if (props.ContainsKey("type"))
{
_text.text = props["type"].ToString();
}
RefreshBackground();
}
public void RefreshBackground()
{
RectTransform backgroundRect = _background.GetComponent<RectTransform>();
LayoutRebuilder.ForceRebuildLayoutImmediate(backgroundRect);
}
}
}
Here we see the Set()
method that we saw in the PrefabModifier
script, which sets the text of the _text
object of the prefab based on the properties of the GeoJSON feature. It checks if the properties contain a name
, house_num
, or type
key, and sets the text accordingly. It also calls RefreshBackground()
to make sure the background of the text is correctly sized.
Putting it Together
Digging down like this should have hopefully given you an understanding of how this basic functionality works.
You can create your own prefabs with your own functionalities that can be placed anywhere on maps based on the data you have, and have the data dictate their behavior, as long as you follow this pattern.
For different kinds of prefabs you might want to work with, you can select Add New instead of Add Exisiting, and the SDK will create a new one for you under Assets/Mapbox/User/Modifiers/
. There, you can assign any kind of prefab you modified or created, and within the prefab you can create or modify the Set()
method in its script that implements IFeaturePropertySettable
to do whatever you want with the data.
There are of course many more kinds of modifiers that the SDK provides you with, which you are free to explore, but these may exceed the scope of this workshop. The API Documentation is extensive, if a bit dense, and can be found here: Mapbox Unity SDK API Documentation
Mapbox and Cesium Together
For the best of both worlds, we can of course load both Cesium and Mapbox maps into the same scene. The quickest way for this tutorial is to continue in the CitySimulator scene we were already working on, and simply adding a new CesiumGeoreference with Google Photorealistic 3D Tiles to it, like in the last part, using the Cesium window in Unity.
Lining both maps up is as easy as just entering the same coordinates in the CesiumGeoreference as in the Abstract Map component of the Mapbox map, and make sure that the Scaling Options under GENERAL → Others
are set to World Scale
. In our example, the CitySimulatorMap has a Latitude Longitude field with 37.784179, -122.401583
— San Francisco. Takes these numbers and place them into the according CesiumGeoreference fields. You will see that the two maps are perfectly aligned, but may differ in height, depending on what you set as the origin in the CesiumGeoreference. Adjust that number, or move the CitySimulatorMap up or down to match the heights as you see fit, and you're ready to go!