Created
March 6, 2026 17:13
-
-
Save Adjuvant/a94b538463f74bcae0fe565ffc75e93d to your computer and use it in GitHub Desktop.
Getting around NASA Earth data server changes to download SRTM30 heighmap data via Real World Terrain plugin, probably unrecognisable to original. Unity 2022.3.62f3 on Windows 11 worked.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| using System; | |
| using System.Collections.Generic; | |
| using System.IO; | |
| using System.IO.Compression; | |
| using InfinityCode.RealWorldTerrain.Net; | |
| using InfinityCode.RealWorldTerrain.Phases; | |
| using InfinityCode.RealWorldTerrain.Windows; | |
| using UnityEngine; | |
| using UnityEngine.Networking; | |
| namespace InfinityCode.RealWorldTerrain.Generators | |
| { | |
| public class RealWorldTerrainSRTM30ElevationGenerator : RealWorldTerrainElevationGenerator | |
| { | |
| private const string server = "https://data.lpdaac.earthdatacloud.nasa.gov/lp-prod-protected/SRTMGL1.003/"; | |
| // FIXED: Removed 'static' to prevent adjacent tiles from stealing each other's URLs | |
| private List<RealWorldTerrainDownloadItemWebClient> downloaderItems; | |
| public static string login; | |
| public static string pass; | |
| private readonly string filename; | |
| private readonly string arcFilename; | |
| private readonly string heightmapFilename; | |
| private readonly int x1, y1, x2, y2; | |
| private double mx1, mx2, my1, my2; | |
| private static object exclusiveLock; | |
| public RealWorldTerrainSRTM30ElevationGenerator(int X, int Y) // Removed 'ref needAuth' | |
| { | |
| x1 = X; x2 = X + 1; y1 = Y + 1; y2 = Y; | |
| mapSize = 3601; mapSize2 = mapSize - 1; | |
| RealWorldTerrainGeo.LatLongToMercat(x1, y1, out mx1, out my1); | |
| RealWorldTerrainGeo.LatLongToMercat(x2, y2, out mx2, out my2); | |
| string ax = (x1 < 0 ? "W" : "E") + Math.Abs(x1).ToString("D3"); | |
| string ay = (y2 < 0 ? "S" : "N") + Math.Abs(y2).ToString("D2"); | |
| string tileName = $"{ay}{ax}"; | |
| filename = $"{tileName}.hgt"; | |
| arcFilename = Path.Combine(RealWorldTerrainEditorUtils.heightmapCacheFolder, filename + ".zip"); | |
| heightmapFilename = Path.Combine(RealWorldTerrainEditorUtils.heightmapCacheFolder, filename); | |
| string url = $"{server}{tileName}.SRTMGL1.hgt/{tileName}.SRTMGL1.hgt.zip"; | |
| if (exclusiveLock == null) exclusiveLock = new object(); | |
| if (!File.Exists(heightmapFilename) && !File.Exists(arcFilename)) | |
| { | |
| // FORCE AUTH FOR EVERY TILE | |
| RealWorldTerrainDownloadItemAction authItem = new RealWorldTerrainDownloadItemAction { exclusiveLock = exclusiveLock }; | |
| authItem["url"] = url; | |
| authItem["step"] = 1; | |
| authItem["tileName"] = tileName; | |
| authItem.OnStart = OnAuthStep; | |
| authItem.OnCheckComplete = OnAuthCheckComplete; | |
| downloaderItems = new List<RealWorldTerrainDownloadItemWebClient>(); | |
| RealWorldTerrainDownloadItemWebClient item = new RealWorldTerrainDownloadItemWebClient(url) | |
| { | |
| filename = arcFilename, | |
| averageSize = 11000000, | |
| exclusiveLock = exclusiveLock | |
| }; | |
| downloaderItems.Add(item); | |
| item.OnError += OnDownloadError; | |
| } | |
| } | |
| private void OnAuthStep(RealWorldTerrainDownloadItemAction action) | |
| { | |
| string url = action.GetField<string>("url"); | |
| UnityWebRequest uwr = UnityWebRequest.Get(url); | |
| Debug.Log($"<color=cyan>[RWT Auth]</color> Requesting: {url}"); | |
| string auth = Convert.ToBase64String(System.Text.Encoding.UTF8.GetBytes($"{login.Trim()}:{pass.Trim()}")); | |
| uwr.SetRequestHeader("Authorization", "Basic " + auth); | |
| uwr.redirectLimit = 10; | |
| action["uwr"] = uwr; | |
| uwr.SendWebRequest(); | |
| } | |
| private void OnAuthCheckComplete(RealWorldTerrainDownloadItemAction action) | |
| { | |
| UnityWebRequest uwr = action.GetField<UnityWebRequest>("uwr"); | |
| if (!uwr.isDone) return; | |
| Debug.Log($"<color=orange>[RWT Auth Result]</color> Status: {uwr.responseCode} | Final URL: {uwr.url}"); | |
| if (uwr.result == UnityWebRequest.Result.Success && (uwr.responseCode == 200 || uwr.responseCode == 302)) | |
| { | |
| Dictionary<string, string> headers = uwr.GetResponseHeaders(); | |
| foreach (RealWorldTerrainDownloadItemWebClient item in downloaderItems) | |
| { | |
| item.url = uwr.url; | |
| item.headers = new Dictionary<string, string>(); | |
| if (headers != null && headers.ContainsKey("Set-Cookie")) | |
| item.headers.Add("Cookie", headers["Set-Cookie"]); | |
| } | |
| action.Dispose(); | |
| action.complete = true; | |
| } | |
| else | |
| { | |
| Debug.LogError($"[RWT] Auth Failed: {uwr.error}"); | |
| RealWorldTerrainWindow.CancelCapture(); | |
| } | |
| } | |
| public void ParseHeightmap() | |
| { | |
| try | |
| { | |
| heightmap = new short[mapSize, mapSize]; | |
| if (!File.Exists(heightmapFilename)) | |
| { | |
| Debug.LogWarning($"[RWT] Parse failed: {heightmapFilename} not found."); | |
| if (!prefs.ignoreSRTMErrors) RealWorldTerrainWindow.CancelInMainThread(); | |
| return; | |
| } | |
| using (FileStream fs = File.Open(heightmapFilename, FileMode.Open, FileAccess.Read, FileShare.Read)) | |
| { | |
| int c = 0; | |
| long totalLength = fs.Length; | |
| byte[] buffer = new byte[1024 * 1024]; | |
| while (fs.Position < totalLength) | |
| { | |
| // ELEGANT EXIT: Master kill-switch check | |
| if (!RealWorldTerrainWindow.isCapturing) | |
| { | |
| Debug.LogWarning("[RWT] Parsing aborted by system."); | |
| return; | |
| } | |
| int count = fs.Read(buffer, 0, buffer.Length); | |
| for (int i = 0; i < count; i += 2) | |
| { | |
| short s = (short)(buffer[i] * 256 + buffer[i + 1]); | |
| if (s == 0) s = short.MinValue; | |
| heightmap[c % mapSize, c / mapSize] = s; | |
| c++; | |
| } | |
| if (c % 100000 == 0) | |
| { | |
| RealWorldTerrainPhase.phaseProgress = (float)fs.Position / totalLength; | |
| } | |
| } | |
| } | |
| Debug.Log($"<color=green>[RWT]</color> Successfully parsed heightmap: {filename}"); | |
| } | |
| catch (Exception e) | |
| { | |
| Debug.LogError($"[RWT Parse Error] {e.Message}"); | |
| RealWorldTerrainWindow.CancelInMainThread(); | |
| } | |
| finally | |
| { | |
| // ALWAYS clean up memory, even on a crash | |
| GC.Collect(); | |
| } | |
| } | |
| public override void UnzipHeightmap() | |
| { | |
| try | |
| { | |
| if (File.Exists(heightmapFilename)) { unziped = true; return; } | |
| if (!File.Exists(arcFilename)) { return; } | |
| Debug.Log($"[RWT] Attempting Unzip: {arcFilename}"); | |
| int retries = 5; | |
| while (retries > 0) | |
| { | |
| // ELEGANT EXIT | |
| if (!RealWorldTerrainWindow.isCapturing) return; | |
| try | |
| { | |
| using (FileStream fs = File.Open(arcFilename, FileMode.Open, FileAccess.Read, FileShare.Read)) | |
| using (ZipArchive zip = new ZipArchive(fs, ZipArchiveMode.Read)) | |
| { | |
| foreach (ZipArchiveEntry entry in zip.Entries) | |
| { | |
| // ELEGANT EXIT | |
| if (!RealWorldTerrainWindow.isCapturing) return; | |
| Debug.Log($"[RWT Zip Content] Found: {entry.Name} (Looking for: {filename})"); | |
| if (entry.Name.IndexOf(filename, StringComparison.OrdinalIgnoreCase) >= 0) | |
| { | |
| entry.ExtractToFile(heightmapFilename, true); | |
| unziped = true; | |
| Debug.Log($"<color=green>[RWT]</color> Unzipped {entry.Name} successfully."); | |
| return; | |
| } | |
| } | |
| } | |
| // If loop finishes without finding the file | |
| Debug.LogError($"[RWT Unzip Failed] Could not find {filename} inside the archive."); | |
| if (!prefs.ignoreSRTMErrors) RealWorldTerrainWindow.CancelInMainThread(); | |
| return; | |
| } | |
| catch (IOException) | |
| { | |
| // File locked. Do a breakable micro-sleep instead of a hard Thread.Sleep | |
| for (int s = 0; s < 50; s++) | |
| { | |
| if (!RealWorldTerrainWindow.isCapturing) return; // Break out immediately if cancelled | |
| System.Threading.Thread.Sleep(10); | |
| } | |
| retries--; | |
| } | |
| } | |
| Debug.LogError($"[RWT] Unzip timed out due to file locks on {filename}."); | |
| } | |
| catch (Exception e) | |
| { | |
| Debug.LogError($"[RWT Unzip Error] {e.Message}"); | |
| if (!prefs.ignoreSRTMErrors) RealWorldTerrainWindow.CancelInMainThread(); | |
| } | |
| } | |
| public override bool Contains(double x, double y) { return x >= mx1 && x <= mx2 && y >= my1 && y <= my2; } | |
| private void OnDownloadError(RealWorldTerrainDownloadItem item) { if (!prefs.ignoreSRTMErrors) RealWorldTerrainWindow.CancelInMainThread(); } | |
| public new static void Dispose() | |
| { | |
| if (elevations != null) { foreach (var e in elevations) e.heightmap = null; elevations = null; } | |
| lastX = 0; lastElevation = null; | |
| } | |
| public override double GetElevationValue(double x, double y) | |
| { | |
| RealWorldTerrainGeo.MercatToLatLong(x, y, out x, out y); | |
| x -= x1; y = y1 - y; | |
| double cx = x * mapSize2, cy = y * mapSize2; | |
| int ix = (int)cx, iy = (int)cy; | |
| if (prefs.nodataValue != 0 && GetValue(ix, iy) == short.MinValue) return double.MinValue; | |
| double ox = cx - ix, oy = cy - iy; | |
| return GetSmoothElevation(ox - 1, ox - 2, ox + 1, oy - 1, oy - 2, oy + 1, ix, iy, ox, ix + 1, oy, iy + 1, ox * oy, ix - 1, iy - 1, ix + 2, iy + 2); | |
| } | |
| protected override short GetValue(int x, int y) | |
| { | |
| if (x < 0) x = 0; else if (x > mapSize2) x = mapSize2; | |
| if (y < 0) y = 0; else if (y > mapSize2) y = mapSize2; | |
| return heightmap[x, y]; | |
| } | |
| public static void GetSRTMElevationRange(out double minEl, out double maxEl) | |
| { | |
| minEl = double.MaxValue; | |
| maxEl = double.MinValue; | |
| int cx = prefs.terrainCount.x * (prefs.heightmapResolution - 1) + 1; | |
| int cy = prefs.terrainCount.y * (prefs.heightmapResolution - 1) + 1; | |
| const int maxV = 4097; | |
| if (cx > maxV && cx > cy) | |
| { | |
| float sv = maxV / (float)cx; | |
| cx = maxV; | |
| cy = Mathf.RoundToInt(cy * sv); | |
| } | |
| else if (cy > maxV) | |
| { | |
| float sv = maxV / (float)cy; | |
| cy = maxV; | |
| cx = Mathf.RoundToInt(cx * sv); | |
| } | |
| double rx = (prefs.rightLongitude - prefs.leftLongitude) / cx; | |
| double ry = (prefs.topLatitude - prefs.bottomLatitude) / cy; | |
| for (int x = 0; x < cx; x++) | |
| { | |
| double tx = x * rx + prefs.leftLongitude; | |
| for (int y = 0; y < cy; y++) | |
| { | |
| double ty = y * ry + prefs.bottomLatitude; | |
| double mx, my; | |
| RealWorldTerrainGeo.LatLongToMercat(tx, ty, out mx, out my); | |
| double el = GetElevation(mx, my); | |
| if (Math.Abs(el - double.MinValue) > double.Epsilon) | |
| { | |
| if (el < minEl) minEl = el; | |
| if (el > maxEl) maxEl = el; | |
| } | |
| } | |
| } | |
| if (minEl > prefs.nodataValue) minEl = prefs.nodataValue; | |
| } | |
| public static void Init() | |
| { | |
| int sx = (int)Math.Floor(prefs.leftLongitude), sy = (int)Math.Floor(prefs.topLatitude); | |
| int ex = (int)Math.Floor(prefs.rightLongitude), ey = (int)Math.Floor(prefs.bottomLatitude); | |
| for (int x = sx; x <= ex; x++) { | |
| for (int y = sy; y >= ey; y--) { | |
| // No longer passing 'ref needAuth' | |
| elevations.Add(new RealWorldTerrainSRTM30ElevationGenerator(x, y)); | |
| } | |
| } | |
| double rangeX = prefs.rightLongitude - prefs.leftLongitude; | |
| depthStep = (float)(Math.Abs(prefs.depthSharpness) * rangeX / (prefs.heightmapResolution - 1) / prefs.terrainCount.x) * 100; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment