//----------------------------------------------------------------------- // // 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; } } }