Visualize Transport Network

Visualize the edges of a transport network graph.

To run this example, open the Wrld/Demo/Examples.unity scene, click the Play button, and select Visualize Transport Network from the dropdown.

Visualize the edges of a transport network graph.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Wrld;
using Wrld.Space;
using Wrld.Transport;

public class VisualizeTransportNetwork : MonoBehaviour
{
    private readonly float m_lineVerticalOffsetMeters = 1.0f;
    private TransportApi m_transportApi;

    private GameObject m_networkWayMeshesRoot;
    private GameObject m_networkLinkMeshesRoot;

    private Dictionary<string, GameObject> m_keyToWayMeshGameObjects;
    private Dictionary<string, GameObject> m_keyToLinkMeshGameObjects;
    

    private void OnEnable()
    {
        m_transportApi = Api.Instance.TransportApi;
        m_keyToWayMeshGameObjects = new Dictionary<string, GameObject>();
        m_keyToLinkMeshGameObjects = new Dictionary<string, GameObject>();
        m_transportApi.OnTransportNetworkCellAdded += OnTransportNetworkCellAdded;
        m_transportApi.OnTransportNetworkCellRemoved += OnTransportNetworkCellRemoved;
        m_transportApi.OnTransportNetworkCellUpdated += OnTransportNetworkCellUpdated;

        m_networkWayMeshesRoot = new GameObject("networkWayMeshesRoot");
        m_networkWayMeshesRoot.transform.parent = this.transform;
        m_networkWayMeshesRoot.transform.localPosition = Vector3.up * m_lineVerticalOffsetMeters;

        m_networkLinkMeshesRoot = new GameObject("networkLinkMeshesRoot");
        m_networkLinkMeshesRoot.transform.parent = this.transform;
        m_networkLinkMeshesRoot.transform.localPosition = Vector3.up * m_lineVerticalOffsetMeters;
    }

    private void OnDisable()
    {
        GameObject.Destroy(m_networkWayMeshesRoot);
        GameObject.Destroy(m_networkLinkMeshesRoot);
        m_keyToWayMeshGameObjects = null;
        m_keyToLinkMeshGameObjects = null;
        m_transportApi.OnTransportNetworkCellAdded -= OnTransportNetworkCellAdded;
        m_transportApi.OnTransportNetworkCellRemoved -= OnTransportNetworkCellRemoved;
        m_transportApi.OnTransportNetworkCellUpdated -= OnTransportNetworkCellUpdated;
    }

    private void OnTransportNetworkCellAdded(TransportNetworkType transportNetwork, TransportCellKey cellKey)
    {
        var objectKey = MakeObjectKey(transportNetwork, cellKey);

        var ways = m_transportApi.GetWaysForNetworkAndCell(transportNetwork, cellKey);
        CreateAndAddWayMeshes(objectKey, ways);

        var directedEdges = m_transportApi.GetDirectedEdgesForNetworkAndCell(transportNetwork, cellKey);
        CreateAndAddLinkMeshes(objectKey, directedEdges);
    }

    private void OnTransportNetworkCellRemoved(TransportNetworkType transportNetwork, TransportCellKey cellKey)
    {
        var objectKey = MakeObjectKey(transportNetwork, cellKey);
        RemoveAndDestroyWayMeshes(objectKey);
        RemoveAndDestroyLinkMeshes(objectKey);
    }

    private void OnTransportNetworkCellUpdated(TransportNetworkType transportNetwork, TransportCellKey cellKey)
    {
        var objectKey = MakeObjectKey(transportNetwork, cellKey);
        RemoveAndDestroyLinkMeshes(objectKey);
        var directedEdges = m_transportApi.GetDirectedEdgesForNetworkAndCell(transportNetwork, cellKey);
        CreateAndAddLinkMeshes(objectKey, directedEdges);
    }

    private IList<Vector3> WayCenterLinePointsToWorldPoints(TransportWay way)
    {
        return way.CenterLinePoints
            .Select(lla => Api.Instance.SpacesApi.GeographicToWorldPoint(LatLongAltitude.FromECEF(lla)))
            .ToList();
    }

    private string MakeObjectKey(TransportNetworkType transportNetwork, TransportCellKey cellKey)
    {
        return transportNetwork.ToString() + "_" + m_transportApi.TransportCellKeyToString(cellKey);
    }


