Topic: Elevation data caching

Hey,
Would be great to cache height data if elevation is set. Currently in offline mode only flat tiles.

Re: Elevation data caching

Hello.

Caching Bing Maps Elevation data is a very bad idea.
This will create a ton of problems.

But this does not mean that it is impossible to make caching elevation data.
Just need to use a more suitable data source, such as mapbox.

Example:

#if !UNITY_WEBPLAYER && ((!UNITY_WP_8_1 && !UNITY_WEBGL) || UNITY_EDITOR)
#define ALLOW_FILECACHE
#endif

using System;
using System.Text;
using UnityEngine;

#if ALLOW_FILECACHE
using System.IO;
#endif

public class MapboxElevation : MonoBehaviour
{
    public string mapboxKey;
    public bool useCache = true;
    public string fileCacheTilePath = "ElevationCache/{zoom}/{x}/{y}";

    public int countX = 3;
    public int countY = 3;
    public int zoomOffset = 3;

    private OnlineMapsTile[] tiles;
    private OnlineMaps map;
    private OnlineMapsTileSetControl control;

    private Vector2 ctl;
    private Vector2 cbr;

    private void DownloadElevation(OnlineMapsTile tile)
    {
        if (useCache && TryLoadElevationsFromCache(tile)) return;

        string url = new StringBuilder("https://api.mapbox.com/v4/mapbox.terrain-rgb/")
            .Append(tile.zoom).Append("/").Append(tile.x).Append("/").Append(tile.y)
            .Append(".pngraw?access_token=").Append(mapboxKey).ToString();
        OnlineMapsWWW www = OnlineMapsUtils.GetWWW(url);
        www.OnComplete += delegate
        {
            tile.customData = new CData(www.bytes);
            if (useCache) SaveElevationsToCache(tile, www.bytes);
            OnGetElevation(ctl, cbr);
        };
    }

    private StringBuilder GetTilePath(OnlineMapsTile tile)
    {
#if ALLOW_FILECACHE
        int startIndex = 0;
        StringBuilder builder = new StringBuilder(Application.persistentDataPath).Append("/");
        int l = fileCacheTilePath.Length;
        for (int i = 0; i < l; i++)
        {
            char c = fileCacheTilePath[i];
            if (c == '{')
            {
                for (int j = i + 1; j < l; j++)
                {
                    c = fileCacheTilePath[j];
                    if (c == '}')
                    {
                        builder.Append(fileCacheTilePath.Substring(startIndex, i - startIndex));
                        string v = fileCacheTilePath.Substring(i + 1, j - i - 1).ToLower();
                        if (v == "zoom" || v == "z") builder.Append(tile.zoom);
                        else if (v == "x") builder.Append(tile.x);
                        else if (v == "y") builder.Append(tile.y);
                        else builder.Append(v);
                        i = j;
                        startIndex = j + 1;
                        break;
                    }
                }
            }
        }

        builder.Append(fileCacheTilePath.Substring(startIndex, l - startIndex));
        return builder;
#else
        return null;
#endif
    }

    private void OnGetElevation(Vector2 tl, Vector2 br)
    {
        ctl = tl;
        cbr = br;

        double tlx, tly, brx, bry;

        map.projection.CoordinatesToTile(tl.x, tl.y, map.zoom, out tlx, out tly);
        map.projection.CoordinatesToTile(br.x, br.y, map.zoom, out brx, out bry);

        int scale = 1 << zoomOffset;

        int zoom = map.zoom - zoomOffset;

        short[,] heights = new short[32, 32];
        double rx = (brx - tlx) / 31;
        double ry = (bry - tly) / 31;

        for (int x = 0; x < 32; x++)
        {
            double tx = (rx * x + tlx) / scale;

            for (int y = 0; y < 32; y++)
            {
                double ty = (ry * y + tly) / scale;

                OnlineMapsTile tile = OnlineMapsTile.GetTile(zoom, (int)tx, (int)ty);
                if (tile == null)
                {
                    heights[x, y] = 0;
                    continue;
                }
                CData data = tile.customData as CData;
                if (data == null)
                {
                    heights[x, y] = 0;
                    continue;
                }
                heights[x, 31 - y] = data.GetElevation(tx, ty);
            }
        }

        control.SetElevationData(heights);
    }

    private void OnTileDownloaded(OnlineMapsTile tile)
    {
        for (int i = 0; i < tiles.Length; i++)
        {
            if (tiles[i] == tile)
            {
                DownloadElevation(tile);
                return;
            }
        }
    }

