Skip to main content
Version: 12 Dec 2024

Localization Map Examples

These scripts demonstrate various functionalities of the OpenXR Localization Map Feature, which is used for managing spaces in a mixed reality environment. The scripts are broken down into specific functionalities: requesting permissions, retrieving available spaces, localizing into a space, exporting and saving spaces, and importing previously saved spaces. Each script serves as an isolated example of a particular functionality, making it easier to understand and implement these functions in your own projects. They are designed to be used with the Unity game engine and the Magic Leap platform. Note that they should be used in a real device environment for proper functioning.

caution

This feature requires the Magic Leap 2 Localization Maps OpenXR Feature to be enabled in your project's OpenXR Settings (Window > XR Plugin Manager > OpenXR Settings).

Query Latest Localization Map Data

The GetLatestLocalizationMapData method can be used to query the current localization status at any point. The function will return true if the localization status changed the LocalizationResult was retrieved successfully.

using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;

public class QueryLocalizationSample : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature = null;

private void Start()
{
// Obtain the instance of the localization Map Feature
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
// If it is not present return
if (localizationMapFeature == null || !localizationMapFeature.enabled)
{
Debug.LogError("Magic Leap Localization Map Feature does not exists or is disabled.");
return;
}

// Enable the localization Events
XrResult result = localizationMapFeature.EnableLocalizationEvents(true);
if (result != XrResult.Success)
{
Debug.LogError($"Failed to enable localization events with result: {result}");
}
}

private void Update()
{
CheckLocalizationStatus();
}

private void CheckLocalizationStatus()
{
LocalizationEventData data;
if (localizationMapFeature.GetLatestLocalizationMapData(out data))
{
if (data.State == LocalizationMapState.Localized)
{
Debug.Log($"Localized to space: {data.Map.Name}. Origin : {localizationMapFeature.GetMapOrigin()}");
}
else
{
Debug.Log($"Localization State: {data.State}");
}
}
}
}

Get Map Origin

The Magic Leap Localization Map API allows developers to query the Map center using the LocalizationMapFeature.GetMapOrigin function. This pose can be used similar to an Anchor position when creating persistent content or multiplayer experiences. The Map origin should only be queried when the player is Localized into a space.

using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;

public class MapOriginSample : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature = null;

private void Start()
{
// Obtain the instance of the localization Map Feature
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
// If it is not present return
if (localizationMapFeature == null || !localizationMapFeature.enabled)
{
Debug.LogError("Magic Leap Localization Map Feature does not exists or is disabled.");
return;
}

// Enable the localization Events
XrResult result = localizationMapFeature.EnableLocalizationEvents(true);
if (result != XrResult.Success)
{
Debug.LogError($"Failed to enable localization events with result: {result}");
}
}

private void Update()
{
LocalizationEventData data;
if (localizationMapFeature.GetLatestLocalizationMapData(out data) && data.State == LocalizationMapState.Localized)
{
Debug.Log($"Localized Map Origin : {localizationMapFeature.GetMapOrigin()}");
}
}
}

Localization Map Changed Event

The OnLocalizationChanged event is invoked when the localization status of the device changes. Subscribing to this event provides an easy way to receive updates about the localization status.

using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;

public class QueryLocalizationSample : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature = null;

private void Start()
{
// Obtain the instance of the localization Map Feature
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
// If it is not present return
if (localizationMapFeature == null || !localizationMapFeature.enabled)
{
Debug.LogError("Magic Leap Localization Map Feature does not exists or is disabled.");
return;
}

// Enable the localization Events
XrResult result = localizationMapFeature.EnableLocalizationEvents(true);
if (result != XrResult.Success)
{
Debug.LogError($"Failed to enable localization events with result: {result}");
return;
}

MagicLeapLocalizationMapFeature.OnLocalizationChangedEvent += OnLocalizationChanged;
}

private void OnLocalizationChanged(LocalizationEventData data)
{
// Log the localization status and the name of the localized space.
string status = data.State.ToString();

string mapName = data.State
== LocalizationMapState.Localized ? data.Map.Name : "None";

Debug.Log("Localization Status: " + status);
Debug.Log("Localized Map Name: " + mapName);
}
}

List Available Spaces

This script demonstrates how to fetch the list of available Spaces using the MLSpace API. Then the script prints the name of each Space to the debug log.

using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;

// This script fetches the list of available spaces.
public class LocalizationMapTest : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature = null;

private void Start()
{
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
GetListOfAvailableSpaces();
}

public void GetListOfAvailableSpaces()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

XrResult result = localizationMapFeature.GetLocalizationMapsList(out LocalizationMap[] maps);

if (result == XrResult.Success)
{
foreach (LocalizationMap map in maps)
{
Debug.Log($"Map Name: {map.Name} \n"
+ $" Map UUID: {map.MapUUID} \n"
+ $" Map Type: {map.MapType}");
}
}
}
}

Localizing into a Space

This script demonstrates how to localize a Magic Leap device into a specific space. The script automatically localizes to the first available space in the list, showcasing the basic process of using the localization feature.

using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;

