├── .gitignore ├── Controllers └── TileController.cs ├── Global.asax ├── Global.asax.cs ├── LICENSE ├── Properties └── AssemblyInfo.cs ├── README.md ├── Tilecannon.csproj ├── Tilecannon.sln ├── Web.config ├── css └── Control.FullScreen.css ├── default.htm ├── icon-fullscreen.png ├── js └── Control.FullScreen.js ├── lib ├── ICSharpCode.SharpZipLib.dll ├── System.Data.SQLite.dll └── System.Web.Mvc.dll ├── nuget.config └── service.htm /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | -------------------------------------------------------------------------------- /Controllers/TileController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Data.SQLite; 4 | using System.IO; 5 | using System.Web.Mvc; 6 | using ICSharpCode.SharpZipLib.Zip.Compression.Streams; 7 | 8 | namespace Tilecannon.Controllers 9 | { 10 | public class TileController : Controller 11 | { 12 | 13 | public ActionResult Index() 14 | { 15 | return File(Server.MapPath("~/") + "default.htm", "text/html"); 16 | } 17 | 18 | public ActionResult ServiceInfo() 19 | { 20 | return File(Server.MapPath("~/") + "service.htm", "text/html"); 21 | } 22 | 23 | /// 24 | /// Just gets a list of available services 25 | /// hits the database just to get the 'proper' looking name of the service. 26 | /// 27 | /// 28 | public ActionResult GetCatalog() 29 | { 30 | var catalog = new List>(); 31 | 32 | foreach (var conn in tilecannon.SqLiteConnections) 33 | { 34 | 35 | var svc = new Dictionary(); 36 | using (var cmd = conn.Value.CreateCommand()) 37 | { 38 | svc["service"] = conn.Key; 39 | cmd.CommandText = "select value from metadata where name='name'"; 40 | using (var rdr = cmd.ExecuteReader()) 41 | { 42 | rdr.Read(); 43 | svc["serviceName"] = rdr.GetString(0); 44 | } 45 | } 46 | catalog.Add(svc); 47 | } 48 | 49 | var ESRIServices = Directory.GetDirectories(tilecannon.ESRIPATH); 50 | 51 | foreach(var dir in ESRIServices) 52 | { 53 | var svc = new Dictionary(); 54 | svc["service"] = Path.GetFileName(dir); 55 | svc["serviceName"] = Path.GetFileName(dir); 56 | catalog.Add(svc); 57 | } 58 | 59 | return Json(catalog, JsonRequestBehavior.AllowGet); 60 | } 61 | 62 | public ActionResult Tile(string service, int? x, int? y, byte? z) 63 | { 64 | var arr = Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALMw9IgAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjb9TgnoAAABmklEQVR4Xu3QIQEAAAyEwPUv/TO0gDN4bnINoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFryAdsDag8O8rylp3IAAAAASUVORK5CYII="); 65 | 66 | if (tilecannon.SqLiteConnections.ContainsKey(service)){ 67 | 68 | var file = GetTile(service, x, y, z); 69 | 70 | if(file == null){ 71 | 72 | return this.File(arr, "image/png", "image.png"); 73 | } else { 74 | return File(file,"image.png"); 75 | } 76 | 77 | } else { 78 | 79 | //a new site? 80 | 81 | var mbtiles = Directory.GetFiles(tilecannon.TILEPATH); 82 | 83 | foreach (var newfile in mbtiles){ 84 | 85 | if (Path.GetFileNameWithoutExtension(newfile) == service){ 86 | 87 | tilecannon.SqLiteConnections.Add(Path.GetFileNameWithoutExtension(newfile), new SQLiteConnection(@"Data Source="+newfile)); 88 | 89 | var tile = GetTile(service, x, y, z); 90 | 91 | if(tile == null){ 92 | return this.File(arr, "image/png", "image.png"); 93 | } else { 94 | return File(tile,"image.png"); 95 | } 96 | } 97 | } 98 | 99 | return this.File(arr, "image/png", "image.png"); 100 | 101 | } 102 | 103 | } 104 | 105 | private static byte[] GetTile(string service, int? x, int? y, byte? z) 106 | { 107 | //validate input vars 108 | if (x == null || y == null || z == null) 109 | { 110 | return new byte[] { 0 }; 111 | } 112 | 113 | y = IntPow(2, (byte)z) - 1 - y; 114 | 115 | var conn = tilecannon.SqLiteConnections[service]; 116 | 117 | using (var cmd = conn.CreateCommand()) 118 | { 119 | var command = String.Format("select tile_data as t from tiles where zoom_level={0} and tile_column={1} and tile_row={2}", z, x, y); 120 | cmd.CommandText = command; 121 | 122 | var tile = (byte[])cmd.ExecuteScalar(); 123 | 124 | if(tile != null){ 125 | return tile; 126 | } else { 127 | 128 | return Convert.FromBase64String("iVBORw0KGgoAAAANSUhEUgAAAQAAAAEACAMAAABrrFhUAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAMAUExURQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAALMw9IgAAAEAdFJOU////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wBT9wclAAAACXBIWXMAAA7DAAAOwwHHb6hkAAAAGHRFWHRTb2Z0d2FyZQBwYWludC5uZXQgNC4xLjb9TgnoAAABmklEQVR4Xu3QIQEAAAyEwPUv/TO0gDN4bnINoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFoNoFryAdsDag8O8rylp3IAAAAASUVORK5CYII="); 129 | 130 | } 131 | } 132 | } 133 | 134 | //Must allow cross-site json for IE - even if it's not cross-site. 135 | //many hours of my life died to bring us this information 136 | 137 | [AllowCrossSiteJson] 138 | public ActionResult Grid(string service, int? x, int? y, byte? z) 139 | { 140 | //check for callback 141 | //var c = Request.QueryString["callback"]; 142 | 143 | //if (c!="") //something about this conditional isn't perfect 144 | //{ 145 | // return Content(c+"("+GetGrid(service, x, y, z)+")", "application/json"); 146 | 147 | //} 148 | return Content(GetGrid(service, x, y, z), "application/json"); 149 | 150 | } 151 | 152 | private static string GetGrid(string service, int? x, int? y, byte? z) 153 | { 154 | //validate input vars 155 | if (x == null || y == null || z == null) 156 | { 157 | return "{}"; 158 | } 159 | 160 | y = IntPow(2, (byte)z) - 1 - y; 161 | 162 | var conn = tilecannon.SqLiteConnections[service]; 163 | 164 | using (var cmd = conn.CreateCommand()) 165 | { 166 | var command = String.Format("select grid as g from grids where zoom_level={0} and tile_column={1} and tile_row={2}", z, x, y); 167 | cmd.CommandText = command; 168 | 169 | var b = (byte[]) cmd.ExecuteScalar(); 170 | 171 | if(b.Length==0) 172 | { 173 | return "{}"; 174 | } 175 | 176 | var grid = Decompress(b); 177 | 178 | var g = System.Text.Encoding.UTF8.GetString(grid); 179 | 180 | g = g.Substring(0, g.Length - 1); 181 | g += ", \"data\":{"; 182 | 183 | var query = String.Format("SELECT key_name as key, key_json as json from grid_data where zoom_level={0} and tile_column={1} and tile_row={2}", z, x, y); 184 | 185 | using (var keycmd =new SQLiteCommand(query, conn)) 186 | { 187 | 188 | using (var rdr = keycmd.ExecuteReader()) 189 | { 190 | while(rdr.Read()) 191 | { 192 | g += "\""+ rdr.GetString(0) + "\":" + rdr.GetString(1) + ","; 193 | 194 | } 195 | } 196 | } 197 | 198 | g = g.Trim(',')+"}}"; 199 | return g; 200 | } 201 | } 202 | 203 | public ActionResult TileJson(string service) 204 | { 205 | var tilejson = new Dictionary(); 206 | 207 | tilejson["tilejson"] = "2.0.0"; 208 | tilejson["scheme"] = "xyz"; 209 | 210 | //Super haxxorz - uses Portland-ish bounds 211 | if (!tilecannon.SqLiteConnections.ContainsKey(service)) 212 | { 213 | tilejson["name"] = service; 214 | tilejson["tiles"] = new string[1] 215 | {"//" + tilecannon.SERVER + "/mbtiles/" + service + "/{z}/{x}/{y}.png"}; 216 | tilejson["minzoom"] = 0; 217 | tilejson["maxzoom"] = 19; 218 | tilejson["bounds"] = new double[4] 219 | {-123.125, 45.2921, -122.37, 45.6461}; 220 | 221 | return Json(tilejson, JsonRequestBehavior.AllowGet); 222 | } 223 | 224 | var conn = tilecannon.SqLiteConnections[service]; 225 | 226 | using (var metacmd = conn.CreateCommand()) 227 | { 228 | metacmd.CommandText = "select name, value from metadata"; 229 | using (var rdr = metacmd.ExecuteReader()) 230 | { 231 | while(rdr.Read()) 232 | { 233 | if(rdr.GetString(0)=="bounds") 234 | { 235 | var bounds = new double[4]; 236 | var x = rdr.GetString(1).Split(','); 237 | 238 | for(var i=0;i<4;i++) 239 | { 240 | bounds[i] = Convert.ToDouble(x[i]); 241 | } 242 | tilejson["bounds"] = bounds; 243 | } 244 | else if(rdr.GetString(0)=="center") 245 | { 246 | var cen = rdr.GetString(1).Split(','); 247 | var center = new double[3]; 248 | center[0] = Convert.ToDouble(cen[0]); 249 | center[1] = Convert.ToDouble(cen[1]); 250 | center[2] = Convert.ToInt16(cen[2]); 251 | 252 | tilejson["center"] = center; 253 | } 254 | else if(rdr.GetString(0)=="maxzoom") 255 | { 256 | tilejson["maxzoom"] = Convert.ToInt16(rdr.GetString(1)); 257 | } 258 | else if (rdr.GetString(0) == "minzoom") 259 | { 260 | tilejson["minzoom"] = Convert.ToInt16(rdr.GetString(1)); 261 | } 262 | else 263 | { 264 | tilejson[rdr.GetString(0)] = rdr.GetString(1); 265 | } 266 | } 267 | } 268 | } 269 | 270 | var tiles = new string[1]; 271 | tiles[0] = "//"+tilecannon.SERVER+"/mbtiles/" + service + "/{z}/{x}/{y}.png"; 272 | tilejson["tiles"] = tiles; 273 | 274 | //check for UTFgrids 275 | using (var metacmd = conn.CreateCommand()) 276 | { 277 | try 278 | { 279 | metacmd.CommandText = "select grid_id from grid_key LIMIT 1"; 280 | using (var rdr = metacmd.ExecuteReader()) 281 | { 282 | rdr.Read(); 283 | if (rdr.HasRows) 284 | { 285 | var grids = new string[1]; 286 | grids[0] = "//"+tilecannon.SERVER+"/mbtiles/" + service + "/{z}/{x}/{y}.json"; 287 | tilejson["grids"] = grids; 288 | } 289 | } 290 | } 291 | catch(SQLiteException sqlex) 292 | { 293 | 294 | } 295 | } 296 | 297 | return Json(tilejson, JsonRequestBehavior.AllowGet); 298 | 299 | } 300 | 301 | private static string Pad(string num, int zoom, string type) 302 | { 303 | var padding = ((zoom>17 && type=="R") || (zoom>18 && type=="C")) ? 5 : 4; 304 | 305 | while(num.Length 128) ? 128 : _qe; 319 | 320 | var bundle_filename_col = Convert.ToInt32(Math.Floor((double)x/_ne)*_ne); 321 | var bundle_filename_row = Convert.ToInt32(Math.Floor((double)y/_ne)*_ne); 322 | 323 | var filename=Pad(bundle_filename_row.ToString("X"),z, "R")+Pad(bundle_filename_col.ToString("X"),z,"C"); 324 | 325 | //arcgis bundled cache directory path 326 | var path = tilecannon.ESRIPATH; 327 | 328 | var bundlxFileName = path + service + @"\Layers\_alllayers\" + zoom + "/" + filename + ".Bundlx"; 329 | var bundleFileName = path + service+@"\Layers\_alllayers\" + zoom + "/" + filename + ".Bundle"; 330 | 331 | var col = x - bundle_filename_col; 332 | var row = y - bundle_filename_row; 333 | 334 | var index = 128 * (col - 0) + (row - 0); 335 | FileStream isBundlx; 336 | try{ 337 | isBundlx = new FileStream(bundlxFileName, FileMode.Open, FileAccess.Read, FileShare.Read); 338 | }catch(Exception ex){ 339 | return null; 340 | } 341 | 342 | isBundlx.Seek(16 + 5 * index, 0); 343 | 344 | var buffer = new byte[5]; 345 | isBundlx.Read(buffer, 0, buffer.Length); 346 | 347 | var offset = (buffer[0] & 0xff) + (long)(buffer[1] & 0xff) 348 | * 256 + (long)(buffer[2] & 0xff) * 65536 349 | + (long)(buffer[3] & 0xff) * 16777216 350 | + (buffer[4] & 0xff) * 4294967296L; 351 | 352 | var isBundle = new FileStream(bundleFileName, FileMode.Open, FileAccess.Read, FileShare.Read); 353 | 354 | isBundle.Seek(offset, 0); 355 | 356 | var lengthBytes = new byte[4]; 357 | isBundle.Read(lengthBytes, 0, lengthBytes.Length); 358 | 359 | var length = (lengthBytes[0] & 0xff) 360 | + (lengthBytes[1] & 0xff) * 256 361 | + (lengthBytes[2] & 0xff) * 65536 362 | + (lengthBytes[3] & 0xff) * 16777216; 363 | 364 | var result = new byte[length]; 365 | isBundle.Read(result, 0, result.Length); 366 | 367 | isBundle.Close(); 368 | isBundlx.Close(); 369 | 370 | return result; 371 | } 372 | 373 | private static int IntPow(int x, byte pow) 374 | { 375 | var ret = 1; 376 | while (pow != 0) 377 | { 378 | if ((pow & 1) == 1) 379 | ret *= x; 380 | x *= x; 381 | pow >>= 1; 382 | } 383 | return ret; 384 | } 385 | 386 | private static byte[] Decompress(byte[] zLibCompressedBuffer) 387 | { 388 | byte[] resBuffer = null; 389 | 390 | var mInStream = new MemoryStream(zLibCompressedBuffer); 391 | var mOutStream = new MemoryStream(zLibCompressedBuffer.Length); 392 | var infStream = new InflaterInputStream(mInStream); 393 | 394 | mInStream.Position = 0; 395 | 396 | try 397 | { 398 | var tmpBuffer = new byte[zLibCompressedBuffer.Length]; 399 | var read = 0; 400 | 401 | do 402 | { 403 | read = infStream.Read(tmpBuffer, 0, tmpBuffer.Length); 404 | if (read > 0) 405 | mOutStream.Write(tmpBuffer, 0, read); 406 | 407 | } while (read > 0); 408 | 409 | resBuffer = mOutStream.ToArray(); 410 | } 411 | finally 412 | { 413 | infStream.Close(); 414 | mInStream.Close(); 415 | mOutStream.Close(); 416 | } 417 | 418 | return resBuffer; 419 | } 420 | } 421 | 422 | public class AllowCrossSiteJsonAttribute : ActionFilterAttribute 423 | { 424 | public override void OnActionExecuting(ActionExecutingContext filterContext) 425 | { 426 | filterContext.RequestContext.HttpContext.Response.AddHeader("Access-Control-Allow-Origin", "*"); 427 | base.OnActionExecuting(filterContext); 428 | } 429 | } 430 | } -------------------------------------------------------------------------------- /Global.asax: -------------------------------------------------------------------------------- 1 | <%@ Application Codebehind="Global.asax.cs" Inherits="Tilecannon.tilecannon" Language="C#" %> 2 | -------------------------------------------------------------------------------- /Global.asax.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using System.Web; 4 | using System.Web.Mvc; 5 | using System.Web.Routing; 6 | using System.Data.SQLite; 7 | using System.Configuration; 8 | 9 | namespace Tilecannon 10 | { 11 | 12 | public class tilecannon : HttpApplication 13 | { 14 | 15 | public static string TILEPATH = ConfigurationManager.AppSettings["TILEPATH"];//"C:/mbtiles/"; 16 | public static string ESRIPATH = ConfigurationManager.AppSettings["ESRIPATH"];//@"\\alex\agfs\arcgisserver\directories\arcgiscache\"; 17 | public static string SERVER = ConfigurationManager.AppSettings["SERVER"];//"localhost"; 18 | 19 | public static Dictionary SqLiteConnections; 20 | 21 | public static void RegisterRoutes(RouteCollection routes) 22 | { 23 | routes.IgnoreRoute("{resource}.axd/{*pathInfo}"); 24 | 25 | routes.MapRoute( 26 | "Tiles", // Route name 27 | "{service}/{z}/{x}/{y}.png", // URL with parameters 28 | new { controller = "Tile", action = "Tile"} // Parameter defaults 29 | ); 30 | 31 | routes.MapRoute( 32 | "GetBundleTiles", // Route name 33 | "{service}/{z}/{x}/{y}.bung", // URL with parameters 34 | new { controller = "Tile", action = "GetBundleTile" } // Parameter defaults 35 | ); 36 | 37 | routes.MapRoute( 38 | "TileJson", // Route name 39 | "{service}/tilejson/", // URL with parameters 40 | new { controller = "Tile", action = "TileJson" } // Parameter defaults 41 | ); 42 | 43 | routes.MapRoute( 44 | "Grids", // Route name 45 | "{service}/{z}/{x}/{y}.json", // URL with parameters 46 | new { controller = "Tile", action = "Grid" } // Parameter defaults 47 | ); 48 | 49 | routes.MapRoute( 50 | "Default", // Route name 51 | "", // URL with parameters 52 | new { controller = "Tile", action = "Index" } // Parameter defaults 53 | ); 54 | 55 | routes.MapRoute( 56 | "IndexDataService", // Route name 57 | "GetCatalog", // URL with parameters 58 | new { controller = "Tile", action = "GetCatalog" } // Parameter defaults 59 | ); 60 | 61 | routes.MapRoute( 62 | "Service", // Route name 63 | "{service}/", // URL with parameters 64 | new { controller = "Tile", action = "ServiceInfo" } // Parameter defaults 65 | ); 66 | } 67 | 68 | protected void Application_Start() 69 | { 70 | //Mainline the connections 71 | //Oh yeah! 72 | 73 | SqLiteConnections = new Dictionary(); 74 | var mbtiles = Directory.GetFiles(TILEPATH); 75 | 76 | foreach (var file in mbtiles) 77 | { 78 | SqLiteConnections.Add(Path.GetFileNameWithoutExtension(file), new SQLiteConnection(@"Data Source="+file)); 79 | } 80 | 81 | foreach(var conn in SqLiteConnections.Values) 82 | { 83 | conn.Open(); 84 | } 85 | 86 | AreaRegistration.RegisterAllAreas(); 87 | 88 | RegisterRoutes(RouteTable.Routes); 89 | } 90 | 91 | protected void Application_End() 92 | { 93 | foreach (var conn in SqLiteConnections.Values) 94 | { 95 | conn.Close(); 96 | conn.Dispose(); 97 | } 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainsb/tilecannon/e5a87c6fc57f093cc8f90ea6fafa15b8c496896d/LICENSE -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("MvcApplication1")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("MvcApplication1")] 13 | [assembly: AssemblyCopyright("Copyright © 2013")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("f124d230-2ccb-4006-9bec-f4fdcf022ff5")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Revision and Build Numbers 33 | // by using the '*' as shown below: 34 | [assembly: AssemblyVersion("1.0.0.0")] 35 | [assembly: AssemblyFileVersion("1.0.0.0")] 36 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | tilecannon 2 | ========== 3 | 4 | ![alt tag](http://www.guerrillagis.net/tilecannon.png) 5 | 6 | An IIS (ASP.NET MVC) tileserver for mbtiles, UTFGrids and ESRI bundled cache 7 | 8 | * At my agency we do a lot of caching. 9 | * We write code in Python and C#.NET 10 | * We mainly use ArcGIS Server to create caches (lots of air photos) 11 | * We utilize bundled cache. 12 | * We run Windows web servers and IIS. 13 | * ArcGIS Server is mostly super overkill. 14 | * We like Tilemill, Mapbox, Mapnik, Carto CSS, UTFGrids etc. 15 | 16 | To this end, I've written this tileserver to be a swiss army knife for serving 17 | mbtiles, UTFGrids, and ESRI bundled cache. 18 | 19 | Todo 20 | ---- 21 | * Get some meta setup for tilejson requests for bundled cache. 22 | * support exploded cache without having to check on each tile 23 | * Scrub out non-Web Merc bundled cache services. 24 | * Support a different dataframe name (e.g most of the time it's "Layers", but not always) 25 | * Implement WMTS specification. 26 | 27 | FAQ 28 | --- 29 | 30 | * # How do I set this thing up? 31 | 32 | In the global.asax change the directory pointers to where you keep your mbtiles files, your bundled cache, and the name of your host. Then push the big green 'GO' button. 33 | -------------------------------------------------------------------------------- /Tilecannon.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | 7 | 8 | 2.0 9 | {73B10DD8-38BC-4018-A037-176B682A49FA} 10 | Library 11 | Properties 12 | Tilecannon 13 | Tilecannon 14 | v4.6.1 15 | false 16 | false 17 | 18 | 19 | 20 | 21 | 4.0 22 | 23 | publish\ 24 | true 25 | Disk 26 | false 27 | Foreground 28 | 7 29 | Days 30 | false 31 | false 32 | true 33 | 0 34 | 1.0.0.%2a 35 | false 36 | false 37 | true 38 | 39 | 40 | true 41 | full 42 | false 43 | bin\ 44 | DEBUG;TRACE 45 | prompt 46 | 4 47 | AnyCPU 48 | 49 | 50 | pdbonly 51 | true 52 | bin\ 53 | TRACE 54 | prompt 55 | 4 56 | x64 57 | 58 | 59 | 60 | lib\ICSharpCode.SharpZipLib.dll 61 | 62 | 63 | 64 | 65 | 66 | 67 | False 68 | lib\System.Data.SQLite.dll 69 | 70 | 71 | 72 | 73 | lib\System.Web.Mvc.dll 74 | 75 | 76 | 77 | 78 | 79 | 80 | Global.asax 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | Designer 93 | 94 | 95 | 96 | 97 | False 98 | Microsoft .NET Framework 4 %28x86 and x64%29 99 | true 100 | 101 | 102 | False 103 | .NET Framework 3.5 SP1 104 | false 105 | 106 | 107 | False 108 | Windows Installer 4.5 109 | true 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /Tilecannon.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tilecannon", "Tilecannon.csproj", "{73B10DD8-38BC-4018-A037-176B682A49FA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {73B10DD8-38BC-4018-A037-176B682A49FA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {73B10DD8-38BC-4018-A037-176B682A49FA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {73B10DD8-38BC-4018-A037-176B682A49FA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {73B10DD8-38BC-4018-A037-176B682A49FA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /Web.config: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /css/Control.FullScreen.css: -------------------------------------------------------------------------------- 1 | .leaflet-control-zoom-fullscreen { background-image: url(icon-fullscreen.png); } 2 | .leaflet-container:-webkit-full-screen { width: 100% !important; height: 100% !important; } 3 | -------------------------------------------------------------------------------- /default.htm: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Tilecannon 7 | 8 | 9 | 12 | 13 | 14 | 29 | 30 | 31 | 48 | 49 |
50 | 51 | 52 | 53 | 54 | 63 | -------------------------------------------------------------------------------- /icon-fullscreen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainsb/tilecannon/e5a87c6fc57f093cc8f90ea6fafa15b8c496896d/icon-fullscreen.png -------------------------------------------------------------------------------- /js/Control.FullScreen.js: -------------------------------------------------------------------------------- 1 | L.Control.FullScreen = L.Control.extend({ 2 | options: { 3 | position: 'topleft', 4 | title: 'Full Screen', 5 | forceSeparateButton: false 6 | }, 7 | 8 | onAdd: function (map) { 9 | // Do nothing if we can't 10 | if (!fullScreenApi.supportsFullScreen) 11 | return map.zoomControl ? map.zoomControl._container : L.DomUtil.create('div', ''); 12 | 13 | var className = 'leaflet-control-zoom-fullscreen', container; 14 | 15 | if(map.zoomControl && !this.options.forceSeparateButton) { 16 | container = map.zoomControl._container; 17 | } else { 18 | container = L.DomUtil.create('div', 'leaflet-bar'); 19 | } 20 | 21 | this._createButton(this.options.title, className, container, this.toogleFullScreen, map); 22 | 23 | return container; 24 | }, 25 | 26 | _createButton: function (title, className, container, fn, context) { 27 | var link = L.DomUtil.create('a', className, container); 28 | link.href = '#'; 29 | link.title = title; 30 | 31 | L.DomEvent 32 | .addListener(link, 'click', L.DomEvent.stopPropagation) 33 | .addListener(link, 'click', L.DomEvent.preventDefault) 34 | .addListener(link, 'click', fn, context); 35 | 36 | L.DomEvent 37 | .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation) 38 | .addListener(container, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault) 39 | .addListener(container, fullScreenApi.fullScreenEventName, this._handleEscKey, context); 40 | 41 | L.DomEvent 42 | .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.stopPropagation) 43 | .addListener(document, fullScreenApi.fullScreenEventName, L.DomEvent.preventDefault) 44 | .addListener(document, fullScreenApi.fullScreenEventName, this._handleEscKey, context); 45 | 46 | return link; 47 | }, 48 | 49 | toogleFullScreen: function () { 50 | this._exitFired = false; 51 | if (fullScreenApi.supportsFullScreen){ 52 | var container = this._container; 53 | if(fullScreenApi.isFullScreen(container)){ 54 | fullScreenApi.cancelFullScreen(container); 55 | this.invalidateSize(); 56 | this.fire('exitFullscreen'); 57 | this._exitFired = true; 58 | } 59 | else { 60 | fullScreenApi.requestFullScreen(container); 61 | this.invalidateSize(); 62 | this.fire('enterFullscreen'); 63 | } 64 | } 65 | }, 66 | 67 | _handleEscKey: function () { 68 | if(!fullScreenApi.isFullScreen(this) && !this._exitFired){ 69 | this.fire('exitFullscreen'); 70 | this._exitFired = true; 71 | } 72 | } 73 | }); 74 | 75 | L.Map.addInitHook(function () { 76 | if (this.options.fullscreenControl) { 77 | this.fullscreenControl = L.control.fullscreen(this.options.fullscreenControlOptions); 78 | this.addControl(this.fullscreenControl); 79 | } 80 | }); 81 | 82 | L.control.fullscreen = function (options) { 83 | return new L.Control.FullScreen(options); 84 | }; 85 | 86 | /* 87 | Native FullScreen JavaScript API 88 | ------------- 89 | Assumes Mozilla naming conventions instead of W3C for now 90 | 91 | source : http://johndyer.name/native-fullscreen-javascript-api-plus-jquery-plugin/ 92 | 93 | */ 94 | 95 | (function() { 96 | var 97 | fullScreenApi = { 98 | supportsFullScreen: false, 99 | isFullScreen: function() { return false; }, 100 | requestFullScreen: function() {}, 101 | cancelFullScreen: function() {}, 102 | fullScreenEventName: '', 103 | prefix: '' 104 | }, 105 | browserPrefixes = 'webkit moz o ms khtml'.split(' '); 106 | 107 | // check for native support 108 | if (typeof document.exitFullscreen != 'undefined') { 109 | fullScreenApi.supportsFullScreen = true; 110 | } else { 111 | // check for fullscreen support by vendor prefix 112 | for (var i = 0, il = browserPrefixes.length; i < il; i++ ) { 113 | fullScreenApi.prefix = browserPrefixes[i]; 114 | 115 | if (typeof document[fullScreenApi.prefix + 'CancelFullScreen' ] != 'undefined' ) { 116 | fullScreenApi.supportsFullScreen = true; 117 | 118 | break; 119 | } 120 | } 121 | } 122 | 123 | // update methods to do something useful 124 | if (fullScreenApi.supportsFullScreen) { 125 | fullScreenApi.fullScreenEventName = fullScreenApi.prefix + 'fullscreenchange'; 126 | 127 | fullScreenApi.isFullScreen = function() { 128 | switch (this.prefix) { 129 | case '': 130 | return document.fullScreen; 131 | case 'webkit': 132 | return document.webkitIsFullScreen; 133 | default: 134 | return document[this.prefix + 'FullScreen']; 135 | } 136 | } 137 | fullScreenApi.requestFullScreen = function(el) { 138 | return (this.prefix === '') ? el.requestFullscreen() : el[this.prefix + 'RequestFullScreen'](); 139 | } 140 | fullScreenApi.cancelFullScreen = function(el) { 141 | return (this.prefix === '') ? document.exitFullscreen() : document[this.prefix + 'CancelFullScreen'](); 142 | } 143 | } 144 | 145 | // jQuery plugin 146 | if (typeof jQuery != 'undefined') { 147 | jQuery.fn.requestFullScreen = function() { 148 | 149 | return this.each(function() { 150 | var el = jQuery(this); 151 | if (fullScreenApi.supportsFullScreen) { 152 | fullScreenApi.requestFullScreen(el); 153 | } 154 | }); 155 | }; 156 | } 157 | 158 | // export api 159 | window.fullScreenApi = fullScreenApi; 160 | })(); 161 | -------------------------------------------------------------------------------- /lib/ICSharpCode.SharpZipLib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainsb/tilecannon/e5a87c6fc57f093cc8f90ea6fafa15b8c496896d/lib/ICSharpCode.SharpZipLib.dll -------------------------------------------------------------------------------- /lib/System.Data.SQLite.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainsb/tilecannon/e5a87c6fc57f093cc8f90ea6fafa15b8c496896d/lib/System.Data.SQLite.dll -------------------------------------------------------------------------------- /lib/System.Web.Mvc.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sainsb/tilecannon/e5a87c6fc57f093cc8f90ea6fafa15b8c496896d/lib/System.Web.Mvc.dll -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /service.htm: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Tilecannon 7 | 8 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 49 | 50 | 62 | 63 | 64 | 65 | 82 | 83 |
84 | 85 | 86 | 87 | 88 | 89 | 126 | --------------------------------------------------------------------------------