Topic: Using Google Elevation API to generate Terrains

When none of the pre-installed providers have sufficient data quality for your area, you can use the Google Elevation API.

Important: Google Elevation API is not very suitable for generating areas because it cannot return elevations for an area (only for a point or path), which requires many requests. But in some cases it can be a lifesaver.
Don't use large values for row and columns because you might get a big bill to pay.

How to use it:
1. Create a script in the Editor folder, with the contents below.
2. From the menu, select RWT/Using Google Elevation API.
3. Enter your Google API Key. Maps Elevation API must be enabled in google developer console.
4. Enter or get the coordinates of your area from Real World Terrain.
5. Specify the desired number of rows and columns of data.
A single request to the Elevation API can return a maximum of 512 values.
The total number of requests will be Col * Row / 512.
6. Click Download elevations. The requests are made synchronously, so it may seem like the script is hanging, but it is not and you just need to wait.
7. Click Intercept Getting Elevations.
8. Start the generation in the Real World Terrain window.
9. When the generation is complete, click Release Getting Elevations.

Enjoy.

/*         INFINITY CODE         */
/*   https://infinity-code.com   */

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Text;
using System.Xml;
using InfinityCode.RealWorldTerrain;
using InfinityCode.RealWorldTerrain.Generators;
using InfinityCode.RealWorldTerrain.Windows;
using UnityEditor;
using UnityEngine;

namespace InfinityCode.RealWorldTerrainExamples
{
    public class UsingGoogleElevationsExample : EditorWindow
    {
        public string googleAPIKey = "";
        public int rows = 22;
        public int cols = 22;

        public double leftLongitude = 37.6188;
        public double topLatitude = 55.7517;
        public double rightLongitude = 37.6528;
        public double bottomLatitude = 55.7291;
        
        private double mx1;
        private double my1;
        private double mx2;
        private double my2;

        private static double[,] elevations;

        private void DownloadElevations()
        {
            RealWorldTerrainUtils.LatLongToMercat(leftLongitude, topLatitude, out mx1, out my1);
            RealWorldTerrainUtils.LatLongToMercat(rightLongitude, bottomLatitude, out mx2, out my2);
            
            double dx = (mx2 - mx1) / (cols - 1);
            double dy = (my2 - my1) / (rows - 1);
            
            elevations = new double[rows, cols];
            List<Tuple<int, double, double>> points = new List<Tuple<int, double, double>>(512);

            for (int y = 0; y < rows; y++)
            {
                double my = my1 + dy * y;
                for (int x = 0; x < cols; x++)
                {
                    double mx = mx1 + dx * x;
                    double lng, lat;
                    RealWorldTerrainUtils.MercatToLatLong(mx, my, out lng, out lat);
                    int index = y * cols + x;
                    points.Add(new Tuple<int, double, double> (index, lng, lat));
                    if (points.Count == 512)
                    {
                        if (!DownloadElevations(points))
                        {
                            return;
                        }
                        
                        points.Clear();
                    }
                }
            }

            if (points.Count > 0)
            {
                if (!DownloadElevations(points))
                {
                    return;
                }
            }
            
            byte[] bytes = new byte[rows * cols * 8];
            int i = 0;
            foreach (double el in elevations)
            {
                byte[] elBytes = BitConverter.GetBytes(el);
                Array.Copy(elBytes, 0, bytes, i, 8);
                i += 8;
            }
            
            File.WriteAllBytes(GetFilename(), bytes);
            
            EditorUtility.DisplayDialog("Success", "Elevations downloaded", "OK");
        }

        private bool DownloadElevations(List<Tuple<int, double, double>> points)
        {
            string polyline = EncodePolyline(points);
            string url = $"https://maps.googleapis.com/maps/api/elevation/xml?locations=enc:{polyline}&key={googleAPIKey}";
            
            WebClient client = new WebClient();
            string response = client.DownloadString(url);
            
            XmlDocument xml = new XmlDocument();
            xml.LoadXml(response);
            
            // Check for error
            XmlNode statusNode = xml.SelectSingleNode("//status");
            if (statusNode != null && statusNode.InnerText != "OK")
            {
                XmlNode errorNode = xml.SelectSingleNode("//error_message");
                EditorUtility.DisplayDialog("Error", errorNode.InnerText, "OK");
                return false;
            }

            int index = 0;
            XmlNodeList results = xml.SelectNodes("//result");
            foreach (XmlNode result in results)
            {
                string text = result.SelectSingleNode("elevation").InnerText;
                double elevation = double.Parse(text, CultureInfo.InvariantCulture);
                int i = points[index].Item1;
                elevations[i / cols, i % cols] = elevation;
                index++;
            }

            return true;
        }
        
        public static string EncodePolyline(List<Tuple<int, double, double>> points)
        {
            List<int> encodedPoints = new List<int>();

            int prevLat = 0, prevLng = 0;

            foreach (var point in points)
            {
                int lat = (int)Math.Round(point.Item3 * 1e5);
                int lng = (int)Math.Round(point.Item2 * 1e5);

                int dLat = lat - prevLat;
                int dLng = lng - prevLng;

                prevLat = lat;
                prevLng = lng;

                encodedPoints.Add(EncodeValue(dLat));
                encodedPoints.Add(EncodeValue(dLng));
            }

            StringBuilder encodedPolyline = new StringBuilder();

            foreach (int value in encodedPoints)
            {
                encodedPolyline.Append(EncodeSignedNumber(value));
            }

            return encodedPolyline.ToString();
        }