// This script localizes into the first available space.
public class LocalizationMapTest : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature = null;
private LocalizationMap[] maps;

private void Start()
{
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
QueryMaps();
LocalizeIntoMap();
}

public void QueryMaps()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;
localizationMapFeature.GetLocalizationMapsList(out maps);
}

public void LocalizeIntoMap()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

if (maps.Length == 0)
{
Debug.LogError("Must query available maps first");
return;
}

XrResult result = localizationMapFeature.RequestMapLocalization(maps[0].MapUUID);
Debug.Log($"Localize request result: {result}");
}
}

Exporting and Saving Spaces

This script is concerned with exporting a space and saving it to a file. Exporting a space allows you to share it between devices. In this script, the first available space is exported and then saved to a binary file.

caution

This features requires the com.magicleap.permission.SPACE_IMPORT_EXPORT permission to be requested at runtime and enabled in your project's Manifest Settings (Edit > Project Settings > Magic Leap > Manifest Settings).

using System.IO;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;

// This script exports the first available space and saves it to a file.
public class LocalizationMapTest : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature = null;
private LocalizationMap[] maps;
private string spaceFileName = "exported_space.bin";
private string filePath => Path.Combine(Application.persistentDataPath, spaceFileName);

private void Start()
{
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
QueryMaps();
ExportMap();
}

public void QueryMaps()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;
localizationMapFeature.GetLocalizationMapsList(out maps);
}

public void ExportMap()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

if (maps.Length == 0)
{
Debug.LogError("Must query available maps first");
return;
}

XrResult result = localizationMapFeature.ExportLocalizationMap(maps[0].MapUUID, out byte[] mapData);
Debug.Log($"Export map result: {result}");
if (result == XrResult.Success)
{
SaveBytesToFile(mapData);
}
}

private void SaveBytesToFile(byte[] binaryData)
{
using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
{
fileStream.Write(binaryData, 0, binaryData.Length);
}

if (File.Exists(filePath))
{
Debug.Log("Binary data saved to file: " + filePath);
}
else
{
Debug.LogError("Failed to save binary data to file: " + filePath);
}
}
}

Importing Spaces

This script demonstrates importing a previously saved space. Importing a space involves loading a previously exported space, allowing the device to recognize and localize into it. In this case, the script attempts to import a space from a previously saved binary file.

caution

This features requires the com.magicleap.permission.SPACE_IMPORT_EXPORT permission to be requested at runtime and enabled in your project's Manifest Settings (Edit > Project Settings > Magic Leap > Manifest Settings).

using System.IO;
using MagicLeap.Android;
using UnityEngine;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;

// This script imports a previously saved space.
public class ImportSpaceExample : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature = null;
private string spaceFileName = "exported_space.bin";

void Start()
{
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
ImportLastSpace();
}

public void ImportLastSpace()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled || !Permissions.CheckPermission(Permissions.SpaceImportExport))
return;

string filePath = Path.Combine(Application.persistentDataPath, spaceFileName);

if (!File.Exists(filePath))
{
Debug.LogWarning("No spaces have been exported, cannot import last exported space.");
return;
}

byte[] binaryData;
using (FileStream fileStream = new FileStream(filePath, FileMode.Open))
{
binaryData = new byte[fileStream.Length];
fileStream.Read(binaryData, 0, binaryData.Length);
}

XrResult result = localizationMapFeature.ImportLocalizationMap(binaryData, out string mapId);
if (result == XrResult.Success)
{
Debug.Log($"Space {mapId} Imported Successfully");
}
}
}

Complete Example

This example script that script demonstrates how to use the Open Magic Leap Localization Map Features to manage and interact with virtual spaces. The script manages a list of available Maps, keeps track of the localization status, and supports the importing and exporting of spaces. The script logs relevant status updates and errors are logged to the Debug Log, providing visibility to the script's operation.

caution

This features requires the com.magicleap.permission.SPACE_IMPORT_EXPORT permission to be requested at runtime and enabled in your project's Manifest Settings (Edit > Project Settings > Magic Leap > Manifest Settings).

using System;
using System.IO;
using UnityEngine;
using UnityEngine.XR.MagicLeap;
using UnityEngine.XR.OpenXR;
using UnityEngine.XR.OpenXR.NativeTypes;
using MagicLeap.OpenXR.Features.LocalizationMaps;
using UnityEngine.Android;

