| 1 | //␍␊ |
| 2 | // csmapcontrol - a C# map control - http://projects.ciarang.com/p/csmapcontrol␍␊ |
| 3 | // Copyright (C) 2010, Ciaran Gultnieks␍␊ |
| 4 | //␍␊ |
| 5 | // This program is free software: you can redistribute it and/or modify␍␊ |
| 6 | // it under the terms of the GNU Affero General Public License as published by␍␊ |
| 7 | // the Free Software Foundation, either version 3 of the License, or␍␊ |
| 8 | // (at your option) any later version.␍␊ |
| 9 | //␍␊ |
| 10 | // This program is distributed in the hope that it will be useful,␍␊ |
| 11 | // but WITHOUT ANY WARRANTY; without even the implied warranty of␍␊ |
| 12 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the␍␊ |
| 13 | // GNU Affero General Public License for more details.␍␊ |
| 14 | //␍␊ |
| 15 | // You should have received a copy of the GNU Affero General Public License␍␊ |
| 16 | // along with this program. If not, see <http://www.gnu.org/licenses/>. ␍␊ |
| 17 | ␍␊ |
| 18 | using System;␍␊ |
| 19 | using System.Collections.Generic;␍␊ |
| 20 | using System.ComponentModel;␍␊ |
| 21 | using System.Drawing;␍␊ |
| 22 | using System.Windows.Forms;␍␊ |
| 23 | using System.Threading;␍␊ |
| 24 | using System.Net;␍␊ |
| 25 | using System.IO;␍␊ |
| 26 | ␍␊ |
| 27 | namespace csmapcontrol␍␊ |
| 28 | {␍␊ |
| 29 | ␍␊ |
| 30 | ␉public class Marker␍␊ |
| 31 | ␉{␍␊ |
| 32 | ␉␉public double lat;␍␊ |
| 33 | ␉␉public double lon;␍␊ |
| 34 | ␉}␍␊ |
| 35 | ␍␊ |
| 36 | ␉public class TileManager␍␊ |
| 37 | ␉{␍␊ |
| 38 | ␍␊ |
| 39 | ␉␉public delegate void NewDataAvailableHandler();␍␊ |
| 40 | ␉␉public event NewDataAvailableHandler NewDataAvailable;␍␊ |
| 41 | ␍␊ |
| 42 | ␉␉/// <summary>␍␊ |
| 43 | ␉␉/// Dictionary of cached tiles, keyed on a string formed␍␊ |
| 44 | ␉␉/// as "zoom,x,y".␍␊ |
| 45 | ␉␉/// </summary>␍␊ |
| 46 | ␉␉private Dictionary<string, Bitmap> mTiles;␍␊ |
| 47 | ␍␊ |
| 48 | ␉␉/// <summary>␍␊ |
| 49 | ␉␉/// Tile currently being downloaded, or null if none.␍␊ |
| 50 | ␉␉/// </summary>␍␊ |
| 51 | ␉␉private string mDownloadTile=null;␍␊ |
| 52 | ␍␊ |
| 53 | ␉␉public TileManager()␍␊ |
| 54 | ␉␉{␍␊ |
| 55 | ␉␉␉mTiles=new Dictionary<string, Bitmap>();␍␊ |
| 56 | ␉␉}␍␊ |
| 57 | ␍␊ |
| 58 | ␉␉public void EmptyCache()␍␊ |
| 59 | ␉␉{␍␊ |
| 60 | ␉␉␉foreach(string key in mTiles.Keys)␍␊ |
| 61 | ␉␉␉{␍␊ |
| 62 | ␉␉␉␉if(mTiles[key]!=null)␍␊ |
| 63 | ␉␉␉␉␉mTiles[key].Dispose();␍␊ |
| 64 | ␉␉␉}␍␊ |
| 65 | ␉␉␉mTiles.Clear();␍␊ |
| 66 | ␉␉}␍␊ |
| 67 | ␍␊ |
| 68 | ␉␉public Image GetTile(int x,int y,int zoom)␍␊ |
| 69 | ␉␉{␍␊ |
| 70 | ␉␉␉lock(mTiles)␍␊ |
| 71 | ␉␉␉{␍␊ |
| 72 | ␉␉␉␉string key=zoom.ToString()+','+x.ToString()+','+y.ToString();␍␊ |
| 73 | ␉␉␉␉if(mTiles.ContainsKey(key))␍␊ |
| 74 | ␉␉␉␉{␍␊ |
| 75 | ␉␉␉␉␉// We already have the tile in our cache...␍␊ |
| 76 | ␉␉␉␉␉return mTiles[key];␍␊ |
| 77 | ␉␉␉␉}␍␊ |
| 78 | ␉␉␉␉if(mDownloadTile==null)␍␊ |
| 79 | ␉␉␉␉{␍␊ |
| 80 | ␉␉␉␉␉// Start downloading the tile...␍␊ |
| 81 | ␉␉␉␉␉mDownloadTile=key;␍␊ |
| 82 | ␉␉␉␉␉Thread t=new Thread(Download);␍␊ |
| 83 | ␉␉␉␉␉t.Start();␍␊ |
| 84 | ␉␉␉␉}␍␊ |
| 85 | ␉␉␉␉return null;␍␊ |
| 86 | ␉␉␉}␍␊ |
| 87 | ␉␉}␍␊ |
| 88 | ␍␊ |
| 89 | ␉␉private void Download()␍␊ |
| 90 | ␉␉{␍␊ |
| 91 | ␉␉␉try␍␊ |
| 92 | ␉␉␉{␍␊ |
| 93 | ␉␉␉␉string[] vals=mDownloadTile.Split(',');␍␊ |
| 94 | ␉␉␉␉string url="http://tile.openstreetmap.org/"+vals[0]+"/"+vals[1]+"/"+vals[2]+".png";␍␊ |
| 95 | ␉␉␉␉WebClient client=new WebClient();␍␊ |
| 96 | ␉␉␉␉Stream stream=client.OpenRead(url);␍␊ |
| 97 | ␉␉␉␉Bitmap tile=new Bitmap(stream);␍␊ |
| 98 | ␉␉␉␉stream.Flush();␍␊ |
| 99 | ␉␉␉␉stream.Close();␍␊ |
| 100 | ␉␉␉␉lock(mTiles)␍␊ |
| 101 | ␉␉␉␉{␍␊ |
| 102 | ␉␉␉␉␉mTiles[mDownloadTile]=tile;␍␊ |
| 103 | ␉␉␉␉}␍␊ |
| 104 | ␉␉␉␉// TODO: If we've reached the (as yet undefined) limit of how␍␊ |
| 105 | ␉␉␉␉// many tiles we should be caching, we should remove the least␍␊ |
| 106 | ␉␉␉␉// recently used one.␍␊ |
| 107 | ␉␉␉␉if(NewDataAvailable!=null)␍␊ |
| 108 | ␉␉␉␉␉NewDataAvailable();␍␊ |
| 109 | ␉␉␉}␍␊ |
| 110 | ␉␉␉catch(Exception)␍␊ |
| 111 | ␉␉␉{␍␊ |
| 112 | ␉␉␉␉lock(mTiles)␍␊ |
| 113 | ␉␉␉␉{␍␊ |
| 114 | ␉␉␉␉␉mTiles[mDownloadTile]=null;␍␊ |
| 115 | ␉␉␉␉}␍␊ |
| 116 | ␉␉␉␉if(NewDataAvailable!=null)␍␊ |
| 117 | ␉␉␉␉␉NewDataAvailable();␍␊ |
| 118 | ␉␉␉}␍␊ |
| 119 | ␉␉␉finally␍␊ |
| 120 | ␉␉␉{␍␊ |
| 121 | ␉␉␉␉mDownloadTile=null;␍␊ |
| 122 | ␉␉␉}␍␊ |
| 123 | ␍␊ |
| 124 | ␉␉}␍␊ |
| 125 | ␍␊ |
| 126 | ␉}␍␊ |
| 127 | ␍␊ |
| 128 | ␉/// <summary>␍␊ |
| 129 | ␉/// Map control␍␊ |
| 130 | ␉/// </summary>␍␊ |
| 131 | ␉public partial class MapControl : UserControl␍␊ |
| 132 | ␉{␍␊ |
| 133 | ␍␊ |
| 134 | ␉␉/// <summary>␍␊ |
| 135 | ␉␉/// A predetermined list of sensible (-ish) discrete scales that can be␍␊ |
| 136 | ␉␉/// used, for example, to provide a linear zoom range for a scrollbar.␍␊ |
| 137 | ␉␉/// </summary>␍␊ |
| 138 | ␉␉public static double[] DiscreteScales={␍␊ |
| 139 | ␉␉␉0.00005/256,␍␊ |
| 140 | ␉␉␉0.0005/256,␍␊ |
| 141 | ␉␉␉0.0008/256,␉␉␉␍␊ |
| 142 | ␉␉␉0.002/256,␍␊ |
| 143 | ␉␉␉0.002/256,␍␊ |
| 144 | ␉␉␉0.003/256,␍␊ |
| 145 | ␉␉␉0.004/256,␍␊ |
| 146 | ␉␉␉0.005/256,␍␊ |
| 147 | ␉␉␉0.006/256,␍␊ |
| 148 | ␉␉␉0.007/256,␍␊ |
| 149 | ␉␉␉0.008/256,␉␉␍␊ |
| 150 | ␉␉␉0.009/256,␍␊ |
| 151 | ␉␉␉0.01/256,␍␊ |
| 152 | ␉␉␉0.015/256,␍␊ |
| 153 | ␉␉␉0.02/256,␍␊ |
| 154 | ␉␉␉0.025/256,␍␊ |
| 155 | ␉␉␉0.03/256,␍␊ |
| 156 | ␉␉␉0.035/256,␍␊ |
| 157 | ␉␉␉0.04/256,␍␊ |
| 158 | ␉␉␉0.045/256,␍␊ |
| 159 | ␉␉␉0.05/256,␍␊ |
| 160 | ␉␉␉0.055/256,␍␊ |
| 161 | ␉␉␉0.06/256,␍␊ |
| 162 | ␉␉␉0.065/256,␍␊ |
| 163 | ␉␉␉0.07/256,␍␊ |
| 164 | ␉␉␉0.075/256,␍␊ |
| 165 | ␉␉␉0.08/256,␍␊ |
| 166 | ␉␉␉0.085/256,␍␊ |
| 167 | ␉␉␉0.09/256,␍␊ |
| 168 | ␉␉␉0.095/256,␍␊ |
| 169 | ␉␉␉0.1/256,␍␊ |
| 170 | ␉␉␉0.2/256,␍␊ |
| 171 | ␉␉␉0.3/256,␍␊ |
| 172 | ␉␉␉0.4/256,␍␊ |
| 173 | ␉␉␉0.5/256,␍␊ |
| 174 | ␉␉␉0.5/256,␍␊ |
| 175 | ␉␉␉0.6/256,␍␊ |
| 176 | ␉␉␉0.7/256,␍␊ |
| 177 | ␉␉␉0.8/256,␍␊ |
| 178 | ␉␉␉0.9/256,␍␊ |
| 179 | ␉␉␉1.0/256,␍␊ |
| 180 | ␉␉␉2.0/256,␍␊ |
| 181 | ␉␉␉3.0/256,␍␊ |
| 182 | ␉␉␉4.0/256,␍␊ |
| 183 | ␉␉␉5.0/256,␍␊ |
| 184 | ␉␉␉6.0/256,␍␊ |
| 185 | ␉␉␉7.0/256,␍␊ |
| 186 | ␉␉␉8.0/256,␍␊ |
| 187 | ␉␉␉9.0/256,␍␊ |
| 188 | ␉␉␉10.0/256,␍␊ |
| 189 | ␉␉␉15.0/256,␍␊ |
| 190 | ␉␉␉20.0/256,␍␊ |
| 191 | ␉␉␉30.0/256,␍␊ |
| 192 | ␉␉␉40.0/256,␍␊ |
| 193 | ␉␉␉50.0/256,␍␊ |
| 194 | ␉␉␉60.0/256,␍␊ |
| 195 | ␉␉␉70.0/256,␍␊ |
| 196 | ␉␉␉80.0/256,␍␊ |
| 197 | ␉␉␉90.0/256,␍␊ |
| 198 | ␉␉␉100.0/256,␍␊ |
| 199 | ␉␉␉110.0/256,␍␊ |
| 200 | ␉␉␉130.0/256,␍␊ |
| 201 | ␉␉␉150.0/256,␍␊ |
| 202 | ␉␉␉170.0/256,␍␊ |
| 203 | ␉␉␉190.0/256,␍␊ |
| 204 | ␉␉␉210.0/256,␍␊ |
| 205 | ␉␉␉240.0/256,␍␊ |
| 206 | ␉␉␉1};␍␊ |
| 207 | ␉␉␍␊ |
| 208 | ␉␉public List<Marker> Markers␍␊ |
| 209 | ␉␉{␍␊ |
| 210 | ␉␉␉get { return mMarkers; }␍␊ |
| 211 | ␉␉}␍␊ |
| 212 | ␉␉private List<Marker> mMarkers=new List<Marker>();␍␊ |
| 213 | ␍␊ |
| 214 | ␉␉/// <summary>␍␊ |
| 215 | ␉␉/// Latitude at centre of view␍␊ |
| 216 | ␉␉/// </summary>␍␊ |
| 217 | ␉␉public double Latitude␍␊ |
| 218 | ␉␉{␍␊ |
| 219 | ␉␉␉get { return mLat; }␍␊ |
| 220 | ␉␉␉set { mLat=value; Invalidate(); }␍␊ |
| 221 | ␉␉}␍␊ |
| 222 | ␉␉private double mLat=53.9753;␍␊ |
| 223 | ␍␊ |
| 224 | ␉␉/// <summary>␍␊ |
| 225 | ␉␉/// Longitude at centre of view␍␊ |
| 226 | ␉␉/// </summary>␍␊ |
| 227 | ␉␉public double Longitude␍␊ |
| 228 | ␉␉{␍␊ |
| 229 | ␉␉␉get { return mLon; }␍␊ |
| 230 | ␉␉␉set { mLon=value; Invalidate(); }␍␊ |
| 231 | ␉␉}␍␊ |
| 232 | ␉␉private double mLon=-1.7017;␍␊ |
| 233 | ␍␊ |
| 234 | ␉␉/// <summary>␍␊ |
| 235 | ␉␉/// Scale - longitude degrees per pixel␍␊ |
| 236 | ␉␉/// </summary>␍␊ |
| 237 | ␉␉public double LonScale␍␊ |
| 238 | ␉␉{␍␊ |
| 239 | ␉␉␉get { return mScale; }␍␊ |
| 240 | ␉␉␉set { mScale=value; Invalidate(); }␍␊ |
| 241 | ␉␉}␍␊ |
| 242 | ␉␉private double mScale=1.0/256;␍␊ |
| 243 | ␍␊ |
| 244 | ␉␉/// <summary>␍␊ |
| 245 | ␉␉/// Tile cache␍␊ |
| 246 | ␉␉/// </summary>␍␊ |
| 247 | ␉␉private static TileManager mTileManager=new TileManager();␍␊ |
| 248 | ␍␊ |
| 249 | ␉␉private bool mUpdateView=false;␍␊ |
| 250 | ␍␊ |
| 251 | ␉␉/// <summary>␍␊ |
| 252 | ␉␉/// Event raised when the view parameters are changed INTERNALLY.␍␊ |
| 253 | ␉␉/// For example, by the user dragging the map. This event is not raised␍␊ |
| 254 | ␉␉/// when the change is made by the client, e.g. by setting the relevant␍␊ |
| 255 | ␉␉/// properties.␍␊ |
| 256 | ␉␉/// </summary>␍␊ |
| 257 | ␉␉public event ViewChangedEventHandler ViewChanged;␍␊ |
| 258 | ␉␉public delegate void ViewChangedEventHandler(double lat,double lon,double scale);␍␊ |
| 259 | ␉␉private void RaiseViewChanged()␍␊ |
| 260 | ␉␉{␍␊ |
| 261 | ␉␉␉if(ViewChanged!=null)␍␊ |
| 262 | ␉␉␉{␍␊ |
| 263 | ␉␉␉␉ViewChanged(mLat,mLon,mScale);␍␊ |
| 264 | ␉␉␉}␍␊ |
| 265 | ␉␉}␍␊ |
| 266 | ␍␊ |
| 267 | ␉␉/// <summary>␍␊ |
| 268 | ␉␉/// Event raised when there is diagnostics information available.␍␊ |
| 269 | ␉␉/// </summary>␍␊ |
| 270 | ␉␉public event DiagsInfoEventHandler DiagsInfo;␍␊ |
| 271 | ␉␉public delegate void DiagsInfoEventHandler(string sMsg);␍␊ |
| 272 | ␉␉private void Diags(string msg)␍␊ |
| 273 | ␉␉{␍␊ |
| 274 | ␉␉␉if(DiagsInfo!=null)␍␊ |
| 275 | ␉␉␉␉DiagsInfo(msg);␍␊ |
| 276 | ␉␉}␍␊ |
| 277 | ␍␊ |
| 278 | ␉␉/// <summary>␍␊ |
| 279 | ␉␉/// Event raised when the user clicks at a location on the map.␍␊ |
| 280 | ␉␉/// </summary>␍␊ |
| 281 | ␉␉public event LocationClickedEventHandler LocationClicked;␍␊ |
| 282 | ␉␉public delegate void LocationClickedEventHandler(double lat,double lon);␍␊ |
| 283 | ␍␊ |
| 284 | ␉␉/// <summary>␍␊ |
| 285 | ␉␉/// Clear the tile cache. Since the tile manager is static, and shared␍␊ |
| 286 | ␉␉/// between all instances of the control, it will remain in place for␍␊ |
| 287 | ␉␉/// the lifetime of the application unless this is called.␍␊ |
| 288 | ␉␉/// </summary>␍␊ |
| 289 | ␉␉public static void ClearTileCache()␍␊ |
| 290 | ␉␉{␍␊ |
| 291 | ␉␉␉if(mTileManager!=null)␍␊ |
| 292 | ␉␉␉␉mTileManager.EmptyCache();␉␉␉␍␊ |
| 293 | ␉␉}␍␊ |
| 294 | ␍␊ |
| 295 | ␉␉public MapControl()␍␊ |
| 296 | ␉␉{␍␊ |
| 297 | ␍␊ |
| 298 | ␉␉␉InitializeComponent();␍␊ |
| 299 | ␉␉␉mTileManager.NewDataAvailable+=NewTileDataAvailable;␍␊ |
| 300 | ␉␉}␍␊ |
| 301 | ␍␊ |
| 302 | ␉␉/// <summary>␍␊ |
| 303 | ␉␉/// Flag that the view needs updating. Calling this will cause a call␍␊ |
| 304 | ␉␉/// to Invalidate() at the next timer tick. We need to go down this␍␊ |
| 305 | ␉␉/// convoluted route rather than simply calling Invalidate() from␍␊ |
| 306 | ␉␉/// the NewTileDataAvailable event handler, because that could cause␍␊ |
| 307 | ␉␉/// it to be called when a paint is still in progress, and this means␍␊ |
| 308 | ␉␉/// the Invalidate gets ignored.␍␊ |
| 309 | ␉␉/// </summary>␍␊ |
| 310 | ␉␉private void UpdateView()␍␊ |
| 311 | ␉␉{␍␊ |
| 312 | ␉␉␉mUpdateView=true;␍␊ |
| 313 | ␉␉}␍␊ |
| 314 | ␍␊ |
| 315 | ␉␉private void NewTileDataAvailable()␍␊ |
| 316 | ␉␉{␍␊ |
| 317 | ␉␉␉if(InvokeRequired)␍␊ |
| 318 | ␉␉␉{␍␊ |
| 319 | ␉␉␉␉BeginInvoke(new MethodInvoker(delegate␍␊ |
| 320 | ␉␉␉␉␉{ UpdateView(); }));␍␊ |
| 321 | ␉␉␉}␍␊ |
| 322 | ␉␉␉else␍␊ |
| 323 | ␉␉␉{␍␊ |
| 324 | ␉␉␉␉UpdateView();␍␊ |
| 325 | ␉␉␉}␍␊ |
| 326 | ␉␉}␍␊ |
| 327 | ␍␊ |
| 328 | ␉␉/// <summary>␍␊ |
| 329 | ␉␉/// Get the Tile X/Y position for a given latitude and longitude, and␍␊ |
| 330 | ␉␉/// at a given zoom level.␍␊ |
| 331 | ␉␉/// </summary>␍␊ |
| 332 | ␉␉/// <param name="lat">The latitude</param>␍␊ |
| 333 | ␉␉/// <param name="lon">The longitude</param>␍␊ |
| 334 | ␉␉/// <param name="zoom">The zoom level</param>␍␊ |
| 335 | ␉␉/// <param name="x">The tile X</param>␍␊ |
| 336 | ␉␉/// <param name="y">The tile Y</param>␍␊ |
| 337 | ␉␉private void GetTileXYFromLatLon(double lat,double lon,int zoom,out int x,out int y)␍␊ |
| 338 | ␉␉{␍␊ |
| 339 | ␉␉␉x=(int)((lon+180.0)/360.0*Math.Pow(2.0,zoom));␍␊ |
| 340 | ␉␉␉y=(int)((1.0-Math.Log(Math.Tan(lat*Math.PI/180.0)+␍␊ |
| 341 | ␉␉␉␉1.0/Math.Cos(lat*Math.PI/180.0))/Math.PI)/2.0*Math.Pow(2.0,zoom));␍␊ |
| 342 | ␉␉}␍␊ |
| 343 | ␍␊ |
| 344 | ␉␉/// <summary>␍␊ |
| 345 | ␉␉/// Get the latitude and longitude for the tile at the given X/Y and␍␊ |
| 346 | ␉␉/// zoom. The lat/lon returned is for the top left corner of the tile.␍␊ |
| 347 | ␉␉/// </summary>␍␊ |
| 348 | ␉␉/// <param name="x">The tile X</param>␍␊ |
| 349 | ␉␉/// <param name="y">The tile Y</param>␍␊ |
| 350 | ␉␉/// <param name="zoom">The zoom level</param>␍␊ |
| 351 | ␉␉/// <param name="lat">The latitude</param>␍␊ |
| 352 | ␉␉/// <param name="lon">The longitude</param>␍␊ |
| 353 | ␉␉private void GetLatLonFromTileXY(int x,int y,int zoom,out double lat,out double lon)␍␊ |
| 354 | ␉␉{␍␊ |
| 355 | ␉␉␉lon=((x/Math.Pow(2.0,zoom)*360.0)-180.0);␍␊ |
| 356 | ␉␉␉double n=Math.PI-((2.0*Math.PI*y)/Math.Pow(2.0,zoom));␍␊ |
| 357 | ␉␉␉lat=(180.0/Math.PI*Math.Atan(Math.Sinh(n)));␍␊ |
| 358 | ␉␉}␍␊ |
| 359 | ␍␊ |
| 360 | ␉␉/// <summary>␍␊ |
| 361 | ␉␉/// Get the latitude and longitude of a given position in the client␍␊ |
| 362 | ␉␉/// area. This can only work when a view has been drawn. It relies on␍␊ |
| 363 | ␉␉/// information saved during the paint process.␍␊ |
| 364 | ␉␉/// </summary>␍␊ |
| 365 | ␉␉/// <param name="x">The X position</param>␍␊ |
| 366 | ␉␉/// <param name="y">The Y position</param>␍␊ |
| 367 | ␉␉/// <param name="lat">The latitude</param>␍␊ |
| 368 | ␉␉/// <param name="lon">The longitude</param>␍␊ |
| 369 | ␉␉private void ClientPosToLatLon(int x,int y,out double lat,out double lon)␍␊ |
| 370 | ␉␉{␍␊ |
| 371 | ␉␉␉if(mViewTileSizePixels==0)␍␊ |
| 372 | ␉␉␉{␍␊ |
| 373 | ␉␉␉␉// We can't calculate it!␍␊ |
| 374 | ␉␉␉␉lat=lon=0;␍␊ |
| 375 | ␉␉␉␉return;␍␊ |
| 376 | ␉␉␉}␍␊ |
| 377 | ␍␊ |
| 378 | ␉␉␉lon=mLon+mScale*(x-Width/2);␍␊ |
| 379 | ␍␊ |
| 380 | ␉␉␉double offset=(double)(y-mViewYTileYPos)/mViewTileSizePixels;␍␊ |
| 381 | ␉␉␉double ty=mViewYTileNum+offset;␍␊ |
| 382 | ␉␉␉double n=Math.PI-((2.0*Math.PI*ty)/Math.Pow(2.0,mViewTileZoom));␍␊ |
| 383 | ␉␉␉lat=(180.0/Math.PI*Math.Atan(Math.Sinh(n)));␍␊ |
| 384 | ␉␉}␍␊ |
| 385 | ␍␊ |
| 386 | ␉␉/// <summary>␍␊ |
| 387 | ␉␉/// Get the view position in the client area of the given latitude and␍␊ |
| 388 | ␉␉/// longitude. This can only work when a view has been drawn. It relies␍␊ |
| 389 | ␉␉/// on information saved during the paint process.␍␊ |
| 390 | ␉␉/// </summary>␍␊ |
| 391 | ␉␉/// <param name="lat">The latitude</param>␍␊ |
| 392 | ␉␉/// <param name="lon">The longitude</param>␍␊ |
| 393 | ␉␉/// <param name="x">The X position</param>␍␊ |
| 394 | ␉␉/// <param name="y">The Y position</param>␍␊ |
| 395 | ␉␉private void LatLonToClientPos(double lat,double lon,out int x,out int y)␍␊ |
| 396 | ␉␉{␍␊ |
| 397 | ␉␉␉x=Width/2+(int)((lon-mLon)/mScale);␍␊ |
| 398 | ␉␉␉double ty=((1.0-Math.Log(Math.Tan(lat*Math.PI/180.0)+␍␊ |
| 399 | ␉␉␉␉1.0/Math.Cos(lat*Math.PI/180.0))/Math.PI)/2.0*Math.Pow(2.0,mViewTileZoom));␍␊ |
| 400 | ␉␉␉y=mViewYTileYPos+(int)((ty-mViewYTileNum)*mViewTileSizePixels);␍␊ |
| 401 | ␉␉}␍␊ |
| 402 | ␍␊ |
| 403 | ␉␉/// <summary>␍␊ |
| 404 | ␉␉/// These are stored during the paint process to be used when␍␊ |
| 405 | ␉␉/// calculating latitude.␍␊ |
| 406 | ␉␉/// </summary>␍␊ |
| 407 | ␉␉private int mViewYTileNum=0;␍␊ |
| 408 | ␉␉private int mViewYTileYPos=0;␍␊ |
| 409 | ␉␉private int mViewTileSizePixels=0;␍␊ |
| 410 | ␉␉private int mViewTileZoom=0;␍␊ |
| 411 | ␉␉␍␊ |
| 412 | ␉␉protected override void OnPaint(PaintEventArgs e)␍␊ |
| 413 | ␉␉{␍␊ |
| 414 | ␉␉␉base.OnPaint(e);␍␊ |
| 415 | ␍␊ |
| 416 | ␉␉␉if(!DesignMode)␍␊ |
| 417 | ␉␉␉{␍␊ |
| 418 | ␉␉␉␉try␍␊ |
| 419 | ␉␉␉␉{␍␊ |
| 420 | ␍␊ |
| 421 | ␉␉␉␉␉// Figure out what tile zoom we want...␍␊ |
| 422 | ␉␉␉␉␉// We want this many degrees longitude per tile...␍␊ |
| 423 | ␉␉␉␉␉double lonpertilewanted=256*mScale;␍␊ |
| 424 | ␉␉␉␉␉// Meaning we want this many tiles across in total.␍␊ |
| 425 | ␉␉␉␉␉int tilesnumwanted=(int)(360.0/lonpertilewanted);␍␊ |
| 426 | ␉␉␉␉␉// Which corresponds to this zoom level...␍␊ |
| 427 | ␉␉␉␉␉int tilezoom=(int)(Math.Log(tilesnumwanted)/Math.Log(2));␍␊ |
| 428 | ␉␉␉␉␉if(tilezoom>18)␍␊ |
| 429 | ␉␉␉␉␉␉tilezoom=18;␍␊ |
| 430 | ␉␉␉␉␉mViewTileZoom=tilezoom;␍␊ |
| 431 | ␉␍␊ |
| 432 | ␉␉␉␉␉// Get the tile x/y at the centre of the view...␍␊ |
| 433 | ␉␉␉␉␉int x,y;␍␊ |
| 434 | ␉␉␉␉␉GetTileXYFromLatLon(mLat,mLon,tilezoom,out x,out y);␍␊ |
| 435 | ␉␉␉␉␉mViewYTileNum=y;␍␊ |
| 436 | ␉␉␍␊ |
| 437 | ␉␉␉␉␉// Get the lat lon at the top left corner of that tile...␍␊ |
| 438 | ␉␉␉␉␉double lat,lon;␍␊ |
| 439 | ␉␉␉␉␉GetLatLonFromTileXY(x,y,tilezoom,out lat,out lon);␍␊ |
| 440 | ␍␊ |
| 441 | ␉␉␉␉␉// Get the lat lon at the bottom left corner as well...␍␊ |
| 442 | ␉␉␉␉␉double lat2,lon2;␍␊ |
| 443 | ␉␉␉␉␉GetLatLonFromTileXY(x+1,y+1,tilezoom,out lat2,out lon2);␍␊ |
| 444 | ␍␊ |
| 445 | ␉␉␉␉␉// Calculate longitude degrees per tile...␍␊ |
| 446 | ␉␉␉␉␉double lonpertile=360.0/(Math.Pow(2,tilezoom));␍␊ |
| 447 | ␍␊ |
| 448 | ␉␉␉␉␉// Calculate longitude degrees per pixel on the tile...␍␊ |
| 449 | ␉␉␉␉␉double lonpertilepixel=lonpertile/256;␍␊ |
| 450 | ␍␊ |
| 451 | ␉␉␉␉␉// Calculate the size (pixels) that tiles will be scaled to␍␊ |
| 452 | ␉␉␉␉␉// when drawn...␍␊ |
| 453 | ␉␉␉␉␉int tilescalesize=(int)(256*lonpertilepixel/mScale);␍␊ |
| 454 | ␉␉␉␉␉mViewTileSizePixels=tilescalesize;␍␊ |
| 455 | ␍␊ |
| 456 | ␉␉␉␉␉// Find where to draw the centre tile...␍␊ |
| 457 | ␉␉␉␉␉int drawx,drawy;␍␊ |
| 458 | ␉␉␉␉␉drawx=(int)(Width/2-((double)tilescalesize*((mLon-lon)/(lon2-lon))));␍␊ |
| 459 | ␉␉␉␉␉drawy=(int)(Height/2-((double)tilescalesize*((mLat-lat)/(lat2-lat))));␍␊ |
| 460 | ␉␉␉␉␉mViewYTileYPos=drawy;␍␊ |
| 461 | ␍␊ |
| 462 | ␉␉␉␉␉// Move the position back to the top-leftmost tile we need to␍␊ |
| 463 | ␉␉␉␉␉// draw...␍␊ |
| 464 | ␉␉␉␉␉while(drawx>=0)␍␊ |
| 465 | ␉␉␉␉␉{␍␊ |
| 466 | ␉␉␉␉␉␉drawx-=tilescalesize;␍␊ |
| 467 | ␉␉␉␉␉␉x--;␍␊ |
| 468 | ␉␉␉␉␉}␍␊ |
| 469 | ␉␉␉␉␉while(drawy>=0)␍␊ |
| 470 | ␉␉␉␉␉{␍␊ |
| 471 | ␉␉␉␉␉␉drawy-=tilescalesize;␍␊ |
| 472 | ␉␉␉␉␉␉y--;␍␊ |
| 473 | ␉␉␉␉␉}␍␊ |
| 474 | ␉␍␊ |
| 475 | ␉␉␉␉␉// Draw all the tiles...␍␊ |
| 476 | ␉␉␉␉␉int curdrawx;␍␊ |
| 477 | ␉␉␉␉␉int curx;␍␊ |
| 478 | ␉␉␉␉␉while(drawy<Height)␍␊ |
| 479 | ␉␉␉␉␉{␍␊ |
| 480 | ␉␉␉␉␉␉curdrawx=drawx;␍␊ |
| 481 | ␉␉␉␉␉␉curx=x;␍␊ |
| 482 | ␉␉␉␉␉␉while(curdrawx<Width)␍␊ |
| 483 | ␉␉␉␉␉␉{␍␊ |
| 484 | ␉␉␉␉␉␉␉Image img=mTileManager.GetTile(curx,y,tilezoom);␍␊ |
| 485 | ␉␉␉␉␉␉␉if(img!=null)␍␊ |
| 486 | ␉␉␉␉␉␉␉␉e.Graphics.DrawImage(img,curdrawx,drawy,␍␊ |
| 487 | ␉␉␉␉␉␉␉␉␉tilescalesize+1,tilescalesize+1);␍␊ |
| 488 | ␉␉␉␉␉␉␉curdrawx+=tilescalesize;␍␊ |
| 489 | ␉␉␉␉␉␉␉curx++;␍␊ |
| 490 | ␉␉␉␉␉␉}␍␊ |
| 491 | ␉␉␉␉␉␉drawy+=tilescalesize;␍␊ |
| 492 | ␉␉␉␉␉␉y++;␍␊ |
| 493 | ␉␉␉␉␉}␍␊ |
| 494 | ␍␊ |
| 495 | ␉␉␉␉␉// Draw markers...␍␊ |
| 496 | ␉␉␉␉␉foreach(Marker m in mMarkers)␍␊ |
| 497 | ␉␉␉␉␉{␍␊ |
| 498 | ␉␉␉␉␉␉int mx,my;␍␊ |
| 499 | ␉␉␉␉␉␉LatLonToClientPos(m.lat,m.lon,out mx,out my);␍␊ |
| 500 | ␉␉␉␉␉␉Pen pen=new Pen(Color.Red);␍␊ |
| 501 | ␉␉␉␉␉␉e.Graphics.DrawEllipse(pen,mx,my,5,5);␍␊ |
| 502 | ␉␉␉␉␉␉pen.Dispose();␍␊ |
| 503 | ␉␉␉␉␉}␍␊ |
| 504 | ␉␉␉␉}␍␊ |
| 505 | ␉␉␉␉catch(Exception ex)␍␊ |
| 506 | ␉␉␉␉{␍␊ |
| 507 | ␉␉␉␉␉Diags("Drawing Exception: "+ex.Message);␍␊ |
| 508 | ␉␉␉␉}␍␊ |
| 509 | ␍␊ |
| 510 | ␉␉␉}␍␊ |
| 511 | ␉␍␊ |
| 512 | ␉␉}␍␊ |
| 513 | ␍␊ |
| 514 | ␉␉/// <summary>␍␊ |
| 515 | ␉␉/// Disposes resources used by the form.␍␊ |
| 516 | ␉␉/// </summary>␍␊ |
| 517 | ␉␉/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>␍␊ |
| 518 | ␉␉protected override void Dispose(bool disposing)␍␊ |
| 519 | ␉␉{␍␊ |
| 520 | ␉␉␉if(disposing)␍␊ |
| 521 | ␉␉␉{␍␊ |
| 522 | ␉␉␉␉if(components!=null)␍␊ |
| 523 | ␉␉␉␉␉components.Dispose();␍␊ |
| 524 | ␉␉␉␉if(mTileManager!=null)␍␊ |
| 525 | ␉␉␉␉␉mTileManager.NewDataAvailable+=NewTileDataAvailable;␍␊ |
| 526 | ␉␉␉}␍␊ |
| 527 | ␉␉␉base.Dispose(disposing);␍␊ |
| 528 | ␉␉}␍␊ |
| 529 | ␍␊ |
| 530 | ␍␊ |
| 531 | ␉␉void Timer1Tick(object sender, EventArgs e)␍␊ |
| 532 | ␉␉{␍␊ |
| 533 | ␉␉␉if(mUpdateView)␍␊ |
| 534 | ␉␉␉{␍␊ |
| 535 | ␉␉␉␉Invalidate();␍␊ |
| 536 | ␉␉␉␉mUpdateView=false;␍␊ |
| 537 | ␉␉␉}␍␊ |
| 538 | ␉␉}␍␊ |
| 539 | ␍␊ |
| 540 | ␉␉private bool mMouseMoving=false;␍␊ |
| 541 | ␉␉private int mMouseMovingFromX;␍␊ |
| 542 | ␉␉private int mMouseMovingFromY;␍␊ |
| 543 | ␉␉private int mMouseMovingStartX;␍␊ |
| 544 | ␉␉private int mMouseMovingStartY;␍␊ |
| 545 | ␉␉␍␊ |
| 546 | ␉␉void MapControlMouseDown(object sender, System.Windows.Forms.MouseEventArgs e)␍␊ |
| 547 | ␉␉{␍␊ |
| 548 | ␉␉␉mMouseMoving=true;␍␊ |
| 549 | ␉␉␉mMouseMovingFromX=e.X;␍␊ |
| 550 | ␉␉␉mMouseMovingFromY=e.Y;␍␊ |
| 551 | ␉␉␉mMouseMovingStartX=e.X;␍␊ |
| 552 | ␉␉␉mMouseMovingStartY=e.Y;␍␊ |
| 553 | ␉␉}␍␊ |
| 554 | ␍␊ |
| 555 | ␉␉void MapControlMouseUp(object sender, System.Windows.Forms.MouseEventArgs e)␍␊ |
| 556 | ␉␉{␍␊ |
| 557 | ␉␉␉mMouseMoving=false;␍␊ |
| 558 | ␉␉␉if(e.X==mMouseMovingStartX && e.Y==mMouseMovingStartY)␍␊ |
| 559 | ␉␉␉{␍␊ |
| 560 | ␉␉␉␉if(LocationClicked!=null)␍␊ |
| 561 | ␉␉␉␉{␍␊ |
| 562 | ␉␉␉␉␉double lat,lon;␍␊ |
| 563 | ␉␉␉␉␉ClientPosToLatLon(e.X,e.Y,out lat,out lon);␍␊ |
| 564 | ␉␉␉␉␉LocationClicked(lat,lon);␍␊ |
| 565 | ␉␉␉␉}␍␊ |
| 566 | ␉␉␉}␍␊ |
| 567 | ␉␉}␍␊ |
| 568 | ␉␉␍␊ |
| 569 | ␉␉void MapControlMouseLeave(object sender, System.EventArgs e)␍␊ |
| 570 | ␉␉{␍␊ |
| 571 | ␉␉␉mMouseMoving=false;␍␊ |
| 572 | ␉␉}␍␊ |
| 573 | ␉␉␍␊ |
| 574 | ␉␉void MapControlMouseMove(object sender, System.Windows.Forms.MouseEventArgs e)␍␊ |
| 575 | ␉␉{␍␊ |
| 576 | ␉␉␉if(mMouseMoving)␍␊ |
| 577 | ␉␉␉{␍␊ |
| 578 | ␉␉␉␉int movedx=e.X-mMouseMovingFromX;␍␊ |
| 579 | ␉␉␉␉int movedy=e.Y-mMouseMovingFromY;␍␊ |
| 580 | ␉␉␉␉if(movedx!=0 || movedy!=0)␍␊ |
| 581 | ␉␉␉␉{␍␊ |
| 582 | ␉␉␉␉␉mLat+=mScale*movedy;␍␊ |
| 583 | ␉␉␉␉␉mLon-=mScale*movedx;␍␊ |
| 584 | ␉␉␉␉␉Invalidate();␍␊ |
| 585 | ␉␉␉␉␉RaiseViewChanged();␍␊ |
| 586 | ␉␉␉␉}␍␊ |
| 587 | ␉␉␉␉mMouseMovingFromX=e.X;␍␊ |
| 588 | ␉␉␉␉mMouseMovingFromY=e.Y;␍␊ |
| 589 | ␉␉␉}␍␊ |
| 590 | ␉␉}␍␊ |
| 591 | ␍␊ |
| 592 | ␉}␍␊ |
| 593 | }␍␊ |
| 594 | |