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