├── .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 | 
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 |
32 |
33 |
34 |
39 |
Tilecannon
40 |
45 |
46 |
47 |
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 |
66 |
67 |
68 |
73 |
Tilecannon
74 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
126 |
--------------------------------------------------------------------------------