Search code examples
c#.net-coreleafletopenstreetmapasp.net-core-webapi

How to use Openstreetmap(osm) offline tiles file in .net core?


I have web application that uses Leaflet to show map of a small region. The problem is that the application must work offline(on LAN) and map tiles are loaded from Openstreetmap. I downloaded the tiles that I needed but I haven't find a good documentation about how to use the downloaded file(which is a 500MB file with .mbtiles extension). Here is the default approach suggested by Leaflet :

    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
        attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
    }).addTo(map); 

How do I set up a server using dotnet core to use my offline tiles and given parameters and get tiles images?(like the following):

L.tileLayer('https://example.com/DotnetCore/WepApi/GetTiles?{s}/{z}/{x}/{y}').addTo(map);

Solution

  • Here is a basic implementation to read the tiles file and serve them in your WebApi.

    You need to install the NuGet System.Data.SQLite.Core (or similar) to access the database.

    Helper class:

    public class MbTilesReader
    {
        private string _mbTilesFilename;
    
        public MbTilesReader(string mbTilesFilename)
        {
            _mbTilesFilename = mbTilesFilename;
        }
    
        public byte[] GetImageData(int x, int y, int zoom)
        {
            byte[] imageData = null;
            using (SQLiteConnection conn = new SQLiteConnection(string.Format("Data Source={0};Version=3;", _mbTilesFilename)))
            {
                conn.Open();
                using (SQLiteCommand cmd = new SQLiteCommand(conn))
                {
                    cmd.CommandText = "SELECT * FROM tiles WHERE tile_column = @x and tile_row = @y and zoom_level = @z";
                    cmd.CommandType = System.Data.CommandType.Text;
                    cmd.Parameters.Add(new SQLiteParameter("@x", x));
                    cmd.Parameters.Add(new SQLiteParameter("@y", y));
                    cmd.Parameters.Add(new SQLiteParameter("@z", zoom));
                    SQLiteDataReader reader = cmd.ExecuteReader();
                    if (reader.Read())
                    {
                        imageData = reader["tile_data"] as byte[];
                    }
                }
            }
            return imageData;
        }
    }
    

    Then register the class as a singleton in your ConfigureServices method and pass the path to the file:

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddSingleton(new MbTilesReader("c:/temp/map.mbtiles"));
        services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
    }
    

    Finally, you can return the image in your WebApi action as follows:

    [Route("api/[controller]")]
    [ApiController]
    public class MapController : ControllerBase
    {
        private MbTilesReader _tileReader;
    
        public MapController(MbTilesReader tileReader)
        {
            _tileReader = tileReader;
    
        }
    
        [HttpGet]
        public IActionResult Get(int x, int y, int z)
        {
            byte[] imageData = _tileReader.GetImageData(x, y, z);
            return File(imageData, "image/png");
        }
    }
    

    Possible improvements

    • Use a cache to avoid querying all the time for the same images.
    • Make the implementation async (see this question).

    Edit - Formats

    This answer assumes your data is stored in PNG format, .mbtiles files can store data in the following formats pbf (for vectors), jpg, png, and webapp. To know which format your database is using, check the data in the table metadata of the .mbtiles SQLite database.

    See the following link for more info: https://github.com/mapbox/mbtiles-spec/blob/master/1.3/spec.md