    private void Start()
    {
        map = OnlineMaps.instance;
        control = OnlineMapsTileSetControl.instance;
        control.OnGetElevation += OnGetElevation;
        OnlineMapsTile.OnTileDownloaded += OnTileDownloaded;
        if (OnlineMapsCache.instance != null) OnlineMapsCache.instance.OnLoadedFromCache += OnTileDownloaded;
        map.OnMapUpdated += UpdateTiles;

        int countTiles = countX * countY;
        tiles = new OnlineMapsTile[countTiles];
    }

    private bool TryLoadElevationsFromCache(OnlineMapsTile tile)
    {
        Debug.Log("Try load: " + tile);
#if ALLOW_FILECACHE
        StringBuilder filename = GetTilePath(tile);
        string fn = filename.ToString();
        if (File.Exists(fn))
        {
            Debug.Log("Tile loaded: " + tile);

            tile.customData = new CData(File.ReadAllBytes(fn));
            OnGetElevation(ctl, cbr);
            return true;
        }
#endif
        return false;
    }

    private void SaveElevationsToCache(OnlineMapsTile tile, byte[] bytes)
    {
        Debug.Log("Save: " + tile);
#if ALLOW_FILECACHE
        StringBuilder filename = GetTilePath(tile);
        string fn = filename.ToString();
        if (!File.Exists(fn))
        {
            FileInfo fileInfo = new FileInfo(fn);
            if (!Directory.Exists(fileInfo.DirectoryName)) Directory.CreateDirectory(fileInfo.DirectoryName);
            File.WriteAllBytes(fn, bytes);
        }
#endif
    }

    private void UpdateTiles()
    {
        for (int i = 0; i < tiles.Length; i++) if (tiles[i] != null) tiles[i].Unblock(this);

        int zoom = map.zoom - zoomOffset;
        if (zoom < 3) zoom = 3;

        double tx, ty;
        map.GetTilePosition(out tx, out ty, zoom);

        int itx = Mathf.RoundToInt((float)(tx - countX / 2f));
        int ity = Mathf.RoundToInt((float)(ty - countY / 2f));

        int max = 1 << zoom;

        double tlx, tly, brx, bry;
        map.GetCorners(out tlx, out tly, out brx, out bry);

        for (int x = 0; x < countX; x++)
        {
            int tileX = itx + x;
            if (tileX >= max) tileX -= max;

            for (int y = 0; y < countY; y++)
            {
                int tileY = ity + y;

                if (tileY >= max) tileY -= max;

                OnlineMapsTile tile = OnlineMapsTile.GetTile(zoom, tileX, tileY);
                if (tile == null)
                {
                    OnlineMapsTile parentTile = OnlineMapsTile.GetTile(zoom - 1, tileX / 2, tileY / 2);
                    tile = new OnlineMapsTile(tileX, tileY, zoom, map, parentTile);
                }
                int tileIndex = x * countY + y;
                tiles[tileIndex] = tile;
                tile.Block(this);
            }
        }
    }

    internal class CData
    {
        private short[,] heights;

        public CData(byte[] bytes)
        {
            Texture2D texture = new Texture2D(256, 256, TextureFormat.RGB24, false);
            texture.LoadImage(bytes);
            Color[] colors = texture.GetPixels();

            const int res = 256;
            heights = new short[res, res];

            for (int y = 0; y < res; y++)
            {
                int py = (255 - y) * res;

                for (int x = 0; x < res; x++)
                {
                    Color c = colors[py + x];

                    double height = -10000 + (c.r * 255 * 256 * 256 + c.g * 255 * 256 + c.b * 255) * 0.1;
                    heights[x, y] = (short)Math.Round(height);
                }
            }
        }

        public short GetElevation(double tx, double ty)
        {
            double rx = tx - Math.Floor(tx);
            double ry = ty - Math.Floor(ty);
            int x = (int)Math.Round(rx * 256);
            int y = (int)Math.Round(ry * 256);
            if (x > 255) x = 255;
            if (y > 255) y = 255;
            return heights[x, y];
        }
    }
}
Kind Regards,
Infinity Code Team

Re: Elevation data caching

Something strange happens when I'm trying to zoom in or zoom out. Seems script calculate elevation only for initial zoom level( if zoom not touched at runtime everything seems good). (Online maps version 2.5.18.1)

Post's attachments

Attachment icon Compound.png 1.37 mb, 27 downloads since 2017-10-05 

Re: Elevation data caching

Fixed.
Try it:

#if !UNITY_WEBPLAYER && ((!UNITY_WP_8_1 && !UNITY_WEBGL) || UNITY_EDITOR)
#define ALLOW_FILECACHE
#endif

using System;
using System.Text;
using UnityEngine;

#if ALLOW_FILECACHE
using System.IO;
#endif

public class MapboxElevation : MonoBehaviour
{
    public string mapboxKey;
    public bool useCache = true;
    public string fileCacheTilePath = "ElevationCache/{zoom}/{x}/{y}";

    public int countX = 3;
    public int countY = 3;
    public int zoomOffset = 3;

    private OnlineMapsTile[] tiles;
    private OnlineMaps map;
    private OnlineMapsTileSetControl control;

    private Vector2 ctl;
    private Vector2 cbr;

    private void DownloadElevation(OnlineMapsTile tile)
    {
        if (useCache && TryLoadElevationsFromCache(tile)) return;

        string url = new StringBuilder("https://api.mapbox.com/v4/mapbox.terrain-rgb/")
            .Append(tile.zoom).Append("/").Append(tile.x).Append("/").Append(tile.y)
            .Append(".pngraw?access_token=").Append(mapboxKey).ToString();
        OnlineMapsWWW www = OnlineMapsUtils.GetWWW(url);
        www.OnComplete += delegate
        {
            CData cdata = tile.customData as CData;
            if (cdata == null)
            {
                cdata = new CData();
                tile.customData = cdata;
            }
            cdata.Load(www.bytes);
            if (useCache) SaveElevationsToCache(tile, www.bytes);
            OnGetElevation(ctl, cbr);
        };
    }

    private StringBuilder GetTilePath(OnlineMapsTile tile)
    {
#if ALLOW_FILECACHE
        int startIndex = 0;
        StringBuilder builder = new StringBuilder(Application.persistentDataPath).Append("/");
        int l = fileCacheTilePath.Length;
        for (int i = 0; i < l; i++)
        {
            char c = fileCacheTilePath[i];
            if (c == '{')
            {
                for (int j = i + 1; j < l; j++)
                {
                    c = fileCacheTilePath[j];
                    if (c == '}')
                    {
                        builder.Append(fileCacheTilePath.Substring(startIndex, i - startIndex));
                        string v = fileCacheTilePath.Substring(i + 1, j - i - 1).ToLower();
                        if (v == "zoom" || v == "z") builder.Append(tile.zoom);
                        else if (v == "x") builder.Append(tile.x);
                        else if (v == "y") builder.Append(tile.y);
                        else builder.Append(v);
                        i = j;
                        startIndex = j + 1;
                        break;
                    }
                }
            }
        }

        builder.Append(fileCacheTilePath.Substring(startIndex, l - startIndex));
        return builder;
#else
        return null;
#endif
    }

    private void OnGetElevation(Vector2 tl, Vector2 br)
    {
        ctl = tl;
        cbr = br;

        double tlx, tly, brx, bry;

        map.projection.CoordinatesToTile(tl.x, tl.y, map.zoom, out tlx, out tly);
        map.projection.CoordinatesToTile(br.x, br.y, map.zoom, out brx, out bry);

        int scale = 1 << zoomOffset;

        int zoom = map.zoom - zoomOffset;

        short[,] heights = new short[32, 32];
        double rx = (brx - tlx) / 31;
        double ry = (bry - tly) / 31;

        for (int x = 0; x < 32; x++)
        {
            double tx = (rx * x + tlx) / scale;

            for (int y = 0; y < 32; y++)
            {
                double ty = (ry * y + tly) / scale;

                OnlineMapsTile tile = OnlineMapsTile.GetTile(zoom, (int)tx, (int)ty);
                if (tile == null)
                {
                    heights[x, 31 - y] = 0;
                    continue;
                }
                CData data = tile.customData as CData;
                if (data != null) heights[x, 31 - y] = data.GetElevation(tx, ty);
                else heights[x, 31 - y] = 0;
            }
        }

        control.SetElevationData(heights);
    }

    private void Start()
    {
        map = OnlineMaps.instance;
        control = OnlineMapsTileSetControl.instance;
        control.OnGetElevation += OnGetElevation;
        map.OnMapUpdated += UpdateTiles;

        int countTiles = countX * countY;
        tiles = new OnlineMapsTile[countTiles];
    }