public class LocalizationMapTest : MonoBehaviour
{
private MagicLeapLocalizationMapFeature localizationMapFeature;
private LocalizationMap[] maps;
private string saveFilePath => Path.Combine(Application.persistentDataPath, spaceFileName);
private string spaceFileName = "exported_space.bin";

private readonly PermissionCallbacks permissionCallbacks = new PermissionCallbacks();

private void Start()
{
// Setup event listeners for permissions and localization events.
permissionCallbacks.PermissionGranted += OnPermissionGranted;
permissionCallbacks.PermissionDenied += OnPermissionDenied;
permissionCallbacks.PermissionDeniedAndDontAskAgain += OnPermissionDenied;

// Request permissions for space import/export and define the path for the exported space file.
Permission.RequestUserPermission(MagicLeap.Android.Permissions.SpaceImportExport, permissionCallbacks);
}

void OnPermissionDenied(string permission)
{
Debug.LogError($"{permission} Denied, Test Won't Function. Disabling");
enabled = false;
}

void OnPermissionGranted(string permission)
{
// When the requested permission is granted, test the API features.
InitializeFeatureTest();
}

private void InitializeFeatureTest()
{
// Get the Magic Leap Localization Map Feature and make sure it is accessible
localizationMapFeature = OpenXRSettings.Instance.GetFeature<MagicLeapLocalizationMapFeature>();
if (localizationMapFeature == null || !localizationMapFeature.enabled)
{
Debug.LogError("Magic Leap Localization Map Feature is unavailable or disabled. Disabling script.");
return;
}
localizationMapFeature.EnableLocalizationEvents(true);

MagicLeapLocalizationMapFeature.OnLocalizationChangedEvent += OnLocalizationChangedEvent;
ExecuteFeatureTests();
}

private void ExecuteFeatureTests()
{
CheckLocalizationStatus();
QueryMaps();
ExportMap();
ImportMap();
LocalizeIntoMap();
}

void OnDestroy()
{
// Unsubscribe event listeners when the object is destroyed.
permissionCallbacks.PermissionGranted -= OnPermissionGranted;
permissionCallbacks.PermissionDenied -= OnPermissionDenied;
permissionCallbacks.PermissionDeniedAndDontAskAgain -= OnPermissionDenied;

if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

MagicLeapLocalizationMapFeature.OnLocalizationChangedEvent -= OnLocalizationChangedEvent;
}

// Gets an array of all the available maps
public void QueryMaps()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

XrResult result = localizationMapFeature.GetLocalizationMapsList(out maps);

if (result == XrResult.Success)
{
foreach (LocalizationMap map in maps)
{
Debug.Log($"Map Name: {map.Name} \n" + $" Map UUID: {map.MapUUID} \n" + $" Map Type: {map.MapType}");
}
}
}

// Poll the localization status manually.
void CheckLocalizationStatus()
{
if (localizationMapFeature.GetLatestLocalizationMapData(out LocalizationEventData data))
{
OnLocalizationChangedEvent(data);
}
}

// Called when the localization status changes via the MagicLeapLocalizationMapFeature.OnLocalizationChangedEvent callback.
private void OnLocalizationChangedEvent(LocalizationEventData localizationEventData)
{
// Update and log the localization status and the name of the localized space.
string status = localizationEventData.State.ToString();

string mapName = localizationEventData.State
== LocalizationMapState.Localized ? localizationEventData.Map.Name : "None";

Debug.Log("Localization Status: " + status);
Debug.Log("Localized Map Name: " + mapName);
Debug.Log("Localized Map Origin: " + localizationMapFeature.GetMapOrigin());
}

// Export the first Map from the map array to a file on the device using the SaveMapToFile() function
public void ExportMap()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

if (maps.Length == 0)
{
Debug.LogError("Must query available maps first");
return;
}

var result = localizationMapFeature.ExportLocalizationMap(maps[0].MapUUID, out byte[] mapData);
Debug.Log($"Export map result: {result}");
SaveBytesToFile(mapData, saveFilePath);
}

// Import a Map from a saved file path
public void ImportMap()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

byte[] binaryData = LoadFileAsBytes(saveFilePath);
if (binaryData.Length > 0)
{
// Import the byte array as a map
XrResult result = localizationMapFeature.ImportLocalizationMap(binaryData, out string importedMapID);
Debug.Log($"Import map result: {result}. Imported Map UUID: {importedMapID}");
}
}

// Localize into the first map queried from the device
public void LocalizeIntoMap()
{
if (localizationMapFeature == null || !localizationMapFeature.enabled)
return;

if (maps.Length == 0)
{
Debug.LogError("Must query available maps first");
return;
}

XrResult result = localizationMapFeature.RequestMapLocalization(maps[0].MapUUID);
Debug.Log($"Localize request result: {result}");
}


// Saves binary data into a file on the device
private void SaveBytesToFile(byte[] binaryData, string filePath)
{
using (FileStream fileStream = new FileStream(filePath, FileMode.Create))
{
fileStream.Write(binaryData, 0, binaryData.Length);
}

if (File.Exists(filePath))
{
Debug.Log("Binary data saved to file: " + filePath);
}
else
{
Debug.LogError("Failed to save binary data to file: " + filePath);
}
}

// Loads a file as bytes if successful. Otherwise returns an empty byte array.
private byte[] LoadFileAsBytes(string filePath)
{
//Read file from bytes
if (!File.Exists(filePath))
{
Debug.LogWarning("No maps have been exported, cannot import last exported map.");
return Array.Empty<byte>();
}

using FileStream fileStream = new FileStream(filePath, FileMode.Open);
byte[] binaryData = new byte[fileStream.Length];
int bytesRead = fileStream.Read(binaryData, 0, binaryData.Length);
if (bytesRead < binaryData.Length)
{
Debug.LogError($"Could not read all bytes from: {filePath} . Import failed!");
return Array.Empty<byte>();
}

return binaryData;
}
}