很久没有更新博客了,今天无事把GMAP.NET的代码又重新翻了翻,看到了街景地图的例子。
街景地图是谷歌最早提出来的,我不知道谷歌的街景地图是如何实现的,在这个例子中,运用了WPF 3D的原理,对街景地图进行了简单的实现,在我看来更像是全景地图(PanoramaViewer)。先看看实现的效果,在本地运行代码的时候,鼠标拖动后整个图像是可以360旋转的,这里是张静态图片而已。
整篇文档需要对WPF 3D有个基本的了解,至少要知道Viewport3D(视野),PerspectiveCamera(摄像机),ModelVisual3D等概念,如果没有这些概念,可以先去msdn看一下相关的基础知识。因为整篇文档的技术部分其实和地图没有直接的关系,更多是讲3D。
整个项目的所有代码就是3个文件,App.xaml,PanoramaViewer.cs,Window1.xaml。
App.xaml是创建工程时默认生成的;
Window1.xaml主要完成了加载图片并放入到PanoramaViewer的工作;
PanormaViewer, Panorma的英文意思是全景,因此我们给它取了个名字叫全景查看器,这个类是整个项目的核心。
1. 核心类 PanoramaViewer(全景查看器)
整个PanormaViewer继承于Viewport3D,构造了一个最简单的3D模型,里面很多属性,例如FieldOfView, RotationX, RotationY, RotationZ,ModelVisual3D,GeometryModel3D等都是和WPF 3D息息相关的。只是PanoramaImage ImageSource的构造需要注意一下。
具体的代码如下所示:
2. 图片的组织和加载
Window1.xaml则承担了图片的组织和加载工作,和大部分图片加载一样,也是先尝试从本地加载,本地没有,则从网上下载。这里的图片是由许多
小块组成的,看看图片文件夹的结构就清楚了。最后这些图片组成RenderTargetBitmap,赋给前面提到的PanoramaImage。
下面是Window1.xaml的代码:
using System; using System.ComponentModel; using System.IO; using System.Net; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Imaging; namespace Demo.StreetView { /// <summary> /// Interaction logic for Window1.xaml /// </summary> public partial class Window1 : Window { BackgroundWorker loader = new BackgroundWorker(); StackPanel buff = new StackPanel(); public Window1() { InitializeComponent(); Viewer.MouseLeftButtonDown += Viewer_MouseLeftButtonDown; Viewer.MouseMove += Viewer_MouseMove; buff.Orientation = Orientation.Vertical; // removes white lines between tiles! SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased); loader.DoWork += loader_DoWork; loader.ProgressChanged += loader_ProgressChanged; loader.RunWorkerCompleted += loader_RunWorkerCompleted; loader.WorkerReportsProgress = true; } void loader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) { buff.UpdateLayout(); Canvas canvas = new Canvas(); canvas.Children.Add(buff); canvas.Width = 512 * 13; canvas.Height = 512 * 7; canvas.UpdateLayout(); canvas.Measure(new Size((int)canvas.Width, (int)canvas.Height)); canvas.Arrange(new Rect(new Size((int)canvas.Width, (int)canvas.Height))); int Height = ((int)(canvas.ActualHeight)); int Width = ((int)(canvas.ActualWidth)); RenderTargetBitmap _RenderTargetBitmap = new RenderTargetBitmap(Width, Height, 96, 96, PixelFormats.Pbgra32); _RenderTargetBitmap.Render(buff); Image img = new Image(); img.Source = _RenderTargetBitmap; Viewer.PanoramaImage = _RenderTargetBitmap; Title = "Demo.StreetView, enjoy! ;}"; } Vector RotationVector = new Vector(); Point DownPoint = new Point(); void Viewer_MouseMove(object sender, MouseEventArgs e) { if(e.LeftButton == MouseButtonState.Released) return; Vector Offset = Point.Subtract(e.GetPosition(Viewer), DownPoint) * 0.25; Viewer.RotationY = RotationVector.Y + Offset.X; Viewer.RotationX = RotationVector.X - Offset.Y; } void Viewer_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) { DownPoint = e.GetPosition(Viewer); RotationVector.X = Viewer.RotationX; RotationVector.Y = Viewer.RotationY; Cursor = Cursors.SizeAll; } private void Viewer_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) { Cursor = Cursors.Arrow; } void loader_ProgressChanged(object sender, ProgressChangedEventArgs e) { if(e.ProgressPercentage == 100) { Pass p = e.UserState as Pass; Title = "Demo.StreetView, please wait on first time loading: " + p.X + "|" + p.Y + " of 13"; Image i = new Image(); i.Source = p.src; (buff.Children[buff.Children.Count - 1] as StackPanel).Children.Add(i); } else if(e.ProgressPercentage == 0) { Title = "Demo.StreetView, please wait on first time loading: zooming..."; StackPanel ph = new StackPanel(); ph.Orientation = Orientation.Horizontal; buff.Children.Add(ph); } } void loader_DoWork(object sender, DoWorkEventArgs e) { string panoId = "4fe6hEN9GJC6thoQBcgv0Q"; int zoom = 4; //0, 1 //1, 2 //2, 4 //3, 7 //4, 13 //5, 26 for(int y = 0; y <= zoom + 1; y++) { loader.ReportProgress(0); for(int x = 0; x < 13; x++) { Pass p = new Pass(); p.Y = y; p.X = x; string fl = "Tiles\\" + zoom + "\\" + panoId + "\\img_" + x + "_" + y + ".jpg"; string dr = System.IO.Path.GetDirectoryName(fl); if(!Directory.Exists(dr)) { Directory.CreateDirectory(dr); } if(!File.Exists(fl)) { ImageSource src = Get(string.Format("http://cbk{0}.{5}/cbk?output=tile&panoid={1}&zoom={2}&x={3}&y={4}&cb_client=maps_sv", (x + 2 * y) % 3, panoId, zoom, x, y, GMap.NET.MapProviders.GoogleMapProvider.Instance.Server)); p.src = src; SaveImg(src, fl); } else { using(Stream s = File.OpenRead(fl)) { p.src = FromStream(s); } } loader.ReportProgress(100, p); } } GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect(); } void SaveImg(ImageSource src, string file) { using(Stream s = File.OpenWrite(file)) { JpegBitmapEncoder e = new JpegBitmapEncoder(); e.Frames.Add(BitmapFrame.Create(src as BitmapSource)); e.Save(s); } } private void Window_Loaded(object sender, RoutedEventArgs e) { loader.RunWorkerAsync(); } public Stream CopyStream(Stream inputStream) { const int readSize = 256; byte[] buffer = new byte[readSize]; MemoryStream ms = new MemoryStream(); using(inputStream) { int count = inputStream.Read(buffer, 0, readSize); while(count > 0) { ms.Write(buffer, 0, count); count = inputStream.Read(buffer, 0, readSize); } } buffer = null; ms.Seek(0, SeekOrigin.Begin); return ms; } ImageSource FromStream(Stream stream) { ImageSource ret = null; if(stream != null) { { // try png decoder try { JpegBitmapDecoder bitmapDecoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); ImageSource m = bitmapDecoder.Frames[0]; if(m != null) { ret = m; } } catch { ret = null; } // try jpeg decoder if(ret == null) { try { stream.Seek(0, SeekOrigin.Begin); PngBitmapDecoder bitmapDecoder = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad); ImageSource m = bitmapDecoder.Frames[0]; if(m != null) { ret = m; } } catch { ret = null; } } } } return ret; } ImageSource Get(string url) { ImageSource ret; try { HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url); request.ServicePoint.ConnectionLimit = 50; request.Proxy = WebRequest.DefaultWebProxy; request.UserAgent = "Opera/9.62 (Windows NT 5.1; U; en) Presto/2.1.1"; request.Timeout = 10 * 1000; request.ReadWriteTimeout = request.Timeout * 6; request.Referer = string.Format("http://maps.{0}/", GMap.NET.MapProviders.GoogleMapProvider.Instance.Server); request.KeepAlive = true; using(HttpWebResponse response = request.GetResponse() as HttpWebResponse) { using(Stream responseStream = CopyStream(response.GetResponseStream())) { ret = FromStream(responseStream); } } } catch(Exception) { ret = null; } return ret; } } class Pass { public ImageSource src; public int Y; public int X; } }