Loading local XYZ Tiles

751
1
01-10-2023 10:00 AM
ZachRuiz
New Contributor III

Hello, I am attempting to load a local XYZ tiles to my map. I feel like the relevant example is this so I have followed this  is https://developers.arcgis.com/net/wpf/sample-code/web-tiled-layer/ but instead of a Url I am pointing to a local folder on my PC.

private readonly string _templateUri = "file:///C:\\Users/Zach/Desktop/DEM/XYZ2/{z}/{x}/{y}.png";   

        // List of subdomains for use when constructing the web tiled layer
        private readonly List<string> _tiledLayerSubdomains = new List<string> { "12" };

        
        


        public mapviewTest()
        {
            InitializeComponent();
            Initialize();
        }

        private void Initialize()
        {
            // Create the layer from the URL and the subdomain list
            WebTiledLayer myBaseLayer = new WebTiledLayer(_templateUri, _tiledLayerSubdomains);

            // Create a basemap from the layer
            Basemap layerBasemap = new Basemap(myBaseLayer);

            // Create a map to hold the basemap
            Map myMap = new Map(layerBasemap);

            // Add the map to the map view
            MyMapView.Map = myMap;
        }

 The main folder has a Subfolder named "12" then in that folder several other folders with numbers which is where the actual PNG images live.

ZachRuiz_0-1673373521642.png

 

 

 

ZachRuiz_1-1673373581699.png

Am I going about this the correct way?

 

0 Kudos
1 Reply
dotMorten_esri
Esri Notable Contributor

Did you mean to add the backslash in the tile template? It should just be forward slashes. Also the doc states you need to use the level row and column parameter instead of x y z.  That might be why. (If you load the layer, you should get a load error telling you exactly this) Also you don't need the subdomain overload, since you don't have a subdomain in the template uri.
So you'd have something like:

 

private readonly string _templateUri = "file:///C:/Users/Zach/Desktop/DEM/XYZ2/{level}/{col}/{row}.png";   

 

This made it work for me. You can also instead implement a custom ImageTiledLayer, which gives you full control of how the image bytes are loaded. It is more work, but gives a lot better control. Example:

 

using Esri.ArcGISRuntime.ArcGISServices;
using Esri.ArcGISRuntime.Geometry;
using Esri.ArcGISRuntime.Mapping;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace CustomTileLayerSample
{
    public class LocalTiledLayer : ImageTiledLayer
    {
        private readonly string _templateUri = "C:\\Users\\Zach\\Desktop\\DEM\\XYZ2\\{0}\\{1}\\{2}.png";

        public LocalTiledLayer() : base(CreateTileInfo(), new Envelope(-2.003750722959434E7, -1.997186888040859E7, 2.003750722959434E7, 1.9971868880408563E7, SpatialReferences.WebMercator))
        {
        }

        private static TileInfo CreateTileInfo()
        {
            List<LevelOfDetail> levels = new List<LevelOfDetail>();
            var resolution = 156543.03392800014;
            var scale = 5.91657527591555E8;
            int levelCount = 24;
            for (int i = 0; i < levelCount; i++)
            {
                levels.Add(new LevelOfDetail(i, resolution, scale));
                resolution /= 2; scale/=2;
            }
            return new TileInfo(96, TileImageFormat.Png32, levels, new MapPoint(-2.0037508342787E7, 2.0037508342787E7, SpatialReferences.WebMercator), SpatialReferences.WebMercator, 256, 256);
        }

        protected override async Task<ImageTileData> GetTileDataAsync(int level, int row, int column, CancellationToken cancellationToken)
        {
            var file = string.Format(_templateUri, level, row, column);
            if (File.Exists(file))
            {
                var bytes = await File.ReadAllBytesAsync(file).ConfigureAwait(false);
                return new ImageTileData(level, row, column, bytes, "image/png");
            }
            return new ImageTileData(level, row, column, new byte[] { }, "image/png");
        }
    }
}

 

You could even take this a step further and generate tiles on the fly:

 

        protected override Task<ImageTileData> GetTileDataAsync(int level, int row, int column, CancellationToken cancellationToken)
        {
            return Task.Run(() =>
            {
                using var pen = new System.Drawing.Pen(System.Drawing.Color.Black);
                using var font = new System.Drawing.Font("Times New Roman", 12);
                using var image = new System.Drawing.Bitmap(256, 256);
                using var g = Graphics.FromImage(image);
                g.DrawString($"{level}/{row}/{column}", font, System.Drawing.Brushes.Black, new PointF(100, 122));
                g.DrawLine(pen, 0, 0, 255, 0);
                g.DrawLine(pen, 0, 0, 0, 255);
                using var filestream = new MemoryStream();
                image.Save(filestream, System.Drawing.Imaging.ImageFormat.Png);
                return new ImageTileData(level, row, column, filestream.ToArray(), "image/png");
            });
        }

 

 

 

0 Kudos