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

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, 82 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];
        }
    }
}

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.

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.

11 (edited by Amen 2019-05-16 14:37:00)

Re: Elevation data caching

Hi ,
I'have this error using this script

Post's attachments

Attachment icon errrorElevation.PNG 91.2 kb, 29 downloads since 2019-05-16 

Re: Elevation data caching

This is a script for Online Maps v2.
In Online Maps v3, this is much easier:

using System.IO;
using UnityEngine;

public class CacheMapboxElevation: MonoBehaviour
{
    private string GetTilePath(OnlineMapsTiledElevationManager<OnlineMapsMapboxElevationManager>.Tile tile)
    {
        return Application.persistentDataPath + "/Elevation Cache/" + tile.zoom + "/" + tile.x + "/" + tile.y + ".png";
    }

    private void OnDownload(OnlineMapsTiledElevationManager<OnlineMapsMapboxElevationManager>.Tile tile)
    {
        string path = GetTilePath(tile);
        if (!File.Exists(path))
        {
            OnlineMapsMapboxElevationManager.instance.StartDownloadElevationTile(tile);
            return;
        }

        Texture2D texture = new Texture2D(256, 256);
        texture.LoadImage(File.ReadAllBytes(path));
        OnlineMapsMapboxElevationManager.instance.SetElevationTexture(tile, texture);
        OnlineMapsUtils.Destroy(texture);

        Debug.Log("Loaded from cache: " + path);
    }

    private void OnDownloadSuccess(OnlineMapsTiledElevationManager<OnlineMapsMapboxElevationManager>.Tile tile, OnlineMapsWWW www)
    {
        string path = GetTilePath(tile);
        FileInfo info = new FileInfo(path);
        if (!info.Directory.Exists) info.Directory.Create();
        File.WriteAllBytes(path, www.bytes);

        Debug.Log("Elevation saved: " + path);
    }

    private void Start()
    {
        OnlineMapsMapboxElevationManager.instance.OnDownload += OnDownload;
        OnlineMapsMapboxElevationManager.instance.OnDownloadSuccess += OnDownloadSuccess;
    }
}

Re: Elevation data caching

it's possible to have elevation data caching for ArcGIS.
THANK YOUU

Re: Elevation data caching

Yes it is possible.
But this will not work with Horizon, which you asked for in another thread.
Horizon already has everything to implement caching.
You just need to add a save array to the file, check for the existence of the file, and read the array from the file.

It is so easy that I believe that you can do it.
If you still have problems implementing this, write again and I will do it for you.

Re: Elevation data caching

yes i think i need your help if possible .I still do not master the horizon scripts so it's a little  complicated.
Thank you any way

Re: Elevation data caching

http://forum.infinity-code.com/viewtopi … 5279#p5279

Re: Elevation data caching

Is it possible to add a function in the CacheMapboxElevation (Online Maps v3) for requesting real world elevation for specific coordinates(Lon, Lat) at specific zoom?
Thank you!

Re: Elevation data caching

CacheMapboxElevation only saves and reads elevation data for the tile from the cache.
What is the point of this function if you can get elevation data using OnlineMapsMapboxElevationManager?!

19 (edited by savvas_6 2020-03-31 10:46:22)

Re: Elevation data caching

Im sorry, let me explain.

I want to know the real elevation for my markers on the map. Is it possible to read elevation from tiles for a position on map by just providing latitude, longitude and zoom?

I try the following code:

Vector2 center = marker.position;
float unscaledElevation = OnlineMapsElevationManagerBase.GetUnscaledElevation(center.x, center.y); // real elevation value

But the problem is each zoom gives different elevation for my markers. Requesting elevations from tiles would be better?

Re: Elevation data caching

The problem is that GetUnscaledElevation requires a position in the scene, relative to the top-left corner of the map.
http://infinity-code.com/doxygen/online … c87e6b8540
You pass geographic coordinates.
You need to first convert the coordinates to a position in the scene.
http://infinity-code.com/doxygen/online … 369ba44098