Xamarin Google Maps iOS/Androidで、地図タイルアクセスの前後をフック

タイル地図APIで、URLで指定したタイルアクセスの前後をフックしたいことはよくあります。
タイル画像のキャッシュ機構を設けたい時とか、タイルの存在しない大縮尺で、小縮尺のタイルからデジタルズームしたいときとか。
そのフックのかけ方を、Xamarin Android / iOS双方のGoogle Mapsで紹介します。

記事が長くなりそうなので、キャッシュやデジタルズーム自体は別記事で。

## Xamarin Androidでのフックのかけ方

```csharp
using System;
using Android.Gms.Maps;
using Android.Gms.Maps.Model;
using Android.Graphics;
using System.IO;

namespace MyApp
{
    //標準のタイルプロバイダ、Javaだと無名クラスでやるようなあたり
    public class MyTilesProviderInternal : UrlTileProvider
    {
        public static int TILE_WIDTH  = 256;
        public static int TILE_HEIGHT = 256;

        //コンストラクタ
        public MyTilesProviderInternal () : base(TILE_WIDTH,TILE_HEIGHT)
        {
        }

        //タイルURL生成
        public override Java.Net.URL GetTileUrl (int x, int y, int z)
        {
            var url = this.CreateTileUrl(x, y, z);
            return url == null ? null : new Java.Net.URL (url);
        }

         //タイルURL生成本体（ロジック省略）
        protected string CreateTileUrl (int x, int y, int z)
        {
        }
    }

    //ITileProviderを持つクラスを定義、こちらが本体
    public class MyTilesProvider : Java.Lang.Object, ITileProvider
    {
        protected MyTilesProviderInternal intern;

        //コンストラクタ
        public MyTilesProvider () : base()
        {
            this.intern = new MyTilesProviderInternal ();
        }

        //タイル取得
        public Tile GetTile (int x, int y, int zoom)
        {
            //画像取得前のフックはここに書く（キャッシュから取得等）

            //標準のタイルプロバイダから画像タイル取得
            var tile = intern.GetTile (x, y, zoom);

            //画像取得後のフックはここに書く（キャッシュに登録等）
            return tile;
        }
    }
}
```

## Xamarin iOSでのフックのかけ方

```csharp
using System;
using Google.Maps;
using MonoTouch.UIKit;
using MonoTouch.Foundation;
using System.Drawing;
using MonoTouch.CoreGraphics;

namespace MyApp
{
    //Google Maps
    //TileLayerを継承する
    public class MyTilesProvider : TileLayer
    {
        //標準のUrlTileLayer、フックするために内部で持ちます
        protected UrlTileLayer intern;

        //コンストラクタ
        public MyTilesProvider () : base ()
        {
            var self = this;
            //標準のUrlTileLayerはUrl生成用無名関数を与えて作るので生成
            intern = UrlTileLayer.FromUrlConstructor ((uint x, uint y, uint zoom) => {
                var url = self.CreateTileUrl(x, y, zoom);
                return url == null ? null : new NSUrl(url);
            });
        }

        //タイル要求メソッド。結果は、ITileReceiverに返す
        public override void RequestTile (uint x, uint y, uint zoom, ITileReceiver receiver)
        {
            //画像取得前のフックはここに書く（キャッシュから取得等）
            //フックした結果の画像を返す場合は、receiverに直接返して処理終了

            //画像取得後をフックするため、オリジナルのITileReceiverを作ってreceiverをデコレートする
            var wrapped = new MyTilesReceiver (receiver);

            //標準のUrlTileLayerにタイル要求
            intern.RequestTile (x, y, zoom, wrapped);
        }

        //タイルURL生成本体（ロジック省略）
        protected string CreateTileUrl (uint x, uint y, uint z)
        {
        }
    }

    //標準のreceiverをデコレートするオリジナルITileReceiver
    public class MyTilesReceiver : NSObject, ITileReceiver
    {
        //デコレートする元のreceiver
        public ITileReceiver origin;

        //コンストラクタ
        public MyTilesReceiver (ITileReceiver origin) : base ()
        {
            this.origin      = origin;
        }

        //タイル取得結果コールバック用メソッド
        public void ReceiveTile (uint x, uint y, uint zoom, UIImage image)
        {
            //画像取得後のフックのうち、画像そのものに手を加えるものはここに書く
            //（画像加工等）

            //デコレートしている元receiverに画像を返す
            origin.ReceiveTile (x, y, zoom, image);

            //画像取得後のフックのうち、後からの処理でよいものはここに書く
            //（キャッシュに登録等）
        }
    }
}
```