using System.Collections.Generic;
using UnityEngine;
namespace InfinityCode.OnlineMapsSupport
{
///
/// Class for displaying vector labels on the map.
///
public class VectorLabels : MonoBehaviour
{
///
/// Singleton instance of VectorLabels.
///
private static VectorLabels instance;
///
/// Container for labels.
///
private static GameObject labelsContainer;
///
/// The language of the labels.
///
public Language language = Language.Russian;
///
/// The font size of the labels.
///
public float fontSize = 15;
///
/// Instances of labels.
///
private Dictionary labels;
///
/// The map.
///
private OnlineMaps map;
///
/// The control.
///
private OnlineMapsControlBaseDynamicMesh control;
///
/// The camera orbit.
///
private OnlineMapsCameraOrbit cameraOrbit;
///
/// Creates a label from vector tile.
///
/// Vector tile.
/// Name of layer.
private void CreateLabels(OnlineMapsVectorTile tile, string layerName)
{
// Get layer by name.
OnlineMapsVectorTile.Layer layer = tile.GetLayer(layerName);
// If the layer is null, return.
if (layer == null) return;
// Get the language key.
string lang = GetLanguageKey();
// Loop through each feature in the layer.
for (int i = 0; i < layer.featureCount; i++)
{
// Get the feature.
OnlineMapsVectorTile.Feature feature = layer.GetFeature(i);
// Check if a label already exists for this feature ID.
Label label;
if (labels.TryGetValue(feature.id, out label))
{
// If the label already exists, add the tile to the owners.
label.owners.Add(tile);
continue;
}
// Get the properties for the feature.
Dictionary properties = feature.GetProperties();
// Get the label value for the current language.
object labelValue;
if (!properties.TryGetValue(lang, out labelValue)) continue;
// Get the type value for the feature.
object typeValue;
if (!properties.TryGetValue("type", out typeValue)) continue;
string type = typeValue as string;
// Get the geometry for the feature.
List> geometry = feature.Geometry();
if (geometry.Count == 0 || geometry[0].Count == 0) continue;
// Create a new label for the feature.
label = new Label(labelValue as string, geometry, layer.extent, tile, type);
// Add the label to the labels dictionary.
labels.Add(feature.id, label);
// Get the symbol rank for the feature.
object symbolRankO;
int symbolRank = 0;
if (properties.TryGetValue("symbolrank", out symbolRankO)) symbolRank = int.Parse(symbolRankO.ToString());
// Set the zoom range for the label.
label.zoomRange = new OnlineMapsRange(symbolRank, symbolRank + 7);
// Update the label.
label.Update(control, cameraOrbit);
}
}
///
/// Gets the language key.
///
/// Language key
public string GetLanguageKey()
{
switch (language)
{
case Language.Arabic:
return "name_ar";
case Language.Chinese:
return "name_zh";
case Language.Chinese_simplified:
return "name_zh-Hans";
case Language.English:
return "name_en";
case Language.French:
return "name_fr";
case Language.German:
return "name_de";
case Language.International:
return "name";
case Language.Portuguese:
return "name_pt";
case Language.Russian:
return "name_ru";
case Language.Spanish:
return "name_es";
}
return "name";
}
///
/// This method is called when the script instance is being loaded.
///
private void OnEnable()
{
instance = this;
}
///
/// This method is called when the tile being disposed.
///
/// Tile that being disposed.
private void OnTileDisposed(OnlineMapsTile tile)
{
// Get the OnlineMapsVectorTile from the tile.
OnlineMapsVectorTile t = tile["vector_tile"] as OnlineMapsVectorTile;
// If the tile doesn't have a vector tile, return.
if (t == null) return;
// Create a list to keep track of which labels to remove.
List labelsToRemove = new List();
// Loop through all the labels.
foreach (KeyValuePair pair in labels)
{
// Get the owners of this label.
var owners = pair.Value.owners;
// If this label is owned by the current tile, remove it.
if (owners.Contains(t))
{
owners.Remove(t);
// If there are no more owners of this label, dispose of it and add its key to the list of labels to remove.
if (owners.Count == 0)
{
pair.Value.Dispose();
labelsToRemove.Add(pair.Key);
}
}
}
// Loop through all the labels to remove and remove them from the labels dictionary.
foreach (ulong key in labelsToRemove) labels.Remove(key);
}
///
/// This method is called when the tile is loaded.
///
/// The loaded tile.
private void OnTileLoaded(OnlineMapsTile tile)
{
// Try to cast the loaded tile to a vector tile.
OnlineMapsVectorTile mt = tile as OnlineMapsVectorTile;
// If the tile is not a vector tile, create a new Mapbox tile and start downloading it.
if (mt == null)
{
// Create a new Mapbox tile.
mt = new OnlineMapsMapboxTile(tile.x, tile.y, tile.zoom, map, false);
// Store the vector tile in the tile.
tile["vector_tile"] = mt;
// Subscribe to the OnDisposed event of the tile.
tile.OnDisposed += OnTileDisposed;
// Start downloading the tile.
OnlineMapsTileManager.StartDownloadTile(mt);
return;
}
// Create labels for country, state, and place.
CreateLabels(mt, "country_label");
CreateLabels(mt, "state_label");
CreateLabels(mt, "place_label");
}
///
/// This method is called when the script instance is being loaded.
///
private void Start()
{
if (!OnlineMapsKeyManager.hasMapbox)
{
Debug.LogError( "You need to add a Mapbox Access Token to the Online Maps Key Manager." );
return;
}
// Get the OnlineMaps component and set it as the map variable.
map = GetComponent();
if (map == null) map = OnlineMaps.instance;
// Get dynamic mesh control and set it as the control variable.
control = map.control as OnlineMapsControlBaseDynamicMesh;
// Get CameraOrbit component and set up the UpdateLabels event listener if available.
cameraOrbit = map.GetComponent();
if (cameraOrbit != null) cameraOrbit.OnCameraControl += UpdateLabels;
// Set up the OnTileLoaded event listener.
OnlineMapsTileManager.OnTileLoaded += OnTileLoaded;
// Initialize the labels dictionary.
labels = new Dictionary();
// Create a new GameObject to hold the labels.
labelsContainer = new GameObject("Labels");
labelsContainer.transform.SetParent(map.transform);
// Set up the OnMapUpdated event listener.
map.OnMapUpdated += UpdateLabels;
}
///
/// Updates the labels.
///
private void UpdateLabels()
{
foreach (var pair in labels) pair.Value.Update(control, cameraOrbit);
}
///
/// Represents a label that can be displayed on the map.
///
public class Label
{
///
/// The zoom range at which this label should be visible.
///
public OnlineMapsRange zoomRange;
///
/// The set of vector tiles that own this label.
///
public HashSet owners = new HashSet();
///
/// The game object that this label is attached to.
///
public GameObject target;
///
/// The longitude of the label's position.
///
public double longitude;
///
/// The latitude of the label's position.
///
public double latitude;
///
/// Whether the label should always be facing up on the map.
///
public bool alwaysUp;
///
/// The label.
///
private string label;
///
/// The type of the label.
///
private string type;
///
/// The rotation angle of the label.
///
private float angle;
///
/// The label has an instance.
///
private bool exists;
///
/// Initializes a new instance of the Label class.
///
/// The label.
/// The geometry.
/// The extent.
/// The vector tile.
/// The type of label.
public Label(string label, List> geometry, ulong extent, OnlineMapsVectorTile tile, string type)
{
// Set label and type
this.label = label;
this.type = type;
// Get geometry center
List g = geometry[0];
Vector2 center;
if (g.Count == 1)
{
// If there is only one point in the list, set the center to that point and always face up.
OnlineMapsVectorTile.LPoint gg = g[0];
center = new Vector2(gg.x / (float)extent, gg.y / (float)extent);
alwaysUp = true;
}
else
{
// If there are multiple points in the list, find the two points that are farthest apart and set the center between them.
Vector2[] points = new Vector2[g.Count];
Vector2 p1 = points[0];
Vector2 p2 = points[1];
float sqrD = (p1 - p2).sqrMagnitude;
for (int i = 0; i < points.Length; i++)
{
var gg = g[i];
Vector2 p = new Vector2(gg.x / (float)extent, gg.y / (float)extent);
points[i] = p;
if (i > 1)
{
Vector2 cp1 = points[i - 1];
Vector2 cp2 = p;
float cd = (cp1 - cp2).sqrMagnitude;
if (cd > sqrD)
{
sqrD = cd;
p1 = cp1;
p2 = cp2;
}
}
}
center = (p1 + p2) / 2;
if (points.Length > 1)
{
// Calculate the angle between the two farthest points and adjust it if it's outside the range of -90 to 90 degrees.
angle = OnlineMapsUtils.Angle2D(p1, p2);
if (angle < -90) angle += 180;
else if (angle > 90) angle -= 180;
}
}
// Convert the center to longitude and latitude.
double lng, lat;
OnlineMaps.instance.projection.TileToCoordinates(tile.x + center.x, tile.y + center.y, tile.zoom, out lng, out lat);
// Set the longitude and latitude.
longitude = lng;
latitude = lat;
// This line adds the tile to the owners list
owners.Add(tile);
}
///
/// Creates an instance of the label.
///
private void CreateInstance()
{
// Set exists to true.
exists = true;
// Create a new game object.
target = new GameObject(label);
target.transform.SetParent(labelsContainer.transform);
// Add a text mesh component to the game object and set the text, character size, alignment, and anchor.
TextMesh textMesh = target.AddComponent();
textMesh.text = label;
textMesh.characterSize = instance.fontSize;
textMesh.alignment = TextAlignment.Center;
textMesh.anchor = TextAnchor.MiddleCenter;
// Set the rotation of the game object.
target.transform.rotation = Quaternion.Euler(90, 180 - angle, 0);
}
///
/// Disposes of the object and cleans up any resources used.
///
public void Dispose()
{
// Check if the object exists before destroying it
if (exists) OnlineMapsUtils.Destroy(target);
// Set all variables to null to free up memory
owners = null;
target = null;
zoomRange = null;
}
///
/// Destroys the instance and sets the exists flag to false.
///
private void DestroyInstance()
{
// Set exists flag to false
exists = false;
// Destroy target object
OnlineMapsUtils.Destroy(target);
}
///
/// Updates the position and rotation of the label on the map.
///
/// Dynamic mesh control.
/// Camera Orbit component.
public void Update(OnlineMapsControlBaseDynamicMesh control, OnlineMapsCameraOrbit cameraOrbit)
{
// Check if the current zoom level is within the specified range
if (!zoomRange.InRange(control.map.floatZoom))
{
// If the instance exists, destroy it
if (exists) DestroyInstance();
return;
}
// Get the world position of the mesh
Vector3 worldPosition = control.GetWorldPosition(longitude, latitude);
// Check if the mesh is within the bounds of the map
if (worldPosition.x > 0 || worldPosition.x < -control.sizeInScene.x ||
worldPosition.z < 0 || worldPosition.z > control.sizeInScene.y)
{
// If the instance exists, destroy it
if (exists) DestroyInstance();
return;
}
// If the instance doesn't exist, create it
if (!exists) CreateInstance();
// Set the position of the mesh
target.transform.position = worldPosition;
// Set the rotation of the mesh if alwaysUp is true and cameraOrbit is not null
if (alwaysUp && cameraOrbit != null) target.transform.rotation = Quaternion.Euler(90, 180 + cameraOrbit.rotation.y, 0);
}
}
///
/// Language of the label.
///
public enum Language
{
International,
English,
Spanish,
French,
German,
Russian,
Chinese,
Chinese_simplified,
Portuguese,
Arabic
}
}
}