<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title><![CDATA[Infinity Code Forum — Using Google Elevation API to generate Terrains]]></title>
		<link>https://forum.infinity-code.com/viewtopic.php?id=2158</link>
		<atom:link href="https://forum.infinity-code.com/extern.php?action=feed&amp;tid=2158&amp;type=rss" rel="self" type="application/rss+xml" />
		<description><![CDATA[The most recent posts in Using Google Elevation API to generate Terrains.]]></description>
		<lastBuildDate>Fri, 25 Aug 2023 17:48:08 +0000</lastBuildDate>
		<generator>PunBB</generator>
		<item>
			<title><![CDATA[Using Google Elevation API to generate Terrains]]></title>
			<link>https://forum.infinity-code.com/viewtopic.php?pid=9163#p9163</link>
			<description><![CDATA[<p>When none of the pre-installed providers have sufficient data quality for your area, you can use the Google Elevation API.</p><p>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.<br /><strong>Don&#039;t use large values for row and columns because you might get a big bill to pay.</strong></p><p><strong>How to use it:</strong><br />1. Create a script in the Editor folder, with the contents below.<br />2. From the menu, select RWT/Using Google Elevation API.<br />3. Enter your Google API Key. Maps Elevation API must be enabled in google developer console.<br />4. Enter or get the coordinates of your area from Real World Terrain.<br />5. Specify the desired number of rows and columns of data.<br />A single request to the Elevation API can return a maximum of 512 values.<br />The total number of requests will be Col * Row / 512.<br />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.<br />7. Click Intercept Getting Elevations.<br />8. Start the generation in the Real World Terrain window.<br />9. When the generation is complete, click Release Getting Elevations.</p><p>Enjoy.</p><div class="codebox"><pre><code>/*         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 = &quot;&quot;;
        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&lt;Tuple&lt;int, double, double&gt;&gt; points = new List&lt;Tuple&lt;int, double, double&gt;&gt;(512);

            for (int y = 0; y &lt; rows; y++)
            {
                double my = my1 + dy * y;
                for (int x = 0; x &lt; 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&lt;int, double, double&gt; (index, lng, lat));
                    if (points.Count == 512)
                    {
                        if (!DownloadElevations(points))
                        {
                            return;
                        }
                        
                        points.Clear();
                    }
                }
            }

            if (points.Count &gt; 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(&quot;Success&quot;, &quot;Elevations downloaded&quot;, &quot;OK&quot;);
        }

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

            int index = 0;
            XmlNodeList results = xml.SelectNodes(&quot;//result&quot;);
            foreach (XmlNode result in results)
            {
                string text = result.SelectSingleNode(&quot;elevation&quot;).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&lt;Tuple&lt;int, double, double&gt;&gt; points)
        {
            List&lt;int&gt; encodedPoints = new List&lt;int&gt;();

            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 &gt;= 0x20)
            {
                encoded.Append((char)((0x20 | (num &amp; 0x1f)) + 63));
                num &gt;&gt;= 5;
            }

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

            return encoded.ToString();
        }

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

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

        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 &lt; rows; y++)
            {
                for (int x = 0; x &lt; 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(&quot;Google API Key: &quot;, googleAPIKey);
            leftLongitude = EditorGUILayout.DoubleField(&quot;Left Longitude: &quot;, leftLongitude);
            topLatitude = EditorGUILayout.DoubleField(&quot;Top Latitude: &quot;, topLatitude);
            rightLongitude = EditorGUILayout.DoubleField(&quot;Right Longitude: &quot;, rightLongitude);
            bottomLatitude = EditorGUILayout.DoubleField(&quot;Bottom Latitude: &quot;, bottomLatitude);
            rows = EditorGUILayout.IntField(&quot;Rows: &quot;, rows);
            cols = EditorGUILayout.IntField(&quot;Cols: &quot;, cols);
            
            EditorGUILayout.HelpBox($&quot;Number of requests: {Mathf.CeilToInt(rows * cols / 512f)}&quot;, MessageType.Info);

            if (GUILayout.Button(&quot;1. Get Area From Real World Terrain&quot;))
            {
                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(&quot;2. Download elevations&quot;))
            {
                DownloadElevations();
            }
            
            if (GUILayout.Button(&quot;3. Intercept Getting Elevations&quot;))
            {
                if (!HasElevations())
                {
                    EditorUtility.DisplayDialog(&quot;Error&quot;, &quot;Elevations not downloaded&quot;, &quot;OK&quot;);
                }
                else
                {
                    LoadElevations();
                    RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
                    RealWorldTerrainElevationGenerator.OnGetElevation += OnGetElevation;
                    RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
                    RealWorldTerrainElevationGenerator.OnGetElevationRange += OnGetElevationRange;
                }
            }
            
            EditorGUILayout.HelpBox(&quot;4. Start Generation Using Real World Terrain Window&quot;, MessageType.Info);
            
            if (GUILayout.Button(&quot;5. Release Getting Elevations&quot;))
            {
                RealWorldTerrainElevationGenerator.OnGetElevation -= OnGetElevation;
                RealWorldTerrainElevationGenerator.OnGetElevationRange -= OnGetElevationRange;
            }
        }

        private double? OnGetElevation(double mx, double my)
        {
            if (mx &lt; mx1 || mx &gt; mx2 || my &lt; my1 || my &gt; 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 &gt;= elevations.GetLength(1)) x2 = elevations.GetLength(1) - 1;
            if (y2 &gt;= 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 &lt; rows; y++)
            {
                for (int x = 0; x &lt; cols; x++) 
                {
                    double el = elevations[y, x];
                    if (el &lt; min) min = el;
                    if (el &gt; max) max = el;
                }
            }
            
            minElevation = min;
            maxElevation = max;
        }

        [MenuItem(&quot;RWT/Using Google Elevation API&quot;)]
        public static void OpenWindow()
        {
            GetWindow&lt;UsingGoogleElevationsExample&gt;(true, &quot;Google Elevation API Example&quot;);
        }
    }
}</code></pre></div>]]></description>
			<author><![CDATA[null@example.com (Alex Vertax)]]></author>
			<pubDate>Fri, 25 Aug 2023 17:48:08 +0000</pubDate>
			<guid>https://forum.infinity-code.com/viewtopic.php?pid=9163#p9163</guid>
		</item>
	</channel>
</rss>