    private bool TryLoadElevationsFromCache(OnlineMapsTile tile)
    {
        Debug.Log("Try load: " + tile);
#if ALLOW_FILECACHE
        StringBuilder filename = GetTilePath(tile);
        string fn = filename.ToString();
        if (File.Exists(fn))
        {
            Debug.Log("Tile loaded: " + tile);

            (tile.customData as CData).Load(File.ReadAllBytes(fn));
            OnGetElevation(ctl, cbr);
            return true;
        }
#endif
        return false;
    }

    private void SaveElevationsToCache(OnlineMapsTile tile, byte[] bytes)
    {
        Debug.Log("Save: " + tile);
#if ALLOW_FILECACHE
        StringBuilder filename = GetTilePath(tile);
        string fn = filename.ToString();
        if (!File.Exists(fn))
        {
            FileInfo fileInfo = new FileInfo(fn);
            if (!Directory.Exists(fileInfo.DirectoryName)) Directory.CreateDirectory(fileInfo.DirectoryName);
            File.WriteAllBytes(fn, bytes);
        }
#endif
    }

    private void UpdateTiles()
    {
        for (int i = 0; i < tiles.Length; i++) if (tiles[i] != null) tiles[i].Unblock(this);

        int zoom = map.zoom - zoomOffset;
        if (zoom < 3) zoom = 3;

        double tx, ty;
        map.GetTilePosition(out tx, out ty, zoom);

        int itx = Mathf.RoundToInt((float)(tx - countX / 2f));
        int ity = Mathf.RoundToInt((float)(ty - countY / 2f));

        int max = 1 << zoom;

        double tlx, tly, brx, bry;
        map.GetCorners(out tlx, out tly, out brx, out bry);

        for (int x = 0; x < countX; x++)
        {
            int tileX = itx + x;
            if (tileX >= max) tileX -= max;

            for (int y = 0; y < countY; y++)
            {
                int tileY = ity + y;

                if (tileY >= max) tileY -= max;

                OnlineMapsTile tile = OnlineMapsTile.GetTile(zoom, tileX, tileY);
                if (tile == null)
                {
                    OnlineMapsTile parentTile = OnlineMapsTile.GetTile(zoom - 1, tileX / 2, tileY / 2);
                    tile = new OnlineMapsTile(tileX, tileY, zoom, map, parentTile);
                }
                else if (tile.customData == null)
                {
                    CData cdata = new CData();
                    tile.customData = cdata;
                    DownloadElevation(tile);
                }
                int tileIndex = x * countY + y;
                tiles[tileIndex] = tile;
                tile.Block(this);
            }
        }
    }

    internal class CData
    {
        private short[,] heights;

        public CData()
        {
            
        }

        public void Load(byte[] bytes)
        {
            Texture2D texture = new Texture2D(256, 256, TextureFormat.RGB24, false);
            texture.LoadImage(bytes);
            Color[] colors = texture.GetPixels();
            OnlineMapsUtils.DestroyImmediate(texture);

            const int res = 256;
            heights = new short[res, res];

            for (int y = 0; y < res; y++)
            {
                int py = (255 - y) * res;

                for (int x = 0; x < res; x++)
                {
                    Color c = colors[py + x];

                    double height = -10000 + (c.r * 255 * 256 * 256 + c.g * 255 * 256 + c.b * 255) * 0.1;
                    heights[x, y] = (short)Math.Round(height);
                }
            }
        }

        public short GetElevation(double tx, double ty)
        {
            if (heights == null) return 0;

            double rx = tx - Math.Floor(tx);
            double ry = ty - Math.Floor(ty);
            int x = (int)Math.Round(rx * 256);
            int y = (int)Math.Round(ry * 256);
            if (x > 255) x = 255;
            if (y > 255) y = 255;
            return heights[x, y];
        }
    }
}
Kind Regards,
Infinity Code Team

Re: Elevation data caching

Now works perfectly.  A big thank you to Alex.

Re: Elevation data caching

leviniarened wrote:

Now works perfectly.  A big thank you to Alex.

Would you pleased to describe how you using the codes?Should I attach a script which contains the codes to the map?

Re: Elevation data caching

Would you pleased to describe how you using the codes?Should I attach a script which contains the codes to the map?

Re: Elevation data caching

Yes, you must add the script to the map (or any other) GameObject and enter your Mapbox Access Token.

Kind Regards,
Infinity Code Team

Re: Elevation data caching

I'm trying to use this script to achieve the same thing but it doesn't seem to work, i attached the script to the map object and then supplied the mapbox key but i get an error "Array index out of range".

Re: Elevation data caching

Hello.

Please attach a stack trace of the error.

Kind Regards,
Infinity Code Team