using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Collections;
using UnityEngine;
using RestSharp;
using Adrenak.Unex;
namespace Adrenak.UniMap {
public class GooglePanoDownloader {
///
/// Invoked everytime a request is finished. Null if the request was not successful
///
public event Action OnLoaded;
///
/// Invoked everytime a request fails.
///
public event Action OnFailed;
///
/// Invoked when a new download starts
///
public event Action OnStarted;
///
/// Returns the current Panorama Image texture
///
Texture32 m_Texture;
bool m_Running;
List m_Handles = new List();
///
/// Downloads a Google uploaded panorama image using the URL as a Promise
///
/// The URL of the pano
/// The of the pano image to be downloaded
//public IPromise Download(string panoID, PanoSize size, TextureFormat format) {
// var promise = new Promise();
// Download(panoID, size, format,
// result => promise.Resolve(result),
// exception => promise.Reject(exception)
// );
// return promise;
//}
///
/// Downloads a Google uploaded panorama image using the URL
///
/// The URL of the pano
/// The of the pano image to be downloaded
/// Callback for result texture when the download is successful
/// Callback for exception when the download fails
public void Download(string panoID, PanoSize size, TextureFormat format, Action onResult, Action onException) {
Stop();
Dispatcher.Enqueue(() => {
Runnable.Run(DownloadCo(panoID, size, format, onResult, onException));
});
}
IEnumerator DownloadCo(string panoID, PanoSize size, TextureFormat format, Action onResult, Action onException) {
OnStarted?.Invoke();
var uRes = PanoUtility.GetUntrimmedResolution(size);
m_Texture = new Texture32((int)uRes.x, (int)uRes.y);
var count = PanoUtility.GetTileCount(size);
int xCount = (int)count.x;
int yCount = (int)count.y;
int req = xCount * yCount;
int success = 0;
int failed = 0;
bool done = false;
for (int i = 0; i < xCount; i++) {
for (int j = 0; j < yCount; j++) {
var x = i;
var y = j;
DownloadTile(panoID, x, y, size, format,
tile => {
success++;
StitchTexture(tile, size, x, ((int)count.y - 1) - y);
if (success == req) {
done = true;
CropTexture(size);
onResult?.Invoke(m_Texture);
OnLoaded?.Invoke(m_Texture);
}
},
exception => {
if (x == 0 && yCount > y) {
yCount = y;
req = xCount * yCount;
}
if (y == 0 && xCount > x) {
xCount = x;
req = xCount * yCount;
}
failed++;
if (failed == req) {
var thrown = new Exception("Could not download the pano image. ID or URL is incorrect.");
OnFailed?.Invoke(thrown);
onException?.Invoke(thrown);
return;
}
if (success == req && !done) {
done = true;
CropTexture(size);
onResult?.Invoke(m_Texture);
OnLoaded?.Invoke(m_Texture);
}
}
);
yield return null;
}
}
}
Task DownloadTile(string panoID, int x, int y, PanoSize size, TextureFormat format) {
var source = new TaskCompletionSource();
DownloadTile(panoID, x, y, size, format,
result => source.SetResult(result),
exception => source.SetException(exception)
);
return source.Task;
}
async void DownloadTile(string panoID, int x, int y, PanoSize size, TextureFormat format, Action onResult = null, Action onException = null) {
m_Running = true;
var url = "https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en"
+ "&panoid=" + panoID
+ "&output=tile"
+ "&x=" + x
+ "&y=" + y
+ "&zoom=" + PanoUtility.GetZoomValue(size)
+ "&nbt&fover=2";
RestRequestAsyncHandle handle = null;
m_Handles.Add(handle);
var client = new RestClient();
var request = new RestRequest(url, Method.GET);
try {
var task = await client.ExecuteAsync(request, out handle);
var response = task;
Dispatcher.Enqueue(() => {
if (response.IsSuccessful()) {
var tile = new Texture2D(2, 2, format, true);
tile.LoadImage(response.RawBytes);
onResult?.Invoke(tile);
}
else {
onException?.Invoke(response.GetException());
}
});
}
catch (RestSharpException e) {
if (!m_Running) return;
onException?.Invoke(e);
}
}
public Task IsAvailable(string id) {
var promise = new TaskCompletionSource();
IsAvailable(id, result => {
promise.SetResult(result);
});
return promise.Task;
}
///
/// Not all images are uploaded by Google, some panos are User uploads
/// This fucniton returns whether the pano with the given ID can be downloaded
///
///
///
public void IsAvailable(string panoID, Action result) {
var url = "https://geo0.ggpht.com/cbk?cb_client=maps_sv.tactile&authuser=0&hl=en"
+ "&panoid=" + panoID
+ "&output=tile"
+ "&x=0"
+ "&y=0"
+ "&zoom=" + 0
+ "&nbt&fover=2";
var client = new RestClient();
var request = new RestRequest(url, Method.GET);
client.ExecuteAsync(request, response => {
Dispatcher.Enqueue(() => {
if (response.IsSuccessful)
result?.Invoke(response.IsSuccessful());
else
result?.Invoke(false);
});
});
}
// Copies the tile to the right place in the complete (large) texture
// On Android devices, Graphics.CopyTexture results in the editor freezing
// when the PanoSize is set to VeryLast. For this reason we use an extension method
// called Texture2D.Copy. However Graphics.Copytexture is faster so we use that
// wherever possible
void StitchTexture(Texture2D tile, PanoSize size, int x, int y) {
m_Texture.ReplaceBlock(x * tile.width, y * tile.height, Texture32.FromTexture2D(tile));
MonoBehaviour.Destroy(tile);
tile = null;
}
// Removes the blank parts of the texture
// Similar to StitchTexture, we try to use Graphics.CopyTexture wherever possible
// however on VeryLarge PanoSize on Android devices, we use an extenion method called
// Texture2D.Crop, which is slower than Graphics.CopyTexture and is a last resort
void CropTexture(PanoSize level) {
var uRes = PanoUtility.GetUntrimmedResolution(level);
var tRes = PanoUtility.DetectBlankBands(m_Texture);
tRes = new Vector2(
PanoUtility.DetectWidth(m_Texture),
tRes.y
);
// If the trimmed resolutionm is the same as untrimmed, we don't need to
if (tRes.x == uRes.x && tRes.y == uRes.y)
return;
m_Texture.Crop(0, (int)uRes.y - (int)tRes.y, (int)tRes.x, (int)tRes.y);
}
///
/// Destroys the internal Texture2D
///
public void ClearTexture() {
if (m_Texture != null)
m_Texture.Clear();
m_Texture = null;
}
public void Stop() {
m_Running = false;
ClearTexture();
foreach (var handle in m_Handles) {
if (handle != null) {
handle.WebRequest.Abort();
handle.Abort();
}
}
}
}
}