        private static string EncodeSignedNumber(int num)
        {
            StringBuilder encoded = new StringBuilder();

            while (num >= 0x20)
            {
                encoded.Append((char)((0x20 | (num & 0x1f)) + 63));
                num >>= 5;
            }

            encoded.Append((char)(num + 63));

            return encoded.ToString();
        }

        private static int EncodeValue(int value)
        {
            value = value < 0 ? ~(value << 1) : (value << 1);
            return value;
        }

        private string GetFilename()
        {
            return Path.Combine(RealWorldTerrainEditorUtils.heightmapCacheFolder, $"google_{leftLongitude}_{topLatitude}_{rightLongitude}_{bottomLatitude}_{rows}_{cols}.raw");
        }

        private bool HasElevations()
        {
            return File.Exists(GetFilename());
        }

        private void LoadElevations()
        {
            if (!HasElevations()) return;
            byte[] bytes = File.ReadAllBytes(GetFilename());
            elevations = new double[rows, cols];
            int i = 0;
            for (int y = 0; y < rows; y++)
            {
                for (int x = 0; x < cols; x++)
                {
                    elevations[y, x] = BitConverter.ToDouble(bytes, i);
                    i += 8;
                }
            }
            
            RealWorldTerrainUtils.LatLongToMercat(leftLongitude, topLatitude, out mx1, out my1);
            RealWorldTerrainUtils.LatLongToMercat(rightLongitude, bottomLatitude, out mx2, out my2);
        }
        
        private void OnDestroy()
        {
            RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
            RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
        }

        private void OnGUI()
        {
            googleAPIKey = EditorGUILayout.TextField("Google API Key: ", googleAPIKey);
            leftLongitude = EditorGUILayout.DoubleField("Left Longitude: ", leftLongitude);
            topLatitude = EditorGUILayout.DoubleField("Top Latitude: ", topLatitude);
            rightLongitude = EditorGUILayout.DoubleField("Right Longitude: ", rightLongitude);
            bottomLatitude = EditorGUILayout.DoubleField("Bottom Latitude: ", bottomLatitude);
            rows = EditorGUILayout.IntField("Rows: ", rows);
            cols = EditorGUILayout.IntField("Cols: ", cols);
            
            EditorGUILayout.HelpBox($"Number of requests: {Mathf.CeilToInt(rows * cols / 512f)}", MessageType.Info);

            if (GUILayout.Button("1. Get Area From Real World Terrain"))
            {
                if (RealWorldTerrainWindow.wnd == null) RealWorldTerrainWindow.OpenWindow(RealWorldTerrainGenerateType.full);
                
                RealWorldTerrainPrefs prefs = RealWorldTerrainWindow.prefs;
                leftLongitude = prefs.leftLongitude;
                topLatitude = prefs.topLatitude;
                rightLongitude = prefs.rightLongitude;
                bottomLatitude = prefs.bottomLatitude;
            }
            
            if (GUILayout.Button("2. Download elevations"))
            {
                DownloadElevations();
            }
            
            if (GUILayout.Button("3. Intercept Getting Elevations"))
            {
                if (!HasElevations())
                {
                    EditorUtility.DisplayDialog("Error", "Elevations not downloaded", "OK");
                }
                else
                {
                    LoadElevations();
                    RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
                    RealWorldTerrainElevationGenerator.OnGetElevation += OnGetElevation;
                    RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
                    RealWorldTerrainElevationGenerator.OnGetElevationRange += OnGetElevationRange;
                }
            }
            
            EditorGUILayout.HelpBox("4. Start Generation Using Real World Terrain Window", MessageType.Info);
            
            if (GUILayout.Button("5. Release Getting Elevations"))
            {
                RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
                RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
            }
        }

        private double? OnGetElevation(double mx, double my)
        {
            if (mx < mx1 || mx > mx2 || my < my1 || my > my2) return null;
            double x = (mx - mx1) / (mx2 - mx1) * (elevations.GetLength(1) - 1);
            double y = (my - my1) / (my2 - my1) * (elevations.GetLength(0) - 1);
            int x1 = (int) x;
            int y1 = (int) y;
            int x2 = x1 + 1;
            int y2 = y1 + 1;
            
            if (x2 >= elevations.GetLength(1)) x2 = elevations.GetLength(1) - 1;
            if (y2 >= elevations.GetLength(0)) y2 = elevations.GetLength(0) - 1;
            
            double dx = x - x1;
            double dy = y - y1;
            double el1 = elevations[y1, x1];
            double el2 = elevations[y1, x2];
            double el3 = elevations[y2, x1];
            double el4 = elevations[y2, x2];
            double el = el1 * (1 - dx) * (1 - dy) + el2 * dx * (1 - dy) + el3 * (1 - dx) * dy + el4 * dx * dy;
            return el;
        }

        private void OnGetElevationRange(out double minElevation, out double maxElevation)
        {
            double min = double.MaxValue;
            double max = double.MinValue;
            
            for (int y = 0; y < rows; y++)
            {
                for (int x = 0; x < cols; x++) 
                {
                    double el = elevations[y, x];
                    if (el < min) min = el;
                    if (el > max) max = el;
                }
            }
            
            minElevation = min;
            maxElevation = max;
        }

        [MenuItem("RWT/Using Google Elevation API")]
        public static void OpenWindow()
        {
            GetWindow<UsingGoogleElevationsExample>(true, "Google Elevation API Example");
        }
    }
}
Kind Regards,
Infinity Code Team.

Boost your productivity a lot and immediately using Ultimate Editor Enhancer. Trial and non-commerce versions available.