    private GameObject CreateGameObjectForCell(string objectName, Color color)
    {
        var go = new GameObject(objectName);

        var meshRenderer = go.AddComponent<MeshRenderer>();
        var material = new Material(Shader.Find("Sprites/Default"));
        material.color = color;
        
        meshRenderer.material = material;
        meshRenderer.receiveShadows = false;
        meshRenderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off;

        go.AddComponent<MeshFilter>();

        return go;
    }

    private void CreateAndAddWayMeshes(string objectKey, IList<TransportWay> ways)
    {
        var waysGameObject = CreateGameObjectForCell(objectKey, Color.cyan);
        waysGameObject.transform.SetParent(m_networkWayMeshesRoot.transform, false);
        m_keyToWayMeshGameObjects.Add(objectKey, waysGameObject);

        var waysMeshFilter = waysGameObject.GetComponent<MeshFilter>();
        waysMeshFilter.mesh = CreateMeshForWays(ways);
    }

    private void RemoveAndDestroyWayMeshes(string objectKey)
    {
        if (m_keyToWayMeshGameObjects.ContainsKey(objectKey))
        {
            var go = m_keyToWayMeshGameObjects[objectKey];
            go.transform.parent = null;
            m_keyToWayMeshGameObjects.Remove(objectKey);
            Destroy(go);
        }
    }

    private void CreateAndAddLinkMeshes(string objectKey, IList<TransportDirectedEdge> directedEdges)
    {
        var linksGameObject = CreateGameObjectForCell(objectKey, Color.yellow);
        linksGameObject.transform.SetParent(m_networkLinkMeshesRoot.transform, false);
        m_keyToLinkMeshGameObjects.Add(objectKey, linksGameObject);

        var linksMeshFilter = linksGameObject.GetComponent<MeshFilter>();
        linksMeshFilter.mesh = CreateMeshForLinks(directedEdges);
    }

    private void RemoveAndDestroyLinkMeshes(string objectKey)
    {
        if (m_keyToLinkMeshGameObjects.ContainsKey(objectKey))
        {
            var go = m_keyToLinkMeshGameObjects[objectKey];
            go.transform.parent = null;
            m_keyToLinkMeshGameObjects.Remove(objectKey);
            Destroy(go);
        }
    }


    private Mesh CreateMeshForWays(IList<TransportWay> ways)
    {
        Mesh mesh = new Mesh();
        if (!ways.Any())
        {
            return mesh;
        }

        var vertices = new List<Vector3>();
        var indices = new List<int>();
        foreach (var way in ways)
        {
            var points = WayCenterLinePointsToWorldPoints(way);
            indices.AddRange(BuildLineSegmentIndices(vertices.Count, points.Count - 1));
            vertices.AddRange(points);
        }

        mesh.Clear();
        mesh.vertices = vertices.ToArray();
        mesh.SetIndices(indices.ToArray(), MeshTopology.Lines, 0);
        mesh.RecalculateBounds();
        return mesh;
    }

    private Mesh CreateMeshForLinks(IList<TransportDirectedEdge> directedEdges)
    {
        Mesh mesh = new Mesh();
        if (!directedEdges.Any())
        {
            return mesh;
        }

        var vertices = new List<Vector3>();
        var indices = new List<int>();

        const float unlinkedCellBoundaryIndicatorLength = 50.0f;
        const float nodeIndicatorLength = 5.0f;

        foreach (var directedEdge in directedEdges)
        {
            bool isUnlinkedCellBoundary = (directedEdge.NodeIdB.LocalNodeId < 0);
            var indicatorLength = isUnlinkedCellBoundary ? unlinkedCellBoundaryIndicatorLength : nodeIndicatorLength;

            TransportNode node;
            if (m_transportApi.TryGetNode(directedEdge.NodeIdA, out node))
            {
                var pointA = Api.Instance.SpacesApi.GeographicToWorldPoint(LatLongAltitude.FromECEF(node.Point));

                indices.Add(vertices.Count);
                indices.Add(vertices.Count + 1);
                vertices.Add(pointA);
                vertices.Add(pointA + Vector3.up*indicatorLength);
            }
        }

        mesh.Clear();
        mesh.vertices = vertices.ToArray();
        mesh.SetIndices(indices.ToArray(), MeshTopology.Lines, 0);
        mesh.RecalculateBounds();
        return mesh;
    }

    private int[] BuildLineSegmentIndices(int indexOffset, int lineSegmentCount)
    {
        var count = lineSegmentCount * 2;
        var indices = new int[count];

        int j = 0;
        for (int i = 0; i < lineSegmentCount; ++i)
        {
            indices[j++] = indexOffset + i;
            indices[j++] = indexOffset + i + 1;
        }
        return indices;
    }
}
v0.8.17