//-----------------------------------------------------------------------
//
// Copyright (c) 2016 Mapbox. All rights reserved.
//
//-----------------------------------------------------------------------
namespace Mapbox.Map
{
using System;
using Mapbox.Platform;
using System.Linq;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using Mapbox.Unity.Utilities;
///
/// A Map tile, a square with vector or raster data representing a geographic
/// bounding box. More info
/// here .
///
public abstract class Tile : IAsyncRequest
{
private CanonicalTileId _id;
private List _exceptions;
private State _state = State.New;
private IAsyncRequest _request;
private Action _callback;
/// Tile state.
public enum State
{
/// New tile, not yet initialized.
New,
/// Loading data.
Loading,
/// Data loaded and parsed.
Loaded,
/// Data loading cancelled.
Canceled,
/// Data has been loaded before and got updated.
Updated
}
/// Gets the identifier.
/// The canonical tile identifier.
public CanonicalTileId Id
{
get { return _id; }
set { _id = value; }
}
/// Flag to indicate if the request was successful
public bool HasError
{
get
{
return _exceptions == null ? false : _exceptions.Count > 0;
}
}
/// Exceptions that might have occured during creation of the tile.
public ReadOnlyCollection Exceptions
{
get { return null == _exceptions ? null : _exceptions.AsReadOnly(); }
}
/// Messages of exceptions otherwise empty string.
public string ExceptionsAsString
{
get
{
if (null == _exceptions || _exceptions.Count == 0) { return string.Empty; }
return string.Join(Environment.NewLine, _exceptions.Select(e => e.Message).ToArray());
}
}
///
/// Sets the error message.
///
///
internal void AddException(Exception ex)
{
if (null == _exceptions) { _exceptions = new List(); }
_exceptions.Add(ex);
}
///
/// Gets the current state. When fully loaded, you must
/// check if the data actually arrived and if the tile
/// is accusing any error.
///
/// The tile state.
public State CurrentState
{
get
{
return _state;
}
}
public HttpRequestType RequestType { get { return _request.RequestType; } }
public bool IsCompleted
{
get
{
return _state == State.Loaded;
}
}
///
/// Initializes the object. It will
/// start a network request and fire the callback when completed.
///
/// Initialization parameters.
/// The completion callback.
public void Initialize(Parameters param, Action callback)
{
Cancel();
_state = State.Loading;
_id = param.Id;
_callback = callback;
_request = param.Fs.Request(MakeTileResource(param.TilesetId).GetUrl(), HandleTileResponse, tileId: _id, tilesetId: param.TilesetId);
}
internal void Initialize(IFileSource fileSource, CanonicalTileId canonicalTileId, string tilesetId, Action p)
{
Cancel();
_state = State.Loading;
_id = canonicalTileId;
_callback = p;
_request = fileSource.Request(MakeTileResource(tilesetId).GetUrl(), HandleTileResponse, tileId: _id, tilesetId: tilesetId);
}
///
/// Returns a that represents the current
/// .
///
///
/// A that represents the current
/// .
///
public override string ToString()
{
return Id.ToString();
}
///
/// Cancels the request for the object.
/// It will stop a network request and set the tile's state to Canceled.
///
///
///
/// // Do not request tiles that we are already requesting
/// // but at the same time exclude the ones we don't need
/// // anymore, cancelling the network request.
/// tiles.RemoveWhere((T tile) =>
/// {
/// if (cover.Remove(tile.Id))
/// {
/// return false;
/// }
/// else
/// {
/// tile.Cancel();
/// NotifyNext(tile);
/// return true;
/// }
/// });
///
///
public void Cancel()
{
if (_request != null)
{
_request.Cancel();
_request = null;
}
_state = State.Canceled;
}
// Get the tile resource (raster/vector/etc).
internal abstract TileResource MakeTileResource(string tilesetId);
// Decode the tile.
internal abstract bool ParseTileData(byte[] data);
// TODO: Currently the tile decoding is done on the main thread. We must implement
// a Worker class to abstract this, so on platforms that support threads (like Unity
// on the desktop, Android, etc) we can use worker threads and when building for
// the browser, we keep it single-threaded.
List ids = new List();
private void HandleTileResponse(Response response)
{
if (response.HasError)
{
if (!ids.Contains(_id.ToString()))
ids.Add(_id.ToString());
else
return;
response.Exceptions.ToList().ForEach(e => AddException(e));
}
else
{
// only try to parse if request was successful
// current implementation doesn't need to check if parsing is successful:
// * Mapbox.Map.VectorTile.ParseTileData() already adds any exception to the list
// * Mapbox.Map.RasterTile.ParseTileData() doesn't do any parsing
ParseTileData(response.Data);
}
// Cancelled is not the same as loaded!
if (_state != State.Canceled)
{
if (response.IsUpdate)
{
_state = State.Updated;
}
else
{
_state = State.Loaded;
}
}
_callback();
}
///
/// Parameters for initializing a Tile object.
///
///
///
/// var parameters = new Tile.Parameters();
/// parameters.Fs = MapboxAccess.Instance;
/// parameters.Id = new CanonicalTileId(_zoom, _tileCoorindateX, _tileCoordinateY);
/// parameters.TilesetId = "mapbox.mapbox-streets-v7";
///
///
public struct Parameters
{
/// The tile id.
public CanonicalTileId Id;
///
/// The tileset ID, usually in the format "user.mapid". Exceptionally,
/// will take the full style URL
/// from where the tile is composited from, like mapbox://styles/mapbox/streets-v9.
///
public string TilesetId;
/// The data source abstraction.
public IFileSource Fs;
}
}
}