// WPF MapControl - http://wpfmapcontrol.codeplex.com/ // Copyright © 2012 Clemens Fischer // Licensed under the Microsoft Public License (Ms-PL) using System; using System.Collections.Generic; using System.Windows; using System.Windows.Media; using System.Windows.Threading; namespace MapControl { internal class TileContainer : ContainerVisual { private const double maxScaledTileSize = 400d; // scaled tile size 200..400 units private static double zoomLevelSwitchOffset = Math.Log(maxScaledTileSize / 256d, 2d); private Size size; private Point origin; private Vector offset; private double rotation; private double zoomLevel; private int tileZoomLevel; private Int32Rect tileGrid; private readonly DispatcherTimer updateTimer; public readonly MatrixTransform ViewportTransform = new MatrixTransform(); public TileContainer() { updateTimer = new DispatcherTimer(TimeSpan.FromSeconds(0.5), DispatcherPriority.Background, UpdateTiles, Dispatcher); } public void AddTileLayers(int index, IEnumerable tileLayers) { Matrix transform = GetVisualTransform(); foreach (TileLayer tileLayer in tileLayers) { if (string.IsNullOrWhiteSpace(tileLayer.SourceName)) { throw new ArgumentException("TileLayer.SourceName property must not be null or empty or white-space only."); } Children.Insert(index++, tileLayer); tileLayer.TransformMatrix = transform; tileLayer.UpdateTiles(tileZoomLevel, tileGrid); } } public void RemoveTileLayers(int index, IEnumerable tileLayers) { int count = 0; foreach (TileLayer tileLayer in tileLayers) { tileLayer.ClearTiles(); count++; } Children.RemoveRange(index, count); } public void ClearTileLayers() { foreach (TileLayer tileLayer in Children) { tileLayer.ClearTiles(); } Children.Clear(); } public double SetViewportTransform(double mapZoomLevel, double mapRotation, Point mapOrigin, Point viewportOrigin, Size viewportSize) { zoomLevel = mapZoomLevel; rotation = mapRotation; size = viewportSize; origin = viewportOrigin; double scale = Math.Pow(2d, zoomLevel) * 256d / 360d; offset.X = origin.X - (180d + mapOrigin.X) * scale; offset.Y = origin.Y - (180d - mapOrigin.Y) * scale; Matrix transform = new Matrix(1d, 0d, 0d, -1d, 180d, 180d); transform.Scale(scale, scale); transform.Translate(offset.X, offset.Y); transform.RotateAt(rotation, origin.X, origin.Y); ViewportTransform.Matrix = transform; transform = GetVisualTransform(); foreach (TileLayer tileLayer in Children) { tileLayer.TransformMatrix = transform; } updateTimer.IsEnabled = true; return scale; } private Matrix GetVisualTransform() { // Calculates the transform matrix that enables rendering of 256x256 tile rectangles in // TileLayer.UpdateTiles with origin at tileGrid.X and tileGrid.Y to minimize rounding errors. double scale = Math.Pow(2d, zoomLevel - tileZoomLevel); Matrix transform = new Matrix(1d, 0d, 0d, 1d, tileGrid.X * 256d, tileGrid.Y * 256d); transform.Scale(scale, scale); transform.Translate(offset.X, offset.Y); transform.RotateAt(rotation, origin.X, origin.Y); return transform; } private void UpdateTiles(object sender, EventArgs e) { updateTimer.IsEnabled = false; int zoom = (int)Math.Floor(zoomLevel + 1d - zoomLevelSwitchOffset); int numTiles = 1 << zoom; double mapToTileScale = (double)numTiles / 360d; Matrix transform = ViewportTransform.Matrix; transform.Invert(); // view to map coordinates transform.Translate(180d, -180d); transform.Scale(mapToTileScale, -mapToTileScale); // map coordinates to tile indices // tile indices of visible rectangle Point p1 = transform.Transform(new Point(0d, 0d)); Point p2 = transform.Transform(new Point(size.Width, 0d)); Point p3 = transform.Transform(new Point(0d, size.Height)); Point p4 = transform.Transform(new Point(size.Width, size.Height)); double left = Math.Min(p1.X, Math.Min(p2.X, Math.Min(p3.X, p4.X))); double right = Math.Max(p1.X, Math.Max(p2.X, Math.Max(p3.X, p4.X))); double top = Math.Min(p1.Y, Math.Min(p2.Y, Math.Min(p3.Y, p4.Y))); double bottom = Math.Max(p1.Y, Math.Max(p2.Y, Math.Max(p3.Y, p4.Y))); // index ranges of visible tiles int x1 = (int)Math.Floor(left); int x2 = (int)Math.Floor(right); int y1 = Math.Max((int)Math.Floor(top), 0); int y2 = Math.Min((int)Math.Floor(bottom), numTiles - 1); Int32Rect grid = new Int32Rect(x1, y1, x2 - x1 + 1, y2 - y1 + 1); if (tileZoomLevel != zoom || tileGrid != grid) { tileZoomLevel = zoom; tileGrid = grid; transform = GetVisualTransform(); foreach (TileLayer tileLayer in Children) { tileLayer.TransformMatrix = transform; tileLayer.UpdateTiles(tileZoomLevel, tileGrid); } } } } }