· 8 years ago · Dec 28, 2017, 07:16 PM
1
2// ==UserScript==
3// @id ingress-intel-total-conversion@jonatkins
4// @name IITC: Ingress intel map total conversion
5// @version 0.26.0.20170210.164403
6// @namespace https://github.com/jonatkins/ingress-intel-total-conversion
7// @updateURL https://raw.githubusercontent.com/ifchen0/IITC_TW/master/build/Release/total-conversion-build.meta.js
8// @downloadURL https://raw.githubusercontent.com/ifchen0/IITC_TW/master/build/Release/total-conversion-build.user.js
9// @description [Release-2017-02-10-164403] Total conversion for the ingress intel map.
10// @include https://*.ingress.com/intel*
11// @include http://*.ingress.com/intel*
12// @match https://*.ingress.com/intel*
13// @match http://*.ingress.com/intel*
14// @include https://*.ingress.com/mission/*
15// @include http://*.ingress.com/mission/*
16// @match https://*.ingress.com/mission/*
17// @match http://*.ingress.com/mission/*
18// @grant none
19// ==/UserScript==
20
21
22// REPLACE ORIG SITE ///////////////////////////////////////////////////
23if(document.getElementsByTagName('html')[0].getAttribute('itemscope') != null)
24 throw('Ingress Intel 網站關閉了, 䏿˜¯ IITC userscript çš„å•題.');
25window.iitcBuildDate = '2017-02-10-164403';
26
27// disable vanilla JS
28window.onload = function() {};
29document.body.onload = function() {};
30
31
32//originally code here parsed the <Script> tags from the page to find the one that defined the PLAYER object
33//however, that's already been executed, so we can just access PLAYER - no messing around needed!
34
35if (typeof(window.PLAYER)!="object" || typeof(window.PLAYER.nickname) != "string") {
36 // page doesn’t have a script tag with player information.
37 if(document.getElementById('header_email')) {
38 // however, we are logged in.
39 // it used to be regularly common to get temporary 'account not enabled' messages from the intel site.
40 // however, this is no longer common. more common is users getting account suspended/banned - and this
41 // currently shows the 'not enabled' message. so it's safer to not repeatedly reload in this case
42// setTimeout('location.reload();', 3*1000);
43 throw("您已經登入, 但é 颿‰¾ä¸åˆ°çŽ©å®¶è³‡æ–™.");
44 }
45 // FIXME: handle nia takedown in progress
46 throw("無法接收玩家資料. 您有登入了嗎?");
47}
48
49
50// player information is now available in a hash like this:
51// window.PLAYER = {"ap": "123", "energy": 123, "available_invites": 123, "nickname": "somenick", "team": "ENLIGHTENED||RESISTANCE"};
52
53// remove complete page. We only wanted the user-data and the page’s
54// security context so we can access the API easily. Setup as much as
55// possible without requiring scripts.
56document.getElementsByTagName('head')[0].innerHTML = ''
57 + '<title>Ingress Intel 地圖</title>'
58 + '<style>/* general rules ******************************************************/\n\n/* for printing directly from the browser, hide all UI components\n * NOTE: @media needs to be first?\n */\n@media print {\n .leaflet-control-container { display: none !important; }\n #chatcontrols, #chat, #chatinput { display: none !important; }\n #sidebartoggle, #sidebar { display: none !important; }\n #updatestatus { display: none !important; }\n #portal_highlight_select { display: none !important; }\n}\n\n\nhtml, body, #map {\n height: 100%;\n width: 100%;\n overflow: hidden; /* workaround for #373 */\n background: #dddddd;\n}\n\n\nbody {\n font-size: 14px;\n font-family: "Roboto", "Helvetica Neue", Helvetica, sans-serif;\n margin: 0;\n}\n\n#scrollwrapper {\n overflow-x: hidden;\n overflow-y: auto;\n position: fixed;\n right: -38px;\n top: 0;\n width: 340px;\n bottom: 45px;\n z-index: 1001;\n pointer-events: none;\n}\n\n#sidebar {\n background-color: rgba(8, 48, 78, 0.7);\n border-left: 1px solid #00FFFF;\n color: #888;\n position: relative;\n left: 0;\n top: 0;\n max-height: 100%;\n overflow-y:scroll;\n overflow-x:hidden;\n z-index: 3000;\n pointer-events: auto;\n}\n\n#sidebartoggle {\n display: block;\n padding: 8px 5px; /* iF: Make it smaller. */\n margin-top: -31px; /* -(toggle height / 2) */\n line-height: 10px;\n position: absolute;\n top: 108px;\n z-index: 3001;\n background-color: rgba(8, 48, 78, 0.7);\n color: #00FFFF;\n border: 1px solid #00FFFF;\n border-right: none;\n /*border-radius: 5px 0 0 5px;*/ /* iF: Square looks better. */\n text-decoration: none;\n right: -50px; /* overwritten later by the script with SIDEBAR_WIDTH */\n}\n\n.enl {\n color: #03fe03 !important;\n}\n\n.res {\n color: #00c5ff !important;\n}\n\n.none {\n color: #fff;\n}\n\n.nickname {\n cursor: pointer !important;\n}\n\na {\n color: #00FFFF;\n cursor: pointer;\n text-decoration: none;\n}\n\na:hover {\n text-decoration: underline;\n}\n\n/* map display, required because GMaps uses a high z-index which is\n * normally above Leaflet’s vector pane */\n.leaflet-map-pane {\n z-index: 1000;\n}\n\n/* leaflet layer chooser, when opened, is above other panels */\n/* doesn\'t actually work :( - left commented out for reference\n.leaflet-control-layers-expanded {\n z-index: 9999 !important;\n}\n*/\n\n\n.leaflet-control-layers-overlays label.disabled {\n text-decoration: line-through;\n cursor: help;\n}\n\n\n\n/* base layer selection - first column */\n.leaflet-control-layers-base {\n float: left;\n overflow-y: auto;\n max-height: 600px;\n}\n\n/* overlays layer selection - 2nd column */\n.leaflet-control-layers-overlays {\n float: left;\n margin-left: 8px;\n border-left: 1px solid #DDDDDD;\n padding-left: 8px;\n overflow-y: auto;\n max-height: 600px;\n}\n\n/* hide the usual separator */\n.leaflet-control-layers-separator {\n display: none;\n}\n\n\n\n.help {\n cursor: help;\n}\n\n.toggle {\n display: block;\n height: 0;\n width: 0;\n}\n\n/* field mu count */\n.fieldmu {\n color: #00FFFF;\n font-size: 13px;\n font-family: Roboto, "Helvetica Neue", Helvetica, sans-serif; /*override leaflet-container */\n text-align: center;\n text-shadow: 0 0 0.2em black, 0 0 0.2em black, 0 0 0.2em black;\n pointer-events: none;\n}\n\n\n/* chat ***************************************************************/\n\n#chatcontrols {\n color: #00FFFF;\n background: rgba(8, 48, 78, 0.7);\n position: absolute;\n left: 0;\n z-index: 3000;\n height: 26px;\n padding-left:1px;\n}\n\n#chatcontrols.expand {\n top: 0;\n bottom: auto;\n}\n\n#chatcontrols a {\n margin-left: -1px;\n display: inline-block;\n width: 94px;\n text-align: center;\n height: 24px;\n line-height: 24px;\n border: 1px solid #00FFFF;\n vertical-align: top;\n}\n\n#chatcontrols a:first-child {\n letter-spacing:-1px;\n text-decoration: none !important;\n}\n\n#chatcontrols a.active {\n border-color: #00FFFF;\n border-bottom-width:0px;\n font-weight:bold;\n background: rgb(8, 48, 78);\n}\n\n#chatcontrols a.active + a {\n border-left-color: #00FFFF\n}\n\n\n#chatcontrols .toggle {\n border-left: 10px solid transparent;\n border-right: 10px solid transparent;\n margin: 6px auto auto;\n}\n\n#chatcontrols .expand {\n border-bottom: 10px solid #00FFFF;\n}\n\n#chatcontrols .shrink {\n border-top: 10px solid #00FFFF;\n}\n\n#chatcontrols .loading {\n background-color: rgba(255,0,0,0.3);\n -webkit-animation: chatloading 1.2s infinite linear;\n -moz-animation: chatloading 1.2s infinite linear;\n animation: chatloading 1.2s infinite linear;\n}\n\n@-webkit-keyframes chatloading {\n 0% { background-color: rgba(255,0,0,0.4) }\n 50% { background-color: rgba(255,0,0,0.1) }\n 100% { background-color: rgba(255,0,0,0.4) }\n}\n\n@-moz-keyframes chatloading {\n 0% { background-color: rgba(255,0,0,0.4) }\n 50% { background-color: rgba(255,0,0,0.1) }\n 100% { background-color: rgba(255,0,0,0.4) }\n}\n\n@keyframes chatloading {\n 0% { background-color: rgba(255,0,0,0.4) }\n 50% { background-color: rgba(255,0,0,0.1) }\n 100% { background-color: rgba(255,0,0,0.4) }\n}\n\n\n\n#chat {\n position: absolute;\n width: 708px;\n bottom: 23px;\n left: 0;\n z-index: 3000;\n background: rgba(8, 48, 78, 0.7);\n line-height: 15px;\n color: #eee;\n border: 1px solid #00FFFF;\n border-bottom: 0;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\nem {\n color: red;\n font-style: normal;\n}\n\n#chat.expand {\n height:auto;\n top: 25px;\n}\n\n\n#chat > div {\n overflow-x:hidden;\n overflow-y:scroll;\n height: 100%;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n padding: 2px;\n position:relative;\n}\n\n#chat table, #chatinput table {\n width: 100%;\n table-layout: fixed;\n border-spacing: 0;\n border-collapse: collapse;\n}\n\n#chatinput table {\n height: 100%;\n}\n\n#chat td, #chatinput td {\n font-size: 13px;\n vertical-align: top;\n padding-bottom: 3px;\n}\n\n/* time */\n#chat td:first-child, #chatinput td:first-child {\n width: 44px;\n overflow: hidden;\n padding-left: 2px;\n color: #bbb;\n white-space: nowrap;\n}\n\n#chat time {\n cursor: help;\n}\n\n/* nick */\n#chat td:nth-child(2), #chatinput td:nth-child(2) {\n width: 91px;\n overflow: hidden;\n padding-left: 2px;\n white-space: nowrap;\n}\n\n#chat td .system_narrowcast {\n color: #f66 !important;\n}\n\nmark {\n background: transparent;\n}\n\n.invisep {\n display: inline-block;\n width: 1px;\n height: 1px;\n overflow:hidden;\n color: transparent;\n}\n\n/* divider */\nsummary {\n color: #bbb;\n display: inline-block;\n height: 16px;\n overflow: hidden;\n padding: 0 2px;\n white-space: nowrap;\n width: 100%;\n}\n\n#chatinput {\n position: absolute;\n bottom: 0;\n left: 0;\n padding: 0 2px;\n background: rgba(8, 48, 78, 0.7);\n width: 708px;\n height: 23px;\n border: 1px solid #00FFFF;\n z-index: 3001;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n#chatinput td {\n padding-bottom: 1px;\n vertical-align: middle;\n}\n\n\n#chatinput input {\n background: transparent;\n color: #EEEEEE;\n width: 100%;\n height: 100%;\n padding:3px 4px 1px 4px;\n}\n\n\n\n/* sidebar ************************************************************/\n\n#sidebar > * {\n border-bottom: 1px solid #00FFFF;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n\n\n#sidebartoggle .toggle {\n border-bottom: 10px solid transparent;\n border-top: 10px solid transparent;\n}\n\n#sidebartoggle .open {\n border-right: 10px solid #00FFFF;\n}\n\n#sidebartoggle .close {\n border-left: 10px solid #00FFFF;\n}\n\n/* player stats */\n#playerstat {\n height: 30px;\n}\n\nh2 {\n color: #00FFFF;\n font-size: 21px;\n padding: 0 4px;\n margin: 0;\n cursor:help;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n width: 100%;\n}\n\nh2 #name {\n font-weight: 300;\n display: inline-block;\n overflow: hidden;\n text-overflow: ellipsis;\n vertical-align: top;\n white-space: nowrap;\n width: 205px;\n position: relative;\n}\n\nh2 #stats {\n float: right;\n height: 100%;\n overflow: hidden;\n}\n\n#signout {\n font-size: 12px;\n font-weight: normal;\n line-height: 29px;\n padding: 0 4px;\n position: absolute;\n top: 0;\n right: 0;\n background-color: rgba(8, 48, 78, 0.5);\n display: none; /* starts hidden */\n}\n#name:hover #signout {\n display: block;\n}\n\nh2 sup, h2 sub {\n display: block;\n font-size: 11px;\n margin-bottom: -2px;\n}\n\n\n/* gamestats */\n#gamestat {\n height: 22px;\n}\n\n#gamestat span {\n display: block;\n float: left;\n font-weight: bold;\n cursor:help;\n height: 21px;\n line-height: 22px;\n}\n\n#gamestat .res {\n background: #005684;\n text-align: right;\n}\n\n#gamestat .enl {\n background: #017f01;\n}\n\n\n/* search input, and others */\ninput:not([type]), .input,\ninput[type="text"], input[type="password"],\ninput[type="number"], input[type="email"],\ninput[type="search"], input[type="url"] {\n background-color: rgba(0, 0, 0, 0.3);\n color: #00FFFF;\n height: 24px;\n padding:0px 4px 0px 4px;\n font-size: 12px;\n border:0;\n font-family:inherit;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n#searchwrapper {\n position: relative;\n}\n#search {\n width: 100%;\n padding-right: 24px;\n}\n#buttongeolocation {\n position: absolute;\n right: 0;\n top: 0;\n margin: 0;\n border: 0 none transparent;\n padding: 0 2px 0 0;\n height: 24px;\n background-color: transparent;\n}\n#buttongeolocation:focus {\n outline: 1px dotted #ffce00;\n}\n#buttongeolocation img {\n display: block;\n}\n#searchwrapper h3 {\n font-size: 1em;\n height: auto;\n cursor: pointer;\n}\n.searchquery {\n max-height: 25em;\n overflow-y: auto;\n}\n#searchwrapper .ui-accordion-header::before {\n font-size: 18px;\n margin-right: 2px;\n font-weight: normal;\n line-height: 1em;\n content: "⊞";\n}\n#searchwrapper .ui-accordion-header-active::before {\n content: "⊟";\n}\n#searchwrapper .ui-accordion-content {\n margin: 0;\n overflow: hidden;\n}\n#searchwrapper ul {\n padding-left: 14px;\n}\n#searchwrapper li {\n cursor: pointer;\n}\n#searchwrapper li a {\n margin-left: -14px;\n padding-left: 14px;\n background-position: 1px center;\n background-repeat: no-repeat;\n background-size: 12px 12px;\n}\n#searchwrapper li:focus a, #searchwrapper li:hover a {\n text-decoration: underline;\n}\n#searchwrapper li em {\n color: #ccc;\n font-size: 0.9em;\n}\n\n::-webkit-input-placeholder {\n font-style: italic;\n}\n\n:-moz-placeholder {\n font-style: italic;\n}\n\n::-moz-placeholder {\n font-style: italic;\n}\n\n.leaflet-control-layers input {\n height: auto;\n padding: 0;\n}\n\n\n/* portal title and image */\nh3 {\n font-size: 16px;\n padding: 0 4px;\n margin:0;\n height: 23px;\n width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n.imgpreview {\n height: 190px;\n background: no-repeat center center;\n background-size: contain;\n cursor: help;\n overflow: hidden;\n position: relative;\n}\n\n.imgpreview img.hide {\n display: none;\n}\n\n.imgpreview .portalDetails {\n display: none;\n}\n\n#level {\n font-size: 40px;\n text-shadow: -1px -1px #000, 1px -1px #000, -1px 1px #000, 1px 1px #000, 0 0 5px #fff;\n display: block;\n margin-right: 15px;\n text-align:right;\n float: right;\n}\n\n/* portal mods */\n.mods {\n margin: 3px auto 1px auto;\n width: 296px;\n height: 67px;\n text-align: center;\n}\n\n.mods span {\n background-color: rgba(0, 0, 0, 0.3);\n /* can’t use inline-block because Webkit\'s implementation is buggy and\n * introduces additional margins in random cases. No clear necessary,\n * as that’s solved by setting height on .mods. */\n display: block;\n float:left;\n height: 63px;\n margin: 0 2px;\n overflow: hidden;\n padding: 2px;\n text-align: center;\n width: 63px;\n cursor:help;\n border: 1px solid #666;\n}\n\n.mods span:not([title]) {\n cursor: auto;\n}\n\n.res .mods span, .res .meter {\n border: 1px solid #0076b6;\n}\n.enl .mods span, .enl .meter {\n border: 1px solid #017f01;\n}\n\n/* random details, resonator details */\n#randdetails, #resodetails {\n width: 100%;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n padding: 0 4px;\n table-layout: fixed;\n border-spacing: 0m;\n border-collapse: collapse;\n}\n\n#randdetails td, #resodetails td {\n overflow: hidden;\n text-overflow: ellipsis;\n vertical-align: top;\n white-space: nowrap;\n width: 50%;\n}\n\n#randdetails th, #resodetails th {\n font-weight: normal;\n text-align: right;\n width: 62px;\n padding:0px;\n padding-right:4px;\n padding-left:4px;\n}\n\n#randdetails th + th, #resodetails th + th {\n text-align: left;\n padding-right: 4px;\n padding-left: 4px;\n}\n\n#randdetails td:first-child, #resodetails td:first-child {\n text-align: right;\n padding-left: 2px;\n}\n\n#randdetails td:last-child, #resodetails td:last-child {\n text-align: left;\n padding-right: 2px;\n}\n\n\n#randdetails {\n margin-top: 4px;\n margin-bottom: 5px;\n}\n\n\n#randdetails tt {\n font-family: inherit;\n cursor: help;\n}\n\n#artifact_target, #artifact_fragments {\n margin-top: 4px;\n margin-bottom: 4px;\n\n margin-left: 8px;\n margin-right: 8px;\n}\n\n\n/* resonators */\n#resodetails {\n margin-bottom: 0px;\n}\n\n.meter {\n background: #000;\n cursor: help;\n display: inline-block;\n height: 18px;\n padding: 1px;\n width: 100%;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n position: relative;\n left: 0;\n top: 0;\n}\n\n.meter.north {\n overflow: hidden;\n}\n.meter.north:before {\n content: "";\n background-color: red;\n border: 1px solid #000000;\n border-radius: 100%;\n display: block;\n height: 6px;\n width: 6px;\n left: 50%;\n top: -3px;\n margin-left: -4px;\n position: absolute;\n}\n\n.meter span {\n display: block;\n height: 14px;\n}\n\n.meter-level {\n position: absolute;\n left: 0;\n right: 0;\n top: 0; /*iF: Center is 0 not -2*/\n text-shadow: 0.0em 0.0em 0.3em #808080;\n text-align: center;\n word-spacing: 4px; /* to leave some space for the north indicator */\n}\n\n/* links below resos */\n\n.linkdetails {\n margin-bottom: 0px;\n text-align: center;\n}\n\n.linkdetails aside {\n display: inline-block;\n white-space: nowrap;\n margin-left: 5px;\n margin-right: 5px;\n}\n\n#toolbox {\n text-align: left; /* centre didn\'t look as nice here as it did above in .linkdetails */\n}\n\n#toolbox > a {\n margin-left: 5px;\n margin-right: 5px;\n white-space: nowrap;\n display: inline-block;\n}\n\n/* a common portal display takes this much space (prevents moving\n * content when first selecting a portal) */\n\n#portaldetails {\n min-height: 63px;\n position: relative; /* so the below \'#portaldetails .close\' is relative to this */\n}\n\n#portaldetails .close {\n position: absolute;\n top: -2px;\n right: 2px;\n cursor: pointer;\n color: #00FFFF;\n font-size: 16px;\n}\n\n/* update status */\n#updatestatus {\n background-color: rgba(8, 48, 78, 0.7);\n border-bottom: 0;\n border-top: 1px solid #00FFFF;\n border-left: 1px solid #00FFFF;\n bottom: 0;\n color: #00FFFF;\n font-size:13px;\n padding: 4px;\n position: fixed;\n right: 0;\n z-index: 3002;\n -webkit-box-sizing: border-box;\n -moz-box-sizing: border-box;\n box-sizing: border-box;\n}\n\n#updatestatus .map {\n margin-left: 8px;\n}\n\n#loadlevel {\n /*background: #FFF;*/ /*iF:Remove duplicate background for transparent*/\n color: #00FFFF;\n display: inline-block;\n min-width: 1.8em;\n border: 1px solid #00FFFF;\n border-width: 0 1px;\n margin: -4px 0;\n padding: 4px 0.2em;\n}\n\n/* Dialogs\n */\n.ui-tooltip, .ui-dialog {\n position: absolute;\n z-index: 9999;\n background-color: rgba(8, 48, 78, 0.7);\n border: 1px solid #00FFFF;\n color: #eee;\n font-size: 13px;\n line-height: 15px;\n padding: 2px 4px;\n}\n\n.ui-tooltip {\n max-width: 300px;\n}\n\n.ui-widget-overlay {\n height: 100%;\n left: 0;\n position: fixed;\n top: 0;\n width: 100%;\n z-index: 10000;\n background: #444;\n opacity: 0.6;\n}\n\n.ui-modal {\n z-index: 10001 !important;\n}\n\n.ui-tooltip {\n z-index: 10002 !important;\n}\n\n.ui-tooltip, .ui-dialog a {\n color: #00FFFF;\n}\n\n.ui-dialog {\n padding: 0;\n border-radius: 2px;\n}\n\n.ui-dialog-modal .ui-dialog-titlebar-close {\n display: none;\n}\n\n.ui-dialog-titlebar {\n font-size: 13px;\n line-height: 15px;\n text-align: center;\n padding: 4px;\n background-color: rgba(8, 60, 78, 0.9);\n min-width: 250px;\n}\n\n.ui-dialog-title {\n padding: 2px;\n font-weight: bold;\n}\n\n.ui-dialog-title-active {\n color: #00FFFF;\n}\n\n.ui-dialog-title-inactive {\n color: #ffffff;\n}\n\n.ui-dialog-titlebar-button {\n position: absolute;\n display: table-cell;\n vertical-align: middle;\n text-align: center;\n width: 17px;\n height: 17px;\n top: 3px;\n cursor: pointer;\n border: 1px solid rgb(32, 168, 177);\n background-color: rgba(0, 0, 0, 0);\n padding: 0;\n}\n\n.ui-dialog-titlebar-button:active {\n background-color: rgb(32, 168, 177);\n}\n\n.ui-dialog-titlebar-button-close {\n right: 4px;\n}\n\n.ui-dialog-titlebar-button-collapse {\n right: 25px;\n}\n\n.ui-dialog-titlebar-button-collapse-expanded {\n /* For future changes */\n}\n\n.ui-dialog-titlebar-button-collapse-collapsed {\n background-color: rgb(32, 168, 177);\n}\n\n.ui-dialog-titlebar-button-collapse::after,\n.ui-dialog-titlebar-button-close::after,\n.ui-dialog-titlebar-button-close::before {\n content: "";\n position: absolute;\n top: 3px;\n left: 50%;\n width: 11px;\n margin-left: -6px;\n height: 0;\n border-top: 2px solid rgb(32, 168, 177);\n}\n.ui-dialog-titlebar-button-close::after {\n transform: translateY(3.5px) rotate(45deg);\n -webkit-transform: translateY(3.5px) rotate(45deg);\n}\n.ui-dialog-titlebar-button-close::before {\n transform: translateY(3.5px) rotate(-45deg);\n -webkit-transform: translateY(3.5px) rotate(-45deg);\n}\n.ui-dialog-titlebar-button.ui-state-active::after,\n.ui-dialog-titlebar-button.ui-state-active::before,\n.ui-dialog-titlebar-button.ui-dialog-titlebar-button-collapse-collapsed::after,\n.ui-dialog-titlebar-button.ui-dialog-titlebar-button-collapse-collapsed::before,\n.ui-dialog-titlebar-button:active::after,\n.ui-dialog-titlebar-button:active::before {\n border-top-color: rgba(8, 60, 78, 0.9);\n}\n\n.ui-dialog-content {\n padding: 12px;\n overflow-y: auto;\n overflow-x: hidden;\n max-height: 600px !important;\n max-width: 700px !important;\n position: relative;\n}\n\n.ui-dialog-content-hidden {\n display: none !important;\n}\n\n.ui-dialog-buttonpane {\n padding: 6px;\n border-top: 1px solid #00FFFF;\n}\n\n.ui-dialog-buttonset {\n text-align: right;\n}\n\n.ui-dialog-buttonset button,\n.ui-dialog-content button {\n padding: 2px;\n min-width: 40px;\n color: #00FFFF;\n border: 1px solid #00FFFF;\n background-color: rgba(8, 48, 78, 0.7);\n}\n\n.ui-dialog-buttonset button:hover {\n text-decoration: underline;\n}\n\n.ui-dialog-aboutIITC {\n width: auto !important;\n min-width: 400px !important;\n max-width: 600px !important;\n}\n\ntd {\n padding: 0;\n vertical-align: top;\n}\n\ntd + td {\n padding-left: 4px;\n}\n\n#qrcode > canvas {\n border: 8px solid white;\n}\n\n/* redeem results *****************************************************/\n.redeemReward {\n font-family: Inconsolata, Consolas, Menlo, "Courier New", monospace;\n list-style-type: none;\n padding: 0;\n font-size: 14px;\n}\n.redeemReward .itemlevel {\n font-weight: bold;\n text-shadow: 0 0 1px #000; /* L8 is hard to read on blue background */\n}\n/*\n.redeem-result-table {\n font-size: 14px;\n table-layout: fixed;\n}\n\n.redeem-result tr > td:first-child {\n width: 50px;\n text-align: right;\n}\n\n.redeem-result-html {\n font-family: Inconsolata, Consolas, Menlo, "Courier New", monospace;\n}\n*/\n\n.pl_nudge_date {\n background-color: #724510;\n border-left: 1px solid #ffd652;\n border-bottom: 1px solid #ffd652;\n border-top: 1px solid #ffd652;\n color: #ffd652;\n display: inline-block;\n float: left;\n height: 18px;\n text-align: center;\n}\n\n.pl_nudge_pointy_spacer {\n background: no-repeat url(//commondatastorage.googleapis.com/ingress.com/img/nudge_pointy.png);\n display: inline-block;\n float: left;\n height: 20px;\n left: 47px;\n width: 5px;\n}\n\n.pl_nudge_player {\n cursor: pointer;\n}\n\n.pl_nudge_me {\n color: #ffd652;\n}\n\n.RESISTANCE {\n color: #00c2ff;\n}\n\n.ALIENS, .ENLIGHTENED {\n color: #28f428;\n}\n\n#portal_highlight_select {\n position: absolute;\n top:5px;\n left:10px;\n z-index: 2500;\n font-size:11px;\n background-color: rgba(8, 48, 78, 0.7);\n border: 1px solid #00FFFF;\n color:#00FFFF;\n height: 20px\n}\n\n\n\n.portal_details th, .portal_details td {\n vertical-align: top;\n text-align: left;\n}\n\n.portal_details th {\n white-space: nowrap;\n padding-right: 1em;\n}\n\n.portal_details tr.padding-top th, .portal_details tr.padding-top td {\n padding-top: 0.7em;\n}\n\n#play_button {\n display: none;\n}\n\n\n/** artifact dialog *****************/\ntable.artifact tr > * {\n background: rgba(8, 48, 78, 0.7);\n}\n\ntable.artifact td.info {\n min-width: 110px; /* min-width for info column, to ensure really long portal names don\'t crowd things out */\n}\n\ntable.artifact .portal {\n min-width: 200px; /* min-width for portal names, to ensure really long lists of artifacts don\'t crowd names out */\n}\n\n\n/* leaflet popups - restyle to match the theme of IITC */\n#map .leaflet-popup {\n pointer-events: none;\n}\n\n#map .leaflet-popup-content-wrapper {\n border-radius: 0px;\n -webkit-border-radius: 0px;\n border: 1px solid #00FFFF;\n background: rgba(8, 48, 78, 0.7);\n pointer-events: auto;\n}\n\n#map .leaflet-popup-content {\n color: #00FFFF;\n margin: 5px 8px;\n}\n\n#map .leaflet-popup-close-button {\n padding: 2px 1px 0 0;\n font-size: 12px;\n line-height: 8px;\n width: 10px;\n height: 10px;\n pointer-events: auto;\n}\n\n\n#map .leaflet-popup-tip {\n /* change the tip from an arrow to a simple line */\n background: #00FFFF;\n width: 1px;\n height: 20px;\n padding: 0;\n margin: 0 0 0 20px;\n -webkit-transform: none;\n -moz-transform: none;\n -ms-transform: none;\n -o-transform: none;\n transform: none;\n}\n\n\n/* misc */\n\n.no-pointer-events {\n pointer-events: none;\n}\n\n\n.layer_off_warning {\n color: #00FFFF;\n margin: 8px;\n text-align: center;\n}\n\n/* region scores */\n.cellscore .ui-accordion-header, .cellscore .ui-accordion-content {\n border: 1px solid #00FFFF;\n margin-top: -1px;\n display: block;\n}\n.cellscore .ui-accordion-header {\n color: #00FFFF;\n outline: none\n}\n.cellscore .ui-accordion-header:before {\n font-size: 18px;\n margin-right: 2px;\n content: "⊞";\n}\n.cellscore .ui-accordion-header-active:before {\n content: "⊟";\n}\ng.checkpoint:hover circle {\n fill-opacity: 1;\n stroke-width: 2px;\n}\n.checkpoint_table {\n border-collapse: collapse;\n}\n.checkpoint_table td {\n text-align: right;\n padding-left: 10px;\n}\n\n.text-overflow-ellipsis {\n display: inline-block;\n overflow: hidden;\n white-space: nowrap;\n text-overflow: ellipsis;\n vertical-align: text-bottom;\n width: 100%;\n}\n\n/* tabs */\n.ui-tabs-nav {\n display: block;\n border-bottom: 1px solid #00FFFF;\n border-top: 1px solid transparent;\n margin: 3px 0 0;\n padding: 0;\n}\n.ui-tabs-nav::after {\n content: \'\';\n clear: left;\n display: block;\n height: 0;\n width: 0;\n}\n.ui-tabs-nav li {\n list-style: none;\n display: block;\n float:left;\n margin: 0 0 -1px;\n border: 1px solid #00FFFF;\n}\n.ui-tabs-nav li.ui-tabs-active {\n border-bottom-color: #0F2C3F;\n background: #0F2C3F;\n border-width: 2px 2px 1px;\n font-weight: bold;\n margin: -1px 1px;\n}\n.ui-tabs-nav a {\n display: inline-block;\n padding: 0.2em 0.7em;\n}\n.ui-tabs-nav .ui-icon {\n display: inline-block;\n font-size: 0;\n height: 22px;\n overflow: hidden;\n position: relative;\n vertical-align: top;\n width: 16px;\n}\n.ui-tabs-nav .ui-icon-close::before {\n content: "×";\n font-size: 16px;\n height: 16px;\n position: absolute;\n text-align: center;\n top: 2px;\n vertical-align: baseline;\n width: 16px;\n cursor: pointer;\n}\n\n</style>'
59 + '<style>/* required styles */\n\n.leaflet-map-pane,\n.leaflet-tile,\n.leaflet-marker-icon,\n.leaflet-marker-shadow,\n.leaflet-tile-pane,\n.leaflet-tile-container,\n.leaflet-overlay-pane,\n.leaflet-shadow-pane,\n.leaflet-marker-pane,\n.leaflet-popup-pane,\n.leaflet-overlay-pane svg,\n.leaflet-zoom-box,\n.leaflet-image-layer,\n.leaflet-layer {\n position: absolute;\n left: 0;\n top: 0;\n }\n.leaflet-container {\n overflow: hidden;\n -ms-touch-action: none;\n touch-action: none;\n }\n.leaflet-tile,\n.leaflet-marker-icon,\n.leaflet-marker-shadow {\n -webkit-user-select: none;\n -moz-user-select: none;\n user-select: none;\n -webkit-user-drag: none;\n }\n.leaflet-marker-icon,\n.leaflet-marker-shadow {\n display: block;\n }\n/* map is broken in FF if you have max-width: 100% on tiles */\n.leaflet-container img {\n max-width: none !important;\n }\n/* stupid Android 2 doesn\'t understand "max-width: none" properly */\n.leaflet-container img.leaflet-image-layer {\n max-width: 15000px !important;\n }\n.leaflet-tile {\n filter: inherit;\n visibility: hidden;\n }\n.leaflet-tile-loaded {\n visibility: inherit;\n }\n.leaflet-zoom-box {\n width: 0;\n height: 0;\n }\n/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */\n.leaflet-overlay-pane svg {\n -moz-user-select: none;\n }\n\n.leaflet-tile-pane { z-index: 2; }\n.leaflet-objects-pane { z-index: 3; }\n.leaflet-overlay-pane { z-index: 4; }\n.leaflet-shadow-pane { z-index: 5; }\n.leaflet-marker-pane { z-index: 6; }\n.leaflet-popup-pane { z-index: 7; }\n\n.leaflet-vml-shape {\n width: 1px;\n height: 1px;\n }\n.lvml {\n behavior: url(#default#VML);\n display: inline-block;\n position: absolute;\n }\n\n\n/* control positioning */\n\n.leaflet-control {\n position: relative;\n z-index: 7;\n pointer-events: auto;\n }\n.leaflet-top,\n.leaflet-bottom {\n position: absolute;\n z-index: 1000;\n pointer-events: none;\n }\n.leaflet-top {\n top: 0;\n }\n.leaflet-right {\n right: 0;\n }\n.leaflet-bottom {\n bottom: 0;\n }\n.leaflet-left {\n left: 0;\n }\n.leaflet-control {\n float: left;\n clear: both;\n }\n.leaflet-right .leaflet-control {\n float: right;\n }\n.leaflet-top .leaflet-control {\n margin-top: 10px;\n }\n.leaflet-bottom .leaflet-control {\n margin-bottom: 10px;\n }\n.leaflet-left .leaflet-control {\n margin-left: 10px;\n }\n.leaflet-right .leaflet-control {\n margin-right: 10px;\n }\n\n\n/* zoom and fade animations */\n\n.leaflet-fade-anim .leaflet-tile,\n.leaflet-fade-anim .leaflet-popup {\n opacity: 0;\n -webkit-transition: opacity 0.2s linear;\n -moz-transition: opacity 0.2s linear;\n -o-transition: opacity 0.2s linear;\n transition: opacity 0.2s linear;\n }\n.leaflet-fade-anim .leaflet-tile-loaded,\n.leaflet-fade-anim .leaflet-map-pane .leaflet-popup {\n opacity: 1;\n }\n\n.leaflet-zoom-anim .leaflet-zoom-animated {\n -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1);\n -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1);\n -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1);\n transition: transform 0.25s cubic-bezier(0,0,0.25,1);\n }\n.leaflet-zoom-anim .leaflet-tile,\n.leaflet-pan-anim .leaflet-tile,\n.leaflet-touching .leaflet-zoom-animated {\n -webkit-transition: none;\n -moz-transition: none;\n -o-transition: none;\n transition: none;\n }\n\n.leaflet-zoom-anim .leaflet-zoom-hide {\n visibility: hidden;\n }\n\n\n/* cursors */\n\n.leaflet-clickable {\n cursor: pointer;\n }\n.leaflet-container {\n cursor: -webkit-grab;\n cursor: -moz-grab;\n }\n.leaflet-popup-pane,\n.leaflet-control {\n cursor: auto;\n }\n.leaflet-dragging .leaflet-container,\n.leaflet-dragging .leaflet-clickable {\n cursor: move;\n cursor: -webkit-grabbing;\n cursor: -moz-grabbing;\n }\n\n\n/* visual tweaks */\n\n.leaflet-container {\n background: #ddd;\n outline: 0;\n }\n.leaflet-container a {\n color: #FFF;\n }\n.leaflet-container a.leaflet-active {\n /*outline: 2px solid orange;*/\n }\n.leaflet-zoom-box {\n border: 2px dotted #38f;\n background: rgba(255,255,255,0.5);\n }\n\n\n/* general typography */\n.leaflet-container {\n font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif;\n }\n\n\n/* general toolbar styles */\n\n.leaflet-bar {\n box-shadow: 0 1px 5px rgba(0,0,0,0.65);\n border-radius: 0px;\n }\n.leaflet-bar a,\n.leaflet-bar a:hover {\n background-color: rgba(8, 48, 78, 0.7);\n border-top: none;\n border-left: 1px solid #00FFFF;\n border-right: 1px solid #00FFFF;\n border-bottom: 1px solid #00FFFF;\n width: 26px;\n height: 26px;\n line-height: 26px;\n display: block;\n text-align: center;\n text-decoration: none;\n color: white;\n }\n.leaflet-bar a,\n.leaflet-control-layers-toggle {\n background-position: 50% 50%;\n background-repeat: no-repeat;\n display: block;\n }\n.leaflet-bar a:hover {\n /*background-color: #f4f4f4;*/\n }\n.leaflet-bar a:first-child {\n border: 1px solid #00FFFF;\n /*border-top-left-radius: 0px;*/\n /*border-top-right-radius: 0px;*/\n }\n.leaflet-bar a:last-child {\n /*border-bottom-left-radius: 0px;*/\n /*border-bottom-right-radius: 0px;*/\n }\n.leaflet-bar a.leaflet-disabled {\n cursor: default;\n background-color: rgba(8, 48, 78, 0.7);\n color: #888;\n }\n\n.leaflet-touch .leaflet-bar a {\n\n }\n.leaflet-touch .leaflet-bar a:last-child {\n\n }\n\n/* zoom control */\n\n.leaflet-control-zoom-in,\n.leaflet-control-zoom-out {\n font: bold 18px \'Lucida Console\', Monaco, monospace;\n text-indent: 1px;\n }\n.leaflet-control-zoom-out {\n font-size: 20px;\n }\n\n.leaflet-touch .leaflet-control-zoom-in {\n font-size: 22px;\n }\n.leaflet-touch .leaflet-control-zoom-out {\n font-size: 24px;\n }\n\n\n/* layers control */\n\n.leaflet-control-layers {\n box-shadow: 0 1px 5px rgba(0,0,0,0.4);\n background: rgba(8, 48, 78, 0.7);\n border: 1px solid #00FFFF;\n border-radius: 0px;\n }\n.leaflet-control-layers-toggle {\n background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABoAAAAaCAYAAACpSkzOAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADdYAAA3WAZBveZwAAAAZdEVYdFNvZnR3YXJlAHd3dy5pbmtzY2FwZS5vcmeb7jwaAAADRElEQVRIS7WUX0gUURTGV4hedDP68xBUEBESFFEQEQUS1IOEQURET6ERJRERpiKaKZulRVHm9hCYRBgUkUEPFhTrKhgZii25ZBEYFFIqYaVY4X593+yddXaazC078LGXc7/zOzP3nlnfX0Sa0f+LA/eRVdKLcklrk565WFOP9GOd2Fc9iOC5sbi0Vk57xvZvcfABsk/0o8Zu4Jb25DH21CPvLhYXRVBY+9W7gVPyyKsaU/7nWFiF2Uc7sDvwEXUe0GvVw3gpae3aC6pGtWIYnHfwCNaVvULADZDOjCDEexknDJLWynl5xRDLYCcjtxELirpxiIVX3EU8lluEDtgN3NKePO46scQU22py+DG2V73DebeRxVd5RD2ETbjhHpqQVzVujtjq4fMH0FESSTbwaVr4pF88gFNKNap1ssRWD5//+OAOf/GnvrVB3OFTNVH9XpBUJIZYYoqtHtbxbQxi7vo6hJecRoxT41mcisQQS0yxrSackOzSPkQp8EvHykvAtkag7I03ZCqpRrViiCWm2OrhY+d2TseQSVri/xgWBeK/VQPeUKfkcdY4WWKrh31HkT1NaCuNImYbeInIuQ5kXQQKu70bSNqTR17VJJqQJabYiTviq2auOIvQ8guY0PkmzNSRMLCsBth1Azj5drKB1sppTx5njRjMx/gA7byneVYTJjfRGNEmv2arMKfB9XQU/8esoyl4FJfWyjk91imwVgyxxCS7j9qq/7Y2flAflDQb2HvTnHdLMojnDU6RJa2de/KqRrVi2Dyyh5lvs+5oTtHQiy0NCNNgHZ0xYEM9sPpy0gT9Iu3JI69q7HqyYry3dt5RT+KO2CidqvVXjEY4NVHbLGmKODXWk/KCJ5twrZz25EmqacHrjIqxLjHFtpo4g8lV7B7mGevtPtuFOopcXrygempJa+Wcx8T1KAeglYyQWAb720ijMZ/G6M4mdNgQSUOgj1HS2rnHt3tqRjlfjDhqGuGv/DafzRoyKr8/4wT1O6FOFTzE+8xTeCKvakx56sEn3ExIFy88xKMZtxtw/YPT18q36JTH2P8t+AnMIqzYXz7Sm3cbXfubEeHgPFdOe8Y2c8FJWkr4Pb5hs9YmPY3w+X4Cv22EMdzAozYAAAAASUVORK5CYII=);\n width: 36px;\n height: 36px;\n }\n.leaflet-retina .leaflet-control-layers-toggle {\n background-image: url(data:image/png;base64,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);\n background-size: 26px 26px;\n }\n.leaflet-touch .leaflet-control-layers-toggle {\n width: 44px;\n height: 44px;\n }\n.leaflet-control-layers .leaflet-control-layers-list,\n.leaflet-control-layers-expanded .leaflet-control-layers-toggle {\n display: none;\n }\n.leaflet-control-layers-expanded .leaflet-control-layers-list {\n display: block;\n position: relative;\n }\n.leaflet-control-layers-expanded {\n padding: 6px 10px 6px 6px;\n color: #00FFFF;\n background: rgba(8, 48, 78, 0.7);\n }\n.leaflet-control-layers-selector {\n margin-top: 2px;\n position: relative;\n top: 1px;\n }\n.leaflet-control-layers label {\n display: block;\n }\n.leaflet-control-layers-separator {\n height: 0;\n border-top: 1px solid #ddd;\n margin: 5px -10px 5px -6px;\n }\n\n\n/* attribution and scale controls */\n\n.leaflet-container .leaflet-control-attribution {\n background: #fff;\n background: rgba(255, 255, 255, 0.7);\n margin: 0;\n }\n.leaflet-control-attribution,\n.leaflet-control-scale-line {\n padding: 0 5px;\n color: #333;\n }\n.leaflet-control-attribution a {\n text-decoration: none;\n }\n.leaflet-control-attribution a:hover {\n text-decoration: underline;\n }\n.leaflet-container .leaflet-control-attribution,\n.leaflet-container .leaflet-control-scale {\n font-size: 11px;\n }\n.leaflet-left .leaflet-control-scale {\n margin-left: 5px;\n }\n.leaflet-bottom .leaflet-control-scale {\n margin-bottom: 5px;\n }\n.leaflet-control-scale-line {\n border: 2px solid #777;\n border-top: none;\n line-height: 1.1;\n padding: 2px 5px 1px;\n font-size: 11px;\n white-space: nowrap;\n overflow: hidden;\n -moz-box-sizing: content-box;\n box-sizing: content-box;\n\n background: #fff;\n background: rgba(255, 255, 255, 0.5);\n }\n.leaflet-control-scale-line:not(:first-child) {\n border-top: 2px solid #777;\n border-bottom: none;\n margin-top: -2px;\n }\n.leaflet-control-scale-line:not(:first-child):not(:last-child) {\n border-bottom: 2px solid #777;\n }\n\n.leaflet-touch .leaflet-control-attribution,\n.leaflet-touch .leaflet-control-layers,\n.leaflet-touch .leaflet-bar {\n box-shadow: none;\n }\n.leaflet-touch .leaflet-control-layers,\n.leaflet-touch .leaflet-bar {\n border: 2px solid rgba(0,0,0,0.2);\n background-clip: padding-box;\n }\n\n\n/* popup */\n\n.leaflet-popup {\n position: absolute;\n text-align: center;\n }\n.leaflet-popup-content-wrapper {\n padding: 1px;\n text-align: left;\n border-radius: 12px;\n }\n.leaflet-popup-content {\n margin: 13px 19px;\n line-height: 1.4;\n }\n.leaflet-popup-content p {\n margin: 18px 0;\n }\n.leaflet-popup-tip-container {\n margin: 0 auto;\n width: 40px;\n height: 20px;\n position: relative;\n overflow: hidden;\n }\n.leaflet-popup-tip {\n width: 17px;\n height: 17px;\n padding: 1px;\n\n margin: -10px auto 0;\n\n -webkit-transform: rotate(45deg);\n -moz-transform: rotate(45deg);\n -ms-transform: rotate(45deg);\n -o-transform: rotate(45deg);\n transform: rotate(45deg);\n }\n.leaflet-popup-content-wrapper,\n.leaflet-popup-tip {\n background: white;\n\n box-shadow: 0 3px 14px rgba(0,0,0,0.4);\n }\n.leaflet-container a.leaflet-popup-close-button {\n position: absolute;\n top: 0;\n right: 0;\n padding: 4px 4px 0 0;\n text-align: center;\n width: 18px;\n height: 14px;\n font: 16px/14px Tahoma, Verdana, sans-serif;\n color: #c3c3c3;\n text-decoration: none;\n font-weight: bold;\n background: transparent;\n }\n.leaflet-container a.leaflet-popup-close-button:hover {\n color: #999;\n }\n.leaflet-popup-scrolled {\n overflow: auto;\n border-bottom: 1px solid #ddd;\n border-top: 1px solid #ddd;\n }\n\n.leaflet-oldie .leaflet-popup-content-wrapper {\n zoom: 1;\n }\n.leaflet-oldie .leaflet-popup-tip {\n width: 24px;\n margin: 0 auto;\n\n -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)";\n filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678);\n }\n.leaflet-oldie .leaflet-popup-tip-container {\n margin-top: -1px;\n }\n\n.leaflet-oldie .leaflet-control-zoom,\n.leaflet-oldie .leaflet-control-layers,\n.leaflet-oldie .leaflet-popup-content-wrapper,\n.leaflet-oldie .leaflet-popup-tip {\n border: 1px solid #999;\n }\n\n\n/* div icon */\n\n.leaflet-div-icon {\n background: #fff;\n border: 1px solid #666;\n }\n</style>'
60//note: smartphone.css injection moved into code/smartphone.js
61 + '<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Roboto:100,100italic,300,300italic,400,400italic,500,500italic,700,700italic&subset=latin,cyrillic-ext,greek-ext,greek,vietnamese,latin-ext,cyrillic"/>';
62
63// remove body element entirely to remove event listeners
64document.body = document.createElement('body');
65document.body.innerHTML = ''
66 + '<div id="map">讀å–ä¸, è«‹ç¨å¾Œ</div>'
67 + '<div id="chatcontrols" style="display:none">'
68 + '<a accesskey="0" title="[0]"><span class="toggle expand"></span></a>'
69 + '<a accesskey="1" title="[1]">all</a>'
70 + '<a accesskey="2" title="[2]" class="active">faction</a>'
71 + '<a accesskey="3" title="[3]">alerts</a>'
72 + '</div>'
73 + '<div id="chat" style="display:none">'
74 + ' <div id="chatfaction"></div>'
75 + ' <div id="chatall"></div>'
76 + ' <div id="chatalerts"></div>'
77 + '</div>'
78 + '<form id="chatinput" style="display:none"><table><tr>'
79 + ' <td><time></time></td>'
80 + ' <td><mark>tell faction:</mark></td>'
81 + ' <td><input id="chattext" type="text" maxlength="256" accesskey="c" title="[c]" /></td>'
82 + '</tr></table></form>'
83 + '<a id="sidebartoggle" accesskey="i" title="切æ›å´é‚Šæ”” [i]"><span class="toggle close"></span></a>'
84 + '<div id="scrollwrapper">' // enable scrolling for small screens
85 + ' <div id="sidebar" style="display: none">'
86 + ' <div id="playerstat">t</div>'
87 + ' <div id="gamestat"> 讀å–全局控制統計數據</div>'
88 + ' <div id="searchwrapper">'
89 + ' <button title="ç›®å‰ä½ç½®" id="buttongeolocation"><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAAVElEQVR42mNgIBZ8/v+fgaqALANBmtAxNgPxqSPaFWTJ0dVAssIXXQJbWGELM6IdQqyBFMU6UQbi0kDIQKpYNDBeplks0zwdDs6sR9XSZvAXsFQAAEwpNOmMKNo3AAAAAElFTkSuQmCC" alt="Current location"/></button>'
90 + ' <input id="search" placeholder="æœå°‹åœ°é»žâ€¦" type="search" accesskey="f" title="æœå°‹ä¸€å€‹åœ°é»ž [f]"/>'
91 + ' </div>'
92 + ' <div id="portaldetails"></div>'
93 + ' <input id="redeem" placeholder="å…Œæ›ä»£ç¢¼â€¦" type="text"/>'
94 + ' <div id="toolbox">'
95 + ' <a onmouseover="setPermaLink(this)" onclick="setPermaLink(this);return androidPermalink()" title="æ¤åœ°åœ–的連çµ">intel連çµ</a>'
96 + ' <a onclick="window.aboutIITC()" style="cursor: help">關於IITC</a>'
97 + ' <a onclick="window.regionScoreboard()" title="查看該å€åŸŸè¨˜åˆ†æ¿">å€åŸŸåˆ†æ•¸</a>'
98 + ' </div>'
99 + ' </div>'
100 + '</div>'
101 + '<div id="updatestatus"><div id="innerstatus"></div></div>'
102 // avoid error by stock JS
103 + '<div id="play_button"></div>';
104
105// putting everything in a wrapper function that in turn is placed in a
106// script tag on the website allows us to execute in the site’s context
107// instead of in the Greasemonkey/Extension/etc. context.
108function wrapper(info) {
109// a cut-down version of GM_info is passed as a parameter to the script
110// (not the full GM_info - it contains the ENTIRE script source!)
111window.script_info = info;
112
113
114
115
116// LEAFLET PREFER CANVAS ///////////////////////////////////////////////
117// Set to true if Leaflet should draw things using Canvas instead of SVG
118// Disabled for now because it has several bugs: flickering, constant
119// CPU usage and it continuously fires the moveend event.
120
121//L_PREFER_CANVAS = false;
122
123// CONFIG OPTIONS ////////////////////////////////////////////////////
124window.REFRESH = 30; // refresh view every 30s (base time)
125window.ZOOM_LEVEL_ADJ = 5; // add 5 seconds per zoom level
126window.ON_MOVE_REFRESH = 2.5; //refresh time to use after a movement event
127window.MINIMUM_OVERRIDE_REFRESH = 10; //limit on refresh time since previous refresh, limiting repeated move refresh rate
128window.REFRESH_GAME_SCORE = 15*60; // refresh game score every 15 minutes
129window.MAX_IDLE_TIME = 15*60; // stop updating map after 15min idling
130window.HIDDEN_SCROLLBAR_ASSUMED_WIDTH = 20;
131window.SIDEBAR_WIDTH = 300;
132
133// how many pixels to the top before requesting new data
134window.CHAT_REQUEST_SCROLL_TOP = 200;
135window.CHAT_SHRINKED = 60;
136
137// Minimum area to zoom ratio that field M's will display
138window.FIELD_MU_DISPLAY_AREA_ZOOM_RATIO = 0.001;
139
140// Point tolerance for displaying M's
141window.FIELD_MU_DISPLAY_POINT_TOLERANCE = 60
142
143window.COLOR_SELECTED_PORTAL = '#F80';
144window.COLORS = ['#888888', '#0088FF', '#03DC03']; // none, res, enl
145window.COLORS_LVL = ['#000', '#FECE5A', '#FFA630', '#FF7315', '#E40000', '#FD2992', '#EB26CD', '#C124E0', '#9627F4'];
146window.COLORS_MOD = {VERY_RARE: '#b08cff', RARE: '#73a8ff', COMMON: '#8cffbf'};
147
148
149window.MOD_TYPE = {RES_SHIELD:'Shield', MULTIHACK:'Multi-hack', FORCE_AMP:'Force Amp', HEATSINK:'Heat Sink', TURRET:'Turret', LINK_AMPLIFIER: 'Link Amp'};
150
151// circles around a selected portal that show from where you can hack
152// it and how far the portal reaches (i.e. how far links may be made
153// from this portal)
154window.ACCESS_INDICATOR_COLOR = 'orange';
155window.RANGE_INDICATOR_COLOR = 'red'
156
157// min zoom for intel map - should match that used by stock intel
158window.MIN_ZOOM = 3;
159
160window.DEFAULT_PORTAL_IMG = '//commondatastorage.googleapis.com/ingress.com/img/default-portal-image.png';
161//window.NOMINATIM = '//open.mapquestapi.com/nominatim/v1/search.php?format=json&polygon_geojson=1&q=';
162window.NOMINATIM = '//nominatim.openstreetmap.org/search?format=json&polygon_geojson=1&q=';
163
164// INGRESS CONSTANTS /////////////////////////////////////////////////
165// http://decodeingress.me/2012/11/18/ingress-portal-levels-and-link-range/
166window.RESO_NRG = [0, 1000, 1500, 2000, 2500, 3000, 4000, 5000, 6000];
167window.HACK_RANGE = 40; // in meters, max. distance from portal to be able to access it
168window.OCTANTS = ['æ±', 'æ±åŒ—', '北', '西北', '西', '西å—', 'å—', 'æ±å—'];
169window.OCTANTS_ARROW = ['→', '↗', '↑', '↖', 'â†', '↙', '↓', '↘'];
170window.DESTROY_RESONATOR = 75; //AP for destroying portal
171window.DESTROY_LINK = 187; //AP for destroying link
172window.DESTROY_FIELD = 750; //AP for destroying field
173window.CAPTURE_PORTAL = 500; //AP for capturing a portal
174window.DEPLOY_RESONATOR = 125; //AP for deploying a resonator
175window.COMPLETION_BONUS = 250; //AP for deploying all resonators on portal
176window.UPGRADE_ANOTHERS_RESONATOR = 65; //AP for upgrading another's resonator
177window.MAX_PORTAL_LEVEL = 8;
178window.MAX_RESO_PER_PLAYER = [0, 8, 4, 4, 4, 2, 2, 1, 1];
179
180// OTHER MORE-OR-LESS CONSTANTS //////////////////////////////////////
181window.TEAM_NONE = 0;
182window.TEAM_RES = 1;
183window.TEAM_ENL = 2;
184window.TEAM_TO_CSS = ['none', 'res', 'enl'];
185
186window.SLOT_TO_LAT = [0, Math.sqrt(2)/2, 1, Math.sqrt(2)/2, 0, -Math.sqrt(2)/2, -1, -Math.sqrt(2)/2];
187window.SLOT_TO_LNG = [1, Math.sqrt(2)/2, 0, -Math.sqrt(2)/2, -1, -Math.sqrt(2)/2, 0, Math.sqrt(2)/2];
188window.EARTH_RADIUS=6378137;
189window.DEG2RAD = Math.PI / 180;
190
191// STORAGE ///////////////////////////////////////////////////////////
192// global variables used for storage. Most likely READ ONLY. Proper
193// way would be to encapsulate them in an anonymous function and write
194// getters/setters, but if you are careful enough, this works.
195window.refreshTimeout = undefined;
196window.urlPortal = null;
197window.urlPortalLL = null;
198window.selectedPortal = null;
199window.portalRangeIndicator = null;
200window.portalAccessIndicator = null;
201window.mapRunsUserAction = false;
202//var portalsLayers, linksLayer, fieldsLayer;
203var portalsFactionLayers, linksFactionLayers, fieldsFactionLayers;
204
205// contain references to all entities loaded from the server. If render limits are hit,
206// not all may be added to the leaflet layers
207window.portals = {};
208window.links = {};
209window.fields = {};
210
211window.resonators = {};
212
213// contain current status(on/off) of overlay layerGroups.
214// But you should use isLayerGroupDisplayed(name) to check the status
215window.overlayStatus = {};
216
217// plugin framework. Plugins may load earlier than iitc, so don’t
218// overwrite data
219if(typeof window.plugin !== 'function') window.plugin = function() {};
220
221
222// ARTIFACT ///////////////////////////////////////////////////////
223
224// added as part of the ingress #13magnus in november 2013, artifacts
225// are additional game elements overlayed on the intel map
226// currently there are only jarvis-related entities
227// - shards: move between portals (along links) each hour. more than one can be at a portal
228// - targets: specific portals - one per team
229// the artifact data includes details for the specific portals, so can be useful
230// 2014-02-06: intel site updates hint at new 'amar artifacts', likely following the same system as above
231
232
233window.artifact = function() {}
234
235window.artifact.setup = function() {
236 artifact.REFRESH_JITTER = 2*60; // 2 minute random period so not all users refresh at once
237 artifact.REFRESH_SUCCESS = 60*60; // 60 minutes on success
238 artifact.REFRESH_FAILURE = 2*60; // 2 minute retry on failure
239
240 artifact.idle = false;
241 artifact.clearData();
242
243 addResumeFunction(artifact.idleResume);
244
245 // move the initial data request onto a very short timer. prevents thrown exceptions causing IITC boot failures
246 setTimeout (artifact.requestData, 1);
247
248 artifact._layer = new L.LayerGroup();
249 addLayerGroup ('神器', artifact._layer, true);
250
251 $('#toolbox').append(' <a onclick="window.artifact.showArtifactList()" title="顯示神器清單">神器</a>');
252
253}
254
255window.artifact.requestData = function() {
256 if (isIdle()) {
257 artifact.idle = true;
258 } else {
259 window.postAjax('getArtifactPortals', {}, artifact.handleSuccess, artifact.handleError);
260 }
261}
262
263window.artifact.idleResume = function() {
264 if (artifact.idle) {
265 artifact.idle = false;
266 artifact.requestData();
267 }
268}
269
270window.artifact.handleSuccess = function(data) {
271 artifact.processData (data);
272
273 // start the next refresh at a multiple of REFRESH_SUCCESS seconds, plus a random REFRESH_JITTER amount to prevent excessive server hits at one time
274 var now = Date.now();
275 var nextTime = Math.ceil(now/(artifact.REFRESH_SUCCESS*1000))*(artifact.REFRESH_SUCCESS*1000) + Math.floor(Math.random()*artifact.REFRESH_JITTER*1000);
276
277 setTimeout (artifact.requestData, nextTime - now);
278}
279
280window.artifact.handleFailure = function(data) {
281 // no useful data on failure - do nothing
282
283 setTimeout (artifact.requestData, artifact.REFRESH_FAILURE*1000);
284}
285
286
287window.artifact.processData = function(data) {
288
289 if (data.error || !data.result) {
290 console.warn('Failed to find result in getArtifactPortals response');
291 return;
292 }
293
294 var oldArtifacts = artifact.entities;
295 artifact.clearData();
296
297 artifact.processResult(data.result);
298 runHooks('artifactsUpdated', {old: oldArtifacts, 'new': artifact.entities});
299
300 // redraw the artifact layer
301 artifact.updateLayer();
302
303}
304
305
306window.artifact.clearData = function() {
307 artifact.portalInfo = {};
308 artifact.artifactTypes = {};
309
310 artifact.entities = [];
311}
312
313
314window.artifact.processResult = function (portals) {
315 // portals is an object, keyed from the portal GUID, containing the portal entity array
316
317 for (var guid in portals) {
318 var ent = portals[guid];
319 var data = decodeArray.portalSummary(ent);
320
321 // we no longer know the faction for the target portals, and we don't know which fragment numbers are at the portals
322 // all we know, from the portal summary data, for each type of artifact, is that each artifact portal is
323 // - a target portal or not - no idea for which faction
324 // - has one (or more) fragments, or not
325
326 if (!artifact.portalInfo[guid]) artifact.portalInfo[guid] = {};
327
328 // store the decoded data - needed for lat/lng for layer markers
329 artifact.portalInfo[guid]._data = data;
330
331 for(var type in data.artifactBrief.target) {
332 if (!artifact.artifactTypes[type]) artifact.artifactTypes[type] = {};
333
334 if (!artifact.portalInfo[guid][type]) artifact.portalInfo[guid][type] = {};
335
336 artifact.portalInfo[guid][type].target = TEAM_NONE; // as we no longer know the team...
337 }
338
339 for(var type in data.artifactBrief.fragment) {
340 if (!artifact.artifactTypes[type]) artifact.artifactTypes[type] = {};
341
342 if (!artifact.portalInfo[guid][type]) artifact.portalInfo[guid][type] = {};
343
344 artifact.portalInfo[guid][type].fragments = true; //as we no longer have a list of the fragments there
345 }
346
347
348 // let's pre-generate the entities needed to render the map - array of [guid, timestamp, ent_array]
349 artifact.entities.push ( [guid, data.timestamp, ent] );
350
351 }
352
353}
354
355window.artifact.getArtifactTypes = function() {
356 return Object.keys(artifact.artifactTypes);
357}
358
359window.artifact.isArtifact = function(type) {
360 return type in artifact.artifactTypes;
361}
362
363// used to render portals that would otherwise be below the visible level
364window.artifact.getArtifactEntities = function() {
365 return artifact.entities;
366}
367
368window.artifact.getInterestingPortals = function() {
369 return Object.keys(artifact.portalInfo);
370}
371
372// quick test for portal being relevant to artifacts - of any type
373window.artifact.isInterestingPortal = function(guid) {
374 return guid in artifact.portalInfo;
375}
376
377// get the artifact data for a specified artifact id (e.g. 'jarvis'), if it exists - otherwise returns something 'false'y
378window.artifact.getPortalData = function(guid,artifactId) {
379 return artifact.portalInfo[guid] && artifact.portalInfo[guid][artifactId];
380}
381
382window.artifact.updateLayer = function() {
383 artifact._layer.clearLayers();
384
385 $.each(artifact.portalInfo, function(guid,data) {
386 var latlng = L.latLng ([data._data.latE6/1E6, data._data.lngE6/1E6]);
387
388 $.each(data, function(type,detail) {
389
390 // we'll construct the URL form the type - stock seems to do that now
391
392 var iconUrl;
393 if (data[type].target !== undefined) {
394 // target portal
395 if (type == 'abaddonenl')
396 var iconUrl = 'data:image/png;base64,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' //iF
397 else
398 var iconUrl = 'data:image/png;base64,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' //iF
399 var iconSize = 80;
400 var opacity = 1.0;
401
402 var icon = L.icon({
403 iconUrl: iconUrl,
404 iconSize: [iconSize,iconSize],
405 iconAnchor: [iconSize/2,iconSize/2],
406 className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath
407 });
408
409 var marker = L.marker (latlng, {icon: icon, clickable: false, keyboard: false, opacity: opacity });
410
411 artifact._layer.addLayer(marker);
412
413 } else if (data[type].fragments) {
414 // fragment(s) at portal
415
416 var iconUrl = 'data:image/png;base64,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' //iF
417 var iconSize = 50;
418 var opacity = 0.6;
419
420 var icon = L.icon({
421 iconUrl: iconUrl,
422 iconSize: [iconSize,iconSize],
423 iconAnchor: [iconSize/2,iconSize/2],
424 className: 'no-pointer-events' // the clickable: false below still blocks events going through to the svg underneath
425 });
426
427 var marker = L.marker (latlng, {icon: icon, clickable: false, keyboard: false, opacity: opacity });
428
429 artifact._layer.addLayer(marker);
430
431 }
432
433 }); //end $.each(data, function(type,detail)
434
435 }); //end $.each(artifact.portalInfo, function(guid,data)
436
437}
438
439
440window.artifact.showArtifactList = function() {
441 var html = '';
442
443 if (Object.keys(artifact.artifactTypes).length == 0) {
444 html += '<i>該時段沒有任何神器</i>';
445 }
446
447 var first = true;
448 $.each(artifact.artifactTypes, function(type,type2) {
449 // no nice way to convert the Niantic internal name into the correct display name
450 // (we do get the description string once a portal with that shard type is selected - could cache that somewhere?)
451 var name = type.capitalize() + ' 碎片';
452
453 if (!first) html += '<hr>';
454 first = false;
455 html += '<div><b>'+name+'</b></div>';
456
457 html += '<table class="artifact artifact-'+type+'">';
458 html += '<tr><th>能é‡å¡”</th><th>細節</th></tr>';
459
460 var tableRows = [];
461
462 $.each(artifact.portalInfo, function(guid, data) {
463 if (type in data) {
464 // this portal has data for this artifact type - add it to the table
465
466 var onclick = 'zoomToAndShowPortal(\''+guid+'\',['+data._data.latE6/1E6+','+data._data.lngE6/1E6+'])';
467 var row = '<tr><td class="portal"><a onclick="'+onclick+'">'+escapeHtmlSpecialChars(data._data.title)+'</a></td>';
468
469 row += '<td class="info">';
470
471 if (data[type].target !== undefined) {
472 if (data[type].target == TEAM_NONE) {
473 row += '<span class="target">目標能é‡å¡”</span> ';
474 } else {
475 row += '<span class="target '+TEAM_TO_CSS[data[type].target]+'">'+(data[type].target==TEAM_RES?'Resistance':'Enlightened')+' 目標</span> ';
476 }
477 }
478
479 if (data[type].fragments) {
480 if (data[type].target !== undefined) {
481 row += '<br>';
482 }
483 var fragmentName = '碎片';
484// row += '<span class="fragments'+(data[type].target?' '+TEAM_TO_CSS[data[type].target]:'')+'">'+fragmentName+': #'+data[type].fragments.join(', #')+'</span> ';
485 row += '<span class="碎片'+(data[type].target?' '+TEAM_TO_CSS[data[type].target]:'')+'">'+fragmentName+': 是</span> ';
486 }
487
488 row += '</td></tr>';
489
490 // sort by target portals first, then by portal GUID
491 var sortVal = (data[type].target !== undefined ? 'A' : 'Z') + guid;
492
493 tableRows.push ( [sortVal, row] );
494 }
495 });
496
497 // check for no rows, and add a note to the table instead
498 if (tableRows.length == 0) {
499 html += '<tr><td colspan="2"><i>這個時段沒有神器</i></td></tr>';
500 }
501
502 // sort the rows
503 tableRows.sort(function(a,b) {
504 if (a[0] == b[0]) return 0;
505 else if (a[0] < b[0]) return -1;
506 else return 1;
507 });
508
509 // and add them to the table
510 html += tableRows.map(function(a){return a[1];}).join('');
511
512
513 html += '</table>';
514 });
515
516
517 html += "<hr />"
518 + "<p>在2015å¹´å¤å¤©, Niantic 更改了神器能é‡å¡”çš„æ•¸æ“šæ ¼å¼. 我們已無法得知:</p>"
519 + "<ul><li>目標能é‡å¡”是哪支陣營 - åªçŸ¥é“它是一個目標能é‡å¡”</li>"
520 + "<li>碎片是在哪個能é‡å¡”, åªçŸ¥é“它具有一個或多個碎片</li></ul>"
521 + "<p>您å¯ä»¥é¸æ“‡ä¸€å€‹èƒ½é‡å¡”,詳細的數據ä¸åŒ…å«ç¢Žç‰‡è™Ÿç¢¼åˆ—表, 但ä¾ç„¶æ²’有"
522 + "該目標更詳細的資料.</p>";
523
524 dialog({
525 title: '神器',
526 html: html,
527 width: 400,
528 position: {my: 'right center', at: 'center-60 center', of: window, collision: 'fit'}
529 });
530
531}
532
533
534;
535
536/// SETUP /////////////////////////////////////////////////////////////
537// these functions set up specific areas after the boot function
538// created a basic framework. All of these functions should only ever
539// be run once.
540
541window.setupLargeImagePreview = function() {
542 $('#portaldetails').on('click', '.imgpreview', function() {
543 var img = $(this).find('img')[0];
544 var details = $(this).find('div.portalDetails')[0];
545 //dialogs have 12px padding around the content
546 var dlgWidth = Math.max(img.naturalWidth+24,500);
547 if (details) {
548 dialog({
549 html: '<div style="text-align: center">' + img.outerHTML + '</div>' + details.outerHTML,
550 title: $(this).parent().find('h3.title').text(),
551 width: dlgWidth,
552 });
553 } else {
554 dialog({
555 html: '<div style="text-align: center">' + img.outerHTML + '</div>',
556 title: $(this).parent().find('h3.title').text(),
557 width: dlgWidth,
558 });
559 }
560 });
561}
562
563// adds listeners to the layer chooser such that a long press hides
564// all custom layers except the long pressed one.
565window.setupLayerChooserSelectOne = function() {
566 $('.leaflet-control-layers-overlays').on('click taphold', 'label', function(e) {
567 if(!e) return;
568 if(!(e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.type === 'taphold')) return;
569 var m = window.map;
570
571 var add = function(layer) {
572 if(!m.hasLayer(layer.layer)) m.addLayer(layer.layer);
573 };
574 var rem = function(layer) {
575 if(m.hasLayer(layer.layer)) m.removeLayer(layer.layer);
576 };
577
578 var isChecked = $(e.target).find('input').is(':checked');
579 var checkSize = $('.leaflet-control-layers-overlays input:checked').length;
580 if((isChecked && checkSize === 1) || checkSize === 0) {
581 // if nothing is selected or the users long-clicks the only
582 // selected element, assume all boxes should be checked again
583 $.each(window.layerChooser._layers, function(ind, layer) {
584 if(!layer.overlay) return true;
585 add(layer);
586 });
587 } else {
588 // uncheck all
589 var keep = $.trim($(e.target).text());
590 $.each(window.layerChooser._layers, function(ind, layer) {
591 if(layer.overlay !== true) return true;
592 if(layer.name === keep) { add(layer); return true; }
593 rem(layer);
594 });
595 }
596 e.preventDefault();
597 });
598}
599
600// Setup the function to record the on/off status of overlay layerGroups
601window.setupLayerChooserStatusRecorder = function() {
602 // Record already added layerGroups
603 $.each(window.layerChooser._layers, function(ind, chooserEntry) {
604 if(!chooserEntry.overlay) return true;
605 var display = window.map.hasLayer(chooserEntry.layer);
606 window.updateDisplayedLayerGroup(chooserEntry.name, display);
607 });
608
609 // Record layerGroups change
610 window.map.on('overlayadd overlayremove', function(e) {
611 var display = (e.type === 'overlayadd');
612 window.updateDisplayedLayerGroup(e.name, display);
613 });
614}
615
616window.layerChooserSetDisabledStates = function() {
617// layer selector - enable/disable layers that aren't visible due to zoom level
618 var minlvl = getMinPortalLevel();
619 var portalSelection = $('.leaflet-control-layers-overlays label');
620 //it's an array - 0=unclaimed, 1=lvl 1, 2=lvl 2, ..., 8=lvl 8 - 9 relevant entries
621 //mark all levels below (but not at) minlvl as disabled
622 portalSelection.slice(0, minlvl).addClass('disabled').attr('title', 'å°‡åœ°åœ–æ”¾å¤§ä¾†é¡¯ç¤ºé€™å€‹é …ç›®.');
623 //and all from minlvl to 8 as enabled
624 portalSelection.slice(minlvl, 8+1).removeClass('disabled').attr('title', '');
625
626//TODO? some generic mechanism where other layers can have their disabled state marked on/off? a few
627//plugins have code to do it by hand already
628}
629
630
631window.setupStyles = function() {
632 $('head').append('<style>' +
633 [ '#largepreview.enl img { border:2px solid '+COLORS[TEAM_ENL]+'; } ',
634 '#largepreview.res img { border:2px solid '+COLORS[TEAM_RES]+'; } ',
635 '#largepreview.none img { border:2px solid '+COLORS[TEAM_NONE]+'; } ',
636 '#chatcontrols { bottom: '+(CHAT_SHRINKED+22)+'px; }',
637 '#chat { height: '+CHAT_SHRINKED+'px; } ',
638 '.leaflet-right { margin-right: '+(SIDEBAR_WIDTH+1)+'px } ',
639 '#updatestatus { width:'+(SIDEBAR_WIDTH+2)+'px; } ',
640 '#sidebar { width:'+(SIDEBAR_WIDTH + HIDDEN_SCROLLBAR_ASSUMED_WIDTH + 1 /*border*/)+'px; } ',
641 '#sidebartoggle { right:'+(SIDEBAR_WIDTH+1)+'px; } ',
642 '#scrollwrapper { width:'+(SIDEBAR_WIDTH + 2*HIDDEN_SCROLLBAR_ASSUMED_WIDTH)+'px; right:-'+(2*HIDDEN_SCROLLBAR_ASSUMED_WIDTH-2)+'px } ',
643 '#sidebar > * { width:'+(SIDEBAR_WIDTH+1)+'px; }'].join("\n")
644 + '</style>');
645}
646
647function createDefaultBaseMapLayers() {
648 var baseLayers = {};
649
650 //OpenStreetMap attribution - required by several of the layers
651 osmAttribution = 'Map data © OpenStreetMap contributors';
652
653 // MapQuest - http://developer.mapquest.com/web/products/open/map
654 // now requires an API key
655 //var mqSubdomains = [ 'otile1','otile2', 'otile3', 'otile4' ];
656 //var mqTileUrlPrefix = window.location.protocol !== 'https:' ? 'http://{s}.mqcdn.com' : 'https://{s}-s.mqcdn.com';
657 //var mqMapOpt = {attribution: osmAttribution+', Tiles Courtesy of MapQuest', maxNativeZoom: 18, maxZoom: 21, subdomains: mqSubdomains};
658 //baseLayers['MapQuest OSM'] = new L.TileLayer(mqTileUrlPrefix+'/tiles/1.0.0/map/{z}/{x}/{y}.jpg',mqMapOpt);
659
660 // MapBox - https://www.mapbox.com/api-documentation/
661 // Access MapBox via the GNOME Project proxy.
662 // In the future, this URL will provide improved tiles from the GNOME Project with localized labels.
663 var gnomeStreetUrl = 'https://gis.gnome.org/tiles/street/v1/{z}/{x}/{y}';
664 var gnomeAerialUrl = 'https://gis.gnome.org/tiles/satellite/v1/{z}/{x}/{y}';
665 baseLayers['MapBox Street'] = L.tileLayer(gnomeStreetUrl);
666 baseLayers['MapBox Satellite'] = L.tileLayer(gnomeAerialUrl);
667
668 // cartodb has some nice tiles too - both dark and light subtle maps - http://cartodb.com/basemaps/
669 // (not available over https though - not on the right domain name anyway)
670 var cartoAttr = '© <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a href="http://cartodb.com/attributions">CartoDB</a>';
671 var cartoUrl = 'http://{s}.basemaps.cartocdn.com/{theme}/{z}/{x}/{y}.png';
672 baseLayers['CartoDB 黑暗'] = L.tileLayer(cartoUrl,{attribution:cartoAttr,theme:'dark_all'});
673 baseLayers['CartoDB 明亮'] = L.tileLayer(cartoUrl,{attribution:cartoAttr,theme:'light_all'});
674
675
676 // we'll include google maps too - in the ingress default style, and a few other standard ones
677 // as the stock intel map already uses the googme maps API, we just hijack their inclusion of the javascript and API key :)
678 var ingressGMapOptions = {
679 backgroundColor: '#000000', //or #dddddd ? - that's the Google tile layer default
680 styles: [
681 { featureType:"all", elementType:"all",
682 stylers: [{visibility:"on"}, {hue:"#131c1c"}, {saturation:"-50"}, {invert_lightness:true}] },
683 { featureType:"water", elementType:"all",
684 stylers: [{visibility:"on"}, {hue:"#005eff"}, {invert_lightness:true}] },
685 { featureType:"poi", stylers:[{visibility:"off"}]},
686 { featureType:"transit", elementType:"all", stylers:[{visibility:"off"}] }
687 ]
688 };
689 baseLayers['Google é è¨ Ingress 地圖'] = new L.Google('ROADMAP',{maxZoom:21, mapOptions:ingressGMapOptions});
690 baseLayers['Google é“路地圖'] = new L.Google('ROADMAP',{maxZoom:21});
691 baseLayers['Google 衛星地圖'] = new L.Google('SATELLITE',{maxZoom:21});
692 baseLayers['Google 衛星地圖 + é“è·¯'] = new L.Google('HYBRID',{maxZoom:21});
693 baseLayers['Google 地形圖'] = new L.Google('TERRAIN',{maxZoom:15});
694
695
696 return baseLayers;
697}
698
699
700window.setupMap = function() {
701 $('#map').text('');
702
703
704
705
706 // proper initial position is now delayed until all plugins are loaded and the base layer is set
707 window.map = new L.Map('map', {
708 center: [0,0],
709 zoom: 1,
710 zoomControl: (typeof android !== 'undefined' && android && android.showZoom) ? android.showZoom() : true,
711 minZoom: MIN_ZOOM,
712// zoomAnimation: false,
713 markerZoomAnimation: false,
714 bounceAtZoomLimits: false
715 });
716
717 if (L.Path.CANVAS) {
718 // for canvas, 2% overdraw only - to help performance
719 L.Path.CLIP_PADDING = 0.02;
720 } else if (L.Path.SVG) {
721 if (L.Browser.mobile) {
722 // mobile SVG - 10% ovredraw. might help performance?
723 L.Path.CLIP_PADDING = 0.1;
724 } else {
725 // for svg, 100% overdraw - so we have a full screen worth in all directions
726 L.Path.CLIP_PADDING = 1.0;
727 }
728 }
729
730 // add empty div to leaflet control areas - to force other leaflet controls to move around IITC UI elements
731 // TODO? move the actual IITC DOM into the leaflet control areas, so dummy <div>s aren't needed
732 if(!isSmartphone()) {
733 // chat window area
734 $(window.map._controlCorners['bottomleft']).append(
735 $('<div>').width(708).height(108).addClass('leaflet-control').css({'pointer-events': 'none', 'margin': '0'}));
736 }
737
738 var addLayers = {};
739 var hiddenLayer = [];
740
741 portalsFactionLayers = [];
742 var portalsLayers = [];
743 for(var i = 0; i <= 8; i++) {
744 portalsFactionLayers[i] = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
745 portalsLayers[i] = L.layerGroup(portalsFactionLayers[i]);
746 map.addLayer(portalsLayers[i]);
747 var t = (i === 0 ? '顯示更多' : 'ç‰ç´š' + i) + '能é‡å¡”';
748 addLayers[t] = portalsLayers[i];
749 // Store it in hiddenLayer to remove later
750 if(!isLayerGroupDisplayed(t, true)) hiddenLayer.push(portalsLayers[i]);
751 }
752
753 fieldsFactionLayers = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
754 var fieldsLayer = L.layerGroup(fieldsFactionLayers);
755 map.addLayer(fieldsLayer, true);
756 addLayers['æŽ§åˆ¶å ´'] = fieldsLayer;
757 // Store it in hiddenLayer to remove later
758 if(!isLayerGroupDisplayed('Fields', true)) hiddenLayer.push(fieldsLayer);
759
760 linksFactionLayers = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
761 var linksLayer = L.layerGroup(linksFactionLayers);
762 map.addLayer(linksLayer, true);
763 addLayers['連線'] = linksLayer;
764 // Store it in hiddenLayer to remove later
765 if(!isLayerGroupDisplayed('Links', true)) hiddenLayer.push(linksLayer);
766
767 // faction-specific layers
768 // these layers don't actually contain any data. instead, every time they're added/removed from the map,
769 // the matching sub-layers within the above portals/fields/links are added/removed from their parent with
770 // the below 'onoverlayadd/onoverlayremove' events
771 var factionLayers = [L.layerGroup(), L.layerGroup(), L.layerGroup()];
772 for (var fac in factionLayers) {
773 map.addLayer (factionLayers[fac]);
774 }
775
776 var setFactionLayersState = function(fac,enabled) {
777 if (enabled) {
778 if (!fieldsLayer.hasLayer(fieldsFactionLayers[fac])) fieldsLayer.addLayer (fieldsFactionLayers[fac]);
779 if (!linksLayer.hasLayer(linksFactionLayers[fac])) linksLayer.addLayer (linksFactionLayers[fac]);
780 for (var lvl in portalsLayers) {
781 if (!portalsLayers[lvl].hasLayer(portalsFactionLayers[lvl][fac])) portalsLayers[lvl].addLayer (portalsFactionLayers[lvl][fac]);
782 }
783 } else {
784 if (fieldsLayer.hasLayer(fieldsFactionLayers[fac])) fieldsLayer.removeLayer (fieldsFactionLayers[fac]);
785 if (linksLayer.hasLayer(linksFactionLayers[fac])) linksLayer.removeLayer (linksFactionLayers[fac]);
786 for (var lvl in portalsLayers) {
787 if (portalsLayers[lvl].hasLayer(portalsFactionLayers[lvl][fac])) portalsLayers[lvl].removeLayer (portalsFactionLayers[lvl][fac]);
788 }
789 }
790 }
791
792 // to avoid any favouritism, we'll put the player's own faction layer first
793 if (PLAYER.team == 'RESISTANCE') {
794 addLayers['åæŠ—è»'] = factionLayers[TEAM_RES];
795 addLayers['啟蒙è»'] = factionLayers[TEAM_ENL];
796 } else {
797 addLayers['啟蒙è»'] = factionLayers[TEAM_ENL];
798 addLayers['åæŠ—è»'] = factionLayers[TEAM_RES];
799 }
800 if (!isLayerGroupDisplayed('åæŠ—è»', true)) hiddenLayer.push (factionLayers[TEAM_RES]);
801 if (!isLayerGroupDisplayed('啟蒙è»', true)) hiddenLayer.push (factionLayers[TEAM_ENL]);
802
803 setFactionLayersState (TEAM_NONE, true);
804 setFactionLayersState (TEAM_RES, isLayerGroupDisplayed('åæŠ—è»', true));
805 setFactionLayersState (TEAM_ENL, isLayerGroupDisplayed('啟蒙è»', true));
806
807 // NOTE: these events are fired by the layer chooser, so won't happen until that's created and added to the map
808 window.map.on('overlayadd overlayremove', function(e) {
809 var displayed = (e.type == 'overlayadd');
810 switch (e.name) {
811 case 'åæŠ—è»':
812 setFactionLayersState (TEAM_RES, displayed);
813 break;
814 case '啟蒙è»':
815 setFactionLayersState (TEAM_ENL, displayed);
816 break;
817 }
818 });
819
820 var baseLayers = createDefaultBaseMapLayers();
821
822 window.layerChooser = new L.Control.Layers(baseLayers, addLayers);
823
824 // Remove the hidden layer after layerChooser built, to avoid messing up ordering of layers
825 $.each(hiddenLayer, function(ind, layer){
826 map.removeLayer(layer);
827
828 // as users often become confused if they accidentally switch a standard layer off, display a warning in this case
829 $('#portaldetails').html('<div class="layer_off_warning">'
830 +'<p><b>è¦å‘Š</b>: 有些標準圖層被關閉。 部分 能é‡å¡”/連線/æŽ§åˆ¶å ´ 將䏿œƒé¡¯ç¤º.</p>'
831 +'<a id="enable_standard_layers">啟用標準圖層</a>'
832 +'</div>');
833
834 $('#enable_standard_layers').on('click', function() {
835 $.each(addLayers, function(ind, layer) {
836 if (!map.hasLayer(layer)) map.addLayer(layer);
837 });
838 $('#portaldetails').html('');
839 });
840
841 });
842
843 map.addControl(window.layerChooser);
844
845 map.attributionControl.setPrefix('');
846 // listen for changes and store them in cookies
847 map.on('moveend', window.storeMapPosition);
848
849 map.on('moveend', function(e) {
850 // two limits on map position
851 // we wrap longitude (the L.LatLng 'wrap' method) - so we don't find ourselves looking beyond +-180 degrees
852 // then latitude is clamped with the clampLatLng function (to the 85 deg north/south limits)
853 var newPos = clampLatLng(map.getCenter().wrap());
854 if (!map.getCenter().equals(newPos)) {
855 map.panTo(newPos,{animate:false})
856 }
857 });
858
859 // map update status handling & update map hooks
860 // ensures order of calls
861 map.on('movestart', function() { window.mapRunsUserAction = true; window.requests.abort(); window.startRefreshTimeout(-1); });
862 map.on('moveend', function() { window.mapRunsUserAction = false; window.startRefreshTimeout(ON_MOVE_REFRESH*1000); });
863
864 map.on('zoomend', function() { window.layerChooserSetDisabledStates(); });
865 window.layerChooserSetDisabledStates();
866
867 // on zoomend, check to see the zoom level is an int, and reset the view if not
868 // (there's a bug on mobile where zoom levels sometimes end up as fractional levels. this causes the base map to be invisible)
869 map.on('zoomend', function() {
870 var z = map.getZoom();
871 if (z != parseInt(z))
872 {
873 console.warn('Non-integer zoom level at zoomend: '+z+' - trying to fix...');
874 map.setZoom(parseInt(z), {animate:false});
875 }
876 });
877
878
879 // set a 'moveend' handler for the map to clear idle state. e.g. after mobile 'my location' is used.
880 // possibly some cases when resizing desktop browser too
881 map.on('moveend', idleReset);
882
883 window.addResumeFunction(function() { window.startRefreshTimeout(ON_MOVE_REFRESH*1000); });
884
885 // create the map data requester
886 window.mapDataRequest = new MapDataRequest();
887 window.mapDataRequest.start();
888
889 // start the refresh process with a small timeout, so the first data request happens quickly
890 // (the code originally called the request function directly, and triggered a normal delay for the next refresh.
891 // however, the moveend/zoomend gets triggered on map load, causing a duplicate refresh. this helps prevent that
892 window.startRefreshTimeout(ON_MOVE_REFRESH*1000);
893};
894
895//adds a base layer to the map. done separately from the above, so that plugins that add base layers can be the default
896window.setMapBaseLayer = function() {
897 //create a map name -> layer mapping - depends on internals of L.Control.Layers
898 var nameToLayer = {};
899 var firstLayer = null;
900
901 for (i in window.layerChooser._layers) {
902 var obj = window.layerChooser._layers[i];
903 if (!obj.overlay) {
904 nameToLayer[obj.name] = obj.layer;
905 if (!firstLayer) firstLayer = obj.layer;
906 }
907 }
908
909 var baseLayer = nameToLayer[localStorage['iitc-base-map']] || firstLayer;
910 map.addLayer(baseLayer);
911
912 // now we have a base layer we can set the map position
913 // (setting an initial position, before a base layer is added, causes issues with leaflet)
914 var pos = getPosition();
915 map.setView (pos.center, pos.zoom, {reset:true});
916
917
918 //event to track layer changes and store the name
919 map.on('baselayerchange', function(info) {
920 for(i in window.layerChooser._layers) {
921 var obj = window.layerChooser._layers[i];
922 if (info.layer === obj.layer) {
923 localStorage['iitc-base-map'] = obj.name;
924 break;
925 }
926 }
927
928 //also, leaflet no longer ensures the base layer zoom is suitable for the map (a bug? feature change?), so do so here
929 map.setZoom(map.getZoom());
930
931
932 });
933
934
935}
936
937// renders player details into the website. Since the player info is
938// included as inline script in the original site, the data is static
939// and cannot be updated.
940window.setupPlayerStat = function() {
941 // stock site updated to supply the actual player level, AP requirements and XM capacity values
942 var level = PLAYER.verified_level;
943 PLAYER.level = level; //for historical reasons IITC expects PLAYER.level to contain the current player level
944
945 var n = window.PLAYER.nickname;
946 PLAYER.nickMatcher = new RegExp('\\b('+n+')\\b', 'ig');
947
948 var ap = parseInt(PLAYER.ap);
949 var thisLvlAp = parseInt(PLAYER.min_ap_for_current_level);
950 var nextLvlAp = parseInt(PLAYER.min_ap_for_next_level);
951
952 if (nextLvlAp) {
953 var lvlUpAp = digits(nextLvlAp-ap);
954 var lvlApProg = Math.round((ap-thisLvlAp)/(nextLvlAp-thisLvlAp)*100);
955 } // else zero nextLvlAp - so at maximum level(?)
956
957 var xmMax = parseInt(PLAYER.xm_capacity);
958 var xmRatio = Math.round(PLAYER.energy/xmMax*100);
959
960 var cls = PLAYER.team === 'RESISTANCE' ? 'res' : 'enl';
961
962
963 var t = 'ç‰ç´š:\t' + level + '\n'
964 + '能é‡:\t' + PLAYER.energy + ' / ' + xmMax + '\n'
965 + 'ç¶“é©—:\t' + digits(ap) + '\n'
966 + (nextLvlAp > 0 ? 'è·é›¢ä¸‹æ¬¡å‡ç´šé‚„å·®:\t' + lvlUpAp + ' AP' : 'å·²é”到最高ç‰ç´š(!)')
967 + '\n\邀請函:\t'+PLAYER.available_invites
968 + '\n\næç¤º: æ‚¨çš„çŽ©å®¶ç‹€æ…‹åªæœƒåœ¨ç¶²é 刷新時更新 (F5)';
969
970 $('#playerstat').html(''
971 + '<h2 title="'+t+'">'+level+' '
972 + '<div id="name">'
973 + '<span class="'+cls+'">'+PLAYER.nickname+'</span>'
974 + '<a href="/_ah/logout?continue=https://www.google.com/accounts/Logout%3Fcontinue%3Dhttps://appengine.google.com/_ah/logout%253Fcontinue%253Dhttps://www.ingress.com/intel%26service%3Dah" id="signout">登出</a>'
975 + '</div>'
976 + '<div id="stats">'
977 + '<sup>能é‡:'+xmRatio+'%</sup>'
978 + '<sub>' + (nextLvlAp > 0 ? '經驗:'+lvlApProg+'%' : '最高級別') + '</sub>'
979 + '</div>'
980 + '</h2>'
981 );
982}
983
984window.setupSidebarToggle = function() {
985 $('#sidebartoggle').on('click', function() {
986 var toggle = $('#sidebartoggle');
987 var sidebar = $('#scrollwrapper');
988 if(sidebar.is(':visible')) {
989 sidebar.hide().css('z-index', 1);
990 $('.leaflet-right').css('margin-right','0');
991 toggle.html('<span class="toggle open"></span>');
992 toggle.css('right', '0');
993 } else {
994 sidebar.css('z-index', 1001).show();
995 $('.leaflet-right').css('margin-right', SIDEBAR_WIDTH+1+'px');
996 toggle.html('<span class="toggle close"></span>');
997 toggle.css('right', SIDEBAR_WIDTH+1+'px');
998 }
999 $('.ui-tooltip').remove();
1000 });
1001}
1002
1003window.setupTooltips = function(element) {
1004 element = element || $(document);
1005 element.tooltip({
1006 // disable show/hide animation
1007 show: { effect: 'none', duration: 0, delay: 350 },
1008 hide: false,
1009 open: function(event, ui) {
1010 // ensure all other tooltips are closed
1011 $(".ui-tooltip").not(ui.tooltip).remove();
1012 },
1013 content: function() {
1014 var title = $(this).attr('title');
1015 return window.convertTextToTableMagic(title);
1016 }
1017 });
1018
1019 if(!window.tooltipClearerHasBeenSetup) {
1020 window.tooltipClearerHasBeenSetup = true;
1021 $(document).on('click', '.ui-tooltip', function() { $(this).remove(); });
1022 }
1023}
1024
1025window.setupTaphold = function() {
1026 // @author Rich Adams <rich@richadams.me>
1027
1028// Implements a tap and hold functionality. If you click/tap and release, it will trigger a normal
1029// click event. But if you click/tap and hold for 1s (default), it will trigger a taphold event instead.
1030
1031;(function($)
1032{
1033 // Default options
1034 var defaults = {
1035 duration: 1000, // ms
1036 clickHandler: null
1037 }
1038
1039 // When start of a taphold event is triggered.
1040 function startHandler(event)
1041 {
1042 var $elem = jQuery(this);
1043
1044 // Merge the defaults and any user defined settings.
1045 settings = jQuery.extend({}, defaults, event.data);
1046
1047 // If object also has click handler, store it and unbind. Taphold will trigger the
1048 // click itself, rather than normal propagation.
1049 if (typeof $elem.data("events") != "undefined"
1050 && typeof $elem.data("events").click != "undefined")
1051 {
1052 // Find the one without a namespace defined.
1053 for (var c in $elem.data("events").click)
1054 {
1055 if ($elem.data("events").click[c].namespace == "")
1056 {
1057 var handler = $elem.data("events").click[c].handler
1058 $elem.data("taphold_click_handler", handler);
1059 $elem.unbind("click", handler);
1060 break;
1061 }
1062 }
1063 }
1064 // Otherwise, if a custom click handler was explicitly defined, then store it instead.
1065 else if (typeof settings.clickHandler == "function")
1066 {
1067 $elem.data("taphold_click_handler", settings.clickHandler);
1068 }
1069
1070 // Reset the flags
1071 $elem.data("taphold_triggered", false); // If a hold was triggered
1072 $elem.data("taphold_clicked", false); // If a click was triggered
1073 $elem.data("taphold_cancelled", false); // If event has been cancelled.
1074
1075 // Set the timer for the hold event.
1076 $elem.data("taphold_timer",
1077 setTimeout(function()
1078 {
1079 // If event hasn't been cancelled/clicked already, then go ahead and trigger the hold.
1080 if (!$elem.data("taphold_cancelled")
1081 && !$elem.data("taphold_clicked"))
1082 {
1083 // Trigger the hold event, and set the flag to say it's been triggered.
1084 $elem.trigger(jQuery.extend(event, jQuery.Event("taphold")));
1085 $elem.data("taphold_triggered", true);
1086 }
1087 }, settings.duration));
1088 }
1089
1090 // When user ends a tap or click, decide what we should do.
1091 function stopHandler(event)
1092 {
1093 var $elem = jQuery(this);
1094
1095 // If taphold has been cancelled, then we're done.
1096 if ($elem.data("taphold_cancelled")) { return; }
1097
1098 // Clear the hold timer. If it hasn't already triggered, then it's too late anyway.
1099 clearTimeout($elem.data("taphold_timer"));
1100
1101 // If hold wasn't triggered and not already clicked, then was a click event.
1102 if (!$elem.data("taphold_triggered")
1103 && !$elem.data("taphold_clicked"))
1104 {
1105 // If click handler, trigger it.
1106 if (typeof $elem.data("taphold_click_handler") == "function")
1107 {
1108 $elem.data("taphold_click_handler")(jQuery.extend(event, jQuery.Event("click")));
1109 }
1110
1111 // Set flag to say we've triggered the click event.
1112 $elem.data("taphold_clicked", true);
1113 }
1114 }
1115
1116 // If a user prematurely leaves the boundary of the object we're working on.
1117 function leaveHandler(event)
1118 {
1119 // Cancel the event.
1120 $(this).data("taphold_cancelled", true);
1121 }
1122
1123 // Determine if touch events are supported.
1124 var touchSupported = ("ontouchstart" in window) // Most browsers
1125 || ("onmsgesturechange" in window); // Mircosoft
1126
1127 var taphold = $.event.special.taphold =
1128 {
1129 setup: function(data)
1130 {
1131 $(this).bind((touchSupported ? "touchstart" : "mousedown"), data, startHandler)
1132 .bind((touchSupported ? "touchend" : "mouseup"), stopHandler)
1133 .bind((touchSupported ? "touchmove" : "mouseleave"), leaveHandler);
1134 if(touchSupported)
1135 $(this).bind("touchcancel", leaveHandler);
1136 },
1137 teardown: function(namespaces)
1138 {
1139 $(this).unbind((touchSupported ? "touchstart" : "mousedown"), startHandler)
1140 .unbind((touchSupported ? "touchend" : "mouseup"), stopHandler)
1141 .unbind((touchSupported ? "touchmove" : "mouseleave"), leaveHandler);
1142 if(touchSupported)
1143 $(this).unbind("touchcancel", leaveHandler);
1144 }
1145 };
1146})(jQuery);
1147
1148}
1149
1150
1151window.setupQRLoadLib = function() {
1152 (function(r){r.fn.qrcode=function(h){var s;function u(a){this.mode=s;this.data=a}function o(a,c){this.typeNumber=a;this.errorCorrectLevel=c;this.modules=null;this.moduleCount=0;this.dataCache=null;this.dataList=[]}function q(a,c){if(void 0==a.length)throw Error(a.length+"/"+c);for(var d=0;d<a.length&&0==a[d];)d++;this.num=Array(a.length-d+c);for(var b=0;b<a.length-d;b++)this.num[b]=a[b+d]}function p(a,c){this.totalCount=a;this.dataCount=c}function t(){this.buffer=[];this.length=0}u.prototype={getLength:function(){return this.data.length},
1153write:function(a){for(var c=0;c<this.data.length;c++)a.put(this.data.charCodeAt(c),8)}};o.prototype={addData:function(a){this.dataList.push(new u(a));this.dataCache=null},isDark:function(a,c){if(0>a||this.moduleCount<=a||0>c||this.moduleCount<=c)throw Error(a+","+c);return this.modules[a][c]},getModuleCount:function(){return this.moduleCount},make:function(){if(1>this.typeNumber){for(var a=1,a=1;40>a;a++){for(var c=p.getRSBlocks(a,this.errorCorrectLevel),d=new t,b=0,e=0;e<c.length;e++)b+=c[e].dataCount;
1154for(e=0;e<this.dataList.length;e++)c=this.dataList[e],d.put(c.mode,4),d.put(c.getLength(),j.getLengthInBits(c.mode,a)),c.write(d);if(d.getLengthInBits()<=8*b)break}this.typeNumber=a}this.makeImpl(!1,this.getBestMaskPattern())},makeImpl:function(a,c){this.moduleCount=4*this.typeNumber+17;this.modules=Array(this.moduleCount);for(var d=0;d<this.moduleCount;d++){this.modules[d]=Array(this.moduleCount);for(var b=0;b<this.moduleCount;b++)this.modules[d][b]=null}this.setupPositionProbePattern(0,0);this.setupPositionProbePattern(this.moduleCount-
11557,0);this.setupPositionProbePattern(0,this.moduleCount-7);this.setupPositionAdjustPattern();this.setupTimingPattern();this.setupTypeInfo(a,c);7<=this.typeNumber&&this.setupTypeNumber(a);null==this.dataCache&&(this.dataCache=o.createData(this.typeNumber,this.errorCorrectLevel,this.dataList));this.mapData(this.dataCache,c)},setupPositionProbePattern:function(a,c){for(var d=-1;7>=d;d++)if(!(-1>=a+d||this.moduleCount<=a+d))for(var b=-1;7>=b;b++)-1>=c+b||this.moduleCount<=c+b||(this.modules[a+d][c+b]=
11560<=d&&6>=d&&(0==b||6==b)||0<=b&&6>=b&&(0==d||6==d)||2<=d&&4>=d&&2<=b&&4>=b?!0:!1)},getBestMaskPattern:function(){for(var a=0,c=0,d=0;8>d;d++){this.makeImpl(!0,d);var b=j.getLostPoint(this);if(0==d||a>b)a=b,c=d}return c},createMovieClip:function(a,c,d){a=a.createEmptyMovieClip(c,d);this.make();for(c=0;c<this.modules.length;c++)for(var d=1*c,b=0;b<this.modules[c].length;b++){var e=1*b;this.modules[c][b]&&(a.beginFill(0,100),a.moveTo(e,d),a.lineTo(e+1,d),a.lineTo(e+1,d+1),a.lineTo(e,d+1),a.endFill())}return a},
1157setupTimingPattern:function(){for(var a=8;a<this.moduleCount-8;a++)null==this.modules[a][6]&&(this.modules[a][6]=0==a%2);for(a=8;a<this.moduleCount-8;a++)null==this.modules[6][a]&&(this.modules[6][a]=0==a%2)},setupPositionAdjustPattern:function(){for(var a=j.getPatternPosition(this.typeNumber),c=0;c<a.length;c++)for(var d=0;d<a.length;d++){var b=a[c],e=a[d];if(null==this.modules[b][e])for(var f=-2;2>=f;f++)for(var i=-2;2>=i;i++)this.modules[b+f][e+i]=-2==f||2==f||-2==i||2==i||0==f&&0==i?!0:!1}},setupTypeNumber:function(a){for(var c=
1158j.getBCHTypeNumber(this.typeNumber),d=0;18>d;d++){var b=!a&&1==(c>>d&1);this.modules[Math.floor(d/3)][d%3+this.moduleCount-8-3]=b}for(d=0;18>d;d++)b=!a&&1==(c>>d&1),this.modules[d%3+this.moduleCount-8-3][Math.floor(d/3)]=b},setupTypeInfo:function(a,c){for(var d=j.getBCHTypeInfo(this.errorCorrectLevel<<3|c),b=0;15>b;b++){var e=!a&&1==(d>>b&1);6>b?this.modules[b][8]=e:8>b?this.modules[b+1][8]=e:this.modules[this.moduleCount-15+b][8]=e}for(b=0;15>b;b++)e=!a&&1==(d>>b&1),8>b?this.modules[8][this.moduleCount-
1159b-1]=e:9>b?this.modules[8][15-b-1+1]=e:this.modules[8][15-b-1]=e;this.modules[this.moduleCount-8][8]=!a},mapData:function(a,c){for(var d=-1,b=this.moduleCount-1,e=7,f=0,i=this.moduleCount-1;0<i;i-=2)for(6==i&&i--;;){for(var g=0;2>g;g++)if(null==this.modules[b][i-g]){var n=!1;f<a.length&&(n=1==(a[f]>>>e&1));j.getMask(c,b,i-g)&&(n=!n);this.modules[b][i-g]=n;e--; -1==e&&(f++,e=7)}b+=d;if(0>b||this.moduleCount<=b){b-=d;d=-d;break}}}};o.PAD0=236;o.PAD1=17;o.createData=function(a,c,d){for(var c=p.getRSBlocks(a,
1160c),b=new t,e=0;e<d.length;e++){var f=d[e];b.put(f.mode,4);b.put(f.getLength(),j.getLengthInBits(f.mode,a));f.write(b)}for(e=a=0;e<c.length;e++)a+=c[e].dataCount;if(b.getLengthInBits()>8*a)throw Error("code length overflow. ("+b.getLengthInBits()+">"+8*a+")");for(b.getLengthInBits()+4<=8*a&&b.put(0,4);0!=b.getLengthInBits()%8;)b.putBit(!1);for(;!(b.getLengthInBits()>=8*a);){b.put(o.PAD0,8);if(b.getLengthInBits()>=8*a)break;b.put(o.PAD1,8)}return o.createBytes(b,c)};o.createBytes=function(a,c){for(var d=
11610,b=0,e=0,f=Array(c.length),i=Array(c.length),g=0;g<c.length;g++){var n=c[g].dataCount,h=c[g].totalCount-n,b=Math.max(b,n),e=Math.max(e,h);f[g]=Array(n);for(var k=0;k<f[g].length;k++)f[g][k]=255&a.buffer[k+d];d+=n;k=j.getErrorCorrectPolynomial(h);n=(new q(f[g],k.getLength()-1)).mod(k);i[g]=Array(k.getLength()-1);for(k=0;k<i[g].length;k++)h=k+n.getLength()-i[g].length,i[g][k]=0<=h?n.get(h):0}for(k=g=0;k<c.length;k++)g+=c[k].totalCount;d=Array(g);for(k=n=0;k<b;k++)for(g=0;g<c.length;g++)k<f[g].length&&
1162(d[n++]=f[g][k]);for(k=0;k<e;k++)for(g=0;g<c.length;g++)k<i[g].length&&(d[n++]=i[g][k]);return d};s=4;for(var j={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,
116378,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(a){for(var c=a<<10;0<=j.getBCHDigit(c)-j.getBCHDigit(j.G15);)c^=j.G15<<j.getBCHDigit(c)-j.getBCHDigit(j.G15);return(a<<10|c)^j.G15_MASK},getBCHTypeNumber:function(a){for(var c=a<<12;0<=j.getBCHDigit(c)-
1164j.getBCHDigit(j.G18);)c^=j.G18<<j.getBCHDigit(c)-j.getBCHDigit(j.G18);return a<<12|c},getBCHDigit:function(a){for(var c=0;0!=a;)c++,a>>>=1;return c},getPatternPosition:function(a){return j.PATTERN_POSITION_TABLE[a-1]},getMask:function(a,c,d){switch(a){case 0:return 0==(c+d)%2;case 1:return 0==c%2;case 2:return 0==d%3;case 3:return 0==(c+d)%3;case 4:return 0==(Math.floor(c/2)+Math.floor(d/3))%2;case 5:return 0==c*d%2+c*d%3;case 6:return 0==(c*d%2+c*d%3)%2;case 7:return 0==(c*d%3+(c+d)%2)%2;default:throw Error("bad maskPattern:"+
1165a);}},getErrorCorrectPolynomial:function(a){for(var c=new q([1],0),d=0;d<a;d++)c=c.multiply(new q([1,l.gexp(d)],0));return c},getLengthInBits:function(a,c){if(1<=c&&10>c)switch(a){case 1:return 10;case 2:return 9;case s:return 8;case 8:return 8;default:throw Error("mode:"+a);}else if(27>c)switch(a){case 1:return 12;case 2:return 11;case s:return 16;case 8:return 10;default:throw Error("mode:"+a);}else if(41>c)switch(a){case 1:return 14;case 2:return 13;case s:return 16;case 8:return 12;default:throw Error("mode:"+
1166a);}else throw Error("type:"+c);},getLostPoint:function(a){for(var c=a.getModuleCount(),d=0,b=0;b<c;b++)for(var e=0;e<c;e++){for(var f=0,i=a.isDark(b,e),g=-1;1>=g;g++)if(!(0>b+g||c<=b+g))for(var h=-1;1>=h;h++)0>e+h||c<=e+h||0==g&&0==h||i==a.isDark(b+g,e+h)&&f++;5<f&&(d+=3+f-5)}for(b=0;b<c-1;b++)for(e=0;e<c-1;e++)if(f=0,a.isDark(b,e)&&f++,a.isDark(b+1,e)&&f++,a.isDark(b,e+1)&&f++,a.isDark(b+1,e+1)&&f++,0==f||4==f)d+=3;for(b=0;b<c;b++)for(e=0;e<c-6;e++)a.isDark(b,e)&&!a.isDark(b,e+1)&&a.isDark(b,e+
11672)&&a.isDark(b,e+3)&&a.isDark(b,e+4)&&!a.isDark(b,e+5)&&a.isDark(b,e+6)&&(d+=40);for(e=0;e<c;e++)for(b=0;b<c-6;b++)a.isDark(b,e)&&!a.isDark(b+1,e)&&a.isDark(b+2,e)&&a.isDark(b+3,e)&&a.isDark(b+4,e)&&!a.isDark(b+5,e)&&a.isDark(b+6,e)&&(d+=40);for(e=f=0;e<c;e++)for(b=0;b<c;b++)a.isDark(b,e)&&f++;a=Math.abs(100*f/c/c-50)/5;return d+10*a}},l={glog:function(a){if(1>a)throw Error("glog("+a+")");return l.LOG_TABLE[a]},gexp:function(a){for(;0>a;)a+=255;for(;256<=a;)a-=255;return l.EXP_TABLE[a]},EXP_TABLE:Array(256),
1168LOG_TABLE:Array(256)},m=0;8>m;m++)l.EXP_TABLE[m]=1<<m;for(m=8;256>m;m++)l.EXP_TABLE[m]=l.EXP_TABLE[m-4]^l.EXP_TABLE[m-5]^l.EXP_TABLE[m-6]^l.EXP_TABLE[m-8];for(m=0;255>m;m++)l.LOG_TABLE[l.EXP_TABLE[m]]=m;q.prototype={get:function(a){return this.num[a]},getLength:function(){return this.num.length},multiply:function(a){for(var c=Array(this.getLength()+a.getLength()-1),d=0;d<this.getLength();d++)for(var b=0;b<a.getLength();b++)c[d+b]^=l.gexp(l.glog(this.get(d))+l.glog(a.get(b)));return new q(c,0)},mod:function(a){if(0>
1169this.getLength()-a.getLength())return this;for(var c=l.glog(this.get(0))-l.glog(a.get(0)),d=Array(this.getLength()),b=0;b<this.getLength();b++)d[b]=this.get(b);for(b=0;b<a.getLength();b++)d[b]^=l.gexp(l.glog(a.get(b))+c);return(new q(d,0)).mod(a)}};p.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],
1170[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,
1171116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,
117243,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,
11733,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,
117455,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,
117545,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]];p.getRSBlocks=function(a,c){var d=p.getRsBlockTable(a,c);if(void 0==d)throw Error("bad rs block @ typeNumber:"+a+"/errorCorrectLevel:"+c);for(var b=d.length/3,e=[],f=0;f<b;f++)for(var h=d[3*f+0],g=d[3*f+1],j=d[3*f+2],l=0;l<h;l++)e.push(new p(g,j));return e};p.getRsBlockTable=function(a,c){switch(c){case 1:return p.RS_BLOCK_TABLE[4*(a-1)+0];case 0:return p.RS_BLOCK_TABLE[4*(a-1)+1];case 3:return p.RS_BLOCK_TABLE[4*
1176(a-1)+2];case 2:return p.RS_BLOCK_TABLE[4*(a-1)+3]}};t.prototype={get:function(a){return 1==(this.buffer[Math.floor(a/8)]>>>7-a%8&1)},put:function(a,c){for(var d=0;d<c;d++)this.putBit(1==(a>>>c-d-1&1))},getLengthInBits:function(){return this.length},putBit:function(a){var c=Math.floor(this.length/8);this.buffer.length<=c&&this.buffer.push(0);a&&(this.buffer[c]|=128>>>this.length%8);this.length++}};"string"===typeof h&&(h={text:h});h=r.extend({},{render:"canvas",width:256,height:256,typeNumber:-1,
1177correctLevel:2,background:"#ffffff",foreground:"#000000"},h);return this.each(function(){var a;if("canvas"==h.render){a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();var c=document.createElement("canvas");c.width=h.width;c.height=h.height;for(var d=c.getContext("2d"),b=h.width/a.getModuleCount(),e=h.height/a.getModuleCount(),f=0;f<a.getModuleCount();f++)for(var i=0;i<a.getModuleCount();i++){d.fillStyle=a.isDark(f,i)?h.foreground:h.background;var g=Math.ceil((i+1)*b)-Math.floor(i*b),
1178j=Math.ceil((f+1)*b)-Math.floor(f*b);d.fillRect(Math.round(i*b),Math.round(f*e),g,j)}}else{a=new o(h.typeNumber,h.correctLevel);a.addData(h.text);a.make();c=r("<table></table>").css("width",h.width+"px").css("height",h.height+"px").css("border","0px").css("border-collapse","collapse").css("background-color",h.background);d=h.width/a.getModuleCount();b=h.height/a.getModuleCount();for(e=0;e<a.getModuleCount();e++){f=r("<tr></tr>").css("height",b+"px").appendTo(c);for(i=0;i<a.getModuleCount();i++)r("<td></td>").css("width",
1179d+"px").css("background-color",a.isDark(e,i)?h.foreground:h.background).appendTo(f)}}a=c;jQuery(a).appendTo(this)})}})(jQuery);
1180
1181}
1182
1183window.setupLayerChooserApi = function() {
1184 // hide layer chooser if booted with the iitcm android app
1185 if (typeof android !== 'undefined' && android && android.setLayers) {
1186 $('.leaflet-control-layers').hide();
1187 }
1188
1189 //hook some additional code into the LayerControl so it's easy for the mobile app to interface with it
1190 //WARNING: does depend on internals of the L.Control.Layers code
1191 window.layerChooser.getLayers = function() {
1192 var baseLayers = new Array();
1193 var overlayLayers = new Array();
1194
1195 for (i in this._layers) {
1196 var obj = this._layers[i];
1197 var layerActive = window.map.hasLayer(obj.layer);
1198 var info = {
1199 layerId: L.stamp(obj.layer),
1200 name: obj.name,
1201 active: layerActive
1202 }
1203 if (obj.overlay) {
1204 overlayLayers.push(info);
1205 } else {
1206 baseLayers.push(info);
1207 }
1208 }
1209
1210 var overlayLayersJSON = JSON.stringify(overlayLayers);
1211 var baseLayersJSON = JSON.stringify(baseLayers);
1212
1213 if (typeof android !== 'undefined' && android && android.setLayers) {
1214 if(this.androidTimer) clearTimeout(this.androidTimer);
1215 this.androidTimer = setTimeout(function() {
1216 this.androidTimer = null;
1217 android.setLayers(baseLayersJSON, overlayLayersJSON);
1218 }, 1000);
1219 }
1220
1221 return {
1222 baseLayers: baseLayers,
1223 overlayLayers: overlayLayers
1224 }
1225 }
1226
1227 window.layerChooser.showLayer = function(id,show) {
1228 if (show === undefined) show = true;
1229 obj = this._layers[id];
1230 if (!obj) return false;
1231
1232 if(show) {
1233 if (!this._map.hasLayer(obj.layer)) {
1234 //the layer to show is not currently active
1235 this._map.addLayer(obj.layer);
1236
1237 //if it's a base layer, remove any others
1238 if (!obj.overlay) {
1239 for(i in this._layers) {
1240 if (i != id) {
1241 var other = this._layers[i];
1242 if (!other.overlay && this._map.hasLayer(other.layer)) this._map.removeLayer(other.layer);
1243 }
1244 }
1245 }
1246 }
1247 } else {
1248 if (this._map.hasLayer(obj.layer)) {
1249 this._map.removeLayer(obj.layer);
1250 }
1251 }
1252
1253 //below logic based on code in L.Control.Layers _onInputClick
1254 if(!obj.overlay) {
1255 this._map.setZoom(this._map.getZoom());
1256 this._map.fire('baselayerchange', {layer: obj.layer});
1257 }
1258
1259 return true;
1260 };
1261
1262 var _update = window.layerChooser._update;
1263 window.layerChooser._update = function() {
1264 // update layer menu in IITCm
1265 try {
1266 if(typeof android != 'undefined')
1267 window.layerChooser.getLayers();
1268 } catch(e) {
1269 console.error(e);
1270 }
1271 // call through
1272 return _update.apply(this, arguments);
1273 }
1274 // as this setupLayerChooserApi function is called after the layer menu is populated, we need to also get they layers once
1275 // so they're passed through to the android app
1276 try {
1277 if(typeof android != 'undefined')
1278 window.layerChooser.getLayers();
1279 } catch(e) {
1280 console.error(e);
1281 }
1282}
1283
1284// BOOTING ///////////////////////////////////////////////////////////
1285
1286function boot() {
1287 if(!isSmartphone()) // TODO remove completely?
1288 window.debug.console.overwriteNativeIfRequired();
1289
1290 console.log('loading done, booting. Built: 2017-02-10-164403');
1291 if(window.deviceID) console.log('Your device ID: ' + window.deviceID);
1292 window.runOnSmartphonesBeforeBoot();
1293
1294 var iconDefImage = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAApCAYAAADAk4LOAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAIGNIUk0AAHolAACAgwAA+f8AAIDpAAB1MAAA6mAAADqYAAAXb5JfxUYAAAocSURBVHjanNRbUFN3Hgfwv9Pdzu5sZ7cP3d1eprP7sC/bPvSls9MmB5CLcg2IIAhSWwS8rFqrpbVbu3Vdd2ov04qJgGBESAJJCOR2AiFcEpKT+wkhiIiCoCJKkQhCIEgEvvsQcaoVdffhM+fMOf///3v+5/c7h8S8lk64f0wkH7/9LZGnsUSczBBREkNESRaieM9JOg6fJzUJljckPGajhMfsF6dYjolTLMV16bZMxRbnW2KehUhSGSJKtBBlvptIM+2kJt5CRIkWIkljiCSVIWS1EHEyQ6SZtrdVBe5jomRLd126dVa6ybZYn+OAbJN9qS7dOidJYy6Iki3fS3gM5/8J+bMo0VJZm2pdaHjPiaZ9XrR+dg6tn59D26FetB06h9Z/nEPzvm4ot7lRl25drI43Vzd+4PrLM4WIkpjImkRmWJHnQktxD9o+70XLJz7o9nWD3uMFvdsLeo8Xug+7oS/23b/fg4b3XBClMNfFyUx8TeIjIWtfTSPv/iGeHHj7GyLnseniJGZGs8ODtkO9aP6oG9qdXdDs8EC3x4s+5SjujMzhIn0DTfu6odnhgXZnF5o+6kbboV5odnZBlMQEaxIsuQ+FJLy+mUS/toF88vb3f5Mlu+9od3XBcPActDu7oC70QF3kgbP0Mu5cD2LOv4DFhSXM+Rcwc3MebMUQ1EUeqAs90OwMz6N3e1GTYJkVJVooSSpDalNthFTEtJKKmNbfnonruKDaxsJwsAfq7R6oClmYjl7Arb5p3J25hz7lKFo/78XsrbswHu7DOekI5qdCmLg4A/OxfqgKWai3e2D4tAfKAjeq15sHqtebf3c6ro2QmnUMqY61HJJutMPwaQ80OzzQ7/dhqGMc94KLuO68jdbPzkFVwEJ/wIfQ3CLaDvVCVcDC8GkPrjITuBdcxBXzLbQU9zwIkmU4ULHW8GX869mEnI0z//5snHlcu6sLur1euMuHMHvrLvwDAZi/7odymxvKfBbKfBa6vd0Y892B/uMeKLexYfn3d9w/jTn/ArqEw9Dt9YL+uxfCGOPE/re+e5lUxXTmSVKt0B8It+P0aBCDhh+hKmShzHdDXchCs90D7Y4welfXg3PNdg80RR405ruhKmTRr72B6dEglNvcaD7gQ22aFeI4x1ZyJsokVuQ5odvrhSLPhduDAdiOD6D9n+H3Hxibx/RoEJPDs5geDWL6ehDTo0FMXZnF9PUgAmPzmPMvwHT0Asxf9cM/GIAizwXdXi8a8pw4E2WSEGGUyakqYKHZ4YFiSzjEXX4ZjVtdGD8/DQBYureMPuUoTEf6YDx8HqYjfeiVj+De3SUAgH8wgMb33bAfH8DtwQAUW1zQbPdAVcBCGGV0E+Fa41X1/QsNueEQtnwIDVtcaP/iPEL3ix8Ym8c16wSMh/swbBzH7PhdjDj8uDe/CNO/L0CR54KjZBC3BwNoyHVBVRDuNuFa4zUiXGu8odnugTLfDflmB/yDAbjKLkOR64Qi14mhjnGMspPQfdiNUddtLC8t46Z3Cvr9PlxlJjBi80OR60R9jhO245fgHwxAvtkBZb4bmnDIDVIZ2e5uzHdDuc0NWbYD/oFwSP1mB+Q5TqiLWCwE7sHyzUU05LkwPxWCusgD4+E+hIKLoHd7Ic9xQr7ZAdsPl+AfCECW7YAyn0XjB25URrazpJwyyGTZdqiLPJBussM/GIC9ZACybDtMR/qgL/bBW3MFMzeC0O31IjA2j+b9PkwOz6K3fgRNH3aj8z8XIM92gPn6IvwDAUg3hdeTZdtRTrU2kNPR7Xuqkzqh2d4FWZYdE/0z8ImvYkA/hsW7S3CfGoIs246pa3MYNPwI/2AAg/oxzIwGUZ/jhP34AELBRQx1jMNbdQUT/TOQZdmh2dGF6qROnI5p30fKI/R/rYhqDakKWNTnOnH7cgAAMMpOoqW4B9JMO2SZdpi/6sfy0jJCwUUAgO2HS5BtskOaaYd+vw8jdj+wDExemUV9rhPqAhanogyh8gjDm6SMal5zkqNrrctkoMxn4au9hqXQEi63/whlgRvSDBvqNtohzbBhxOEHANzsnoI0w/6A8gM3LjXdxPLSMnrlI1BtY1GbweDku7qW8gj9GlIWoScCLp1TEWuAqsADaYYN+mIfxnqmEJxcgE98FfU5TtSl29C0rxvzd0IwHOxB3UYbZFl2dFVdwZx/AePnp2E42ANppg3qQg8qYw3gc+iMk5SOkBMcNSnhqF8QcOgheY4Dii1OiHkMJKkMLN/0487IHKauzcF8rB+1G6zQ7e5C3QYrOo/2YXJoFjM3grD9cAkSHgMxj4EizwX5Zgf4HLr/BFfzqxNcDSF8Skv4lJac4GiOnEnogDKfhSQtHCJJZSDLssMnuYb5qRBueCZhPNKHEYcfd6dDOF9/HYocZ3gsj4EkjYEqn4Uwvh18jvZgKdVESqkmQkojmsOopj8JKN1teY4D8mwHxCnhJxPzGIhTGKiLWAybbmH+TgjXrBPQ7OqCmGeFhGeFOIWBKIVBfY4D8s0OCLj0mICiXxZQNBFQNCHlES0P8DnaY8L4djRudYcnJjEQJTMQr0j6OVFyeJyYx6DxfTdOr2sDn0N/sbKLUqqJkJW0+14RcOlxaZYdsk121CRYIEp8upoES7idN9kg4NLXS6mmlx4K4XO1DznB0Xx5el0bFHkuiJLCCzyNKNkCRZ4LlXGtEHDo4p8GPDaEz9W+JODSo9JMG6QZdpyNM6N63erOxpkhzbSjLsMKAVc3LKDoFwWUjvwUeTS1lGoiAg79SWVsKxS5TlSvt+BsbHixn4k1ozreAkWOExUxBgi4ur1lEXryqEdrsuJFAYcelqQzqNtgQ1VMJ6pif+5MTCfq0m0Qb2DA52gvlXBUL5SEv7uHkEe3toLP1e6uiDZAnuVA9TozqqI7w2ErojtRvd4MebYDp6INKOGoi0o4KvI4pDzSsIqW3/A52osingW1qVYIo4w4E2V6QBhlRG2qFSKeGXwufZ7P1f76MfUlfK72sYX/aacVnFrbAmmGHVWxnRBGGiGMMkIYaURVbCekGXaURelRRjVvPR3ZTioj2x6LnKR0T/IrPofuqUnuhIRnRWVkB05HdaAysgMSnhU1yZ3gc7TeEo76+RMcNVkNWe09rjjBUeeWR+lRt8EGYYwRp6hWCGOMqNtgQ3mUHgKKzlr5/62GPG0An9L+UsCl2eoEE0RJFpRTBoiSLDibYMJJSuesjjf/oibBTJ6EVMd3PlFNgplURBvSSyOaIE5hUBVngjiFQVlkM757pz7t23dk5GnIqUjDs3iOz9UyZ9Z1hL+b9SZ8/26Def3rWc+tfYVHol9Ne6KnFf4BPleTWBbZDFGSBWWRehznqBJ2v3mU7HzjMNn1xr+e6Ikt/Ig1AopuK4vQQ0DRrXyudk15RAs5FWF4qtV+K6uJE1DaUPj47PP+15DnBRRdeP/4zPP+OwCV955x/18hzAAAAABJRU5ErkJggg==';
1295 var iconDefRetImage = 'data:image/png;base64,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';
1296
1297 L.Icon.Default = L.Icon.extend({options: {
1298 iconUrl: iconDefImage,
1299 iconRetinaUrl: iconDefRetImage,
1300 iconSize: new L.Point(25, 41),
1301 iconAnchor: new L.Point(12, 41),
1302 popupAnchor: new L.Point(1, -34),
1303 }});
1304
1305 window.extractFromStock();
1306 window.setupIdle();
1307 window.setupTaphold();
1308 window.setupStyles();
1309 window.setupDialogs();
1310 window.setupDataTileParams();
1311 window.setupMap();
1312 window.setupOMS();
1313 window.search.setup();
1314 window.setupRedeem();
1315 window.setupLargeImagePreview();
1316 window.setupSidebarToggle();
1317 window.updateGameScore();
1318 window.artifact.setup();
1319 window.ornaments.setup();
1320 window.setupPlayerStat();
1321 window.setupTooltips();
1322 window.chat.setup();
1323 window.portalDetail.setup();
1324 window.setupQRLoadLib();
1325 window.setupLayerChooserSelectOne();
1326 window.setupLayerChooserStatusRecorder();
1327 // read here ONCE, so the URL is only evaluated one time after the
1328 // necessary data has been loaded.
1329 urlPortalLL = getURLParam('pll');
1330 if(urlPortalLL) {
1331 urlPortalLL = urlPortalLL.split(",");
1332 urlPortalLL = [parseFloat(urlPortalLL[0]) || 0.0, parseFloat(urlPortalLL[1]) || 0.0];
1333 }
1334 urlPortal = getURLParam('pguid');
1335
1336 $('#sidebar').show();
1337
1338 if(window.bootPlugins) {
1339 // check to see if a known 'bad' plugin is installed. If so, alert the user, and don't boot any plugins
1340 var badPlugins = {
1341 'arc': 'Contains hidden code to report private data to a 3rd party server: <a href="https://plus.google.com/105383756361375410867/posts/4b2EjP3Du42">details here</a>',
1342 };
1343
1344 // remove entries from badPlugins which are not installed
1345 $.each(badPlugins, function(name,desc) {
1346 if (!(window.plugin && window.plugin[name])) {
1347 // not detected: delete from the list
1348 delete badPlugins[name];
1349 }
1350 });
1351
1352 // if any entries remain in the list, report this to the user and don't boot ANY plugins
1353 // (why not any? it's tricky to know which of the plugin boot entries were safe/unsafe)
1354 if (Object.keys(badPlugins).length > 0) {
1355 var warning = 'One or more known unsafe plugins were detected. For your safety, IITC has disabled all plugins.<ul>';
1356 $.each(badPlugins,function(name,desc) {
1357 warning += '<li><b>'+name+'</b>: '+desc+'</li>';
1358 });
1359 warning += '</ul><p>Please uninstall the problem plugins and reload the page. See this <a href="http://iitc.me/faq/#uninstall">FAQ entry</a> for help.</p><p><i>Note: It is tricky for IITC to safely disable just problem plugins</i></p>';
1360
1361 dialog({
1362 title: 'Plugin Warning',
1363 html: warning,
1364 width: 400
1365 });
1366 } else {
1367 // no known unsafe plugins detected - boot all plugins
1368 $.each(window.bootPlugins, function(ind, ref) {
1369 try {
1370 ref();
1371 } catch(err) {
1372 console.error("error starting plugin: index "+ind+", error: "+err);
1373 debugger;
1374 }
1375 });
1376 }
1377 }
1378
1379 window.setMapBaseLayer();
1380 window.setupLayerChooserApi();
1381
1382 window.runOnSmartphonesAfterBoot();
1383
1384 // workaround for #129. Not sure why this is required.
1385 // setTimeout('window.map.invalidateSize(false);', 500);
1386
1387 window.iitcLoaded = true;
1388 window.runHooks('iitcLoaded');
1389
1390
1391 if (typeof android !== 'undefined' && android && android.bootFinished) {
1392 android.bootFinished();
1393 }
1394
1395}
1396
1397
1398/* Copyright (c) 2010 Chris O'Hara <cohara87@gmail.com>. MIT Licensed */
1399
1400//Include the chain.js microframework (http://github.com/chriso/chain.js)
1401(function(a){a=a||{};var b={},c,d;c=function(a,d,e){var f=a.halt=!1;a.error=function(a){throw a},a.next=function(c){c&&(f=!1);if(!a.halt&&d&&d.length){var e=d.shift(),g=e.shift();f=!0;try{b[g].apply(a,[e,e.length,g])}catch(h){a.error(h)}}return a};for(var g in b){if(typeof a[g]==="function")continue;(function(e){a[e]=function(){var g=Array.prototype.slice.call(arguments);if(e==="onError"){if(d){b.onError.apply(a,[g,g.length]);return a}var h={};b.onError.apply(h,[g,g.length]);return c(h,null,"onError")}g.unshift(e);if(!d)return c({},[g],e);a.then=a[e],d.push(g);return f?a:a.next()}})(g)}e&&(a.then=a[e]),a.call=function(b,c){c.unshift(b),d.unshift(c),a.next(!0)};return a.next()},d=a.addMethod=function(d){var e=Array.prototype.slice.call(arguments),f=e.pop();for(var g=0,h=e.length;g<h;g++)typeof e[g]==="string"&&(b[e[g]]=f);--h||(b["then"+d.substr(0,1).toUpperCase()+d.substr(1)]=f),c(a)},d("chain",function(a){var b=this,c=function(){if(!b.halt){if(!a.length)return b.next(!0);try{null!=a.shift().call(b,c,b.error)&&c()}catch(d){b.error(d)}}};c()}),d("run",function(a,b){var c=this,d=function(){c.halt||--b||c.next(!0)},e=function(a){c.error(a)};for(var f=0,g=b;!c.halt&&f<g;f++)null!=a[f].call(c,d,e)&&d()}),d("defer",function(a){var b=this;setTimeout(function(){b.next(!0)},a.shift())}),d("onError",function(a,b){var c=this;this.error=function(d){c.halt=!0;for(var e=0;e<b;e++)a[e].call(c,d)}})})(this);
1402
1403var head = document.getElementsByTagName('head')[0] || document.documentElement;
1404
1405addMethod('load', function (args, argc) {
1406 for (var queue = [], i = 0; i < argc; i++) {
1407 (function (i) {
1408 queue.push(asyncLoadScript(args[i]));
1409 }(i));
1410 }
1411 this.call('run', queue);
1412});
1413
1414function asyncLoadScript(src) {
1415 return function (onload, onerror) {
1416 var script = document.createElement('script');
1417 script.type = 'text/javascript';
1418 script.src = src;
1419 script.onload = onload;
1420 script.onerror = onerror;
1421 script.onreadystatechange = function () {
1422 var state = this.readyState;
1423 if (state === 'loaded' || state === 'complete') {
1424 script.onreadystatechange = null;
1425 onload();
1426 }
1427 };
1428 head.insertBefore(script, head.firstChild);
1429 }
1430}
1431
1432
1433try { console.log('Loading included JS now'); } catch(e) {}
1434/*
1435 Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com
1436 (c) 2010-2013, Vladimir Agafonkin
1437 (c) 2010-2011, CloudMade
1438*/
1439(function (window, document, undefined) {
1440var oldL = window.L,
1441 L = {};
1442
1443L.version = '0.7.7';
1444
1445// define Leaflet for Node module pattern loaders, including Browserify
1446if (typeof module === 'object' && typeof module.exports === 'object') {
1447 module.exports = L;
1448
1449// define Leaflet as an AMD module
1450} else if (typeof define === 'function' && define.amd) {
1451 define(L);
1452}
1453
1454// define Leaflet as a global L variable, saving the original L to restore later if needed
1455
1456L.noConflict = function () {
1457 window.L = oldL;
1458 return this;
1459};
1460
1461window.L = L;
1462
1463
1464/*
1465 * L.Util contains various utility functions used throughout Leaflet code.
1466 */
1467
1468L.Util = {
1469 extend: function (dest) { // (Object[, Object, ...]) ->
1470 var sources = Array.prototype.slice.call(arguments, 1),
1471 i, j, len, src;
1472
1473 for (j = 0, len = sources.length; j < len; j++) {
1474 src = sources[j] || {};
1475 for (i in src) {
1476 if (src.hasOwnProperty(i)) {
1477 dest[i] = src[i];
1478 }
1479 }
1480 }
1481 return dest;
1482 },
1483
1484 bind: function (fn, obj) { // (Function, Object) -> Function
1485 var args = arguments.length > 2 ? Array.prototype.slice.call(arguments, 2) : null;
1486 return function () {
1487 return fn.apply(obj, args || arguments);
1488 };
1489 },
1490
1491 stamp: (function () {
1492 var lastId = 0,
1493 key = '_leaflet_id';
1494 return function (obj) {
1495 obj[key] = obj[key] || ++lastId;
1496 return obj[key];
1497 };
1498 }()),
1499
1500 invokeEach: function (obj, method, context) {
1501 var i, args;
1502
1503 if (typeof obj === 'object') {
1504 args = Array.prototype.slice.call(arguments, 3);
1505
1506 for (i in obj) {
1507 method.apply(context, [i, obj[i]].concat(args));
1508 }
1509 return true;
1510 }
1511
1512 return false;
1513 },
1514
1515 limitExecByInterval: function (fn, time, context) {
1516 var lock, execOnUnlock;
1517
1518 return function wrapperFn() {
1519 var args = arguments;
1520
1521 if (lock) {
1522 execOnUnlock = true;
1523 return;
1524 }
1525
1526 lock = true;
1527
1528 setTimeout(function () {
1529 lock = false;
1530
1531 if (execOnUnlock) {
1532 wrapperFn.apply(context, args);
1533 execOnUnlock = false;
1534 }
1535 }, time);
1536
1537 fn.apply(context, args);
1538 };
1539 },
1540
1541 falseFn: function () {
1542 return false;
1543 },
1544
1545 formatNum: function (num, digits) {
1546 var pow = Math.pow(10, digits || 5);
1547 return Math.round(num * pow) / pow;
1548 },
1549
1550 trim: function (str) {
1551 return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
1552 },
1553
1554 splitWords: function (str) {
1555 return L.Util.trim(str).split(/\s+/);
1556 },
1557
1558 setOptions: function (obj, options) {
1559 obj.options = L.extend({}, obj.options, options);
1560 return obj.options;
1561 },
1562
1563 getParamString: function (obj, existingUrl, uppercase) {
1564 var params = [];
1565 for (var i in obj) {
1566 params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
1567 }
1568 return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
1569 },
1570 template: function (str, data) {
1571 return str.replace(/\{ *([\w_]+) *\}/g, function (str, key) {
1572 var value = data[key];
1573 if (value === undefined) {
1574 throw new Error('No value provided for variable ' + str);
1575 } else if (typeof value === 'function') {
1576 value = value(data);
1577 }
1578 return value;
1579 });
1580 },
1581
1582 isArray: Array.isArray || function (obj) {
1583 return (Object.prototype.toString.call(obj) === '[object Array]');
1584 },
1585
1586 emptyImageUrl: 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='
1587};
1588
1589(function () {
1590
1591 // inspired by http://paulirish.com/2011/requestanimationframe-for-smart-animating/
1592
1593 function getPrefixed(name) {
1594 var i, fn,
1595 prefixes = ['webkit', 'moz', 'o', 'ms'];
1596
1597 for (i = 0; i < prefixes.length && !fn; i++) {
1598 fn = window[prefixes[i] + name];
1599 }
1600
1601 return fn;
1602 }
1603
1604 var lastTime = 0;
1605
1606 function timeoutDefer(fn) {
1607 var time = +new Date(),
1608 timeToCall = Math.max(0, 16 - (time - lastTime));
1609
1610 lastTime = time + timeToCall;
1611 return window.setTimeout(fn, timeToCall);
1612 }
1613
1614 var requestFn = window.requestAnimationFrame ||
1615 getPrefixed('RequestAnimationFrame') || timeoutDefer;
1616
1617 var cancelFn = window.cancelAnimationFrame ||
1618 getPrefixed('CancelAnimationFrame') ||
1619 getPrefixed('CancelRequestAnimationFrame') ||
1620 function (id) { window.clearTimeout(id); };
1621
1622
1623 L.Util.requestAnimFrame = function (fn, context, immediate, element) {
1624 fn = L.bind(fn, context);
1625
1626 if (immediate && requestFn === timeoutDefer) {
1627 fn();
1628 } else {
1629 return requestFn.call(window, fn, element);
1630 }
1631 };
1632
1633 L.Util.cancelAnimFrame = function (id) {
1634 if (id) {
1635 cancelFn.call(window, id);
1636 }
1637 };
1638
1639}());
1640
1641// shortcuts for most used utility functions
1642L.extend = L.Util.extend;
1643L.bind = L.Util.bind;
1644L.stamp = L.Util.stamp;
1645L.setOptions = L.Util.setOptions;
1646
1647
1648/*
1649 * L.Class powers the OOP facilities of the library.
1650 * Thanks to John Resig and Dean Edwards for inspiration!
1651 */
1652
1653L.Class = function () {};
1654
1655L.Class.extend = function (props) {
1656
1657 // extended class with the new prototype
1658 var NewClass = function () {
1659
1660 // call the constructor
1661 if (this.initialize) {
1662 this.initialize.apply(this, arguments);
1663 }
1664
1665 // call all constructor hooks
1666 if (this._initHooks) {
1667 this.callInitHooks();
1668 }
1669 };
1670
1671 // instantiate class without calling constructor
1672 var F = function () {};
1673 F.prototype = this.prototype;
1674
1675 var proto = new F();
1676 proto.constructor = NewClass;
1677
1678 NewClass.prototype = proto;
1679
1680 //inherit parent's statics
1681 for (var i in this) {
1682 if (this.hasOwnProperty(i) && i !== 'prototype') {
1683 NewClass[i] = this[i];
1684 }
1685 }
1686
1687 // mix static properties into the class
1688 if (props.statics) {
1689 L.extend(NewClass, props.statics);
1690 delete props.statics;
1691 }
1692
1693 // mix includes into the prototype
1694 if (props.includes) {
1695 L.Util.extend.apply(null, [proto].concat(props.includes));
1696 delete props.includes;
1697 }
1698
1699 // merge options
1700 if (props.options && proto.options) {
1701 props.options = L.extend({}, proto.options, props.options);
1702 }
1703
1704 // mix given properties into the prototype
1705 L.extend(proto, props);
1706
1707 proto._initHooks = [];
1708
1709 var parent = this;
1710 // jshint camelcase: false
1711 NewClass.__super__ = parent.prototype;
1712
1713 // add method for calling all hooks
1714 proto.callInitHooks = function () {
1715
1716 if (this._initHooksCalled) { return; }
1717
1718 if (parent.prototype.callInitHooks) {
1719 parent.prototype.callInitHooks.call(this);
1720 }
1721
1722 this._initHooksCalled = true;
1723
1724 for (var i = 0, len = proto._initHooks.length; i < len; i++) {
1725 proto._initHooks[i].call(this);
1726 }
1727 };
1728
1729 return NewClass;
1730};
1731
1732
1733// method for adding properties to prototype
1734L.Class.include = function (props) {
1735 L.extend(this.prototype, props);
1736};
1737
1738// merge new default options to the Class
1739L.Class.mergeOptions = function (options) {
1740 L.extend(this.prototype.options, options);
1741};
1742
1743// add a constructor hook
1744L.Class.addInitHook = function (fn) { // (Function) || (String, args...)
1745 var args = Array.prototype.slice.call(arguments, 1);
1746
1747 var init = typeof fn === 'function' ? fn : function () {
1748 this[fn].apply(this, args);
1749 };
1750
1751 this.prototype._initHooks = this.prototype._initHooks || [];
1752 this.prototype._initHooks.push(init);
1753};
1754
1755
1756/*
1757 * L.Mixin.Events is used to add custom events functionality to Leaflet classes.
1758 */
1759
1760var eventsKey = '_leaflet_events';
1761
1762L.Mixin = {};
1763
1764L.Mixin.Events = {
1765
1766 addEventListener: function (types, fn, context) { // (String, Function[, Object]) or (Object[, Object])
1767
1768 // types can be a map of types/handlers
1769 if (L.Util.invokeEach(types, this.addEventListener, this, fn, context)) { return this; }
1770
1771 var events = this[eventsKey] = this[eventsKey] || {},
1772 contextId = context && context !== this && L.stamp(context),
1773 i, len, event, type, indexKey, indexLenKey, typeIndex;
1774
1775 // types can be a string of space-separated words
1776 types = L.Util.splitWords(types);
1777
1778 for (i = 0, len = types.length; i < len; i++) {
1779 event = {
1780 action: fn,
1781 context: context || this
1782 };
1783 type = types[i];
1784
1785 if (contextId) {
1786 // store listeners of a particular context in a separate hash (if it has an id)
1787 // gives a major performance boost when removing thousands of map layers
1788
1789 indexKey = type + '_idx';
1790 indexLenKey = indexKey + '_len';
1791
1792 typeIndex = events[indexKey] = events[indexKey] || {};
1793
1794 if (!typeIndex[contextId]) {
1795 typeIndex[contextId] = [];
1796
1797 // keep track of the number of keys in the index to quickly check if it's empty
1798 events[indexLenKey] = (events[indexLenKey] || 0) + 1;
1799 }
1800
1801 typeIndex[contextId].push(event);
1802
1803
1804 } else {
1805 events[type] = events[type] || [];
1806 events[type].push(event);
1807 }
1808 }
1809
1810 return this;
1811 },
1812
1813 hasEventListeners: function (type) { // (String) -> Boolean
1814 var events = this[eventsKey];
1815 return !!events && ((type in events && events[type].length > 0) ||
1816 (type + '_idx' in events && events[type + '_idx_len'] > 0));
1817 },
1818
1819 removeEventListener: function (types, fn, context) { // ([String, Function, Object]) or (Object[, Object])
1820
1821 if (!this[eventsKey]) {
1822 return this;
1823 }
1824
1825 if (!types) {
1826 return this.clearAllEventListeners();
1827 }
1828
1829 if (L.Util.invokeEach(types, this.removeEventListener, this, fn, context)) { return this; }
1830
1831 var events = this[eventsKey],
1832 contextId = context && context !== this && L.stamp(context),
1833 i, len, type, listeners, j, indexKey, indexLenKey, typeIndex, removed;
1834
1835 types = L.Util.splitWords(types);
1836
1837 for (i = 0, len = types.length; i < len; i++) {
1838 type = types[i];
1839 indexKey = type + '_idx';
1840 indexLenKey = indexKey + '_len';
1841
1842 typeIndex = events[indexKey];
1843
1844 if (!fn) {
1845 // clear all listeners for a type if function isn't specified
1846 delete events[type];
1847 delete events[indexKey];
1848 delete events[indexLenKey];
1849
1850 } else {
1851 listeners = contextId && typeIndex ? typeIndex[contextId] : events[type];
1852
1853 if (listeners) {
1854 for (j = listeners.length - 1; j >= 0; j--) {
1855 if ((listeners[j].action === fn) && (!context || (listeners[j].context === context))) {
1856 removed = listeners.splice(j, 1);
1857 // set the old action to a no-op, because it is possible
1858 // that the listener is being iterated over as part of a dispatch
1859 removed[0].action = L.Util.falseFn;
1860 }
1861 }
1862
1863 if (context && typeIndex && (listeners.length === 0)) {
1864 delete typeIndex[contextId];
1865 events[indexLenKey]--;
1866 }
1867 }
1868 }
1869 }
1870
1871 return this;
1872 },
1873
1874 clearAllEventListeners: function () {
1875 delete this[eventsKey];
1876 return this;
1877 },
1878
1879 fireEvent: function (type, data) { // (String[, Object])
1880 if (!this.hasEventListeners(type)) {
1881 return this;
1882 }
1883
1884 var event = L.Util.extend({}, data, { type: type, target: this });
1885
1886 var events = this[eventsKey],
1887 listeners, i, len, typeIndex, contextId;
1888
1889 if (events[type]) {
1890 // make sure adding/removing listeners inside other listeners won't cause infinite loop
1891 listeners = events[type].slice();
1892
1893 for (i = 0, len = listeners.length; i < len; i++) {
1894 listeners[i].action.call(listeners[i].context, event);
1895 }
1896 }
1897
1898 // fire event for the context-indexed listeners as well
1899 typeIndex = events[type + '_idx'];
1900
1901 for (contextId in typeIndex) {
1902 listeners = typeIndex[contextId].slice();
1903
1904 if (listeners) {
1905 for (i = 0, len = listeners.length; i < len; i++) {
1906 listeners[i].action.call(listeners[i].context, event);
1907 }
1908 }
1909 }
1910
1911 return this;
1912 },
1913
1914 addOneTimeEventListener: function (types, fn, context) {
1915
1916 if (L.Util.invokeEach(types, this.addOneTimeEventListener, this, fn, context)) { return this; }
1917
1918 var handler = L.bind(function () {
1919 this
1920 .removeEventListener(types, fn, context)
1921 .removeEventListener(types, handler, context);
1922 }, this);
1923
1924 return this
1925 .addEventListener(types, fn, context)
1926 .addEventListener(types, handler, context);
1927 }
1928};
1929
1930L.Mixin.Events.on = L.Mixin.Events.addEventListener;
1931L.Mixin.Events.off = L.Mixin.Events.removeEventListener;
1932L.Mixin.Events.once = L.Mixin.Events.addOneTimeEventListener;
1933L.Mixin.Events.fire = L.Mixin.Events.fireEvent;
1934
1935
1936/*
1937 * L.Browser handles different browser and feature detections for internal Leaflet use.
1938 */
1939
1940(function () {
1941
1942 var ie = 'ActiveXObject' in window,
1943 ielt9 = ie && !document.addEventListener,
1944
1945 // terrible browser detection to work around Safari / iOS / Android browser bugs
1946 ua = navigator.userAgent.toLowerCase(),
1947 webkit = ua.indexOf('webkit') !== -1,
1948 chrome = ua.indexOf('chrome') !== -1,
1949 phantomjs = ua.indexOf('phantom') !== -1,
1950 android = ua.indexOf('android') !== -1,
1951 android23 = ua.search('android [23]') !== -1,
1952 gecko = ua.indexOf('gecko') !== -1,
1953
1954 mobile = typeof orientation !== undefined + '',
1955 msPointer = !window.PointerEvent && window.MSPointerEvent,
1956 pointer = (window.PointerEvent && window.navigator.pointerEnabled) ||
1957 msPointer,
1958 retina = ('devicePixelRatio' in window && window.devicePixelRatio > 1) ||
1959 ('matchMedia' in window && window.matchMedia('(min-resolution:144dpi)') &&
1960 window.matchMedia('(min-resolution:144dpi)').matches),
1961
1962 doc = document.documentElement,
1963 ie3d = ie && ('transition' in doc.style),
1964 webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23,
1965 gecko3d = 'MozPerspective' in doc.style,
1966 opera3d = 'OTransition' in doc.style,
1967 any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d || opera3d) && !phantomjs;
1968
1969 var touch = !window.L_NO_TOUCH && !phantomjs && (pointer || 'ontouchstart' in window ||
1970 (window.DocumentTouch && document instanceof window.DocumentTouch));
1971
1972 L.Browser = {
1973 ie: ie,
1974 ielt9: ielt9,
1975 webkit: webkit,
1976 gecko: gecko && !webkit && !window.opera && !ie,
1977
1978 android: android,
1979 android23: android23,
1980
1981 chrome: chrome,
1982
1983 ie3d: ie3d,
1984 webkit3d: webkit3d,
1985 gecko3d: gecko3d,
1986 opera3d: opera3d,
1987 any3d: any3d,
1988
1989 mobile: mobile,
1990 mobileWebkit: mobile && webkit,
1991 mobileWebkit3d: mobile && webkit3d,
1992 mobileOpera: mobile && window.opera,
1993
1994 touch: touch,
1995 msPointer: msPointer,
1996 pointer: pointer,
1997
1998 retina: retina
1999 };
2000
2001}());
2002
2003
2004/*
2005 * L.Point represents a point with x and y coordinates.
2006 */
2007
2008L.Point = function (/*Number*/ x, /*Number*/ y, /*Boolean*/ round) {
2009 this.x = (round ? Math.round(x) : x);
2010 this.y = (round ? Math.round(y) : y);
2011};
2012
2013L.Point.prototype = {
2014
2015 clone: function () {
2016 return new L.Point(this.x, this.y);
2017 },
2018
2019 // non-destructive, returns a new point
2020 add: function (point) {
2021 return this.clone()._add(L.point(point));
2022 },
2023
2024 // destructive, used directly for performance in situations where it's safe to modify existing point
2025 _add: function (point) {
2026 this.x += point.x;
2027 this.y += point.y;
2028 return this;
2029 },
2030
2031 subtract: function (point) {
2032 return this.clone()._subtract(L.point(point));
2033 },
2034
2035 _subtract: function (point) {
2036 this.x -= point.x;
2037 this.y -= point.y;
2038 return this;
2039 },
2040
2041 divideBy: function (num) {
2042 return this.clone()._divideBy(num);
2043 },
2044
2045 _divideBy: function (num) {
2046 this.x /= num;
2047 this.y /= num;
2048 return this;
2049 },
2050
2051 multiplyBy: function (num) {
2052 return this.clone()._multiplyBy(num);
2053 },
2054
2055 _multiplyBy: function (num) {
2056 this.x *= num;
2057 this.y *= num;
2058 return this;
2059 },
2060
2061 round: function () {
2062 return this.clone()._round();
2063 },
2064
2065 _round: function () {
2066 this.x = Math.round(this.x);
2067 this.y = Math.round(this.y);
2068 return this;
2069 },
2070
2071 floor: function () {
2072 return this.clone()._floor();
2073 },
2074
2075 _floor: function () {
2076 this.x = Math.floor(this.x);
2077 this.y = Math.floor(this.y);
2078 return this;
2079 },
2080
2081 distanceTo: function (point) {
2082 point = L.point(point);
2083
2084 var x = point.x - this.x,
2085 y = point.y - this.y;
2086
2087 return Math.sqrt(x * x + y * y);
2088 },
2089
2090 equals: function (point) {
2091 point = L.point(point);
2092
2093 return point.x === this.x &&
2094 point.y === this.y;
2095 },
2096
2097 contains: function (point) {
2098 point = L.point(point);
2099
2100 return Math.abs(point.x) <= Math.abs(this.x) &&
2101 Math.abs(point.y) <= Math.abs(this.y);
2102 },
2103
2104 toString: function () {
2105 return 'Point(' +
2106 L.Util.formatNum(this.x) + ', ' +
2107 L.Util.formatNum(this.y) + ')';
2108 }
2109};
2110
2111L.point = function (x, y, round) {
2112 if (x instanceof L.Point) {
2113 return x;
2114 }
2115 if (L.Util.isArray(x)) {
2116 return new L.Point(x[0], x[1]);
2117 }
2118 if (x === undefined || x === null) {
2119 return x;
2120 }
2121 return new L.Point(x, y, round);
2122};
2123
2124
2125/*
2126 * L.Bounds represents a rectangular area on the screen in pixel coordinates.
2127 */
2128
2129L.Bounds = function (a, b) { //(Point, Point) or Point[]
2130 if (!a) { return; }
2131
2132 var points = b ? [a, b] : a;
2133
2134 for (var i = 0, len = points.length; i < len; i++) {
2135 this.extend(points[i]);
2136 }
2137};
2138
2139L.Bounds.prototype = {
2140 // extend the bounds to contain the given point
2141 extend: function (point) { // (Point)
2142 point = L.point(point);
2143
2144 if (!this.min && !this.max) {
2145 this.min = point.clone();
2146 this.max = point.clone();
2147 } else {
2148 this.min.x = Math.min(point.x, this.min.x);
2149 this.max.x = Math.max(point.x, this.max.x);
2150 this.min.y = Math.min(point.y, this.min.y);
2151 this.max.y = Math.max(point.y, this.max.y);
2152 }
2153 return this;
2154 },
2155
2156 getCenter: function (round) { // (Boolean) -> Point
2157 return new L.Point(
2158 (this.min.x + this.max.x) / 2,
2159 (this.min.y + this.max.y) / 2, round);
2160 },
2161
2162 getBottomLeft: function () { // -> Point
2163 return new L.Point(this.min.x, this.max.y);
2164 },
2165
2166 getTopRight: function () { // -> Point
2167 return new L.Point(this.max.x, this.min.y);
2168 },
2169
2170 getSize: function () {
2171 return this.max.subtract(this.min);
2172 },
2173
2174 contains: function (obj) { // (Bounds) or (Point) -> Boolean
2175 var min, max;
2176
2177 if (typeof obj[0] === 'number' || obj instanceof L.Point) {
2178 obj = L.point(obj);
2179 } else {
2180 obj = L.bounds(obj);
2181 }
2182
2183 if (obj instanceof L.Bounds) {
2184 min = obj.min;
2185 max = obj.max;
2186 } else {
2187 min = max = obj;
2188 }
2189
2190 return (min.x >= this.min.x) &&
2191 (max.x <= this.max.x) &&
2192 (min.y >= this.min.y) &&
2193 (max.y <= this.max.y);
2194 },
2195
2196 intersects: function (bounds) { // (Bounds) -> Boolean
2197 bounds = L.bounds(bounds);
2198
2199 var min = this.min,
2200 max = this.max,
2201 min2 = bounds.min,
2202 max2 = bounds.max,
2203 xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
2204 yIntersects = (max2.y >= min.y) && (min2.y <= max.y);
2205
2206 return xIntersects && yIntersects;
2207 },
2208
2209 isValid: function () {
2210 return !!(this.min && this.max);
2211 }
2212};
2213
2214L.bounds = function (a, b) { // (Bounds) or (Point, Point) or (Point[])
2215 if (!a || a instanceof L.Bounds) {
2216 return a;
2217 }
2218 return new L.Bounds(a, b);
2219};
2220
2221
2222/*
2223 * L.Transformation is an utility class to perform simple point transformations through a 2d-matrix.
2224 */
2225
2226L.Transformation = function (a, b, c, d) {
2227 this._a = a;
2228 this._b = b;
2229 this._c = c;
2230 this._d = d;
2231};
2232
2233L.Transformation.prototype = {
2234 transform: function (point, scale) { // (Point, Number) -> Point
2235 return this._transform(point.clone(), scale);
2236 },
2237
2238 // destructive transform (faster)
2239 _transform: function (point, scale) {
2240 scale = scale || 1;
2241 point.x = scale * (this._a * point.x + this._b);
2242 point.y = scale * (this._c * point.y + this._d);
2243 return point;
2244 },
2245
2246 untransform: function (point, scale) {
2247 scale = scale || 1;
2248 return new L.Point(
2249 (point.x / scale - this._b) / this._a,
2250 (point.y / scale - this._d) / this._c);
2251 }
2252};
2253
2254
2255/*
2256 * L.DomUtil contains various utility functions for working with DOM.
2257 */
2258
2259L.DomUtil = {
2260 get: function (id) {
2261 return (typeof id === 'string' ? document.getElementById(id) : id);
2262 },
2263
2264 getStyle: function (el, style) {
2265
2266 var value = el.style[style];
2267
2268 if (!value && el.currentStyle) {
2269 value = el.currentStyle[style];
2270 }
2271
2272 if ((!value || value === 'auto') && document.defaultView) {
2273 var css = document.defaultView.getComputedStyle(el, null);
2274 value = css ? css[style] : null;
2275 }
2276
2277 return value === 'auto' ? null : value;
2278 },
2279
2280 getViewportOffset: function (element) {
2281
2282 var top = 0,
2283 left = 0,
2284 el = element,
2285 docBody = document.body,
2286 docEl = document.documentElement,
2287 pos;
2288
2289 do {
2290 top += el.offsetTop || 0;
2291 left += el.offsetLeft || 0;
2292
2293 //add borders
2294 top += parseInt(L.DomUtil.getStyle(el, 'borderTopWidth'), 10) || 0;
2295 left += parseInt(L.DomUtil.getStyle(el, 'borderLeftWidth'), 10) || 0;
2296
2297 pos = L.DomUtil.getStyle(el, 'position');
2298
2299 if (el.offsetParent === docBody && pos === 'absolute') { break; }
2300
2301 if (pos === 'fixed') {
2302 top += docBody.scrollTop || docEl.scrollTop || 0;
2303 left += docBody.scrollLeft || docEl.scrollLeft || 0;
2304 break;
2305 }
2306
2307 if (pos === 'relative' && !el.offsetLeft) {
2308 var width = L.DomUtil.getStyle(el, 'width'),
2309 maxWidth = L.DomUtil.getStyle(el, 'max-width'),
2310 r = el.getBoundingClientRect();
2311
2312 if (width !== 'none' || maxWidth !== 'none') {
2313 left += r.left + el.clientLeft;
2314 }
2315
2316 //calculate full y offset since we're breaking out of the loop
2317 top += r.top + (docBody.scrollTop || docEl.scrollTop || 0);
2318
2319 break;
2320 }
2321
2322 el = el.offsetParent;
2323
2324 } while (el);
2325
2326 el = element;
2327
2328 do {
2329 if (el === docBody) { break; }
2330
2331 top -= el.scrollTop || 0;
2332 left -= el.scrollLeft || 0;
2333
2334 el = el.parentNode;
2335 } while (el);
2336
2337 return new L.Point(left, top);
2338 },
2339
2340 documentIsLtr: function () {
2341 if (!L.DomUtil._docIsLtrCached) {
2342 L.DomUtil._docIsLtrCached = true;
2343 L.DomUtil._docIsLtr = L.DomUtil.getStyle(document.body, 'direction') === 'ltr';
2344 }
2345 return L.DomUtil._docIsLtr;
2346 },
2347
2348 create: function (tagName, className, container) {
2349
2350 var el = document.createElement(tagName);
2351 el.className = className;
2352
2353 if (container) {
2354 container.appendChild(el);
2355 }
2356
2357 return el;
2358 },
2359
2360 hasClass: function (el, name) {
2361 if (el.classList !== undefined) {
2362 return el.classList.contains(name);
2363 }
2364 var className = L.DomUtil._getClass(el);
2365 return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
2366 },
2367
2368 addClass: function (el, name) {
2369 if (el.classList !== undefined) {
2370 var classes = L.Util.splitWords(name);
2371 for (var i = 0, len = classes.length; i < len; i++) {
2372 el.classList.add(classes[i]);
2373 }
2374 } else if (!L.DomUtil.hasClass(el, name)) {
2375 var className = L.DomUtil._getClass(el);
2376 L.DomUtil._setClass(el, (className ? className + ' ' : '') + name);
2377 }
2378 },
2379
2380 removeClass: function (el, name) {
2381 if (el.classList !== undefined) {
2382 el.classList.remove(name);
2383 } else {
2384 L.DomUtil._setClass(el, L.Util.trim((' ' + L.DomUtil._getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
2385 }
2386 },
2387
2388 _setClass: function (el, name) {
2389 if (el.className.baseVal === undefined) {
2390 el.className = name;
2391 } else {
2392 // in case of SVG element
2393 el.className.baseVal = name;
2394 }
2395 },
2396
2397 _getClass: function (el) {
2398 return el.className.baseVal === undefined ? el.className : el.className.baseVal;
2399 },
2400
2401 setOpacity: function (el, value) {
2402
2403 if ('opacity' in el.style) {
2404 el.style.opacity = value;
2405
2406 } else if ('filter' in el.style) {
2407
2408 var filter = false,
2409 filterName = 'DXImageTransform.Microsoft.Alpha';
2410
2411 // filters collection throws an error if we try to retrieve a filter that doesn't exist
2412 try {
2413 filter = el.filters.item(filterName);
2414 } catch (e) {
2415 // don't set opacity to 1 if we haven't already set an opacity,
2416 // it isn't needed and breaks transparent pngs.
2417 if (value === 1) { return; }
2418 }
2419
2420 value = Math.round(value * 100);
2421
2422 if (filter) {
2423 filter.Enabled = (value !== 100);
2424 filter.Opacity = value;
2425 } else {
2426 el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
2427 }
2428 }
2429 },
2430
2431 testProp: function (props) {
2432
2433 var style = document.documentElement.style;
2434
2435 for (var i = 0; i < props.length; i++) {
2436 if (props[i] in style) {
2437 return props[i];
2438 }
2439 }
2440 return false;
2441 },
2442
2443 getTranslateString: function (point) {
2444 // on WebKit browsers (Chrome/Safari/iOS Safari/Android) using translate3d instead of translate
2445 // makes animation smoother as it ensures HW accel is used. Firefox 13 doesn't care
2446 // (same speed either way), Opera 12 doesn't support translate3d
2447
2448 var is3d = L.Browser.webkit3d,
2449 open = 'translate' + (is3d ? '3d' : '') + '(',
2450 close = (is3d ? ',0' : '') + ')';
2451
2452 return open + point.x + 'px,' + point.y + 'px' + close;
2453 },
2454
2455 getScaleString: function (scale, origin) {
2456
2457 var preTranslateStr = L.DomUtil.getTranslateString(origin.add(origin.multiplyBy(-1 * scale))),
2458 scaleStr = ' scale(' + scale + ') ';
2459
2460 return preTranslateStr + scaleStr;
2461 },
2462
2463 setPosition: function (el, point, disable3D) { // (HTMLElement, Point[, Boolean])
2464
2465 // jshint camelcase: false
2466 el._leaflet_pos = point;
2467
2468 if (!disable3D && L.Browser.any3d) {
2469 el.style[L.DomUtil.TRANSFORM] = L.DomUtil.getTranslateString(point);
2470 } else {
2471 el.style.left = point.x + 'px';
2472 el.style.top = point.y + 'px';
2473 }
2474 },
2475
2476 getPosition: function (el) {
2477 // this method is only used for elements previously positioned using setPosition,
2478 // so it's safe to cache the position for performance
2479
2480 // jshint camelcase: false
2481 return el._leaflet_pos;
2482 }
2483};
2484
2485
2486// prefix style property names
2487
2488L.DomUtil.TRANSFORM = L.DomUtil.testProp(
2489 ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform']);
2490
2491// webkitTransition comes first because some browser versions that drop vendor prefix don't do
2492// the same for the transitionend event, in particular the Android 4.1 stock browser
2493
2494L.DomUtil.TRANSITION = L.DomUtil.testProp(
2495 ['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);
2496
2497L.DomUtil.TRANSITION_END =
2498 L.DomUtil.TRANSITION === 'webkitTransition' || L.DomUtil.TRANSITION === 'OTransition' ?
2499 L.DomUtil.TRANSITION + 'End' : 'transitionend';
2500
2501(function () {
2502 if ('onselectstart' in document) {
2503 L.extend(L.DomUtil, {
2504 disableTextSelection: function () {
2505 L.DomEvent.on(window, 'selectstart', L.DomEvent.preventDefault);
2506 },
2507
2508 enableTextSelection: function () {
2509 L.DomEvent.off(window, 'selectstart', L.DomEvent.preventDefault);
2510 }
2511 });
2512 } else {
2513 var userSelectProperty = L.DomUtil.testProp(
2514 ['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);
2515
2516 L.extend(L.DomUtil, {
2517 disableTextSelection: function () {
2518 if (userSelectProperty) {
2519 var style = document.documentElement.style;
2520 this._userSelect = style[userSelectProperty];
2521 style[userSelectProperty] = 'none';
2522 }
2523 },
2524
2525 enableTextSelection: function () {
2526 if (userSelectProperty) {
2527 document.documentElement.style[userSelectProperty] = this._userSelect;
2528 delete this._userSelect;
2529 }
2530 }
2531 });
2532 }
2533
2534 L.extend(L.DomUtil, {
2535 disableImageDrag: function () {
2536 L.DomEvent.on(window, 'dragstart', L.DomEvent.preventDefault);
2537 },
2538
2539 enableImageDrag: function () {
2540 L.DomEvent.off(window, 'dragstart', L.DomEvent.preventDefault);
2541 }
2542 });
2543})();
2544
2545
2546/*
2547 * L.LatLng represents a geographical point with latitude and longitude coordinates.
2548 */
2549
2550L.LatLng = function (lat, lng, alt) { // (Number, Number, Number)
2551 lat = parseFloat(lat);
2552 lng = parseFloat(lng);
2553
2554 if (isNaN(lat) || isNaN(lng)) {
2555 throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
2556 }
2557
2558 this.lat = lat;
2559 this.lng = lng;
2560
2561 if (alt !== undefined) {
2562 this.alt = parseFloat(alt);
2563 }
2564};
2565
2566L.extend(L.LatLng, {
2567 DEG_TO_RAD: Math.PI / 180,
2568 RAD_TO_DEG: 180 / Math.PI,
2569 MAX_MARGIN: 1.0E-9 // max margin of error for the "equals" check
2570});
2571
2572L.LatLng.prototype = {
2573 equals: function (obj) { // (LatLng) -> Boolean
2574 if (!obj) { return false; }
2575
2576 obj = L.latLng(obj);
2577
2578 var margin = Math.max(
2579 Math.abs(this.lat - obj.lat),
2580 Math.abs(this.lng - obj.lng));
2581
2582 return margin <= L.LatLng.MAX_MARGIN;
2583 },
2584
2585 toString: function (precision) { // (Number) -> String
2586 return 'LatLng(' +
2587 L.Util.formatNum(this.lat, precision) + ', ' +
2588 L.Util.formatNum(this.lng, precision) + ')';
2589 },
2590
2591 // Haversine distance formula, see http://en.wikipedia.org/wiki/Haversine_formula
2592 // TODO move to projection code, LatLng shouldn't know about Earth
2593 distanceTo: function (other) { // (LatLng) -> Number
2594 other = L.latLng(other);
2595
2596 var R = 6367000.0, // earth radius in meters
2597 d2r = L.LatLng.DEG_TO_RAD,
2598 dLat = (other.lat - this.lat) * d2r,
2599 dLon = (other.lng - this.lng) * d2r,
2600 lat1 = this.lat * d2r,
2601 lat2 = other.lat * d2r,
2602 sin1 = Math.sin(dLat / 2),
2603 sin2 = Math.sin(dLon / 2);
2604
2605 var a = sin1 * sin1 + sin2 * sin2 * Math.cos(lat1) * Math.cos(lat2);
2606
2607 return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
2608 },
2609
2610 wrap: function (a, b) { // (Number, Number) -> LatLng
2611 var lng = this.lng;
2612
2613 a = a || -180;
2614 b = b || 180;
2615
2616 lng = (lng + b) % (b - a) + (lng < a || lng === b ? b : a);
2617
2618 return new L.LatLng(this.lat, lng);
2619 }
2620};
2621
2622L.latLng = function (a, b) { // (LatLng) or ([Number, Number]) or (Number, Number)
2623 if (a instanceof L.LatLng) {
2624 return a;
2625 }
2626 if (L.Util.isArray(a)) {
2627 if (typeof a[0] === 'number' || typeof a[0] === 'string') {
2628 return new L.LatLng(a[0], a[1], a[2]);
2629 } else {
2630 return null;
2631 }
2632 }
2633 if (a === undefined || a === null) {
2634 return a;
2635 }
2636 if (typeof a === 'object' && 'lat' in a) {
2637 return new L.LatLng(a.lat, 'lng' in a ? a.lng : a.lon);
2638 }
2639 if (b === undefined) {
2640 return null;
2641 }
2642 return new L.LatLng(a, b);
2643};
2644
2645
2646
2647/*
2648 * L.LatLngBounds represents a rectangular area on the map in geographical coordinates.
2649 */
2650
2651L.LatLngBounds = function (southWest, northEast) { // (LatLng, LatLng) or (LatLng[])
2652 if (!southWest) { return; }
2653
2654 var latlngs = northEast ? [southWest, northEast] : southWest;
2655
2656 for (var i = 0, len = latlngs.length; i < len; i++) {
2657 this.extend(latlngs[i]);
2658 }
2659};
2660
2661L.LatLngBounds.prototype = {
2662 // extend the bounds to contain the given point or bounds
2663 extend: function (obj) { // (LatLng) or (LatLngBounds)
2664 if (!obj) { return this; }
2665
2666 var latLng = L.latLng(obj);
2667 if (latLng !== null) {
2668 obj = latLng;
2669 } else {
2670 obj = L.latLngBounds(obj);
2671 }
2672
2673 if (obj instanceof L.LatLng) {
2674 if (!this._southWest && !this._northEast) {
2675 this._southWest = new L.LatLng(obj.lat, obj.lng);
2676 this._northEast = new L.LatLng(obj.lat, obj.lng);
2677 } else {
2678 this._southWest.lat = Math.min(obj.lat, this._southWest.lat);
2679 this._southWest.lng = Math.min(obj.lng, this._southWest.lng);
2680
2681 this._northEast.lat = Math.max(obj.lat, this._northEast.lat);
2682 this._northEast.lng = Math.max(obj.lng, this._northEast.lng);
2683 }
2684 } else if (obj instanceof L.LatLngBounds) {
2685 this.extend(obj._southWest);
2686 this.extend(obj._northEast);
2687 }
2688 return this;
2689 },
2690
2691 // extend the bounds by a percentage
2692 pad: function (bufferRatio) { // (Number) -> LatLngBounds
2693 var sw = this._southWest,
2694 ne = this._northEast,
2695 heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
2696 widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;
2697
2698 return new L.LatLngBounds(
2699 new L.LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
2700 new L.LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
2701 },
2702
2703 getCenter: function () { // -> LatLng
2704 return new L.LatLng(
2705 (this._southWest.lat + this._northEast.lat) / 2,
2706 (this._southWest.lng + this._northEast.lng) / 2);
2707 },
2708
2709 getSouthWest: function () {
2710 return this._southWest;
2711 },
2712
2713 getNorthEast: function () {
2714 return this._northEast;
2715 },
2716
2717 getNorthWest: function () {
2718 return new L.LatLng(this.getNorth(), this.getWest());
2719 },
2720
2721 getSouthEast: function () {
2722 return new L.LatLng(this.getSouth(), this.getEast());
2723 },
2724
2725 getWest: function () {
2726 return this._southWest.lng;
2727 },
2728
2729 getSouth: function () {
2730 return this._southWest.lat;
2731 },
2732
2733 getEast: function () {
2734 return this._northEast.lng;
2735 },
2736
2737 getNorth: function () {
2738 return this._northEast.lat;
2739 },
2740
2741 contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
2742 if (typeof obj[0] === 'number' || obj instanceof L.LatLng) {
2743 obj = L.latLng(obj);
2744 } else {
2745 obj = L.latLngBounds(obj);
2746 }
2747
2748 var sw = this._southWest,
2749 ne = this._northEast,
2750 sw2, ne2;
2751
2752 if (obj instanceof L.LatLngBounds) {
2753 sw2 = obj.getSouthWest();
2754 ne2 = obj.getNorthEast();
2755 } else {
2756 sw2 = ne2 = obj;
2757 }
2758
2759 return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
2760 (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
2761 },
2762
2763 intersects: function (bounds) { // (LatLngBounds)
2764 bounds = L.latLngBounds(bounds);
2765
2766 var sw = this._southWest,
2767 ne = this._northEast,
2768 sw2 = bounds.getSouthWest(),
2769 ne2 = bounds.getNorthEast(),
2770
2771 latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
2772 lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);
2773
2774 return latIntersects && lngIntersects;
2775 },
2776
2777 toBBoxString: function () {
2778 return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
2779 },
2780
2781 equals: function (bounds) { // (LatLngBounds)
2782 if (!bounds) { return false; }
2783
2784 bounds = L.latLngBounds(bounds);
2785
2786 return this._southWest.equals(bounds.getSouthWest()) &&
2787 this._northEast.equals(bounds.getNorthEast());
2788 },
2789
2790 isValid: function () {
2791 return !!(this._southWest && this._northEast);
2792 }
2793};
2794
2795//TODO International date line?
2796
2797L.latLngBounds = function (a, b) { // (LatLngBounds) or (LatLng, LatLng)
2798 if (!a || a instanceof L.LatLngBounds) {
2799 return a;
2800 }
2801 return new L.LatLngBounds(a, b);
2802};
2803
2804
2805/*
2806 * L.Projection contains various geographical projections used by CRS classes.
2807 */
2808
2809L.Projection = {};
2810
2811
2812/*
2813 * Spherical Mercator is the most popular map projection, used by EPSG:3857 CRS used by default.
2814 */
2815
2816L.Projection.SphericalMercator = {
2817 MAX_LATITUDE: 85.0511287798,
2818
2819 project: function (latlng) { // (LatLng) -> Point
2820 var d = L.LatLng.DEG_TO_RAD,
2821 max = this.MAX_LATITUDE,
2822 lat = Math.max(Math.min(max, latlng.lat), -max),
2823 x = latlng.lng * d,
2824 y = lat * d;
2825
2826 y = Math.log(Math.tan((Math.PI / 4) + (y / 2)));
2827
2828 return new L.Point(x, y);
2829 },
2830
2831 unproject: function (point) { // (Point, Boolean) -> LatLng
2832 var d = L.LatLng.RAD_TO_DEG,
2833 lng = point.x * d,
2834 lat = (2 * Math.atan(Math.exp(point.y)) - (Math.PI / 2)) * d;
2835
2836 return new L.LatLng(lat, lng);
2837 }
2838};
2839
2840
2841/*
2842 * Simple equirectangular (Plate Carree) projection, used by CRS like EPSG:4326 and Simple.
2843 */
2844
2845L.Projection.LonLat = {
2846 project: function (latlng) {
2847 return new L.Point(latlng.lng, latlng.lat);
2848 },
2849
2850 unproject: function (point) {
2851 return new L.LatLng(point.y, point.x);
2852 }
2853};
2854
2855
2856/*
2857 * L.CRS is a base object for all defined CRS (Coordinate Reference Systems) in Leaflet.
2858 */
2859
2860L.CRS = {
2861 latLngToPoint: function (latlng, zoom) { // (LatLng, Number) -> Point
2862 var projectedPoint = this.projection.project(latlng),
2863 scale = this.scale(zoom);
2864
2865 return this.transformation._transform(projectedPoint, scale);
2866 },
2867
2868 pointToLatLng: function (point, zoom) { // (Point, Number[, Boolean]) -> LatLng
2869 var scale = this.scale(zoom),
2870 untransformedPoint = this.transformation.untransform(point, scale);
2871
2872 return this.projection.unproject(untransformedPoint);
2873 },
2874
2875 project: function (latlng) {
2876 return this.projection.project(latlng);
2877 },
2878
2879 scale: function (zoom) {
2880 return 256 * Math.pow(2, zoom);
2881 },
2882
2883 getSize: function (zoom) {
2884 var s = this.scale(zoom);
2885 return L.point(s, s);
2886 }
2887};
2888
2889
2890/*
2891 * A simple CRS that can be used for flat non-Earth maps like panoramas or game maps.
2892 */
2893
2894L.CRS.Simple = L.extend({}, L.CRS, {
2895 projection: L.Projection.LonLat,
2896 transformation: new L.Transformation(1, 0, -1, 0),
2897
2898 scale: function (zoom) {
2899 return Math.pow(2, zoom);
2900 }
2901});
2902
2903
2904/*
2905 * L.CRS.EPSG3857 (Spherical Mercator) is the most common CRS for web mapping
2906 * and is used by Leaflet by default.
2907 */
2908
2909L.CRS.EPSG3857 = L.extend({}, L.CRS, {
2910 code: 'EPSG:3857',
2911
2912 projection: L.Projection.SphericalMercator,
2913 transformation: new L.Transformation(0.5 / Math.PI, 0.5, -0.5 / Math.PI, 0.5),
2914
2915 project: function (latlng) { // (LatLng) -> Point
2916 var projectedPoint = this.projection.project(latlng),
2917 earthRadius = 6367000.0;
2918 return projectedPoint.multiplyBy(earthRadius);
2919 }
2920});
2921
2922L.CRS.EPSG900913 = L.extend({}, L.CRS.EPSG3857, {
2923 code: 'EPSG:900913'
2924});
2925
2926
2927/*
2928 * L.CRS.EPSG4326 is a CRS popular among advanced GIS specialists.
2929 */
2930
2931L.CRS.EPSG4326 = L.extend({}, L.CRS, {
2932 code: 'EPSG:4326',
2933
2934 projection: L.Projection.LonLat,
2935 transformation: new L.Transformation(1 / 360, 0.5, -1 / 360, 0.5)
2936});
2937
2938
2939/*
2940 * L.Map is the central class of the API - it is used to create a map.
2941 */
2942
2943L.Map = L.Class.extend({
2944
2945 includes: L.Mixin.Events,
2946
2947 options: {
2948 crs: L.CRS.EPSG3857,
2949
2950 /*
2951 center: LatLng,
2952 zoom: Number,
2953 layers: Array,
2954 */
2955
2956 fadeAnimation: L.DomUtil.TRANSITION && !L.Browser.android23,
2957 trackResize: true,
2958 markerZoomAnimation: L.DomUtil.TRANSITION && L.Browser.any3d
2959 },
2960
2961 initialize: function (id, options) { // (HTMLElement or String, Object)
2962 options = L.setOptions(this, options);
2963
2964
2965 this._initContainer(id);
2966 this._initLayout();
2967
2968 // hack for https://github.com/Leaflet/Leaflet/issues/1980
2969 this._onResize = L.bind(this._onResize, this);
2970
2971 this._initEvents();
2972
2973 if (options.maxBounds) {
2974 this.setMaxBounds(options.maxBounds);
2975 }
2976
2977 if (options.center && options.zoom !== undefined) {
2978 this.setView(L.latLng(options.center), options.zoom, {reset: true});
2979 }
2980
2981 this._handlers = [];
2982
2983 this._layers = {};
2984 this._zoomBoundLayers = {};
2985 this._tileLayersNum = 0;
2986
2987 this.callInitHooks();
2988
2989 this._addLayers(options.layers);
2990 },
2991
2992
2993 // public methods that modify map state
2994
2995 // replaced by animation-powered implementation in Map.PanAnimation.js
2996 setView: function (center, zoom) {
2997 zoom = zoom === undefined ? this.getZoom() : zoom;
2998 this._resetView(L.latLng(center), this._limitZoom(zoom));
2999 return this;
3000 },
3001
3002 setZoom: function (zoom, options) {
3003 if (!this._loaded) {
3004 this._zoom = this._limitZoom(zoom);
3005 return this;
3006 }
3007 return this.setView(this.getCenter(), zoom, {zoom: options});
3008 },
3009
3010 zoomIn: function (delta, options) {
3011 return this.setZoom(this._zoom + (delta || 1), options);
3012 },
3013
3014 zoomOut: function (delta, options) {
3015 return this.setZoom(this._zoom - (delta || 1), options);
3016 },
3017
3018 setZoomAround: function (latlng, zoom, options) {
3019 var scale = this.getZoomScale(zoom),
3020 viewHalf = this.getSize().divideBy(2),
3021 containerPoint = latlng instanceof L.Point ? latlng : this.latLngToContainerPoint(latlng),
3022
3023 centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
3024 newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));
3025
3026 return this.setView(newCenter, zoom, {zoom: options});
3027 },
3028
3029 fitBounds: function (bounds, options) {
3030
3031 options = options || {};
3032 bounds = bounds.getBounds ? bounds.getBounds() : L.latLngBounds(bounds);
3033
3034 var paddingTL = L.point(options.paddingTopLeft || options.padding || [0, 0]),
3035 paddingBR = L.point(options.paddingBottomRight || options.padding || [0, 0]),
3036
3037 zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));
3038
3039 zoom = (options.maxZoom) ? Math.min(options.maxZoom, zoom) : zoom;
3040
3041 var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),
3042
3043 swPoint = this.project(bounds.getSouthWest(), zoom),
3044 nePoint = this.project(bounds.getNorthEast(), zoom),
3045 center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);
3046
3047 return this.setView(center, zoom, options);
3048 },
3049
3050 fitWorld: function (options) {
3051 return this.fitBounds([[-90, -180], [90, 180]], options);
3052 },
3053
3054 panTo: function (center, options) { // (LatLng)
3055 return this.setView(center, this._zoom, {pan: options});
3056 },
3057
3058 panBy: function (offset) { // (Point)
3059 // replaced with animated panBy in Map.PanAnimation.js
3060 this.fire('movestart');
3061
3062 this._rawPanBy(L.point(offset));
3063
3064 this.fire('move');
3065 return this.fire('moveend');
3066 },
3067
3068 setMaxBounds: function (bounds) {
3069 bounds = L.latLngBounds(bounds);
3070
3071 this.options.maxBounds = bounds;
3072
3073 if (!bounds) {
3074 return this.off('moveend', this._panInsideMaxBounds, this);
3075 }
3076
3077 if (this._loaded) {
3078 this._panInsideMaxBounds();
3079 }
3080
3081 return this.on('moveend', this._panInsideMaxBounds, this);
3082 },
3083
3084 panInsideBounds: function (bounds, options) {
3085 var center = this.getCenter(),
3086 newCenter = this._limitCenter(center, this._zoom, bounds);
3087
3088 if (center.equals(newCenter)) { return this; }
3089
3090 return this.panTo(newCenter, options);
3091 },
3092
3093 addLayer: function (layer) {
3094 // TODO method is too big, refactor
3095
3096 var id = L.stamp(layer);
3097
3098 if (this._layers[id]) { return this; }
3099
3100 this._layers[id] = layer;
3101
3102 // TODO getMaxZoom, getMinZoom in ILayer (instead of options)
3103 if (layer.options && (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom))) {
3104 this._zoomBoundLayers[id] = layer;
3105 this._updateZoomLevels();
3106 }
3107
3108 // TODO looks ugly, refactor!!!
3109 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
3110 this._tileLayersNum++;
3111 this._tileLayersToLoad++;
3112 layer.on('load', this._onTileLayerLoad, this);
3113 }
3114
3115 if (this._loaded) {
3116 this._layerAdd(layer);
3117 }
3118
3119 return this;
3120 },
3121
3122 removeLayer: function (layer) {
3123 var id = L.stamp(layer);
3124
3125 if (!this._layers[id]) { return this; }
3126
3127 if (this._loaded) {
3128 layer.onRemove(this);
3129 }
3130
3131 delete this._layers[id];
3132
3133 if (this._loaded) {
3134 this.fire('layerremove', {layer: layer});
3135 }
3136
3137 if (this._zoomBoundLayers[id]) {
3138 delete this._zoomBoundLayers[id];
3139 this._updateZoomLevels();
3140 }
3141
3142 // TODO looks ugly, refactor
3143 if (this.options.zoomAnimation && L.TileLayer && (layer instanceof L.TileLayer)) {
3144 this._tileLayersNum--;
3145 this._tileLayersToLoad--;
3146 layer.off('load', this._onTileLayerLoad, this);
3147 }
3148
3149 return this;
3150 },
3151
3152 hasLayer: function (layer) {
3153 if (!layer) { return false; }
3154
3155 return (L.stamp(layer) in this._layers);
3156 },
3157
3158 eachLayer: function (method, context) {
3159 for (var i in this._layers) {
3160 method.call(context, this._layers[i]);
3161 }
3162 return this;
3163 },
3164
3165 invalidateSize: function (options) {
3166 if (!this._loaded) { return this; }
3167
3168 options = L.extend({
3169 animate: false,
3170 pan: true
3171 }, options === true ? {animate: true} : options);
3172
3173 var oldSize = this.getSize();
3174 this._sizeChanged = true;
3175 this._initialCenter = null;
3176
3177 var newSize = this.getSize(),
3178 oldCenter = oldSize.divideBy(2).round(),
3179 newCenter = newSize.divideBy(2).round(),
3180 offset = oldCenter.subtract(newCenter);
3181
3182 if (!offset.x && !offset.y) { return this; }
3183
3184 if (options.animate && options.pan) {
3185 this.panBy(offset);
3186
3187 } else {
3188 if (options.pan) {
3189 this._rawPanBy(offset);
3190 }
3191
3192 this.fire('move');
3193
3194 if (options.debounceMoveend) {
3195 clearTimeout(this._sizeTimer);
3196 this._sizeTimer = setTimeout(L.bind(this.fire, this, 'moveend'), 200);
3197 } else {
3198 this.fire('moveend');
3199 }
3200 }
3201
3202 return this.fire('resize', {
3203 oldSize: oldSize,
3204 newSize: newSize
3205 });
3206 },
3207
3208 // TODO handler.addTo
3209 addHandler: function (name, HandlerClass) {
3210 if (!HandlerClass) { return this; }
3211
3212 var handler = this[name] = new HandlerClass(this);
3213
3214 this._handlers.push(handler);
3215
3216 if (this.options[name]) {
3217 handler.enable();
3218 }
3219
3220 return this;
3221 },
3222
3223 remove: function () {
3224 if (this._loaded) {
3225 this.fire('unload');
3226 }
3227
3228 this._initEvents('off');
3229
3230 try {
3231 // throws error in IE6-8
3232 delete this._container._leaflet;
3233 } catch (e) {
3234 this._container._leaflet = undefined;
3235 }
3236
3237 this._clearPanes();
3238 if (this._clearControlPos) {
3239 this._clearControlPos();
3240 }
3241
3242 this._clearHandlers();
3243
3244 return this;
3245 },
3246
3247
3248 // public methods for getting map state
3249
3250 getCenter: function () { // (Boolean) -> LatLng
3251 this._checkIfLoaded();
3252
3253 if (this._initialCenter && !this._moved()) {
3254 return this._initialCenter;
3255 }
3256 return this.layerPointToLatLng(this._getCenterLayerPoint());
3257 },
3258
3259 getZoom: function () {
3260 return this._zoom;
3261 },
3262
3263 getBounds: function () {
3264 var bounds = this.getPixelBounds(),
3265 sw = this.unproject(bounds.getBottomLeft()),
3266 ne = this.unproject(bounds.getTopRight());
3267
3268 return new L.LatLngBounds(sw, ne);
3269 },
3270
3271 getMinZoom: function () {
3272 return this.options.minZoom === undefined ?
3273 (this._layersMinZoom === undefined ? 0 : this._layersMinZoom) :
3274 this.options.minZoom;
3275 },
3276
3277 getMaxZoom: function () {
3278 return this.options.maxZoom === undefined ?
3279 (this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
3280 this.options.maxZoom;
3281 },
3282
3283 getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
3284 bounds = L.latLngBounds(bounds);
3285
3286 var zoom = this.getMinZoom() - (inside ? 1 : 0),
3287 maxZoom = this.getMaxZoom(),
3288 size = this.getSize(),
3289
3290 nw = bounds.getNorthWest(),
3291 se = bounds.getSouthEast(),
3292
3293 zoomNotFound = true,
3294 boundsSize;
3295
3296 padding = L.point(padding || [0, 0]);
3297
3298 do {
3299 zoom++;
3300 boundsSize = this.project(se, zoom).subtract(this.project(nw, zoom)).add(padding);
3301 zoomNotFound = !inside ? size.contains(boundsSize) : boundsSize.x < size.x || boundsSize.y < size.y;
3302
3303 } while (zoomNotFound && zoom <= maxZoom);
3304
3305 if (zoomNotFound && inside) {
3306 return null;
3307 }
3308
3309 return inside ? zoom : zoom - 1;
3310 },
3311
3312 getSize: function () {
3313 if (!this._size || this._sizeChanged) {
3314 this._size = new L.Point(
3315 this._container.clientWidth,
3316 this._container.clientHeight);
3317
3318 this._sizeChanged = false;
3319 }
3320 return this._size.clone();
3321 },
3322
3323 getPixelBounds: function () {
3324 var topLeftPoint = this._getTopLeftPoint();
3325 return new L.Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
3326 },
3327
3328 getPixelOrigin: function () {
3329 this._checkIfLoaded();
3330 return this._initialTopLeftPoint;
3331 },
3332
3333 getPanes: function () {
3334 return this._panes;
3335 },
3336
3337 getContainer: function () {
3338 return this._container;
3339 },
3340
3341
3342 // TODO replace with universal implementation after refactoring projections
3343
3344 getZoomScale: function (toZoom) {
3345 var crs = this.options.crs;
3346 return crs.scale(toZoom) / crs.scale(this._zoom);
3347 },
3348
3349 getScaleZoom: function (scale) {
3350 return this._zoom + (Math.log(scale) / Math.LN2);
3351 },
3352
3353
3354 // conversion methods
3355
3356 project: function (latlng, zoom) { // (LatLng[, Number]) -> Point
3357 zoom = zoom === undefined ? this._zoom : zoom;
3358 return this.options.crs.latLngToPoint(L.latLng(latlng), zoom);
3359 },
3360
3361 unproject: function (point, zoom) { // (Point[, Number]) -> LatLng
3362 zoom = zoom === undefined ? this._zoom : zoom;
3363 return this.options.crs.pointToLatLng(L.point(point), zoom);
3364 },
3365
3366 layerPointToLatLng: function (point) { // (Point)
3367 var projectedPoint = L.point(point).add(this.getPixelOrigin());
3368 return this.unproject(projectedPoint);
3369 },
3370
3371 latLngToLayerPoint: function (latlng) { // (LatLng)
3372 var projectedPoint = this.project(L.latLng(latlng))._round();
3373 return projectedPoint._subtract(this.getPixelOrigin());
3374 },
3375
3376 containerPointToLayerPoint: function (point) { // (Point)
3377 return L.point(point).subtract(this._getMapPanePos());
3378 },
3379
3380 layerPointToContainerPoint: function (point) { // (Point)
3381 return L.point(point).add(this._getMapPanePos());
3382 },
3383
3384 containerPointToLatLng: function (point) {
3385 var layerPoint = this.containerPointToLayerPoint(L.point(point));
3386 return this.layerPointToLatLng(layerPoint);
3387 },
3388
3389 latLngToContainerPoint: function (latlng) {
3390 return this.layerPointToContainerPoint(this.latLngToLayerPoint(L.latLng(latlng)));
3391 },
3392
3393 mouseEventToContainerPoint: function (e) { // (MouseEvent)
3394 return L.DomEvent.getMousePosition(e, this._container);
3395 },
3396
3397 mouseEventToLayerPoint: function (e) { // (MouseEvent)
3398 return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
3399 },
3400
3401 mouseEventToLatLng: function (e) { // (MouseEvent)
3402 return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
3403 },
3404
3405
3406 // map initialization methods
3407
3408 _initContainer: function (id) {
3409 var container = this._container = L.DomUtil.get(id);
3410
3411 if (!container) {
3412 throw new Error('Map container not found.');
3413 } else if (container._leaflet) {
3414 throw new Error('Map container is already initialized.');
3415 }
3416
3417 container._leaflet = true;
3418 },
3419
3420 _initLayout: function () {
3421 var container = this._container;
3422
3423 L.DomUtil.addClass(container, 'leaflet-container' +
3424 (L.Browser.touch ? ' leaflet-touch' : '') +
3425 (L.Browser.retina ? ' leaflet-retina' : '') +
3426 (L.Browser.ielt9 ? ' leaflet-oldie' : '') +
3427 (this.options.fadeAnimation ? ' leaflet-fade-anim' : ''));
3428
3429 var position = L.DomUtil.getStyle(container, 'position');
3430
3431 if (position !== 'absolute' && position !== 'relative' && position !== 'fixed') {
3432 container.style.position = 'relative';
3433 }
3434
3435 this._initPanes();
3436
3437 if (this._initControlPos) {
3438 this._initControlPos();
3439 }
3440 },
3441
3442 _initPanes: function () {
3443 var panes = this._panes = {};
3444
3445 this._mapPane = panes.mapPane = this._createPane('leaflet-map-pane', this._container);
3446
3447 this._tilePane = panes.tilePane = this._createPane('leaflet-tile-pane', this._mapPane);
3448 panes.objectsPane = this._createPane('leaflet-objects-pane', this._mapPane);
3449 panes.shadowPane = this._createPane('leaflet-shadow-pane');
3450 panes.overlayPane = this._createPane('leaflet-overlay-pane');
3451 panes.markerPane = this._createPane('leaflet-marker-pane');
3452 panes.popupPane = this._createPane('leaflet-popup-pane');
3453
3454 var zoomHide = ' leaflet-zoom-hide';
3455
3456 if (!this.options.markerZoomAnimation) {
3457 L.DomUtil.addClass(panes.markerPane, zoomHide);
3458 L.DomUtil.addClass(panes.shadowPane, zoomHide);
3459 L.DomUtil.addClass(panes.popupPane, zoomHide);
3460 }
3461 },
3462
3463 _createPane: function (className, container) {
3464 return L.DomUtil.create('div', className, container || this._panes.objectsPane);
3465 },
3466
3467 _clearPanes: function () {
3468 this._container.removeChild(this._mapPane);
3469 },
3470
3471 _addLayers: function (layers) {
3472 layers = layers ? (L.Util.isArray(layers) ? layers : [layers]) : [];
3473
3474 for (var i = 0, len = layers.length; i < len; i++) {
3475 this.addLayer(layers[i]);
3476 }
3477 },
3478
3479
3480 // private methods that modify map state
3481
3482 _resetView: function (center, zoom, preserveMapOffset, afterZoomAnim) {
3483
3484 var zoomChanged = (this._zoom !== zoom);
3485
3486 if (!afterZoomAnim) {
3487 this.fire('movestart');
3488
3489 if (zoomChanged) {
3490 this.fire('zoomstart');
3491 }
3492 }
3493
3494 this._zoom = zoom;
3495 this._initialCenter = center;
3496
3497 this._initialTopLeftPoint = this._getNewTopLeftPoint(center);
3498
3499 if (!preserveMapOffset) {
3500 L.DomUtil.setPosition(this._mapPane, new L.Point(0, 0));
3501 } else {
3502 this._initialTopLeftPoint._add(this._getMapPanePos());
3503 }
3504
3505 this._tileLayersToLoad = this._tileLayersNum;
3506
3507 var loading = !this._loaded;
3508 this._loaded = true;
3509
3510 this.fire('viewreset', {hard: !preserveMapOffset});
3511
3512 if (loading) {
3513 this.fire('load');
3514 this.eachLayer(this._layerAdd, this);
3515 }
3516
3517 this.fire('move');
3518
3519 if (zoomChanged || afterZoomAnim) {
3520 this.fire('zoomend');
3521 }
3522
3523 this.fire('moveend', {hard: !preserveMapOffset});
3524 },
3525
3526 _rawPanBy: function (offset) {
3527 L.DomUtil.setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
3528 },
3529
3530 _getZoomSpan: function () {
3531 return this.getMaxZoom() - this.getMinZoom();
3532 },
3533
3534 _updateZoomLevels: function () {
3535 var i,
3536 minZoom = Infinity,
3537 maxZoom = -Infinity,
3538 oldZoomSpan = this._getZoomSpan();
3539
3540 for (i in this._zoomBoundLayers) {
3541 var layer = this._zoomBoundLayers[i];
3542 if (!isNaN(layer.options.minZoom)) {
3543 minZoom = Math.min(minZoom, layer.options.minZoom);
3544 }
3545 if (!isNaN(layer.options.maxZoom)) {
3546 maxZoom = Math.max(maxZoom, layer.options.maxZoom);
3547 }
3548 }
3549
3550 if (i === undefined) { // we have no tilelayers
3551 this._layersMaxZoom = this._layersMinZoom = undefined;
3552 } else {
3553 this._layersMaxZoom = maxZoom;
3554 this._layersMinZoom = minZoom;
3555 }
3556
3557 if (oldZoomSpan !== this._getZoomSpan()) {
3558 this.fire('zoomlevelschange');
3559 }
3560 },
3561
3562 _panInsideMaxBounds: function () {
3563 this.panInsideBounds(this.options.maxBounds);
3564 },
3565
3566 _checkIfLoaded: function () {
3567 if (!this._loaded) {
3568 throw new Error('Set map center and zoom first.');
3569 }
3570 },
3571
3572 // map events
3573
3574 _initEvents: function (onOff) {
3575 if (!L.DomEvent) { return; }
3576
3577 onOff = onOff || 'on';
3578
3579 L.DomEvent[onOff](this._container, 'click', this._onMouseClick, this);
3580
3581 var events = ['dblclick', 'mousedown', 'mouseup', 'mouseenter',
3582 'mouseleave', 'mousemove', 'contextmenu'],
3583 i, len;
3584
3585 for (i = 0, len = events.length; i < len; i++) {
3586 L.DomEvent[onOff](this._container, events[i], this._fireMouseEvent, this);
3587 }
3588
3589 if (this.options.trackResize) {
3590 L.DomEvent[onOff](window, 'resize', this._onResize, this);
3591 }
3592 },
3593
3594 _onResize: function () {
3595 L.Util.cancelAnimFrame(this._resizeRequest);
3596 this._resizeRequest = L.Util.requestAnimFrame(
3597 function () { this.invalidateSize({debounceMoveend: true}); }, this, false, this._container);
3598 },
3599
3600 _onMouseClick: function (e) {
3601 if (!this._loaded || (!e._simulated &&
3602 ((this.dragging && this.dragging.moved()) ||
3603 (this.boxZoom && this.boxZoom.moved()))) ||
3604 L.DomEvent._skipped(e)) { return; }
3605
3606 this.fire('preclick');
3607 this._fireMouseEvent(e);
3608 },
3609
3610 _fireMouseEvent: function (e) {
3611 if (!this._loaded || L.DomEvent._skipped(e)) { return; }
3612
3613 var type = e.type;
3614
3615 type = (type === 'mouseenter' ? 'mouseover' : (type === 'mouseleave' ? 'mouseout' : type));
3616
3617 if (!this.hasEventListeners(type)) { return; }
3618
3619 if (type === 'contextmenu') {
3620 L.DomEvent.preventDefault(e);
3621 }
3622
3623 var containerPoint = this.mouseEventToContainerPoint(e),
3624 layerPoint = this.containerPointToLayerPoint(containerPoint),
3625 latlng = this.layerPointToLatLng(layerPoint);
3626
3627 this.fire(type, {
3628 latlng: latlng,
3629 layerPoint: layerPoint,
3630 containerPoint: containerPoint,
3631 originalEvent: e
3632 });
3633 },
3634
3635 _onTileLayerLoad: function () {
3636 this._tileLayersToLoad--;
3637 if (this._tileLayersNum && !this._tileLayersToLoad) {
3638 this.fire('tilelayersload');
3639 }
3640 },
3641
3642 _clearHandlers: function () {
3643 for (var i = 0, len = this._handlers.length; i < len; i++) {
3644 this._handlers[i].disable();
3645 }
3646 },
3647
3648 whenReady: function (callback, context) {
3649 if (this._loaded) {
3650 callback.call(context || this, this);
3651 } else {
3652 this.on('load', callback, context);
3653 }
3654 return this;
3655 },
3656
3657 _layerAdd: function (layer) {
3658 layer.onAdd(this);
3659 this.fire('layeradd', {layer: layer});
3660 },
3661
3662
3663 // private methods for getting map state
3664
3665 _getMapPanePos: function () {
3666 return L.DomUtil.getPosition(this._mapPane);
3667 },
3668
3669 _moved: function () {
3670 var pos = this._getMapPanePos();
3671 return pos && !pos.equals([0, 0]);
3672 },
3673
3674 _getTopLeftPoint: function () {
3675 return this.getPixelOrigin().subtract(this._getMapPanePos());
3676 },
3677
3678 _getNewTopLeftPoint: function (center, zoom) {
3679 var viewHalf = this.getSize()._divideBy(2);
3680 // TODO round on display, not calculation to increase precision?
3681 return this.project(center, zoom)._subtract(viewHalf)._round();
3682 },
3683
3684 _latLngToNewLayerPoint: function (latlng, newZoom, newCenter) {
3685 var topLeft = this._getNewTopLeftPoint(newCenter, newZoom).add(this._getMapPanePos());
3686 return this.project(latlng, newZoom)._subtract(topLeft);
3687 },
3688
3689 // layer point of the current center
3690 _getCenterLayerPoint: function () {
3691 return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
3692 },
3693
3694 // offset of the specified place to the current center in pixels
3695 _getCenterOffset: function (latlng) {
3696 return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
3697 },
3698
3699 // adjust center for view to get inside bounds
3700 _limitCenter: function (center, zoom, bounds) {
3701
3702 if (!bounds) { return center; }
3703
3704 var centerPoint = this.project(center, zoom),
3705 viewHalf = this.getSize().divideBy(2),
3706 viewBounds = new L.Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
3707 offset = this._getBoundsOffset(viewBounds, bounds, zoom);
3708
3709 return this.unproject(centerPoint.add(offset), zoom);
3710 },
3711
3712 // adjust offset for view to get inside bounds
3713 _limitOffset: function (offset, bounds) {
3714 if (!bounds) { return offset; }
3715
3716 var viewBounds = this.getPixelBounds(),
3717 newBounds = new L.Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));
3718
3719 return offset.add(this._getBoundsOffset(newBounds, bounds));
3720 },
3721
3722 // returns offset needed for pxBounds to get inside maxBounds at a specified zoom
3723 _getBoundsOffset: function (pxBounds, maxBounds, zoom) {
3724 var nwOffset = this.project(maxBounds.getNorthWest(), zoom).subtract(pxBounds.min),
3725 seOffset = this.project(maxBounds.getSouthEast(), zoom).subtract(pxBounds.max),
3726
3727 dx = this._rebound(nwOffset.x, -seOffset.x),
3728 dy = this._rebound(nwOffset.y, -seOffset.y);
3729
3730 return new L.Point(dx, dy);
3731 },
3732
3733 _rebound: function (left, right) {
3734 return left + right > 0 ?
3735 Math.round(left - right) / 2 :
3736 Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
3737 },
3738
3739 _limitZoom: function (zoom) {
3740 var min = this.getMinZoom(),
3741 max = this.getMaxZoom();
3742
3743 return Math.max(min, Math.min(max, zoom));
3744 }
3745});
3746
3747L.map = function (id, options) {
3748 return new L.Map(id, options);
3749};
3750
3751
3752/*
3753 * Mercator projection that takes into account that the Earth is not a perfect sphere.
3754 * Less popular than spherical mercator; used by projections like EPSG:3395.
3755 */
3756
3757L.Projection.Mercator = {
3758 MAX_LATITUDE: 85.0840591556,
3759
3760 R_MINOR: 6356752.314245179,
3761 R_MAJOR: 6378137,
3762
3763 project: function (latlng) { // (LatLng) -> Point
3764 var d = L.LatLng.DEG_TO_RAD,
3765 max = this.MAX_LATITUDE,
3766 lat = Math.max(Math.min(max, latlng.lat), -max),
3767 r = this.R_MAJOR,
3768 r2 = this.R_MINOR,
3769 x = latlng.lng * d * r,
3770 y = lat * d,
3771 tmp = r2 / r,
3772 eccent = Math.sqrt(1.0 - tmp * tmp),
3773 con = eccent * Math.sin(y);
3774
3775 con = Math.pow((1 - con) / (1 + con), eccent * 0.5);
3776
3777 var ts = Math.tan(0.5 * ((Math.PI * 0.5) - y)) / con;
3778 y = -r * Math.log(ts);
3779
3780 return new L.Point(x, y);
3781 },
3782
3783 unproject: function (point) { // (Point, Boolean) -> LatLng
3784 var d = L.LatLng.RAD_TO_DEG,
3785 r = this.R_MAJOR,
3786 r2 = this.R_MINOR,
3787 lng = point.x * d / r,
3788 tmp = r2 / r,
3789 eccent = Math.sqrt(1 - (tmp * tmp)),
3790 ts = Math.exp(- point.y / r),
3791 phi = (Math.PI / 2) - 2 * Math.atan(ts),
3792 numIter = 15,
3793 tol = 1e-7,
3794 i = numIter,
3795 dphi = 0.1,
3796 con;
3797
3798 while ((Math.abs(dphi) > tol) && (--i > 0)) {
3799 con = eccent * Math.sin(phi);
3800 dphi = (Math.PI / 2) - 2 * Math.atan(ts *
3801 Math.pow((1.0 - con) / (1.0 + con), 0.5 * eccent)) - phi;
3802 phi += dphi;
3803 }
3804
3805 return new L.LatLng(phi * d, lng);
3806 }
3807};
3808
3809
3810
3811L.CRS.EPSG3395 = L.extend({}, L.CRS, {
3812 code: 'EPSG:3395',
3813
3814 projection: L.Projection.Mercator,
3815
3816 transformation: (function () {
3817 var m = L.Projection.Mercator,
3818 r = m.R_MAJOR,
3819 scale = 0.5 / (Math.PI * r);
3820
3821 return new L.Transformation(scale, 0.5, -scale, 0.5);
3822 }())
3823});
3824
3825
3826/*
3827 * L.TileLayer is used for standard xyz-numbered tile layers.
3828 */
3829
3830L.TileLayer = L.Class.extend({
3831 includes: L.Mixin.Events,
3832
3833 options: {
3834 minZoom: 0,
3835 maxZoom: 18,
3836 tileSize: 256,
3837 subdomains: 'abc',
3838 errorTileUrl: '',
3839 attribution: '',
3840 zoomOffset: 0,
3841 opacity: 1,
3842 /*
3843 maxNativeZoom: null,
3844 zIndex: null,
3845 tms: false,
3846 continuousWorld: false,
3847 noWrap: false,
3848 zoomReverse: false,
3849 detectRetina: false,
3850 reuseTiles: false,
3851 bounds: false,
3852 */
3853 unloadInvisibleTiles: L.Browser.mobile,
3854 updateWhenIdle: L.Browser.mobile
3855 },
3856
3857 initialize: function (url, options) {
3858 options = L.setOptions(this, options);
3859
3860 // detecting retina displays, adjusting tileSize and zoom levels
3861 if (options.detectRetina && L.Browser.retina && options.maxZoom > 0) {
3862
3863 options.tileSize = Math.floor(options.tileSize / 2);
3864 options.zoomOffset++;
3865
3866 if (options.minZoom > 0) {
3867 options.minZoom--;
3868 }
3869 this.options.maxZoom--;
3870 }
3871
3872 if (options.bounds) {
3873 options.bounds = L.latLngBounds(options.bounds);
3874 }
3875
3876 this._url = url;
3877
3878 var subdomains = this.options.subdomains;
3879
3880 if (typeof subdomains === 'string') {
3881 this.options.subdomains = subdomains.split('');
3882 }
3883 },
3884
3885 onAdd: function (map) {
3886 this._map = map;
3887 this._animated = map._zoomAnimated;
3888
3889 // create a container div for tiles
3890 this._initContainer();
3891
3892 // set up events
3893 map.on({
3894 'viewreset': this._reset,
3895 'moveend': this._update
3896 }, this);
3897
3898 if (this._animated) {
3899 map.on({
3900 'zoomanim': this._animateZoom,
3901 'zoomend': this._endZoomAnim
3902 }, this);
3903 }
3904
3905 if (!this.options.updateWhenIdle) {
3906 this._limitedUpdate = L.Util.limitExecByInterval(this._update, 150, this);
3907 map.on('move', this._limitedUpdate, this);
3908 }
3909
3910 this._reset();
3911 this._update();
3912 },
3913
3914 addTo: function (map) {
3915 map.addLayer(this);
3916 return this;
3917 },
3918
3919 onRemove: function (map) {
3920 this._container.parentNode.removeChild(this._container);
3921
3922 map.off({
3923 'viewreset': this._reset,
3924 'moveend': this._update
3925 }, this);
3926
3927 if (this._animated) {
3928 map.off({
3929 'zoomanim': this._animateZoom,
3930 'zoomend': this._endZoomAnim
3931 }, this);
3932 }
3933
3934 if (!this.options.updateWhenIdle) {
3935 map.off('move', this._limitedUpdate, this);
3936 }
3937
3938 this._container = null;
3939 this._map = null;
3940 },
3941
3942 bringToFront: function () {
3943 var pane = this._map._panes.tilePane;
3944
3945 if (this._container) {
3946 pane.appendChild(this._container);
3947 this._setAutoZIndex(pane, Math.max);
3948 }
3949
3950 return this;
3951 },
3952
3953 bringToBack: function () {
3954 var pane = this._map._panes.tilePane;
3955
3956 if (this._container) {
3957 pane.insertBefore(this._container, pane.firstChild);
3958 this._setAutoZIndex(pane, Math.min);
3959 }
3960
3961 return this;
3962 },
3963
3964 getAttribution: function () {
3965 return this.options.attribution;
3966 },
3967
3968 getContainer: function () {
3969 return this._container;
3970 },
3971
3972 setOpacity: function (opacity) {
3973 this.options.opacity = opacity;
3974
3975 if (this._map) {
3976 this._updateOpacity();
3977 }
3978
3979 return this;
3980 },
3981
3982 setZIndex: function (zIndex) {
3983 this.options.zIndex = zIndex;
3984 this._updateZIndex();
3985
3986 return this;
3987 },
3988
3989 setUrl: function (url, noRedraw) {
3990 this._url = url;
3991
3992 if (!noRedraw) {
3993 this.redraw();
3994 }
3995
3996 return this;
3997 },
3998
3999 redraw: function () {
4000 if (this._map) {
4001 this._reset({hard: true});
4002 this._update();
4003 }
4004 return this;
4005 },
4006
4007 _updateZIndex: function () {
4008 if (this._container && this.options.zIndex !== undefined) {
4009 this._container.style.zIndex = this.options.zIndex;
4010 }
4011 },
4012
4013 _setAutoZIndex: function (pane, compare) {
4014
4015 var layers = pane.children,
4016 edgeZIndex = -compare(Infinity, -Infinity), // -Infinity for max, Infinity for min
4017 zIndex, i, len;
4018
4019 for (i = 0, len = layers.length; i < len; i++) {
4020
4021 if (layers[i] !== this._container) {
4022 zIndex = parseInt(layers[i].style.zIndex, 10);
4023
4024 if (!isNaN(zIndex)) {
4025 edgeZIndex = compare(edgeZIndex, zIndex);
4026 }
4027 }
4028 }
4029
4030 this.options.zIndex = this._container.style.zIndex =
4031 (isFinite(edgeZIndex) ? edgeZIndex : 0) + compare(1, -1);
4032 },
4033
4034 _updateOpacity: function () {
4035 var i,
4036 tiles = this._tiles;
4037
4038 if (L.Browser.ielt9) {
4039 for (i in tiles) {
4040 L.DomUtil.setOpacity(tiles[i], this.options.opacity);
4041 }
4042 } else {
4043 L.DomUtil.setOpacity(this._container, this.options.opacity);
4044 }
4045 },
4046
4047 _initContainer: function () {
4048 var tilePane = this._map._panes.tilePane;
4049
4050 if (!this._container) {
4051 this._container = L.DomUtil.create('div', 'leaflet-layer');
4052
4053 this._updateZIndex();
4054
4055 if (this._animated) {
4056 var className = 'leaflet-tile-container';
4057
4058 this._bgBuffer = L.DomUtil.create('div', className, this._container);
4059 this._tileContainer = L.DomUtil.create('div', className, this._container);
4060
4061 } else {
4062 this._tileContainer = this._container;
4063 }
4064
4065 tilePane.appendChild(this._container);
4066
4067 if (this.options.opacity < 1) {
4068 this._updateOpacity();
4069 }
4070 }
4071 },
4072
4073 _reset: function (e) {
4074 for (var key in this._tiles) {
4075 this.fire('tileunload', {tile: this._tiles[key]});
4076 }
4077
4078 this._tiles = {};
4079 this._tilesToLoad = 0;
4080
4081 if (this.options.reuseTiles) {
4082 this._unusedTiles = [];
4083 }
4084
4085 this._tileContainer.innerHTML = '';
4086
4087 if (this._animated && e && e.hard) {
4088 this._clearBgBuffer();
4089 }
4090
4091 this._initContainer();
4092 },
4093
4094 _getTileSize: function () {
4095 var map = this._map,
4096 zoom = map.getZoom() + this.options.zoomOffset,
4097 zoomN = this.options.maxNativeZoom,
4098 tileSize = this.options.tileSize;
4099
4100 if (zoomN && zoom > zoomN) {
4101 tileSize = Math.round(map.getZoomScale(zoom) / map.getZoomScale(zoomN) * tileSize);
4102 }
4103
4104 return tileSize;
4105 },
4106
4107 _update: function () {
4108
4109 if (!this._map) { return; }
4110
4111 var map = this._map,
4112 bounds = map.getPixelBounds(),
4113 zoom = map.getZoom(),
4114 tileSize = this._getTileSize();
4115
4116 if (zoom > this.options.maxZoom || zoom < this.options.minZoom) {
4117 return;
4118 }
4119
4120 var tileBounds = L.bounds(
4121 bounds.min.divideBy(tileSize)._floor(),
4122 bounds.max.divideBy(tileSize)._floor());
4123
4124 this._addTilesFromCenterOut(tileBounds);
4125
4126 if (this.options.unloadInvisibleTiles || this.options.reuseTiles) {
4127 this._removeOtherTiles(tileBounds);
4128 }
4129 },
4130
4131 _addTilesFromCenterOut: function (bounds) {
4132 var queue = [],
4133 center = bounds.getCenter();
4134
4135 var j, i, point;
4136
4137 for (j = bounds.min.y; j <= bounds.max.y; j++) {
4138 for (i = bounds.min.x; i <= bounds.max.x; i++) {
4139 point = new L.Point(i, j);
4140
4141 if (this._tileShouldBeLoaded(point)) {
4142 queue.push(point);
4143 }
4144 }
4145 }
4146
4147 var tilesToLoad = queue.length;
4148
4149 if (tilesToLoad === 0) { return; }
4150
4151 // load tiles in order of their distance to center
4152 queue.sort(function (a, b) {
4153 return a.distanceTo(center) - b.distanceTo(center);
4154 });
4155
4156 var fragment = document.createDocumentFragment();
4157
4158 // if its the first batch of tiles to load
4159 if (!this._tilesToLoad) {
4160 this.fire('loading');
4161 }
4162
4163 this._tilesToLoad += tilesToLoad;
4164
4165 for (i = 0; i < tilesToLoad; i++) {
4166 this._addTile(queue[i], fragment);
4167 }
4168
4169 this._tileContainer.appendChild(fragment);
4170 },
4171
4172 _tileShouldBeLoaded: function (tilePoint) {
4173 if ((tilePoint.x + ':' + tilePoint.y) in this._tiles) {
4174 return false; // already loaded
4175 }
4176
4177 var options = this.options;
4178
4179 if (!options.continuousWorld) {
4180 var limit = this._getWrapTileNum();
4181
4182 // don't load if exceeds world bounds
4183 if ((options.noWrap && (tilePoint.x < 0 || tilePoint.x >= limit.x)) ||
4184 tilePoint.y < 0 || tilePoint.y >= limit.y) { return false; }
4185 }
4186
4187 if (options.bounds) {
4188 var tileSize = this._getTileSize(),
4189 nwPoint = tilePoint.multiplyBy(tileSize),
4190 sePoint = nwPoint.add([tileSize, tileSize]),
4191 nw = this._map.unproject(nwPoint),
4192 se = this._map.unproject(sePoint);
4193
4194 // TODO temporary hack, will be removed after refactoring projections
4195 // https://github.com/Leaflet/Leaflet/issues/1618
4196 if (!options.continuousWorld && !options.noWrap) {
4197 nw = nw.wrap();
4198 se = se.wrap();
4199 }
4200
4201 if (!options.bounds.intersects([nw, se])) { return false; }
4202 }
4203
4204 return true;
4205 },
4206
4207 _removeOtherTiles: function (bounds) {
4208 var kArr, x, y, key;
4209
4210 for (key in this._tiles) {
4211 kArr = key.split(':');
4212 x = parseInt(kArr[0], 10);
4213 y = parseInt(kArr[1], 10);
4214
4215 // remove tile if it's out of bounds
4216 if (x < bounds.min.x || x > bounds.max.x || y < bounds.min.y || y > bounds.max.y) {
4217 this._removeTile(key);
4218 }
4219 }
4220 },
4221
4222 _removeTile: function (key) {
4223 var tile = this._tiles[key];
4224
4225 this.fire('tileunload', {tile: tile, url: tile.src});
4226
4227 if (this.options.reuseTiles) {
4228 L.DomUtil.removeClass(tile, 'leaflet-tile-loaded');
4229 this._unusedTiles.push(tile);
4230
4231 } else if (tile.parentNode === this._tileContainer) {
4232 this._tileContainer.removeChild(tile);
4233 }
4234
4235 // for https://github.com/CloudMade/Leaflet/issues/137
4236 if (!L.Browser.android) {
4237 tile.onload = null;
4238 tile.src = L.Util.emptyImageUrl;
4239 }
4240
4241 delete this._tiles[key];
4242 },
4243
4244 _addTile: function (tilePoint, container) {
4245 var tilePos = this._getTilePos(tilePoint);
4246
4247 // get unused tile - or create a new tile
4248 var tile = this._getTile();
4249
4250 /*
4251 Chrome 20 layouts much faster with top/left (verify with timeline, frames)
4252 Android 4 browser has display issues with top/left and requires transform instead
4253 (other browsers don't currently care) - see debug/hacks/jitter.html for an example
4254 */
4255 L.DomUtil.setPosition(tile, tilePos, L.Browser.chrome);
4256
4257 this._tiles[tilePoint.x + ':' + tilePoint.y] = tile;
4258
4259 this._loadTile(tile, tilePoint);
4260
4261 if (tile.parentNode !== this._tileContainer) {
4262 container.appendChild(tile);
4263 }
4264 },
4265
4266 _getZoomForUrl: function () {
4267
4268 var options = this.options,
4269 zoom = this._map.getZoom();
4270
4271 if (options.zoomReverse) {
4272 zoom = options.maxZoom - zoom;
4273 }
4274
4275 zoom += options.zoomOffset;
4276
4277 return options.maxNativeZoom ? Math.min(zoom, options.maxNativeZoom) : zoom;
4278 },
4279
4280 _getTilePos: function (tilePoint) {
4281 var origin = this._map.getPixelOrigin(),
4282 tileSize = this._getTileSize();
4283
4284 return tilePoint.multiplyBy(tileSize).subtract(origin);
4285 },
4286
4287 // image-specific code (override to implement e.g. Canvas or SVG tile layer)
4288
4289 getTileUrl: function (tilePoint) {
4290 return L.Util.template(this._url, L.extend({
4291 s: this._getSubdomain(tilePoint),
4292 z: tilePoint.z,
4293 x: tilePoint.x,
4294 y: tilePoint.y
4295 }, this.options));
4296 },
4297
4298 _getWrapTileNum: function () {
4299 var crs = this._map.options.crs,
4300 size = crs.getSize(this._map.getZoom());
4301 return size.divideBy(this._getTileSize())._floor();
4302 },
4303
4304 _adjustTilePoint: function (tilePoint) {
4305
4306 var limit = this._getWrapTileNum();
4307
4308 // wrap tile coordinates
4309 if (!this.options.continuousWorld && !this.options.noWrap) {
4310 tilePoint.x = ((tilePoint.x % limit.x) + limit.x) % limit.x;
4311 }
4312
4313 if (this.options.tms) {
4314 tilePoint.y = limit.y - tilePoint.y - 1;
4315 }
4316
4317 tilePoint.z = this._getZoomForUrl();
4318 },
4319
4320 _getSubdomain: function (tilePoint) {
4321 var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
4322 return this.options.subdomains[index];
4323 },
4324
4325 _getTile: function () {
4326 if (this.options.reuseTiles && this._unusedTiles.length > 0) {
4327 var tile = this._unusedTiles.pop();
4328 this._resetTile(tile);
4329 return tile;
4330 }
4331 return this._createTile();
4332 },
4333
4334 // Override if data stored on a tile needs to be cleaned up before reuse
4335 _resetTile: function (/*tile*/) {},
4336
4337 _createTile: function () {
4338 var tile = L.DomUtil.create('img', 'leaflet-tile');
4339 tile.style.width = tile.style.height = this._getTileSize() + 'px';
4340 tile.galleryimg = 'no';
4341
4342 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
4343
4344 if (L.Browser.ielt9 && this.options.opacity !== undefined) {
4345 L.DomUtil.setOpacity(tile, this.options.opacity);
4346 }
4347 // without this hack, tiles disappear after zoom on Chrome for Android
4348 // https://github.com/Leaflet/Leaflet/issues/2078
4349 if (L.Browser.mobileWebkit3d) {
4350 tile.style.WebkitBackfaceVisibility = 'hidden';
4351 }
4352 return tile;
4353 },
4354
4355 _loadTile: function (tile, tilePoint) {
4356 tile._layer = this;
4357 tile.onload = this._tileOnLoad;
4358 tile.onerror = this._tileOnError;
4359
4360 this._adjustTilePoint(tilePoint);
4361 tile.src = this.getTileUrl(tilePoint);
4362
4363 this.fire('tileloadstart', {
4364 tile: tile,
4365 url: tile.src
4366 });
4367 },
4368
4369 _tileLoaded: function () {
4370 this._tilesToLoad--;
4371
4372 if (this._animated) {
4373 L.DomUtil.addClass(this._tileContainer, 'leaflet-zoom-animated');
4374 }
4375
4376 if (!this._tilesToLoad) {
4377 this.fire('load');
4378
4379 if (this._animated) {
4380 // clear scaled tiles after all new tiles are loaded (for performance)
4381 clearTimeout(this._clearBgBufferTimer);
4382 this._clearBgBufferTimer = setTimeout(L.bind(this._clearBgBuffer, this), 500);
4383 }
4384 }
4385 },
4386
4387 _tileOnLoad: function () {
4388 var layer = this._layer;
4389
4390 //Only if we are loading an actual image
4391 if (this.src !== L.Util.emptyImageUrl) {
4392 L.DomUtil.addClass(this, 'leaflet-tile-loaded');
4393
4394 layer.fire('tileload', {
4395 tile: this,
4396 url: this.src
4397 });
4398 }
4399
4400 layer._tileLoaded();
4401 },
4402
4403 _tileOnError: function () {
4404 var layer = this._layer;
4405
4406 layer.fire('tileerror', {
4407 tile: this,
4408 url: this.src
4409 });
4410
4411 var newUrl = layer.options.errorTileUrl;
4412 if (newUrl) {
4413 this.src = newUrl;
4414 }
4415
4416 layer._tileLoaded();
4417 }
4418});
4419
4420L.tileLayer = function (url, options) {
4421 return new L.TileLayer(url, options);
4422};
4423
4424
4425/*
4426 * L.TileLayer.WMS is used for putting WMS tile layers on the map.
4427 */
4428
4429L.TileLayer.WMS = L.TileLayer.extend({
4430
4431 defaultWmsParams: {
4432 service: 'WMS',
4433 request: 'GetMap',
4434 version: '1.1.1',
4435 layers: '',
4436 styles: '',
4437 format: 'image/jpeg',
4438 transparent: false
4439 },
4440
4441 initialize: function (url, options) { // (String, Object)
4442
4443 this._url = url;
4444
4445 var wmsParams = L.extend({}, this.defaultWmsParams),
4446 tileSize = options.tileSize || this.options.tileSize;
4447
4448 if (options.detectRetina && L.Browser.retina) {
4449 wmsParams.width = wmsParams.height = tileSize * 2;
4450 } else {
4451 wmsParams.width = wmsParams.height = tileSize;
4452 }
4453
4454 for (var i in options) {
4455 // all keys that are not TileLayer options go to WMS params
4456 if (!this.options.hasOwnProperty(i) && i !== 'crs') {
4457 wmsParams[i] = options[i];
4458 }
4459 }
4460
4461 this.wmsParams = wmsParams;
4462
4463 L.setOptions(this, options);
4464 },
4465
4466 onAdd: function (map) {
4467
4468 this._crs = this.options.crs || map.options.crs;
4469
4470 this._wmsVersion = parseFloat(this.wmsParams.version);
4471
4472 var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
4473 this.wmsParams[projectionKey] = this._crs.code;
4474
4475 L.TileLayer.prototype.onAdd.call(this, map);
4476 },
4477
4478 getTileUrl: function (tilePoint) { // (Point, Number) -> String
4479
4480 var map = this._map,
4481 tileSize = this.options.tileSize,
4482
4483 nwPoint = tilePoint.multiplyBy(tileSize),
4484 sePoint = nwPoint.add([tileSize, tileSize]),
4485
4486 nw = this._crs.project(map.unproject(nwPoint, tilePoint.z)),
4487 se = this._crs.project(map.unproject(sePoint, tilePoint.z)),
4488 bbox = this._wmsVersion >= 1.3 && this._crs === L.CRS.EPSG4326 ?
4489 [se.y, nw.x, nw.y, se.x].join(',') :
4490 [nw.x, se.y, se.x, nw.y].join(','),
4491
4492 url = L.Util.template(this._url, {s: this._getSubdomain(tilePoint)});
4493
4494 return url + L.Util.getParamString(this.wmsParams, url, true) + '&BBOX=' + bbox;
4495 },
4496
4497 setParams: function (params, noRedraw) {
4498
4499 L.extend(this.wmsParams, params);
4500
4501 if (!noRedraw) {
4502 this.redraw();
4503 }
4504
4505 return this;
4506 }
4507});
4508
4509L.tileLayer.wms = function (url, options) {
4510 return new L.TileLayer.WMS(url, options);
4511};
4512
4513
4514/*
4515 * L.TileLayer.Canvas is a class that you can use as a base for creating
4516 * dynamically drawn Canvas-based tile layers.
4517 */
4518
4519L.TileLayer.Canvas = L.TileLayer.extend({
4520 options: {
4521 async: false
4522 },
4523
4524 initialize: function (options) {
4525 L.setOptions(this, options);
4526 },
4527
4528 redraw: function () {
4529 if (this._map) {
4530 this._reset({hard: true});
4531 this._update();
4532 }
4533
4534 for (var i in this._tiles) {
4535 this._redrawTile(this._tiles[i]);
4536 }
4537 return this;
4538 },
4539
4540 _redrawTile: function (tile) {
4541 this.drawTile(tile, tile._tilePoint, this._map._zoom);
4542 },
4543
4544 _createTile: function () {
4545 var tile = L.DomUtil.create('canvas', 'leaflet-tile');
4546 tile.width = tile.height = this.options.tileSize;
4547 tile.onselectstart = tile.onmousemove = L.Util.falseFn;
4548 return tile;
4549 },
4550
4551 _loadTile: function (tile, tilePoint) {
4552 tile._layer = this;
4553 tile._tilePoint = tilePoint;
4554
4555 this._redrawTile(tile);
4556
4557 if (!this.options.async) {
4558 this.tileDrawn(tile);
4559 }
4560 },
4561
4562 drawTile: function (/*tile, tilePoint*/) {
4563 // override with rendering code
4564 },
4565
4566 tileDrawn: function (tile) {
4567 this._tileOnLoad.call(tile);
4568 }
4569});
4570
4571
4572L.tileLayer.canvas = function (options) {
4573 return new L.TileLayer.Canvas(options);
4574};
4575
4576
4577/*
4578 * L.ImageOverlay is used to overlay images over the map (to specific geographical bounds).
4579 */
4580
4581L.ImageOverlay = L.Class.extend({
4582 includes: L.Mixin.Events,
4583
4584 options: {
4585 opacity: 1
4586 },
4587
4588 initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
4589 this._url = url;
4590 this._bounds = L.latLngBounds(bounds);
4591
4592 L.setOptions(this, options);
4593 },
4594
4595 onAdd: function (map) {
4596 this._map = map;
4597
4598 if (!this._image) {
4599 this._initImage();
4600 }
4601
4602 map._panes.overlayPane.appendChild(this._image);
4603
4604 map.on('viewreset', this._reset, this);
4605
4606 if (map.options.zoomAnimation && L.Browser.any3d) {
4607 map.on('zoomanim', this._animateZoom, this);
4608 }
4609
4610 this._reset();
4611 },
4612
4613 onRemove: function (map) {
4614 map.getPanes().overlayPane.removeChild(this._image);
4615
4616 map.off('viewreset', this._reset, this);
4617
4618 if (map.options.zoomAnimation) {
4619 map.off('zoomanim', this._animateZoom, this);
4620 }
4621 },
4622
4623 addTo: function (map) {
4624 map.addLayer(this);
4625 return this;
4626 },
4627
4628 setOpacity: function (opacity) {
4629 this.options.opacity = opacity;
4630 this._updateOpacity();
4631 return this;
4632 },
4633
4634 // TODO remove bringToFront/bringToBack duplication from TileLayer/Path
4635 bringToFront: function () {
4636 if (this._image) {
4637 this._map._panes.overlayPane.appendChild(this._image);
4638 }
4639 return this;
4640 },
4641
4642 bringToBack: function () {
4643 var pane = this._map._panes.overlayPane;
4644 if (this._image) {
4645 pane.insertBefore(this._image, pane.firstChild);
4646 }
4647 return this;
4648 },
4649
4650 setUrl: function (url) {
4651 this._url = url;
4652 this._image.src = this._url;
4653 },
4654
4655 getAttribution: function () {
4656 return this.options.attribution;
4657 },
4658
4659 _initImage: function () {
4660 this._image = L.DomUtil.create('img', 'leaflet-image-layer');
4661
4662 if (this._map.options.zoomAnimation && L.Browser.any3d) {
4663 L.DomUtil.addClass(this._image, 'leaflet-zoom-animated');
4664 } else {
4665 L.DomUtil.addClass(this._image, 'leaflet-zoom-hide');
4666 }
4667
4668 this._updateOpacity();
4669
4670 //TODO createImage util method to remove duplication
4671 L.extend(this._image, {
4672 galleryimg: 'no',
4673 onselectstart: L.Util.falseFn,
4674 onmousemove: L.Util.falseFn,
4675 onload: L.bind(this._onImageLoad, this),
4676 src: this._url
4677 });
4678 },
4679
4680 _animateZoom: function (e) {
4681 var map = this._map,
4682 image = this._image,
4683 scale = map.getZoomScale(e.zoom),
4684 nw = this._bounds.getNorthWest(),
4685 se = this._bounds.getSouthEast(),
4686
4687 topLeft = map._latLngToNewLayerPoint(nw, e.zoom, e.center),
4688 size = map._latLngToNewLayerPoint(se, e.zoom, e.center)._subtract(topLeft),
4689 origin = topLeft._add(size._multiplyBy((1 / 2) * (1 - 1 / scale)));
4690
4691 image.style[L.DomUtil.TRANSFORM] =
4692 L.DomUtil.getTranslateString(origin) + ' scale(' + scale + ') ';
4693 },
4694
4695 _reset: function () {
4696 var image = this._image,
4697 topLeft = this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
4698 size = this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(topLeft);
4699
4700 L.DomUtil.setPosition(image, topLeft);
4701
4702 image.style.width = size.x + 'px';
4703 image.style.height = size.y + 'px';
4704 },
4705
4706 _onImageLoad: function () {
4707 this.fire('load');
4708 },
4709
4710 _updateOpacity: function () {
4711 L.DomUtil.setOpacity(this._image, this.options.opacity);
4712 }
4713});
4714
4715L.imageOverlay = function (url, bounds, options) {
4716 return new L.ImageOverlay(url, bounds, options);
4717};
4718
4719
4720/*
4721 * L.Icon is an image-based icon class that you can use with L.Marker for custom markers.
4722 */
4723
4724L.Icon = L.Class.extend({
4725 options: {
4726 /*
4727 iconUrl: (String) (required)
4728 iconRetinaUrl: (String) (optional, used for retina devices if detected)
4729 iconSize: (Point) (can be set through CSS)
4730 iconAnchor: (Point) (centered by default, can be set in CSS with negative margins)
4731 popupAnchor: (Point) (if not specified, popup opens in the anchor point)
4732 shadowUrl: (String) (no shadow by default)
4733 shadowRetinaUrl: (String) (optional, used for retina devices if detected)
4734 shadowSize: (Point)
4735 shadowAnchor: (Point)
4736 */
4737 className: ''
4738 },
4739
4740 initialize: function (options) {
4741 L.setOptions(this, options);
4742 },
4743
4744 createIcon: function (oldIcon) {
4745 return this._createIcon('icon', oldIcon);
4746 },
4747
4748 createShadow: function (oldIcon) {
4749 return this._createIcon('shadow', oldIcon);
4750 },
4751
4752 _createIcon: function (name, oldIcon) {
4753 var src = this._getIconUrl(name);
4754
4755 if (!src) {
4756 if (name === 'icon') {
4757 throw new Error('iconUrl not set in Icon options (see the docs).');
4758 }
4759 return null;
4760 }
4761
4762 var img;
4763 if (!oldIcon || oldIcon.tagName !== 'IMG') {
4764 img = this._createImg(src);
4765 } else {
4766 img = this._createImg(src, oldIcon);
4767 }
4768 this._setIconStyles(img, name);
4769
4770 return img;
4771 },
4772
4773 _setIconStyles: function (img, name) {
4774 var options = this.options,
4775 size = L.point(options[name + 'Size']),
4776 anchor;
4777
4778 if (name === 'shadow') {
4779 anchor = L.point(options.shadowAnchor || options.iconAnchor);
4780 } else {
4781 anchor = L.point(options.iconAnchor);
4782 }
4783
4784 if (!anchor && size) {
4785 anchor = size.divideBy(2, true);
4786 }
4787
4788 img.className = 'leaflet-marker-' + name + ' ' + options.className;
4789
4790 if (anchor) {
4791 img.style.marginLeft = (-anchor.x) + 'px';
4792 img.style.marginTop = (-anchor.y) + 'px';
4793 }
4794
4795 if (size) {
4796 img.style.width = size.x + 'px';
4797 img.style.height = size.y + 'px';
4798 }
4799 },
4800
4801 _createImg: function (src, el) {
4802 el = el || document.createElement('img');
4803 el.src = src;
4804 return el;
4805 },
4806
4807 _getIconUrl: function (name) {
4808 if (L.Browser.retina && this.options[name + 'RetinaUrl']) {
4809 return this.options[name + 'RetinaUrl'];
4810 }
4811 return this.options[name + 'Url'];
4812 }
4813});
4814
4815L.icon = function (options) {
4816 return new L.Icon(options);
4817};
4818
4819
4820/*
4821 * L.Icon.Default is the blue marker icon used by default in Leaflet.
4822 */
4823
4824L.Icon.Default = L.Icon.extend({
4825
4826 options: {
4827 iconSize: [25, 41],
4828 iconAnchor: [12, 41],
4829 popupAnchor: [1, -34],
4830
4831 shadowSize: [41, 41]
4832 },
4833
4834 _getIconUrl: function (name) {
4835 var key = name + 'Url';
4836
4837 if (this.options[key]) {
4838 return this.options[key];
4839 }
4840
4841 if (L.Browser.retina && name === 'icon') {
4842 name += '-2x';
4843 }
4844
4845 var path = L.Icon.Default.imagePath;
4846
4847 if (!path) {
4848 throw new Error('Couldn\'t autodetect L.Icon.Default.imagePath, set it manually.');
4849 }
4850
4851 return path + '/marker-' + name + '.png';
4852 }
4853});
4854
4855L.Icon.Default.imagePath = (function () {
4856 var scripts = document.getElementsByTagName('script'),
4857 leafletRe = /[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;
4858
4859 var i, len, src, matches, path;
4860
4861 for (i = 0, len = scripts.length; i < len; i++) {
4862 src = scripts[i].src;
4863 matches = src.match(leafletRe);
4864
4865 if (matches) {
4866 path = src.split(leafletRe)[0];
4867 return (path ? path + '/' : '') + 'images';
4868 }
4869 }
4870}());
4871
4872
4873/*
4874 * L.Marker is used to display clickable/draggable icons on the map.
4875 */
4876
4877L.Marker = L.Class.extend({
4878
4879 includes: L.Mixin.Events,
4880
4881 options: {
4882 icon: new L.Icon.Default(),
4883 title: '',
4884 alt: '',
4885 clickable: true,
4886 draggable: false,
4887 keyboard: true,
4888 zIndexOffset: 0,
4889 opacity: 1,
4890 riseOnHover: false,
4891 riseOffset: 250
4892 },
4893
4894 initialize: function (latlng, options) {
4895 L.setOptions(this, options);
4896 this._latlng = L.latLng(latlng);
4897 },
4898
4899 onAdd: function (map) {
4900 this._map = map;
4901
4902 map.on('viewreset', this.update, this);
4903
4904 this._initIcon();
4905 this.update();
4906 this.fire('add');
4907
4908 if (map.options.zoomAnimation && map.options.markerZoomAnimation) {
4909 map.on('zoomanim', this._animateZoom, this);
4910 }
4911 },
4912
4913 addTo: function (map) {
4914 map.addLayer(this);
4915 return this;
4916 },
4917
4918 onRemove: function (map) {
4919 if (this.dragging) {
4920 this.dragging.disable();
4921 }
4922
4923 this._removeIcon();
4924 this._removeShadow();
4925
4926 this.fire('remove');
4927
4928 map.off({
4929 'viewreset': this.update,
4930 'zoomanim': this._animateZoom
4931 }, this);
4932
4933 this._map = null;
4934 },
4935
4936 getLatLng: function () {
4937 return this._latlng;
4938 },
4939
4940 setLatLng: function (latlng) {
4941 this._latlng = L.latLng(latlng);
4942
4943 this.update();
4944
4945 return this.fire('move', { latlng: this._latlng });
4946 },
4947
4948 setZIndexOffset: function (offset) {
4949 this.options.zIndexOffset = offset;
4950 this.update();
4951
4952 return this;
4953 },
4954
4955 setIcon: function (icon) {
4956
4957 this.options.icon = icon;
4958
4959 if (this._map) {
4960 this._initIcon();
4961 this.update();
4962 }
4963
4964 if (this._popup) {
4965 this.bindPopup(this._popup);
4966 }
4967
4968 return this;
4969 },
4970
4971 update: function () {
4972 if (this._icon) {
4973 this._setPos(this._map.latLngToLayerPoint(this._latlng).round());
4974 }
4975 return this;
4976 },
4977
4978 _initIcon: function () {
4979 var options = this.options,
4980 map = this._map,
4981 animation = (map.options.zoomAnimation && map.options.markerZoomAnimation),
4982 classToAdd = animation ? 'leaflet-zoom-animated' : 'leaflet-zoom-hide';
4983
4984 var icon = options.icon.createIcon(this._icon),
4985 addIcon = false;
4986
4987 // if we're not reusing the icon, remove the old one and init new one
4988 if (icon !== this._icon) {
4989 if (this._icon) {
4990 this._removeIcon();
4991 }
4992 addIcon = true;
4993
4994 if (options.title) {
4995 icon.title = options.title;
4996 }
4997
4998 if (options.alt) {
4999 icon.alt = options.alt;
5000 }
5001 }
5002
5003 L.DomUtil.addClass(icon, classToAdd);
5004
5005 if (options.keyboard) {
5006 icon.tabIndex = '0';
5007 }
5008
5009 this._icon = icon;
5010
5011 this._initInteraction();
5012
5013 if (options.riseOnHover) {
5014 L.DomEvent
5015 .on(icon, 'mouseover', this._bringToFront, this)
5016 .on(icon, 'mouseout', this._resetZIndex, this);
5017 }
5018
5019 var newShadow = options.icon.createShadow(this._shadow),
5020 addShadow = false;
5021
5022 if (newShadow !== this._shadow) {
5023 this._removeShadow();
5024 addShadow = true;
5025 }
5026
5027 if (newShadow) {
5028 L.DomUtil.addClass(newShadow, classToAdd);
5029 }
5030 this._shadow = newShadow;
5031
5032
5033 if (options.opacity < 1) {
5034 this._updateOpacity();
5035 }
5036
5037
5038 var panes = this._map._panes;
5039
5040 if (addIcon) {
5041 panes.markerPane.appendChild(this._icon);
5042 }
5043
5044 if (newShadow && addShadow) {
5045 panes.shadowPane.appendChild(this._shadow);
5046 }
5047 },
5048
5049 _removeIcon: function () {
5050 if (this.options.riseOnHover) {
5051 L.DomEvent
5052 .off(this._icon, 'mouseover', this._bringToFront)
5053 .off(this._icon, 'mouseout', this._resetZIndex);
5054 }
5055
5056 this._map._panes.markerPane.removeChild(this._icon);
5057
5058 this._icon = null;
5059 },
5060
5061 _removeShadow: function () {
5062 if (this._shadow) {
5063 this._map._panes.shadowPane.removeChild(this._shadow);
5064 }
5065 this._shadow = null;
5066 },
5067
5068 _setPos: function (pos) {
5069 L.DomUtil.setPosition(this._icon, pos);
5070
5071 if (this._shadow) {
5072 L.DomUtil.setPosition(this._shadow, pos);
5073 }
5074
5075 this._zIndex = pos.y + this.options.zIndexOffset;
5076
5077 this._resetZIndex();
5078 },
5079
5080 _updateZIndex: function (offset) {
5081 this._icon.style.zIndex = this._zIndex + offset;
5082 },
5083
5084 _animateZoom: function (opt) {
5085 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();
5086
5087 this._setPos(pos);
5088 },
5089
5090 _initInteraction: function () {
5091
5092 if (!this.options.clickable) { return; }
5093
5094 // TODO refactor into something shared with Map/Path/etc. to DRY it up
5095
5096 var icon = this._icon,
5097 events = ['dblclick', 'mousedown', 'mouseover', 'mouseout', 'contextmenu'];
5098
5099 L.DomUtil.addClass(icon, 'leaflet-clickable');
5100 L.DomEvent.on(icon, 'click', this._onMouseClick, this);
5101 L.DomEvent.on(icon, 'keypress', this._onKeyPress, this);
5102
5103 for (var i = 0; i < events.length; i++) {
5104 L.DomEvent.on(icon, events[i], this._fireMouseEvent, this);
5105 }
5106
5107 if (L.Handler.MarkerDrag) {
5108 this.dragging = new L.Handler.MarkerDrag(this);
5109
5110 if (this.options.draggable) {
5111 this.dragging.enable();
5112 }
5113 }
5114 },
5115
5116 _onMouseClick: function (e) {
5117 var wasDragged = this.dragging && this.dragging.moved();
5118
5119 if (this.hasEventListeners(e.type) || wasDragged) {
5120 L.DomEvent.stopPropagation(e);
5121 }
5122
5123 if (wasDragged) { return; }
5124
5125 if ((!this.dragging || !this.dragging._enabled) && this._map.dragging && this._map.dragging.moved()) { return; }
5126
5127 this.fire(e.type, {
5128 originalEvent: e,
5129 latlng: this._latlng
5130 });
5131 },
5132
5133 _onKeyPress: function (e) {
5134 if (e.keyCode === 13) {
5135 this.fire('click', {
5136 originalEvent: e,
5137 latlng: this._latlng
5138 });
5139 }
5140 },
5141
5142 _fireMouseEvent: function (e) {
5143
5144 this.fire(e.type, {
5145 originalEvent: e,
5146 latlng: this._latlng
5147 });
5148
5149 // TODO proper custom event propagation
5150 // this line will always be called if marker is in a FeatureGroup
5151 if (e.type === 'contextmenu' && this.hasEventListeners(e.type)) {
5152 L.DomEvent.preventDefault(e);
5153 }
5154 if (e.type !== 'mousedown') {
5155 L.DomEvent.stopPropagation(e);
5156 } else {
5157 L.DomEvent.preventDefault(e);
5158 }
5159 },
5160
5161 setOpacity: function (opacity) {
5162 this.options.opacity = opacity;
5163 if (this._map) {
5164 this._updateOpacity();
5165 }
5166
5167 return this;
5168 },
5169
5170 _updateOpacity: function () {
5171 L.DomUtil.setOpacity(this._icon, this.options.opacity);
5172 if (this._shadow) {
5173 L.DomUtil.setOpacity(this._shadow, this.options.opacity);
5174 }
5175 },
5176
5177 _bringToFront: function () {
5178 this._updateZIndex(this.options.riseOffset);
5179 },
5180
5181 _resetZIndex: function () {
5182 this._updateZIndex(0);
5183 }
5184});
5185
5186L.marker = function (latlng, options) {
5187 return new L.Marker(latlng, options);
5188};
5189
5190
5191/*
5192 * L.DivIcon is a lightweight HTML-based icon class (as opposed to the image-based L.Icon)
5193 * to use with L.Marker.
5194 */
5195
5196L.DivIcon = L.Icon.extend({
5197 options: {
5198 iconSize: [12, 12], // also can be set through CSS
5199 /*
5200 iconAnchor: (Point)
5201 popupAnchor: (Point)
5202 html: (String)
5203 bgPos: (Point)
5204 */
5205 className: 'leaflet-div-icon',
5206 html: false
5207 },
5208
5209 createIcon: function (oldIcon) {
5210 var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
5211 options = this.options;
5212
5213 if (options.html !== false) {
5214 div.innerHTML = options.html;
5215 } else {
5216 div.innerHTML = '';
5217 }
5218
5219 if (options.bgPos) {
5220 div.style.backgroundPosition =
5221 (-options.bgPos.x) + 'px ' + (-options.bgPos.y) + 'px';
5222 }
5223
5224 this._setIconStyles(div, 'icon');
5225 return div;
5226 },
5227
5228 createShadow: function () {
5229 return null;
5230 }
5231});
5232
5233L.divIcon = function (options) {
5234 return new L.DivIcon(options);
5235};
5236
5237
5238/*
5239 * L.Popup is used for displaying popups on the map.
5240 */
5241
5242L.Map.mergeOptions({
5243 closePopupOnClick: true
5244});
5245
5246L.Popup = L.Class.extend({
5247 includes: L.Mixin.Events,
5248
5249 options: {
5250 minWidth: 50,
5251 maxWidth: 300,
5252 // maxHeight: null,
5253 autoPan: true,
5254 closeButton: true,
5255 offset: [0, 7],
5256 autoPanPadding: [5, 5],
5257 // autoPanPaddingTopLeft: null,
5258 // autoPanPaddingBottomRight: null,
5259 keepInView: false,
5260 className: '',
5261 zoomAnimation: true
5262 },
5263
5264 initialize: function (options, source) {
5265 L.setOptions(this, options);
5266
5267 this._source = source;
5268 this._animated = L.Browser.any3d && this.options.zoomAnimation;
5269 this._isOpen = false;
5270 },
5271
5272 onAdd: function (map) {
5273 this._map = map;
5274
5275 if (!this._container) {
5276 this._initLayout();
5277 }
5278
5279 var animFade = map.options.fadeAnimation;
5280
5281 if (animFade) {
5282 L.DomUtil.setOpacity(this._container, 0);
5283 }
5284 map._panes.popupPane.appendChild(this._container);
5285
5286 map.on(this._getEvents(), this);
5287
5288 this.update();
5289
5290 if (animFade) {
5291 L.DomUtil.setOpacity(this._container, 1);
5292 }
5293
5294 this.fire('open');
5295
5296 map.fire('popupopen', {popup: this});
5297
5298 if (this._source) {
5299 this._source.fire('popupopen', {popup: this});
5300 }
5301 },
5302
5303 addTo: function (map) {
5304 map.addLayer(this);
5305 return this;
5306 },
5307
5308 openOn: function (map) {
5309 map.openPopup(this);
5310 return this;
5311 },
5312
5313 onRemove: function (map) {
5314 map._panes.popupPane.removeChild(this._container);
5315
5316 L.Util.falseFn(this._container.offsetWidth); // force reflow
5317
5318 map.off(this._getEvents(), this);
5319
5320 if (map.options.fadeAnimation) {
5321 L.DomUtil.setOpacity(this._container, 0);
5322 }
5323
5324 this._map = null;
5325
5326 this.fire('close');
5327
5328 map.fire('popupclose', {popup: this});
5329
5330 if (this._source) {
5331 this._source.fire('popupclose', {popup: this});
5332 }
5333 },
5334
5335 getLatLng: function () {
5336 return this._latlng;
5337 },
5338
5339 setLatLng: function (latlng) {
5340 this._latlng = L.latLng(latlng);
5341 if (this._map) {
5342 this._updatePosition();
5343 this._adjustPan();
5344 }
5345 return this;
5346 },
5347
5348 getContent: function () {
5349 return this._content;
5350 },
5351
5352 setContent: function (content) {
5353 this._content = content;
5354 this.update();
5355 return this;
5356 },
5357
5358 update: function () {
5359 if (!this._map) { return; }
5360
5361 this._container.style.visibility = 'hidden';
5362
5363 this._updateContent();
5364 this._updateLayout();
5365 this._updatePosition();
5366
5367 this._container.style.visibility = '';
5368
5369 this._adjustPan();
5370 },
5371
5372 _getEvents: function () {
5373 var events = {
5374 viewreset: this._updatePosition
5375 };
5376
5377 if (this._animated) {
5378 events.zoomanim = this._zoomAnimation;
5379 }
5380 if ('closeOnClick' in this.options ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
5381 events.preclick = this._close;
5382 }
5383 if (this.options.keepInView) {
5384 events.moveend = this._adjustPan;
5385 }
5386
5387 return events;
5388 },
5389
5390 _close: function () {
5391 if (this._map) {
5392 this._map.closePopup(this);
5393 }
5394 },
5395
5396 _initLayout: function () {
5397 var prefix = 'leaflet-popup',
5398 containerClass = prefix + ' ' + this.options.className + ' leaflet-zoom-' +
5399 (this._animated ? 'animated' : 'hide'),
5400 container = this._container = L.DomUtil.create('div', containerClass),
5401 closeButton;
5402
5403 if (this.options.closeButton) {
5404 closeButton = this._closeButton =
5405 L.DomUtil.create('a', prefix + '-close-button', container);
5406 closeButton.href = '#close';
5407 closeButton.innerHTML = '×';
5408 L.DomEvent.disableClickPropagation(closeButton);
5409
5410 L.DomEvent.on(closeButton, 'click', this._onCloseButtonClick, this);
5411 }
5412
5413 var wrapper = this._wrapper =
5414 L.DomUtil.create('div', prefix + '-content-wrapper', container);
5415 L.DomEvent.disableClickPropagation(wrapper);
5416
5417 this._contentNode = L.DomUtil.create('div', prefix + '-content', wrapper);
5418
5419 L.DomEvent.disableScrollPropagation(this._contentNode);
5420 L.DomEvent.on(wrapper, 'contextmenu', L.DomEvent.stopPropagation);
5421
5422 this._tipContainer = L.DomUtil.create('div', prefix + '-tip-container', container);
5423 this._tip = L.DomUtil.create('div', prefix + '-tip', this._tipContainer);
5424 },
5425
5426 _updateContent: function () {
5427 if (!this._content) { return; }
5428
5429 if (typeof this._content === 'string') {
5430 this._contentNode.innerHTML = this._content;
5431 } else {
5432 while (this._contentNode.hasChildNodes()) {
5433 this._contentNode.removeChild(this._contentNode.firstChild);
5434 }
5435 this._contentNode.appendChild(this._content);
5436 }
5437 this.fire('contentupdate');
5438 },
5439
5440 _updateLayout: function () {
5441 var container = this._contentNode,
5442 style = container.style;
5443
5444 style.width = '';
5445 style.whiteSpace = 'nowrap';
5446
5447 var width = container.offsetWidth;
5448 width = Math.min(width, this.options.maxWidth);
5449 width = Math.max(width, this.options.minWidth);
5450
5451 style.width = (width + 1) + 'px';
5452 style.whiteSpace = '';
5453
5454 style.height = '';
5455
5456 var height = container.offsetHeight,
5457 maxHeight = this.options.maxHeight,
5458 scrolledClass = 'leaflet-popup-scrolled';
5459
5460 if (maxHeight && height > maxHeight) {
5461 style.height = maxHeight + 'px';
5462 L.DomUtil.addClass(container, scrolledClass);
5463 } else {
5464 L.DomUtil.removeClass(container, scrolledClass);
5465 }
5466
5467 this._containerWidth = this._container.offsetWidth;
5468 },
5469
5470 _updatePosition: function () {
5471 if (!this._map) { return; }
5472
5473 var pos = this._map.latLngToLayerPoint(this._latlng),
5474 animated = this._animated,
5475 offset = L.point(this.options.offset);
5476
5477 if (animated) {
5478 L.DomUtil.setPosition(this._container, pos);
5479 }
5480
5481 this._containerBottom = -offset.y - (animated ? 0 : pos.y);
5482 this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x + (animated ? 0 : pos.x);
5483
5484 // bottom position the popup in case the height of the popup changes (images loading etc)
5485 this._container.style.bottom = this._containerBottom + 'px';
5486 this._container.style.left = this._containerLeft + 'px';
5487 },
5488
5489 _zoomAnimation: function (opt) {
5490 var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center);
5491
5492 L.DomUtil.setPosition(this._container, pos);
5493 },
5494
5495 _adjustPan: function () {
5496 if (!this.options.autoPan) { return; }
5497
5498 var map = this._map,
5499 containerHeight = this._container.offsetHeight,
5500 containerWidth = this._containerWidth,
5501
5502 layerPos = new L.Point(this._containerLeft, -containerHeight - this._containerBottom);
5503
5504 if (this._animated) {
5505 layerPos._add(L.DomUtil.getPosition(this._container));
5506 }
5507
5508 var containerPos = map.layerPointToContainerPoint(layerPos),
5509 padding = L.point(this.options.autoPanPadding),
5510 paddingTL = L.point(this.options.autoPanPaddingTopLeft || padding),
5511 paddingBR = L.point(this.options.autoPanPaddingBottomRight || padding),
5512 size = map.getSize(),
5513 dx = 0,
5514 dy = 0;
5515
5516 if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
5517 dx = containerPos.x + containerWidth - size.x + paddingBR.x;
5518 }
5519 if (containerPos.x - dx - paddingTL.x < 0) { // left
5520 dx = containerPos.x - paddingTL.x;
5521 }
5522 if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
5523 dy = containerPos.y + containerHeight - size.y + paddingBR.y;
5524 }
5525 if (containerPos.y - dy - paddingTL.y < 0) { // top
5526 dy = containerPos.y - paddingTL.y;
5527 }
5528
5529 if (dx || dy) {
5530 map
5531 .fire('autopanstart')
5532 .panBy([dx, dy]);
5533 }
5534 },
5535
5536 _onCloseButtonClick: function (e) {
5537 this._close();
5538 L.DomEvent.stop(e);
5539 }
5540});
5541
5542L.popup = function (options, source) {
5543 return new L.Popup(options, source);
5544};
5545
5546
5547L.Map.include({
5548 openPopup: function (popup, latlng, options) { // (Popup) or (String || HTMLElement, LatLng[, Object])
5549 this.closePopup();
5550
5551 if (!(popup instanceof L.Popup)) {
5552 var content = popup;
5553
5554 popup = new L.Popup(options)
5555 .setLatLng(latlng)
5556 .setContent(content);
5557 }
5558 popup._isOpen = true;
5559
5560 this._popup = popup;
5561 return this.addLayer(popup);
5562 },
5563
5564 closePopup: function (popup) {
5565 if (!popup || popup === this._popup) {
5566 popup = this._popup;
5567 this._popup = null;
5568 }
5569 if (popup) {
5570 this.removeLayer(popup);
5571 popup._isOpen = false;
5572 }
5573 return this;
5574 }
5575});
5576
5577
5578/*
5579 * Popup extension to L.Marker, adding popup-related methods.
5580 */
5581
5582L.Marker.include({
5583 openPopup: function () {
5584 if (this._popup && this._map && !this._map.hasLayer(this._popup)) {
5585 this._popup.setLatLng(this._latlng);
5586 this._map.openPopup(this._popup);
5587 }
5588
5589 return this;
5590 },
5591
5592 closePopup: function () {
5593 if (this._popup) {
5594 this._popup._close();
5595 }
5596 return this;
5597 },
5598
5599 togglePopup: function () {
5600 if (this._popup) {
5601 if (this._popup._isOpen) {
5602 this.closePopup();
5603 } else {
5604 this.openPopup();
5605 }
5606 }
5607 return this;
5608 },
5609
5610 bindPopup: function (content, options) {
5611 var anchor = L.point(this.options.icon.options.popupAnchor || [0, 0]);
5612
5613 anchor = anchor.add(L.Popup.prototype.options.offset);
5614
5615 if (options && options.offset) {
5616 anchor = anchor.add(options.offset);
5617 }
5618
5619 options = L.extend({offset: anchor}, options);
5620
5621 if (!this._popupHandlersAdded) {
5622 this
5623 .on('click', this.togglePopup, this)
5624 .on('remove', this.closePopup, this)
5625 .on('move', this._movePopup, this);
5626 this._popupHandlersAdded = true;
5627 }
5628
5629 if (content instanceof L.Popup) {
5630 L.setOptions(content, options);
5631 this._popup = content;
5632 content._source = this;
5633 } else {
5634 this._popup = new L.Popup(options, this)
5635 .setContent(content);
5636 }
5637
5638 return this;
5639 },
5640
5641 setPopupContent: function (content) {
5642 if (this._popup) {
5643 this._popup.setContent(content);
5644 }
5645 return this;
5646 },
5647
5648 unbindPopup: function () {
5649 if (this._popup) {
5650 this._popup = null;
5651 this
5652 .off('click', this.togglePopup, this)
5653 .off('remove', this.closePopup, this)
5654 .off('move', this._movePopup, this);
5655 this._popupHandlersAdded = false;
5656 }
5657 return this;
5658 },
5659
5660 getPopup: function () {
5661 return this._popup;
5662 },
5663
5664 _movePopup: function (e) {
5665 this._popup.setLatLng(e.latlng);
5666 }
5667});
5668
5669
5670/*
5671 * L.LayerGroup is a class to combine several layers into one so that
5672 * you can manipulate the group (e.g. add/remove it) as one layer.
5673 */
5674
5675L.LayerGroup = L.Class.extend({
5676 initialize: function (layers) {
5677 this._layers = {};
5678
5679 var i, len;
5680
5681 if (layers) {
5682 for (i = 0, len = layers.length; i < len; i++) {
5683 this.addLayer(layers[i]);
5684 }
5685 }
5686 },
5687
5688 addLayer: function (layer) {
5689 var id = this.getLayerId(layer);
5690
5691 this._layers[id] = layer;
5692
5693 if (this._map) {
5694 this._map.addLayer(layer);
5695 }
5696
5697 return this;
5698 },
5699
5700 removeLayer: function (layer) {
5701 var id = layer in this._layers ? layer : this.getLayerId(layer);
5702
5703 if (this._map && this._layers[id]) {
5704 this._map.removeLayer(this._layers[id]);
5705 }
5706
5707 delete this._layers[id];
5708
5709 return this;
5710 },
5711
5712 hasLayer: function (layer) {
5713 if (!layer) { return false; }
5714
5715 return (layer in this._layers || this.getLayerId(layer) in this._layers);
5716 },
5717
5718 clearLayers: function () {
5719 this.eachLayer(this.removeLayer, this);
5720 return this;
5721 },
5722
5723 invoke: function (methodName) {
5724 var args = Array.prototype.slice.call(arguments, 1),
5725 i, layer;
5726
5727 for (i in this._layers) {
5728 layer = this._layers[i];
5729
5730 if (layer[methodName]) {
5731 layer[methodName].apply(layer, args);
5732 }
5733 }
5734
5735 return this;
5736 },
5737
5738 onAdd: function (map) {
5739 this._map = map;
5740 this.eachLayer(map.addLayer, map);
5741 },
5742
5743 onRemove: function (map) {
5744 this.eachLayer(map.removeLayer, map);
5745 this._map = null;
5746 },
5747
5748 addTo: function (map) {
5749 map.addLayer(this);
5750 return this;
5751 },
5752
5753 eachLayer: function (method, context) {
5754 for (var i in this._layers) {
5755 method.call(context, this._layers[i]);
5756 }
5757 return this;
5758 },
5759
5760 getLayer: function (id) {
5761 return this._layers[id];
5762 },
5763
5764 getLayers: function () {
5765 var layers = [];
5766
5767 for (var i in this._layers) {
5768 layers.push(this._layers[i]);
5769 }
5770 return layers;
5771 },
5772
5773 setZIndex: function (zIndex) {
5774 return this.invoke('setZIndex', zIndex);
5775 },
5776
5777 getLayerId: function (layer) {
5778 return L.stamp(layer);
5779 }
5780});
5781
5782L.layerGroup = function (layers) {
5783 return new L.LayerGroup(layers);
5784};
5785
5786
5787/*
5788 * L.FeatureGroup extends L.LayerGroup by introducing mouse events and additional methods
5789 * shared between a group of interactive layers (like vectors or markers).
5790 */
5791
5792L.FeatureGroup = L.LayerGroup.extend({
5793 includes: L.Mixin.Events,
5794
5795 statics: {
5796 EVENTS: 'click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose'
5797 },
5798
5799 addLayer: function (layer) {
5800 if (this.hasLayer(layer)) {
5801 return this;
5802 }
5803
5804 if ('on' in layer) {
5805 layer.on(L.FeatureGroup.EVENTS, this._propagateEvent, this);
5806 }
5807
5808 L.LayerGroup.prototype.addLayer.call(this, layer);
5809
5810 if (this._popupContent && layer.bindPopup) {
5811 layer.bindPopup(this._popupContent, this._popupOptions);
5812 }
5813
5814 return this.fire('layeradd', {layer: layer});
5815 },
5816
5817 removeLayer: function (layer) {
5818 if (!this.hasLayer(layer)) {
5819 return this;
5820 }
5821 if (layer in this._layers) {
5822 layer = this._layers[layer];
5823 }
5824
5825 if ('off' in layer) {
5826 layer.off(L.FeatureGroup.EVENTS, this._propagateEvent, this);
5827 }
5828
5829 L.LayerGroup.prototype.removeLayer.call(this, layer);
5830
5831 if (this._popupContent) {
5832 this.invoke('unbindPopup');
5833 }
5834
5835 return this.fire('layerremove', {layer: layer});
5836 },
5837
5838 bindPopup: function (content, options) {
5839 this._popupContent = content;
5840 this._popupOptions = options;
5841 return this.invoke('bindPopup', content, options);
5842 },
5843
5844 openPopup: function (latlng) {
5845 // open popup on the first layer
5846 for (var id in this._layers) {
5847 this._layers[id].openPopup(latlng);
5848 break;
5849 }
5850 return this;
5851 },
5852
5853 setStyle: function (style) {
5854 return this.invoke('setStyle', style);
5855 },
5856
5857 bringToFront: function () {
5858 return this.invoke('bringToFront');
5859 },
5860
5861 bringToBack: function () {
5862 return this.invoke('bringToBack');
5863 },
5864
5865 getBounds: function () {
5866 var bounds = new L.LatLngBounds();
5867
5868 this.eachLayer(function (layer) {
5869 bounds.extend(layer instanceof L.Marker ? layer.getLatLng() : layer.getBounds());
5870 });
5871
5872 return bounds;
5873 },
5874
5875 _propagateEvent: function (e) {
5876 e = L.extend({
5877 layer: e.target,
5878 target: this
5879 }, e);
5880 this.fire(e.type, e);
5881 }
5882});
5883
5884L.featureGroup = function (layers) {
5885 return new L.FeatureGroup(layers);
5886};
5887
5888
5889/*
5890 * L.Path is a base class for rendering vector paths on a map. Inherited by Polyline, Circle, etc.
5891 */
5892
5893L.Path = L.Class.extend({
5894 includes: [L.Mixin.Events],
5895
5896 statics: {
5897 // how much to extend the clip area around the map view
5898 // (relative to its size, e.g. 0.5 is half the screen in each direction)
5899 // set it so that SVG element doesn't exceed 1280px (vectors flicker on dragend if it is)
5900 CLIP_PADDING: (function () {
5901 var max = L.Browser.mobile ? 1280 : 2000,
5902 target = (max / Math.max(window.outerWidth, window.outerHeight) - 1) / 2;
5903 return Math.max(0, Math.min(0.5, target));
5904 })()
5905 },
5906
5907 options: {
5908 stroke: true,
5909 color: '#0033ff',
5910 dashArray: null,
5911 lineCap: null,
5912 lineJoin: null,
5913 weight: 5,
5914 opacity: 0.5,
5915
5916 fill: false,
5917 fillColor: null, //same as color by default
5918 fillOpacity: 0.2,
5919
5920 clickable: true
5921 },
5922
5923 initialize: function (options) {
5924 L.setOptions(this, options);
5925 },
5926
5927 onAdd: function (map) {
5928 this._map = map;
5929
5930 if (!this._container) {
5931 this._initElements();
5932 this._initEvents();
5933 }
5934
5935 this.projectLatlngs();
5936 this._updatePath();
5937
5938 if (this._container) {
5939 this._map._pathRoot.appendChild(this._container);
5940 }
5941
5942 this.fire('add');
5943
5944 map.on({
5945 'viewreset': this.projectLatlngs,
5946 'moveend': this._updatePath
5947 }, this);
5948 },
5949
5950 addTo: function (map) {
5951 map.addLayer(this);
5952 return this;
5953 },
5954
5955 onRemove: function (map) {
5956 map._pathRoot.removeChild(this._container);
5957
5958 // Need to fire remove event before we set _map to null as the event hooks might need the object
5959 this.fire('remove');
5960 this._map = null;
5961
5962 if (L.Browser.vml) {
5963 this._container = null;
5964 this._stroke = null;
5965 this._fill = null;
5966 }
5967
5968 map.off({
5969 'viewreset': this.projectLatlngs,
5970 'moveend': this._updatePath
5971 }, this);
5972 },
5973
5974 projectLatlngs: function () {
5975 // do all projection stuff here
5976 },
5977
5978 setStyle: function (style) {
5979 L.setOptions(this, style);
5980
5981 if (this._container) {
5982 this._updateStyle();
5983 }
5984
5985 return this;
5986 },
5987
5988 redraw: function () {
5989 if (this._map) {
5990 this.projectLatlngs();
5991 this._updatePath();
5992 }
5993 return this;
5994 }
5995});
5996
5997L.Map.include({
5998 _updatePathViewport: function () {
5999 var p = L.Path.CLIP_PADDING,
6000 size = this.getSize(),
6001 panePos = L.DomUtil.getPosition(this._mapPane),
6002 min = panePos.multiplyBy(-1)._subtract(size.multiplyBy(p)._round()),
6003 max = min.add(size.multiplyBy(1 + p * 2)._round());
6004
6005 this._pathViewport = new L.Bounds(min, max);
6006 }
6007});
6008
6009
6010/*
6011 * Extends L.Path with SVG-specific rendering code.
6012 */
6013
6014L.Path.SVG_NS = 'http://www.w3.org/2000/svg';
6015
6016L.Browser.svg = !!(document.createElementNS && document.createElementNS(L.Path.SVG_NS, 'svg').createSVGRect);
6017
6018L.Path = L.Path.extend({
6019 statics: {
6020 SVG: L.Browser.svg
6021 },
6022
6023 bringToFront: function () {
6024 var root = this._map._pathRoot,
6025 path = this._container;
6026
6027 if (path && root.lastChild !== path) {
6028 root.appendChild(path);
6029 }
6030 return this;
6031 },
6032
6033 bringToBack: function () {
6034 var root = this._map._pathRoot,
6035 path = this._container,
6036 first = root.firstChild;
6037
6038 if (path && first !== path) {
6039 root.insertBefore(path, first);
6040 }
6041 return this;
6042 },
6043
6044 getPathString: function () {
6045 // form path string here
6046 },
6047
6048 _createElement: function (name) {
6049 return document.createElementNS(L.Path.SVG_NS, name);
6050 },
6051
6052 _initElements: function () {
6053 this._map._initPathRoot();
6054 this._initPath();
6055 this._initStyle();
6056 },
6057
6058 _initPath: function () {
6059 this._container = this._createElement('g');
6060
6061 this._path = this._createElement('path');
6062
6063 if (this.options.className) {
6064 L.DomUtil.addClass(this._path, this.options.className);
6065 }
6066
6067 this._container.appendChild(this._path);
6068 },
6069
6070 _initStyle: function () {
6071 if (this.options.stroke) {
6072 this._path.setAttribute('stroke-linejoin', 'round');
6073 this._path.setAttribute('stroke-linecap', 'round');
6074 }
6075 if (this.options.fill) {
6076 this._path.setAttribute('fill-rule', 'evenodd');
6077 }
6078 if (this.options.pointerEvents) {
6079 this._path.setAttribute('pointer-events', this.options.pointerEvents);
6080 }
6081 if (!this.options.clickable && !this.options.pointerEvents) {
6082 this._path.setAttribute('pointer-events', 'none');
6083 }
6084 this._updateStyle();
6085 },
6086
6087 _updateStyle: function () {
6088 if (this.options.stroke) {
6089 this._path.setAttribute('stroke', this.options.color);
6090 this._path.setAttribute('stroke-opacity', this.options.opacity);
6091 this._path.setAttribute('stroke-width', this.options.weight);
6092 if (this.options.dashArray) {
6093 this._path.setAttribute('stroke-dasharray', this.options.dashArray);
6094 } else {
6095 this._path.removeAttribute('stroke-dasharray');
6096 }
6097 if (this.options.lineCap) {
6098 this._path.setAttribute('stroke-linecap', this.options.lineCap);
6099 }
6100 if (this.options.lineJoin) {
6101 this._path.setAttribute('stroke-linejoin', this.options.lineJoin);
6102 }
6103 } else {
6104 this._path.setAttribute('stroke', 'none');
6105 }
6106 if (this.options.fill) {
6107 this._path.setAttribute('fill', this.options.fillColor || this.options.color);
6108 this._path.setAttribute('fill-opacity', this.options.fillOpacity);
6109 } else {
6110 this._path.setAttribute('fill', 'none');
6111 }
6112 },
6113
6114 _updatePath: function () {
6115 var str = this.getPathString();
6116 if (!str) {
6117 // fix webkit empty string parsing bug
6118 str = 'M0 0';
6119 }
6120 this._path.setAttribute('d', str);
6121 },
6122
6123 // TODO remove duplication with L.Map
6124 _initEvents: function () {
6125 if (this.options.clickable) {
6126 if (L.Browser.svg || !L.Browser.vml) {
6127 L.DomUtil.addClass(this._path, 'leaflet-clickable');
6128 }
6129
6130 L.DomEvent.on(this._container, 'click', this._onMouseClick, this);
6131
6132 var events = ['dblclick', 'mousedown', 'mouseover',
6133 'mouseout', 'mousemove', 'contextmenu'];
6134 for (var i = 0; i < events.length; i++) {
6135 L.DomEvent.on(this._container, events[i], this._fireMouseEvent, this);
6136 }
6137 }
6138 },
6139
6140 _onMouseClick: function (e) {
6141 if (this._map.dragging && this._map.dragging.moved()) { return; }
6142
6143 this._fireMouseEvent(e);
6144 },
6145
6146 _fireMouseEvent: function (e) {
6147 if (!this._map || !this.hasEventListeners(e.type)) { return; }
6148
6149 var map = this._map,
6150 containerPoint = map.mouseEventToContainerPoint(e),
6151 layerPoint = map.containerPointToLayerPoint(containerPoint),
6152 latlng = map.layerPointToLatLng(layerPoint);
6153
6154 this.fire(e.type, {
6155 latlng: latlng,
6156 layerPoint: layerPoint,
6157 containerPoint: containerPoint,
6158 originalEvent: e
6159 });
6160
6161 if (e.type === 'contextmenu') {
6162 L.DomEvent.preventDefault(e);
6163 }
6164 if (e.type !== 'mousemove') {
6165 L.DomEvent.stopPropagation(e);
6166 }
6167 }
6168});
6169
6170L.Map.include({
6171 _initPathRoot: function () {
6172 if (!this._pathRoot) {
6173 this._pathRoot = L.Path.prototype._createElement('svg');
6174 this._panes.overlayPane.appendChild(this._pathRoot);
6175
6176 if (this.options.zoomAnimation && L.Browser.any3d) {
6177 L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-animated');
6178
6179 this.on({
6180 'zoomanim': this._animatePathZoom,
6181 'zoomend': this._endPathZoom
6182 });
6183 } else {
6184 L.DomUtil.addClass(this._pathRoot, 'leaflet-zoom-hide');
6185 }
6186
6187 this.on('moveend', this._updateSvgViewport);
6188 this._updateSvgViewport();
6189 }
6190 },
6191
6192 _animatePathZoom: function (e) {
6193 var scale = this.getZoomScale(e.zoom),
6194 offset = this._getCenterOffset(e.center)._multiplyBy(-scale)._add(this._pathViewport.min);
6195
6196 this._pathRoot.style[L.DomUtil.TRANSFORM] =
6197 L.DomUtil.getTranslateString(offset) + ' scale(' + scale + ') ';
6198
6199 this._pathZooming = true;
6200 },
6201
6202 _endPathZoom: function () {
6203 this._pathZooming = false;
6204 },
6205
6206 _updateSvgViewport: function () {
6207
6208 if (this._pathZooming) {
6209 // Do not update SVGs while a zoom animation is going on otherwise the animation will break.
6210 // When the zoom animation ends we will be updated again anyway
6211 // This fixes the case where you do a momentum move and zoom while the move is still ongoing.
6212 return;
6213 }
6214
6215 this._updatePathViewport();
6216
6217 var vp = this._pathViewport,
6218 min = vp.min,
6219 max = vp.max,
6220 width = max.x - min.x,
6221 height = max.y - min.y,
6222 root = this._pathRoot,
6223 pane = this._panes.overlayPane;
6224
6225 // Hack to make flicker on drag end on mobile webkit less irritating
6226 if (L.Browser.mobileWebkit) {
6227 pane.removeChild(root);
6228 }
6229
6230 L.DomUtil.setPosition(root, min);
6231 root.setAttribute('width', width);
6232 root.setAttribute('height', height);
6233 root.setAttribute('viewBox', [min.x, min.y, width, height].join(' '));
6234
6235 if (L.Browser.mobileWebkit) {
6236 pane.appendChild(root);
6237 }
6238 }
6239});
6240
6241
6242/*
6243 * Popup extension to L.Path (polylines, polygons, circles), adding popup-related methods.
6244 */
6245
6246L.Path.include({
6247
6248 bindPopup: function (content, options) {
6249
6250 if (content instanceof L.Popup) {
6251 this._popup = content;
6252 } else {
6253 if (!this._popup || options) {
6254 this._popup = new L.Popup(options, this);
6255 }
6256 this._popup.setContent(content);
6257 }
6258
6259 if (!this._popupHandlersAdded) {
6260 this
6261 .on('click', this._openPopup, this)
6262 .on('remove', this.closePopup, this);
6263
6264 this._popupHandlersAdded = true;
6265 }
6266
6267 return this;
6268 },
6269
6270 unbindPopup: function () {
6271 if (this._popup) {
6272 this._popup = null;
6273 this
6274 .off('click', this._openPopup)
6275 .off('remove', this.closePopup);
6276
6277 this._popupHandlersAdded = false;
6278 }
6279 return this;
6280 },
6281
6282 openPopup: function (latlng) {
6283
6284 if (this._popup) {
6285 // open the popup from one of the path's points if not specified
6286 latlng = latlng || this._latlng ||
6287 this._latlngs[Math.floor(this._latlngs.length / 2)];
6288
6289 this._openPopup({latlng: latlng});
6290 }
6291
6292 return this;
6293 },
6294
6295 closePopup: function () {
6296 if (this._popup) {
6297 this._popup._close();
6298 }
6299 return this;
6300 },
6301
6302 _openPopup: function (e) {
6303 this._popup.setLatLng(e.latlng);
6304 this._map.openPopup(this._popup);
6305 }
6306});
6307
6308
6309/*
6310 * Vector rendering for IE6-8 through VML.
6311 * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
6312 */
6313
6314L.Browser.vml = !L.Browser.svg && (function () {
6315 try {
6316 var div = document.createElement('div');
6317 div.innerHTML = '<v:shape adj="1"/>';
6318
6319 var shape = div.firstChild;
6320 shape.style.behavior = 'url(#default#VML)';
6321
6322 return shape && (typeof shape.adj === 'object');
6323
6324 } catch (e) {
6325 return false;
6326 }
6327}());
6328
6329L.Path = L.Browser.svg || !L.Browser.vml ? L.Path : L.Path.extend({
6330 statics: {
6331 VML: true,
6332 CLIP_PADDING: 0.02
6333 },
6334
6335 _createElement: (function () {
6336 try {
6337 document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
6338 return function (name) {
6339 return document.createElement('<lvml:' + name + ' class="lvml">');
6340 };
6341 } catch (e) {
6342 return function (name) {
6343 return document.createElement(
6344 '<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
6345 };
6346 }
6347 }()),
6348
6349 _initPath: function () {
6350 var container = this._container = this._createElement('shape');
6351
6352 L.DomUtil.addClass(container, 'leaflet-vml-shape' +
6353 (this.options.className ? ' ' + this.options.className : ''));
6354
6355 if (this.options.clickable) {
6356 L.DomUtil.addClass(container, 'leaflet-clickable');
6357 }
6358
6359 container.coordsize = '1 1';
6360
6361 this._path = this._createElement('path');
6362 container.appendChild(this._path);
6363
6364 this._map._pathRoot.appendChild(container);
6365 },
6366
6367 _initStyle: function () {
6368 this._updateStyle();
6369 },
6370
6371 _updateStyle: function () {
6372 var stroke = this._stroke,
6373 fill = this._fill,
6374 options = this.options,
6375 container = this._container;
6376
6377 container.stroked = options.stroke;
6378 container.filled = options.fill;
6379
6380 if (options.stroke) {
6381 if (!stroke) {
6382 stroke = this._stroke = this._createElement('stroke');
6383 stroke.endcap = 'round';
6384 container.appendChild(stroke);
6385 }
6386 stroke.weight = options.weight + 'px';
6387 stroke.color = options.color;
6388 stroke.opacity = options.opacity;
6389
6390 if (options.dashArray) {
6391 stroke.dashStyle = L.Util.isArray(options.dashArray) ?
6392 options.dashArray.join(' ') :
6393 options.dashArray.replace(/( *, *)/g, ' ');
6394 } else {
6395 stroke.dashStyle = '';
6396 }
6397 if (options.lineCap) {
6398 stroke.endcap = options.lineCap.replace('butt', 'flat');
6399 }
6400 if (options.lineJoin) {
6401 stroke.joinstyle = options.lineJoin;
6402 }
6403
6404 } else if (stroke) {
6405 container.removeChild(stroke);
6406 this._stroke = null;
6407 }
6408
6409 if (options.fill) {
6410 if (!fill) {
6411 fill = this._fill = this
6412IITC chinese.js
6413鏿“‡é–‹å•Ÿå·¥å…·
6414ç›®å‰é¡¯ç¤ºçš„æ˜¯ã€ŒIITC chinese.jsã€ã€‚