diff --git a/BIN/MAP/images/APRS Symbols, How to.jpg b/BIN/MAP/images/APRS Symbols, How to.jpg new file mode 100644 index 0000000..c3d6480 Binary files /dev/null and b/BIN/MAP/images/APRS Symbols, How to.jpg differ diff --git a/BIN/MAP/images/_tmp_.png b/BIN/MAP/images/_tmp_.png new file mode 100644 index 0000000..2ded289 Binary files /dev/null and b/BIN/MAP/images/_tmp_.png differ diff --git a/BIN/MAP/images/arrow.png b/BIN/MAP/images/arrow.png new file mode 100644 index 0000000..a9474b9 Binary files /dev/null and b/BIN/MAP/images/arrow.png differ diff --git a/BIN/MAP/images/arrow64.png b/BIN/MAP/images/arrow64.png new file mode 100644 index 0000000..0e61ef9 Binary files /dev/null and b/BIN/MAP/images/arrow64.png differ diff --git a/BIN/MAP/images/primary.png b/BIN/MAP/images/primary.png new file mode 100644 index 0000000..6617b66 Binary files /dev/null and b/BIN/MAP/images/primary.png differ diff --git a/BIN/MAP/images/primary128.png b/BIN/MAP/images/primary128.png new file mode 100644 index 0000000..63c6a8b Binary files /dev/null and b/BIN/MAP/images/primary128.png differ diff --git a/BIN/MAP/images/primary64.png b/BIN/MAP/images/primary64.png new file mode 100644 index 0000000..562290a Binary files /dev/null and b/BIN/MAP/images/primary64.png differ diff --git a/BIN/MAP/images/secondary.png b/BIN/MAP/images/secondary.png new file mode 100644 index 0000000..381049b Binary files /dev/null and b/BIN/MAP/images/secondary.png differ diff --git a/BIN/MAP/images/secondary128.png b/BIN/MAP/images/secondary128.png new file mode 100644 index 0000000..9ba3992 Binary files /dev/null and b/BIN/MAP/images/secondary128.png differ diff --git a/BIN/MAP/images/secondary64.png b/BIN/MAP/images/secondary64.png new file mode 100644 index 0000000..6428c31 Binary files /dev/null and b/BIN/MAP/images/secondary64.png differ diff --git a/BIN/MAP/index.html b/BIN/MAP/index.html new file mode 100644 index 0000000..00d2523 --- /dev/null +++ b/BIN/MAP/index.html @@ -0,0 +1,553 @@ + + + + + Simple APRS Server + + + + + + + + + + + + +
+
+
+ USERS:..... + +
+ + + \ No newline at end of file diff --git a/BIN/MAP/jQueryRotate.js b/BIN/MAP/jQueryRotate.js new file mode 100644 index 0000000..3b33c23 --- /dev/null +++ b/BIN/MAP/jQueryRotate.js @@ -0,0 +1,339 @@ +// VERSION: 2.3 LAST UPDATE: 11.07.2013 +/* + * Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php + * + * Made by Wilq32, wilq32@gmail.com, Wroclaw, Poland, 01.2009 + * Website: http://jqueryrotate.com + */ + +(function($) { + var supportedCSS,supportedCSSOrigin, styles=document.getElementsByTagName("head")[0].style,toCheck="transformProperty WebkitTransform OTransform msTransform MozTransform".split(" "); + for (var a = 0; a < toCheck.length; a++) if (styles[toCheck[a]] !== undefined) { supportedCSS = toCheck[a]; } + if (supportedCSS) { + supportedCSSOrigin = supportedCSS.replace(/[tT]ransform/,"TransformOrigin"); + if (supportedCSSOrigin[0] == "T") supportedCSSOrigin[0] = "t"; + } + + // Bad eval to preven google closure to remove it from code o_O + eval('IE = "v"=="\v"'); + + jQuery.fn.extend({ + rotate:function(parameters) + { + if (this.length===0||typeof parameters=="undefined") return; + if (typeof parameters=="number") parameters={angle:parameters}; + var returned=[]; + for (var i=0,i0=this.length;i this._parameters.duration; + + // TODO: Bug for animatedGif for static rotation ? (to test) + if (checkEnd && !this._parameters.animatedGif) + { + clearTimeout(this._timer); + } + else + { + if (this._canvas||this._vimage||this._img) { + var angle = this._parameters.easing(0, actualTime - this._animateStartTime, this._animateStartAngle, this._parameters.animateTo - this._animateStartAngle, this._parameters.duration); + this._rotate((~~(angle*10))/10); + } + if (this._parameters.step) { + this._parameters.step(this._angle); + } + var self = this; + this._timer = setTimeout(function() + { + self._animate.call(self); + }, 10); + } + + // To fix Bug that prevents using recursive function in callback I moved this function to back + if (this._parameters.callback && checkEnd){ + this._angle = this._parameters.animateTo; + this._rotate(this._angle); + this._parameters.callback.call(this._rootObj); + } + }, + + _rotate : (function() + { + var rad = Math.PI/180; + if (IE) + return function(angle) + { + this._angle = angle; + this._container.style.rotation=(angle%360)+"deg"; + this._vimage.style.top = -(this._rotationCenterY - this._imgHeight/2) + "px"; + this._vimage.style.left = -(this._rotationCenterX - this._imgWidth/2) + "px"; + this._container.style.top = this._rotationCenterY - this._imgHeight/2 + "px"; + this._container.style.left = this._rotationCenterX - this._imgWidth/2 + "px"; + + } + else if (supportedCSS) + return function(angle){ + this._angle = angle; + this._img.style[supportedCSS]="rotate("+(angle%360)+"deg)"; + this._img.style[supportedCSSOrigin]=this._parameters.center.join(" "); + } + else + return function(angle) + { + this._angle = angle; + angle=(angle%360)* rad; + // clear canvas + this._canvas.width = this._width;//+this._widthAdd; + this._canvas.height = this._height;//+this._heightAdd; + + // REMEMBER: all drawings are read from backwards.. so first function is translate, then rotate, then translate, translate.. + this._cnv.translate(this._imgWidth*this._aspectW,this._imgHeight*this._aspectH); // at least center image on screen + this._cnv.translate(this._rotationCenterX,this._rotationCenterY); // we move image back to its orginal + this._cnv.rotate(angle); // rotate image + this._cnv.translate(-this._rotationCenterX,-this._rotationCenterY); // move image to its center, so we can rotate around its center + this._cnv.scale(this._aspectW,this._aspectH); // SCALE - if needed ;) + this._cnv.drawImage(this._img, 0, 0); // First - we draw image + } + + })() + } + + if (IE) + { + Wilq32.PhotoEffect.prototype.createVMLNode=(function(){ + document.createStyleSheet().addRule(".rvml", "behavior:url(#default#VML)"); + try { + !document.namespaces.rvml && document.namespaces.add("rvml", "urn:schemas-microsoft-com:vml"); + return function (tagName) { + return document.createElement(''); + }; + } catch (e) { + return function (tagName) { + return document.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="rvml">'); + }; + } + })(); + } + +})(jQuery); diff --git a/BIN/MAP/jquery.js b/BIN/MAP/jquery.js new file mode 100644 index 0000000..82b98e1 --- /dev/null +++ b/BIN/MAP/jquery.js @@ -0,0 +1,32 @@ +/* + * jQuery 1.2.6 - New Wave Javascript + * + * Copyright (c) 2008 John Resig (jquery.com) + * Dual licensed under the MIT (MIT-LICENSE.txt) + * and GPL (GPL-LICENSE.txt) licenses. + * + * $Date: 2008-05-24 14:22:17 -0400 (Sat, 24 May 2008) $ + * $Rev: 5685 $ + */ +(function(){var _jQuery=window.jQuery,_$=window.$;var jQuery=window.jQuery=window.$=function(selector,context){return new jQuery.fn.init(selector,context);};var quickExpr=/^[^<]*(<(.|\s)+>)[^>]*$|^#(\w+)$/,isSimple=/^.[^:#\[\.]*$/,undefined;jQuery.fn=jQuery.prototype={init:function(selector,context){selector=selector||document;if(selector.nodeType){this[0]=selector;this.length=1;return this;}if(typeof selector=="string"){var match=quickExpr.exec(selector);if(match&&(match[1]||!context)){if(match[1])selector=jQuery.clean([match[1]],context);else{var elem=document.getElementById(match[3]);if(elem){if(elem.id!=match[3])return jQuery().find(selector);return jQuery(elem);}selector=[];}}else +return jQuery(context).find(selector);}else if(jQuery.isFunction(selector))return jQuery(document)[jQuery.fn.ready?"ready":"load"](selector);return this.setArray(jQuery.makeArray(selector));},jquery:"1.2.6",size:function(){return this.length;},length:0,get:function(num){return num==undefined?jQuery.makeArray(this):this[num];},pushStack:function(elems){var ret=jQuery(elems);ret.prevObject=this;return ret;},setArray:function(elems){this.length=0;Array.prototype.push.apply(this,elems);return this;},each:function(callback,args){return jQuery.each(this,callback,args);},index:function(elem){var ret=-1;return jQuery.inArray(elem&&elem.jquery?elem[0]:elem,this);},attr:function(name,value,type){var options=name;if(name.constructor==String)if(value===undefined)return this[0]&&jQuery[type||"attr"](this[0],name);else{options={};options[name]=value;}return this.each(function(i){for(name in options)jQuery.attr(type?this.style:this,name,jQuery.prop(this,options[name],type,i,name));});},css:function(key,value){if((key=='width'||key=='height')&&parseFloat(value)<0)value=undefined;return this.attr(key,value,"curCSS");},text:function(text){if(typeof text!="object"&&text!=null)return this.empty().append((this[0]&&this[0].ownerDocument||document).createTextNode(text));var ret="";jQuery.each(text||this,function(){jQuery.each(this.childNodes,function(){if(this.nodeType!=8)ret+=this.nodeType!=1?this.nodeValue:jQuery.fn.text([this]);});});return ret;},wrapAll:function(html){if(this[0])jQuery(html,this[0].ownerDocument).clone().insertBefore(this[0]).map(function(){var elem=this;while(elem.firstChild)elem=elem.firstChild;return elem;}).append(this);return this;},wrapInner:function(html){return this.each(function(){jQuery(this).contents().wrapAll(html);});},wrap:function(html){return this.each(function(){jQuery(this).wrapAll(html);});},append:function(){return this.domManip(arguments,true,false,function(elem){if(this.nodeType==1)this.appendChild(elem);});},prepend:function(){return this.domManip(arguments,true,true,function(elem){if(this.nodeType==1)this.insertBefore(elem,this.firstChild);});},before:function(){return this.domManip(arguments,false,false,function(elem){this.parentNode.insertBefore(elem,this);});},after:function(){return this.domManip(arguments,false,true,function(elem){this.parentNode.insertBefore(elem,this.nextSibling);});},end:function(){return this.prevObject||jQuery([]);},find:function(selector){var elems=jQuery.map(this,function(elem){return jQuery.find(selector,elem);});return this.pushStack(/[^+>] [^+>]/.test(selector)||selector.indexOf("..")>-1?jQuery.unique(elems):elems);},clone:function(events){var ret=this.map(function(){if(jQuery.browser.msie&&!jQuery.isXMLDoc(this)){var clone=this.cloneNode(true),container=document.createElement("div");container.appendChild(clone);return jQuery.clean([container.innerHTML])[0];}else +return this.cloneNode(true);});var clone=ret.find("*").andSelf().each(function(){if(this[expando]!=undefined)this[expando]=null;});if(events===true)this.find("*").andSelf().each(function(i){if(this.nodeType==3)return;var events=jQuery.data(this,"events");for(var type in events)for(var handler in events[type])jQuery.event.add(clone[i],type,events[type][handler],events[type][handler].data);});return ret;},filter:function(selector){return this.pushStack(jQuery.isFunction(selector)&&jQuery.grep(this,function(elem,i){return selector.call(elem,i);})||jQuery.multiFilter(selector,this));},not:function(selector){if(selector.constructor==String)if(isSimple.test(selector))return this.pushStack(jQuery.multiFilter(selector,this,true));else +selector=jQuery.multiFilter(selector,this);var isArrayLike=selector.length&&selector[selector.length-1]!==undefined&&!selector.nodeType;return this.filter(function(){return isArrayLike?jQuery.inArray(this,selector)<0:this!=selector;});},add:function(selector){return this.pushStack(jQuery.unique(jQuery.merge(this.get(),typeof selector=='string'?jQuery(selector):jQuery.makeArray(selector))));},is:function(selector){return!!selector&&jQuery.multiFilter(selector,this).length>0;},hasClass:function(selector){return this.is("."+selector);},val:function(value){if(value==undefined){if(this.length){var elem=this[0];if(jQuery.nodeName(elem,"select")){var index=elem.selectedIndex,values=[],options=elem.options,one=elem.type=="select-one";if(index<0)return null;for(var i=one?index:0,max=one?index+1:options.length;i=0||jQuery.inArray(this.name,value)>=0);else if(jQuery.nodeName(this,"select")){var values=jQuery.makeArray(value);jQuery("option",this).each(function(){this.selected=(jQuery.inArray(this.value,values)>=0||jQuery.inArray(this.text,values)>=0);});if(!values.length)this.selectedIndex=-1;}else +this.value=value;});},html:function(value){return value==undefined?(this[0]?this[0].innerHTML:null):this.empty().append(value);},replaceWith:function(value){return this.after(value).remove();},eq:function(i){return this.slice(i,i+1);},slice:function(){return this.pushStack(Array.prototype.slice.apply(this,arguments));},map:function(callback){return this.pushStack(jQuery.map(this,function(elem,i){return callback.call(elem,i,elem);}));},andSelf:function(){return this.add(this.prevObject);},data:function(key,value){var parts=key.split(".");parts[1]=parts[1]?"."+parts[1]:"";if(value===undefined){var data=this.triggerHandler("getData"+parts[1]+"!",[parts[0]]);if(data===undefined&&this.length)data=jQuery.data(this[0],key);return data===undefined&&parts[1]?this.data(parts[0]):data;}else +return this.trigger("setData"+parts[1]+"!",[parts[0],value]).each(function(){jQuery.data(this,key,value);});},removeData:function(key){return this.each(function(){jQuery.removeData(this,key);});},domManip:function(args,table,reverse,callback){var clone=this.length>1,elems;return this.each(function(){if(!elems){elems=jQuery.clean(args,this.ownerDocument);if(reverse)elems.reverse();}var obj=this;if(table&&jQuery.nodeName(this,"table")&&jQuery.nodeName(elems[0],"tr"))obj=this.getElementsByTagName("tbody")[0]||this.appendChild(this.ownerDocument.createElement("tbody"));var scripts=jQuery([]);jQuery.each(elems,function(){var elem=clone?jQuery(this).clone(true)[0]:this;if(jQuery.nodeName(elem,"script"))scripts=scripts.add(elem);else{if(elem.nodeType==1)scripts=scripts.add(jQuery("script",elem).remove());callback.call(obj,elem);}});scripts.each(evalScript);});}};jQuery.fn.init.prototype=jQuery.fn;function evalScript(i,elem){if(elem.src)jQuery.ajax({url:elem.src,async:false,dataType:"script"});else +jQuery.globalEval(elem.text||elem.textContent||elem.innerHTML||"");if(elem.parentNode)elem.parentNode.removeChild(elem);}function now(){return+new Date;}jQuery.extend=jQuery.fn.extend=function(){var target=arguments[0]||{},i=1,length=arguments.length,deep=false,options;if(target.constructor==Boolean){deep=target;target=arguments[1]||{};i=2;}if(typeof target!="object"&&typeof target!="function")target={};if(length==i){target=this;--i;}for(;i-1;}},swap:function(elem,options,callback){var old={};for(var name in options){old[name]=elem.style[name];elem.style[name]=options[name];}callback.call(elem);for(var name in options)elem.style[name]=old[name];},css:function(elem,name,force){if(name=="width"||name=="height"){var val,props={position:"absolute",visibility:"hidden",display:"block"},which=name=="width"?["Left","Right"]:["Top","Bottom"];function getWH(){val=name=="width"?elem.offsetWidth:elem.offsetHeight;var padding=0,border=0;jQuery.each(which,function(){padding+=parseFloat(jQuery.curCSS(elem,"padding"+this,true))||0;border+=parseFloat(jQuery.curCSS(elem,"border"+this+"Width",true))||0;});val-=Math.round(padding+border);}if(jQuery(elem).is(":visible"))getWH();else +jQuery.swap(elem,props,getWH);return Math.max(0,val);}return jQuery.curCSS(elem,name,force);},curCSS:function(elem,name,force){var ret,style=elem.style;function color(elem){if(!jQuery.browser.safari)return false;var ret=defaultView.getComputedStyle(elem,null);return!ret||ret.getPropertyValue("color")=="";}if(name=="opacity"&&jQuery.browser.msie){ret=jQuery.attr(style,"opacity");return ret==""?"1":ret;}if(jQuery.browser.opera&&name=="display"){var save=style.outline;style.outline="0 solid black";style.outline=save;}if(name.match(/float/i))name=styleFloat;if(!force&&style&&style[name])ret=style[name];else if(defaultView.getComputedStyle){if(name.match(/float/i))name="float";name=name.replace(/([A-Z])/g,"-$1").toLowerCase();var computedStyle=defaultView.getComputedStyle(elem,null);if(computedStyle&&!color(elem))ret=computedStyle.getPropertyValue(name);else{var swap=[],stack=[],a=elem,i=0;for(;a&&color(a);a=a.parentNode)stack.unshift(a);for(;i]*?)\/>/g,function(all,front,tag){return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i)?all:front+">";});var tags=jQuery.trim(elem).toLowerCase(),div=context.createElement("div");var wrap=!tags.indexOf("",""]||!tags.indexOf("",""]||tags.match(/^<(thead|tbody|tfoot|colg|cap)/)&&[1,"","
"]||!tags.indexOf("",""]||(!tags.indexOf("",""]||!tags.indexOf("",""]||jQuery.browser.msie&&[1,"div
","
"]||[0,"",""];div.innerHTML=wrap[1]+elem+wrap[2];while(wrap[0]--)div=div.lastChild;if(jQuery.browser.msie){var tbody=!tags.indexOf(""&&tags.indexOf("=0;--j)if(jQuery.nodeName(tbody[j],"tbody")&&!tbody[j].childNodes.length)tbody[j].parentNode.removeChild(tbody[j]);if(/^\s/.test(elem))div.insertBefore(context.createTextNode(elem.match(/^\s*/)[0]),div.firstChild);}elem=jQuery.makeArray(div.childNodes);}if(elem.length===0&&(!jQuery.nodeName(elem,"form")&&!jQuery.nodeName(elem,"select")))return;if(elem[0]==undefined||jQuery.nodeName(elem,"form")||elem.options)ret.push(elem);else +ret=jQuery.merge(ret,elem);});return ret;},attr:function(elem,name,value){if(!elem||elem.nodeType==3||elem.nodeType==8)return undefined;var notxml=!jQuery.isXMLDoc(elem),set=value!==undefined,msie=jQuery.browser.msie;name=notxml&&jQuery.props[name]||name;if(elem.tagName){var special=/href|src|style/.test(name);if(name=="selected"&&jQuery.browser.safari)elem.parentNode.selectedIndex;if(name in elem&¬xml&&!special){if(set){if(name=="type"&&jQuery.nodeName(elem,"input")&&elem.parentNode)throw"type property can't be changed";elem[name]=value;}if(jQuery.nodeName(elem,"form")&&elem.getAttributeNode(name))return elem.getAttributeNode(name).nodeValue;return elem[name];}if(msie&¬xml&&name=="style")return jQuery.attr(elem.style,"cssText",value);if(set)elem.setAttribute(name,""+value);var attr=msie&¬xml&&special?elem.getAttribute(name,2):elem.getAttribute(name);return attr===null?undefined:attr;}if(msie&&name=="opacity"){if(set){elem.zoom=1;elem.filter=(elem.filter||"").replace(/alpha\([^)]*\)/,"")+(parseInt(value)+''=="NaN"?"":"alpha(opacity="+value*100+")");}return elem.filter&&elem.filter.indexOf("opacity=")>=0?(parseFloat(elem.filter.match(/opacity=([^)]*)/)[1])/100)+'':"";}name=name.replace(/-([a-z])/ig,function(all,letter){return letter.toUpperCase();});if(set)elem[name]=value;return elem[name];},trim:function(text){return(text||"").replace(/^\s+|\s+$/g,"");},makeArray:function(array){var ret=[];if(array!=null){var i=array.length;if(i==null||array.split||array.setInterval||array.call)ret[0]=array;else +while(i)ret[--i]=array[i];}return ret;},inArray:function(elem,array){for(var i=0,length=array.length;i*",this).remove();while(this.firstChild)this.removeChild(this.firstChild);}},function(name,fn){jQuery.fn[name]=function(){return this.each(fn,arguments);};});jQuery.each(["Height","Width"],function(i,name){var type=name.toLowerCase();jQuery.fn[type]=function(size){return this[0]==window?jQuery.browser.opera&&document.body["client"+name]||jQuery.browser.safari&&window["inner"+name]||document.compatMode=="CSS1Compat"&&document.documentElement["client"+name]||document.body["client"+name]:this[0]==document?Math.max(Math.max(document.body["scroll"+name],document.documentElement["scroll"+name]),Math.max(document.body["offset"+name],document.documentElement["offset"+name])):size==undefined?(this.length?jQuery.css(this[0],type):null):this.css(type,size.constructor==String?size:size+"px");};});function num(elem,prop){return elem[0]&&parseInt(jQuery.curCSS(elem[0],prop,true),10)||0;}var chars=jQuery.browser.safari&&parseInt(jQuery.browser.version)<417?"(?:[\\w*_-]|\\\\.)":"(?:[\\w\u0128-\uFFFF*_-]|\\\\.)",quickChild=new RegExp("^>\\s*("+chars+"+)"),quickID=new RegExp("^("+chars+"+)(#)("+chars+"+)"),quickClass=new RegExp("^([#.]?)("+chars+"*)");jQuery.extend({expr:{"":function(a,i,m){return m[2]=="*"||jQuery.nodeName(a,m[2]);},"#":function(a,i,m){return a.getAttribute("id")==m[2];},":":{lt:function(a,i,m){return im[3]-0;},nth:function(a,i,m){return m[3]-0==i;},eq:function(a,i,m){return m[3]-0==i;},first:function(a,i){return i==0;},last:function(a,i,m,r){return i==r.length-1;},even:function(a,i){return i%2==0;},odd:function(a,i){return i%2;},"first-child":function(a){return a.parentNode.getElementsByTagName("*")[0]==a;},"last-child":function(a){return jQuery.nth(a.parentNode.lastChild,1,"previousSibling")==a;},"only-child":function(a){return!jQuery.nth(a.parentNode.lastChild,2,"previousSibling");},parent:function(a){return a.firstChild;},empty:function(a){return!a.firstChild;},contains:function(a,i,m){return(a.textContent||a.innerText||jQuery(a).text()||"").indexOf(m[3])>=0;},visible:function(a){return"hidden"!=a.type&&jQuery.css(a,"display")!="none"&&jQuery.css(a,"visibility")!="hidden";},hidden:function(a){return"hidden"==a.type||jQuery.css(a,"display")=="none"||jQuery.css(a,"visibility")=="hidden";},enabled:function(a){return!a.disabled;},disabled:function(a){return a.disabled;},checked:function(a){return a.checked;},selected:function(a){return a.selected||jQuery.attr(a,"selected");},text:function(a){return"text"==a.type;},radio:function(a){return"radio"==a.type;},checkbox:function(a){return"checkbox"==a.type;},file:function(a){return"file"==a.type;},password:function(a){return"password"==a.type;},submit:function(a){return"submit"==a.type;},image:function(a){return"image"==a.type;},reset:function(a){return"reset"==a.type;},button:function(a){return"button"==a.type||jQuery.nodeName(a,"button");},input:function(a){return/input|select|textarea|button/i.test(a.nodeName);},has:function(a,i,m){return jQuery.find(m[3],a).length;},header:function(a){return/h\d/i.test(a.nodeName);},animated:function(a){return jQuery.grep(jQuery.timers,function(fn){return a==fn.elem;}).length;}}},parse:[/^(\[) *@?([\w-]+) *([!*$^~=]*) *('?"?)(.*?)\4 *\]/,/^(:)([\w-]+)\("?'?(.*?(\(.*?\))?[^(]*?)"?'?\)/,new RegExp("^([:.#]*)("+chars+"+)")],multiFilter:function(expr,elems,not){var old,cur=[];while(expr&&expr!=old){old=expr;var f=jQuery.filter(expr,elems,not);expr=f.t.replace(/^\s*,\s*/,"");cur=not?elems=f.r:jQuery.merge(cur,f.r);}return cur;},find:function(t,context){if(typeof t!="string")return[t];if(context&&context.nodeType!=1&&context.nodeType!=9)return[];context=context||document;var ret=[context],done=[],last,nodeName;while(t&&last!=t){var r=[];last=t;t=jQuery.trim(t);var foundToken=false,re=quickChild,m=re.exec(t);if(m){nodeName=m[1].toUpperCase();for(var i=0;ret[i];i++)for(var c=ret[i].firstChild;c;c=c.nextSibling)if(c.nodeType==1&&(nodeName=="*"||c.nodeName.toUpperCase()==nodeName))r.push(c);ret=r;t=t.replace(re,"");if(t.indexOf(" ")==0)continue;foundToken=true;}else{re=/^([>+~])\s*(\w*)/i;if((m=re.exec(t))!=null){r=[];var merge={};nodeName=m[2].toUpperCase();m=m[1];for(var j=0,rl=ret.length;j=0;if(!not&&pass||not&&!pass)tmp.push(r[i]);}return tmp;},filter:function(t,r,not){var last;while(t&&t!=last){last=t;var p=jQuery.parse,m;for(var i=0;p[i];i++){m=p[i].exec(t);if(m){t=t.substring(m[0].length);m[2]=m[2].replace(/\\/g,"");break;}}if(!m)break;if(m[1]==":"&&m[2]=="not")r=isSimple.test(m[3])?jQuery.filter(m[3],r,true).r:jQuery(r).not(m[3]);else if(m[1]==".")r=jQuery.classFilter(r,m[2],not);else if(m[1]=="["){var tmp=[],type=m[3];for(var i=0,rl=r.length;i=0)^not)tmp.push(a);}r=tmp;}else if(m[1]==":"&&m[2]=="nth-child"){var merge={},tmp=[],test=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(m[3]=="even"&&"2n"||m[3]=="odd"&&"2n+1"||!/\D/.test(m[3])&&"0n+"+m[3]||m[3]),first=(test[1]+(test[2]||1))-0,last=test[3]-0;for(var i=0,rl=r.length;i=0)add=true;if(add^not)tmp.push(node);}r=tmp;}else{var fn=jQuery.expr[m[1]];if(typeof fn=="object")fn=fn[m[2]];if(typeof fn=="string")fn=eval("false||function(a,i){return "+fn+";}");r=jQuery.grep(r,function(elem,i){return fn(elem,i,m,r);},not);}}return{r:r,t:t};},dir:function(elem,dir){var matched=[],cur=elem[dir];while(cur&&cur!=document){if(cur.nodeType==1)matched.push(cur);cur=cur[dir];}return matched;},nth:function(cur,result,dir,elem){result=result||1;var num=0;for(;cur;cur=cur[dir])if(cur.nodeType==1&&++num==result)break;return cur;},sibling:function(n,elem){var r=[];for(;n;n=n.nextSibling){if(n.nodeType==1&&n!=elem)r.push(n);}return r;}});jQuery.event={add:function(elem,types,handler,data){if(elem.nodeType==3||elem.nodeType==8)return;if(jQuery.browser.msie&&elem.setInterval)elem=window;if(!handler.guid)handler.guid=this.guid++;if(data!=undefined){var fn=handler;handler=this.proxy(fn,function(){return fn.apply(this,arguments);});handler.data=data;}var events=jQuery.data(elem,"events")||jQuery.data(elem,"events",{}),handle=jQuery.data(elem,"handle")||jQuery.data(elem,"handle",function(){if(typeof jQuery!="undefined"&&!jQuery.event.triggered)return jQuery.event.handle.apply(arguments.callee.elem,arguments);});handle.elem=elem;jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];handler.type=parts[1];var handlers=events[type];if(!handlers){handlers=events[type]={};if(!jQuery.event.special[type]||jQuery.event.special[type].setup.call(elem)===false){if(elem.addEventListener)elem.addEventListener(type,handle,false);else if(elem.attachEvent)elem.attachEvent("on"+type,handle);}}handlers[handler.guid]=handler;jQuery.event.global[type]=true;});elem=null;},guid:1,global:{},remove:function(elem,types,handler){if(elem.nodeType==3||elem.nodeType==8)return;var events=jQuery.data(elem,"events"),ret,index;if(events){if(types==undefined||(typeof types=="string"&&types.charAt(0)=="."))for(var type in events)this.remove(elem,type+(types||""));else{if(types.type){handler=types.handler;types=types.type;}jQuery.each(types.split(/\s+/),function(index,type){var parts=type.split(".");type=parts[0];if(events[type]){if(handler)delete events[type][handler.guid];else +for(handler in events[type])if(!parts[1]||events[type][handler].type==parts[1])delete events[type][handler];for(ret in events[type])break;if(!ret){if(!jQuery.event.special[type]||jQuery.event.special[type].teardown.call(elem)===false){if(elem.removeEventListener)elem.removeEventListener(type,jQuery.data(elem,"handle"),false);else if(elem.detachEvent)elem.detachEvent("on"+type,jQuery.data(elem,"handle"));}ret=null;delete events[type];}}});}for(ret in events)break;if(!ret){var handle=jQuery.data(elem,"handle");if(handle)handle.elem=null;jQuery.removeData(elem,"events");jQuery.removeData(elem,"handle");}}},trigger:function(type,data,elem,donative,extra){data=jQuery.makeArray(data);if(type.indexOf("!")>=0){type=type.slice(0,-1);var exclusive=true;}if(!elem){if(this.global[type])jQuery("*").add([window,document]).trigger(type,data);}else{if(elem.nodeType==3||elem.nodeType==8)return undefined;var val,ret,fn=jQuery.isFunction(elem[type]||null),event=!data[0]||!data[0].preventDefault;if(event){data.unshift({type:type,target:elem,preventDefault:function(){},stopPropagation:function(){},timeStamp:now()});data[0][expando]=true;}data[0].type=type;if(exclusive)data[0].exclusive=true;var handle=jQuery.data(elem,"handle");if(handle)val=handle.apply(elem,data);if((!fn||(jQuery.nodeName(elem,'a')&&type=="click"))&&elem["on"+type]&&elem["on"+type].apply(elem,data)===false)val=false;if(event)data.shift();if(extra&&jQuery.isFunction(extra)){ret=extra.apply(elem,val==null?data:data.concat(val));if(ret!==undefined)val=ret;}if(fn&&donative!==false&&val!==false&&!(jQuery.nodeName(elem,'a')&&type=="click")){this.triggered=true;try{elem[type]();}catch(e){}}this.triggered=false;}return val;},handle:function(event){var val,ret,namespace,all,handlers;event=arguments[0]=jQuery.event.fix(event||window.event);namespace=event.type.split(".");event.type=namespace[0];namespace=namespace[1];all=!namespace&&!event.exclusive;handlers=(jQuery.data(this,"events")||{})[event.type];for(var j in handlers){var handler=handlers[j];if(all||handler.type==namespace){event.handler=handler;event.data=handler.data;ret=handler.apply(this,arguments);if(val!==false)val=ret;if(ret===false){event.preventDefault();event.stopPropagation();}}}return val;},fix:function(event){if(event[expando]==true)return event;var originalEvent=event;event={originalEvent:originalEvent};var props="altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target timeStamp toElement type view wheelDelta which".split(" ");for(var i=props.length;i;i--)event[props[i]]=originalEvent[props[i]];event[expando]=true;event.preventDefault=function(){if(originalEvent.preventDefault)originalEvent.preventDefault();originalEvent.returnValue=false;};event.stopPropagation=function(){if(originalEvent.stopPropagation)originalEvent.stopPropagation();originalEvent.cancelBubble=true;};event.timeStamp=event.timeStamp||now();if(!event.target)event.target=event.srcElement||document;if(event.target.nodeType==3)event.target=event.target.parentNode;if(!event.relatedTarget&&event.fromElement)event.relatedTarget=event.fromElement==event.target?event.toElement:event.fromElement;if(event.pageX==null&&event.clientX!=null){var doc=document.documentElement,body=document.body;event.pageX=event.clientX+(doc&&doc.scrollLeft||body&&body.scrollLeft||0)-(doc.clientLeft||0);event.pageY=event.clientY+(doc&&doc.scrollTop||body&&body.scrollTop||0)-(doc.clientTop||0);}if(!event.which&&((event.charCode||event.charCode===0)?event.charCode:event.keyCode))event.which=event.charCode||event.keyCode;if(!event.metaKey&&event.ctrlKey)event.metaKey=event.ctrlKey;if(!event.which&&event.button)event.which=(event.button&1?1:(event.button&2?3:(event.button&4?2:0)));return event;},proxy:function(fn,proxy){proxy.guid=fn.guid=fn.guid||proxy.guid||this.guid++;return proxy;},special:{ready:{setup:function(){bindReady();return;},teardown:function(){return;}},mouseenter:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseover",jQuery.event.special.mouseenter.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseover",jQuery.event.special.mouseenter.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseenter";return jQuery.event.handle.apply(this,arguments);}},mouseleave:{setup:function(){if(jQuery.browser.msie)return false;jQuery(this).bind("mouseout",jQuery.event.special.mouseleave.handler);return true;},teardown:function(){if(jQuery.browser.msie)return false;jQuery(this).unbind("mouseout",jQuery.event.special.mouseleave.handler);return true;},handler:function(event){if(withinElement(event,this))return true;event.type="mouseleave";return jQuery.event.handle.apply(this,arguments);}}}};jQuery.fn.extend({bind:function(type,data,fn){return type=="unload"?this.one(type,data,fn):this.each(function(){jQuery.event.add(this,type,fn||data,fn&&data);});},one:function(type,data,fn){var one=jQuery.event.proxy(fn||data,function(event){jQuery(this).unbind(event,one);return(fn||data).apply(this,arguments);});return this.each(function(){jQuery.event.add(this,type,one,fn&&data);});},unbind:function(type,fn){return this.each(function(){jQuery.event.remove(this,type,fn);});},trigger:function(type,data,fn){return this.each(function(){jQuery.event.trigger(type,data,this,true,fn);});},triggerHandler:function(type,data,fn){return this[0]&&jQuery.event.trigger(type,data,this[0],false,fn);},toggle:function(fn){var args=arguments,i=1;while(i=0){var selector=url.slice(off,url.length);url=url.slice(0,off);}callback=callback||function(){};var type="GET";if(params)if(jQuery.isFunction(params)){callback=params;params=null;}else{params=jQuery.param(params);type="POST";}var self=this;jQuery.ajax({url:url,type:type,dataType:"html",data:params,complete:function(res,status){if(status=="success"||status=="notmodified")self.html(selector?jQuery("
").append(res.responseText.replace(//g,"")).find(selector):res.responseText);self.each(callback,[res.responseText,status,res]);}});return this;},serialize:function(){return jQuery.param(this.serializeArray());},serializeArray:function(){return this.map(function(){return jQuery.nodeName(this,"form")?jQuery.makeArray(this.elements):this;}).filter(function(){return this.name&&!this.disabled&&(this.checked||/select|textarea/i.test(this.nodeName)||/text|hidden|password/i.test(this.type));}).map(function(i,elem){var val=jQuery(this).val();return val==null?null:val.constructor==Array?jQuery.map(val,function(val,i){return{name:elem.name,value:val};}):{name:elem.name,value:val};}).get();}});jQuery.each("ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","),function(i,o){jQuery.fn[o]=function(f){return this.bind(o,f);};});var jsc=now();jQuery.extend({get:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data=null;}return jQuery.ajax({type:"GET",url:url,data:data,success:callback,dataType:type});},getScript:function(url,callback){return jQuery.get(url,null,callback,"script");},getJSON:function(url,data,callback){return jQuery.get(url,data,callback,"json");},post:function(url,data,callback,type){if(jQuery.isFunction(data)){callback=data;data={};}return jQuery.ajax({type:"POST",url:url,data:data,success:callback,dataType:type});},ajaxSetup:function(settings){jQuery.extend(jQuery.ajaxSettings,settings);},ajaxSettings:{url:location.href,global:true,type:"GET",timeout:0,contentType:"application/x-www-form-urlencoded",processData:true,async:true,data:null,username:null,password:null,accepts:{xml:"application/xml, text/xml",html:"text/html",script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},lastModified:{},ajax:function(s){s=jQuery.extend(true,s,jQuery.extend(true,{},jQuery.ajaxSettings,s));var jsonp,jsre=/=\?(&|$)/g,status,data,type=s.type.toUpperCase();if(s.data&&s.processData&&typeof s.data!="string")s.data=jQuery.param(s.data);if(s.dataType=="jsonp"){if(type=="GET"){if(!s.url.match(jsre))s.url+=(s.url.match(/\?/)?"&":"?")+(s.jsonp||"callback")+"=?";}else if(!s.data||!s.data.match(jsre))s.data=(s.data?s.data+"&":"")+(s.jsonp||"callback")+"=?";s.dataType="json";}if(s.dataType=="json"&&(s.data&&s.data.match(jsre)||s.url.match(jsre))){jsonp="jsonp"+jsc++;if(s.data)s.data=(s.data+"").replace(jsre,"="+jsonp+"$1");s.url=s.url.replace(jsre,"="+jsonp+"$1");s.dataType="script";window[jsonp]=function(tmp){data=tmp;success();complete();window[jsonp]=undefined;try{delete window[jsonp];}catch(e){}if(head)head.removeChild(script);};}if(s.dataType=="script"&&s.cache==null)s.cache=false;if(s.cache===false&&type=="GET"){var ts=now();var ret=s.url.replace(/(\?|&)_=.*?(&|$)/,"$1_="+ts+"$2");s.url=ret+((ret==s.url)?(s.url.match(/\?/)?"&":"?")+"_="+ts:"");}if(s.data&&type=="GET"){s.url+=(s.url.match(/\?/)?"&":"?")+s.data;s.data=null;}if(s.global&&!jQuery.active++)jQuery.event.trigger("ajaxStart");var remote=/^(?:\w+:)?\/\/([^\/?#]+)/;if(s.dataType=="script"&&type=="GET"&&remote.test(s.url)&&remote.exec(s.url)[1]!=location.host){var head=document.getElementsByTagName("head")[0];var script=document.createElement("script");script.src=s.url;if(s.scriptCharset)script.charset=s.scriptCharset;if(!jsonp){var done=false;script.onload=script.onreadystatechange=function(){if(!done&&(!this.readyState||this.readyState=="loaded"||this.readyState=="complete")){done=true;success();complete();head.removeChild(script);}};}head.appendChild(script);return undefined;}var requestDone=false;var xhr=window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):new XMLHttpRequest();if(s.username)xhr.open(type,s.url,s.async,s.username,s.password);else +xhr.open(type,s.url,s.async);try{if(s.data)xhr.setRequestHeader("Content-Type",s.contentType);if(s.ifModified)xhr.setRequestHeader("If-Modified-Since",jQuery.lastModified[s.url]||"Thu, 01 Jan 1970 00:00:00 GMT");xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");xhr.setRequestHeader("Accept",s.dataType&&s.accepts[s.dataType]?s.accepts[s.dataType]+", */*":s.accepts._default);}catch(e){}if(s.beforeSend&&s.beforeSend(xhr,s)===false){s.global&&jQuery.active--;xhr.abort();return false;}if(s.global)jQuery.event.trigger("ajaxSend",[xhr,s]);var onreadystatechange=function(isTimeout){if(!requestDone&&xhr&&(xhr.readyState==4||isTimeout=="timeout")){requestDone=true;if(ival){clearInterval(ival);ival=null;}status=isTimeout=="timeout"&&"timeout"||!jQuery.httpSuccess(xhr)&&"error"||s.ifModified&&jQuery.httpNotModified(xhr,s.url)&&"notmodified"||"success";if(status=="success"){try{data=jQuery.httpData(xhr,s.dataType,s.dataFilter);}catch(e){status="parsererror";}}if(status=="success"){var modRes;try{modRes=xhr.getResponseHeader("Last-Modified");}catch(e){}if(s.ifModified&&modRes)jQuery.lastModified[s.url]=modRes;if(!jsonp)success();}else +jQuery.handleError(s,xhr,status);complete();if(s.async)xhr=null;}};if(s.async){var ival=setInterval(onreadystatechange,13);if(s.timeout>0)setTimeout(function(){if(xhr){xhr.abort();if(!requestDone)onreadystatechange("timeout");}},s.timeout);}try{xhr.send(s.data);}catch(e){jQuery.handleError(s,xhr,null,e);}if(!s.async)onreadystatechange();function success(){if(s.success)s.success(data,status);if(s.global)jQuery.event.trigger("ajaxSuccess",[xhr,s]);}function complete(){if(s.complete)s.complete(xhr,status);if(s.global)jQuery.event.trigger("ajaxComplete",[xhr,s]);if(s.global&&!--jQuery.active)jQuery.event.trigger("ajaxStop");}return xhr;},handleError:function(s,xhr,status,e){if(s.error)s.error(xhr,status,e);if(s.global)jQuery.event.trigger("ajaxError",[xhr,s,e]);},active:0,httpSuccess:function(xhr){try{return!xhr.status&&location.protocol=="file:"||(xhr.status>=200&&xhr.status<300)||xhr.status==304||xhr.status==1223||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpNotModified:function(xhr,url){try{var xhrRes=xhr.getResponseHeader("Last-Modified");return xhr.status==304||xhrRes==jQuery.lastModified[url]||jQuery.browser.safari&&xhr.status==undefined;}catch(e){}return false;},httpData:function(xhr,type,filter){var ct=xhr.getResponseHeader("content-type"),xml=type=="xml"||!type&&ct&&ct.indexOf("xml")>=0,data=xml?xhr.responseXML:xhr.responseText;if(xml&&data.documentElement.tagName=="parsererror")throw"parsererror";if(filter)data=filter(data,type);if(type=="script")jQuery.globalEval(data);if(type=="json")data=eval("("+data+")");return data;},param:function(a){var s=[];if(a.constructor==Array||a.jquery)jQuery.each(a,function(){s.push(encodeURIComponent(this.name)+"="+encodeURIComponent(this.value));});else +for(var j in a)if(a[j]&&a[j].constructor==Array)jQuery.each(a[j],function(){s.push(encodeURIComponent(j)+"="+encodeURIComponent(this));});else +s.push(encodeURIComponent(j)+"="+encodeURIComponent(jQuery.isFunction(a[j])?a[j]():a[j]));return s.join("&").replace(/%20/g,"+");}});jQuery.fn.extend({show:function(speed,callback){return speed?this.animate({height:"show",width:"show",opacity:"show"},speed,callback):this.filter(":hidden").each(function(){this.style.display=this.oldblock||"";if(jQuery.css(this,"display")=="none"){var elem=jQuery("<"+this.tagName+" />").appendTo("body");this.style.display=elem.css("display");if(this.style.display=="none")this.style.display="block";elem.remove();}}).end();},hide:function(speed,callback){return speed?this.animate({height:"hide",width:"hide",opacity:"hide"},speed,callback):this.filter(":visible").each(function(){this.oldblock=this.oldblock||jQuery.css(this,"display");this.style.display="none";}).end();},_toggle:jQuery.fn.toggle,toggle:function(fn,fn2){return jQuery.isFunction(fn)&&jQuery.isFunction(fn2)?this._toggle.apply(this,arguments):fn?this.animate({height:"toggle",width:"toggle",opacity:"toggle"},fn,fn2):this.each(function(){jQuery(this)[jQuery(this).is(":hidden")?"show":"hide"]();});},slideDown:function(speed,callback){return this.animate({height:"show"},speed,callback);},slideUp:function(speed,callback){return this.animate({height:"hide"},speed,callback);},slideToggle:function(speed,callback){return this.animate({height:"toggle"},speed,callback);},fadeIn:function(speed,callback){return this.animate({opacity:"show"},speed,callback);},fadeOut:function(speed,callback){return this.animate({opacity:"hide"},speed,callback);},fadeTo:function(speed,to,callback){return this.animate({opacity:to},speed,callback);},animate:function(prop,speed,easing,callback){var optall=jQuery.speed(speed,easing,callback);return this[optall.queue===false?"each":"queue"](function(){if(this.nodeType!=1)return false;var opt=jQuery.extend({},optall),p,hidden=jQuery(this).is(":hidden"),self=this;for(p in prop){if(prop[p]=="hide"&&hidden||prop[p]=="show"&&!hidden)return opt.complete.call(this);if(p=="height"||p=="width"){opt.display=jQuery.css(this,"display");opt.overflow=this.style.overflow;}}if(opt.overflow!=null)this.style.overflow="hidden";opt.curAnim=jQuery.extend({},prop);jQuery.each(prop,function(name,val){var e=new jQuery.fx(self,opt,name);if(/toggle|show|hide/.test(val))e[val=="toggle"?hidden?"show":"hide":val](prop);else{var parts=val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),start=e.cur(true)||0;if(parts){var end=parseFloat(parts[2]),unit=parts[3]||"px";if(unit!="px"){self.style[name]=(end||1)+unit;start=((end||1)/e.cur(true))*start;self.style[name]=start+unit;}if(parts[1])end=((parts[1]=="-="?-1:1)*end)+start;e.custom(start,end,unit);}else +e.custom(start,val,"");}});return true;});},queue:function(type,fn){if(jQuery.isFunction(type)||(type&&type.constructor==Array)){fn=type;type="fx";}if(!type||(typeof type=="string"&&!fn))return queue(this[0],type);return this.each(function(){if(fn.constructor==Array)queue(this,type,fn);else{queue(this,type).push(fn);if(queue(this,type).length==1)fn.call(this);}});},stop:function(clearQueue,gotoEnd){var timers=jQuery.timers;if(clearQueue)this.queue([]);this.each(function(){for(var i=timers.length-1;i>=0;i--)if(timers[i].elem==this){if(gotoEnd)timers[i](true);timers.splice(i,1);}});if(!gotoEnd)this.dequeue();return this;}});var queue=function(elem,type,array){if(elem){type=type||"fx";var q=jQuery.data(elem,type+"queue");if(!q||array)q=jQuery.data(elem,type+"queue",jQuery.makeArray(array));}return q;};jQuery.fn.dequeue=function(type){type=type||"fx";return this.each(function(){var q=queue(this,type);q.shift();if(q.length)q[0].call(this);});};jQuery.extend({speed:function(speed,easing,fn){var opt=speed&&speed.constructor==Object?speed:{complete:fn||!fn&&easing||jQuery.isFunction(speed)&&speed,duration:speed,easing:fn&&easing||easing&&easing.constructor!=Function&&easing};opt.duration=(opt.duration&&opt.duration.constructor==Number?opt.duration:jQuery.fx.speeds[opt.duration])||jQuery.fx.speeds.def;opt.old=opt.complete;opt.complete=function(){if(opt.queue!==false)jQuery(this).dequeue();if(jQuery.isFunction(opt.old))opt.old.call(this);};return opt;},easing:{linear:function(p,n,firstNum,diff){return firstNum+diff*p;},swing:function(p,n,firstNum,diff){return((-Math.cos(p*Math.PI)/2)+0.5)*diff+firstNum;}},timers:[],timerId:null,fx:function(elem,options,prop){this.options=options;this.elem=elem;this.prop=prop;if(!options.orig)options.orig={};}});jQuery.fx.prototype={update:function(){if(this.options.step)this.options.step.call(this.elem,this.now,this);(jQuery.fx.step[this.prop]||jQuery.fx.step._default)(this);if(this.prop=="height"||this.prop=="width")this.elem.style.display="block";},cur:function(force){if(this.elem[this.prop]!=null&&this.elem.style[this.prop]==null)return this.elem[this.prop];var r=parseFloat(jQuery.css(this.elem,this.prop,force));return r&&r>-10000?r:parseFloat(jQuery.curCSS(this.elem,this.prop))||0;},custom:function(from,to,unit){this.startTime=now();this.start=from;this.end=to;this.unit=unit||this.unit||"px";this.now=this.start;this.pos=this.state=0;this.update();var self=this;function t(gotoEnd){return self.step(gotoEnd);}t.elem=this.elem;jQuery.timers.push(t);if(jQuery.timerId==null){jQuery.timerId=setInterval(function(){var timers=jQuery.timers;for(var i=0;ithis.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;var done=true;for(var i in this.options.curAnim)if(this.options.curAnim[i]!==true)done=false;if(done){if(this.options.display!=null){this.elem.style.overflow=this.options.overflow;this.elem.style.display=this.options.display;if(jQuery.css(this.elem,"display")=="none")this.elem.style.display="block";}if(this.options.hide)this.elem.style.display="none";if(this.options.hide||this.options.show)for(var p in this.options.curAnim)jQuery.attr(this.elem.style,p,this.options.orig[p]);}if(done)this.options.complete.call(this.elem);return false;}else{var n=t-this.startTime;this.state=n/this.options.duration;this.pos=jQuery.easing[this.options.easing||(jQuery.easing.swing?"swing":"linear")](this.state,n,0,1,this.options.duration);this.now=this.start+((this.end-this.start)*this.pos);this.update();}return true;}};jQuery.extend(jQuery.fx,{speeds:{slow:600,fast:200,def:400},step:{scrollLeft:function(fx){fx.elem.scrollLeft=fx.now;},scrollTop:function(fx){fx.elem.scrollTop=fx.now;},opacity:function(fx){jQuery.attr(fx.elem.style,"opacity",fx.now);},_default:function(fx){fx.elem.style[fx.prop]=fx.now+fx.unit;}}});jQuery.fn.offset=function(){var left=0,top=0,elem=this[0],results;if(elem)with(jQuery.browser){var parent=elem.parentNode,offsetChild=elem,offsetParent=elem.offsetParent,doc=elem.ownerDocument,safari2=safari&&parseInt(version)<522&&!/adobeair/i.test(userAgent),css=jQuery.curCSS,fixed=css(elem,"position")=="fixed";if(elem.getBoundingClientRect){var box=elem.getBoundingClientRect();add(box.left+Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),box.top+Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));add(-doc.documentElement.clientLeft,-doc.documentElement.clientTop);}else{add(elem.offsetLeft,elem.offsetTop);while(offsetParent){add(offsetParent.offsetLeft,offsetParent.offsetTop);if(mozilla&&!/^t(able|d|h)$/i.test(offsetParent.tagName)||safari&&!safari2)border(offsetParent);if(!fixed&&css(offsetParent,"position")=="fixed")fixed=true;offsetChild=/^body$/i.test(offsetParent.tagName)?offsetChild:offsetParent;offsetParent=offsetParent.offsetParent;}while(parent&&parent.tagName&&!/^body|html$/i.test(parent.tagName)){if(!/^inline|table.*$/i.test(css(parent,"display")))add(-parent.scrollLeft,-parent.scrollTop);if(mozilla&&css(parent,"overflow")!="visible")border(parent);parent=parent.parentNode;}if((safari2&&(fixed||css(offsetChild,"position")=="absolute"))||(mozilla&&css(offsetChild,"position")!="absolute"))add(-doc.body.offsetLeft,-doc.body.offsetTop);if(fixed)add(Math.max(doc.documentElement.scrollLeft,doc.body.scrollLeft),Math.max(doc.documentElement.scrollTop,doc.body.scrollTop));}results={top:top,left:left};}function border(elem){add(jQuery.curCSS(elem,"borderLeftWidth",true),jQuery.curCSS(elem,"borderTopWidth",true));}function add(l,t){left+=parseInt(l,10)||0;top+=parseInt(t,10)||0;}return results;};jQuery.fn.extend({position:function(){var left=0,top=0,results;if(this[0]){var offsetParent=this.offsetParent(),offset=this.offset(),parentOffset=/^body|html$/i.test(offsetParent[0].tagName)?{top:0,left:0}:offsetParent.offset();offset.top-=num(this,'marginTop');offset.left-=num(this,'marginLeft');parentOffset.top+=num(offsetParent,'borderTopWidth');parentOffset.left+=num(offsetParent,'borderLeftWidth');results={top:offset.top-parentOffset.top,left:offset.left-parentOffset.left};}return results;},offsetParent:function(){var offsetParent=this[0].offsetParent;while(offsetParent&&(!/^body|html$/i.test(offsetParent.tagName)&&jQuery.css(offsetParent,'position')=='static'))offsetParent=offsetParent.offsetParent;return jQuery(offsetParent);}});jQuery.each(['Left','Top'],function(i,name){var method='scroll'+name;jQuery.fn[method]=function(val){if(!this[0])return;return val!=undefined?this.each(function(){this==window||this==document?window.scrollTo(!i?val:jQuery(window).scrollLeft(),i?val:jQuery(window).scrollTop()):this[method]=val;}):this[0]==window||this[0]==document?self[i?'pageYOffset':'pageXOffset']||jQuery.boxModel&&document.documentElement[method]||document.body[method]:this[0][method];};});jQuery.each(["Height","Width"],function(i,name){var tl=i?"Left":"Top",br=i?"Right":"Bottom";jQuery.fn["inner"+name]=function(){return this[name.toLowerCase()]()+num(this,"padding"+tl)+num(this,"padding"+br);};jQuery.fn["outer"+name]=function(margin){return this["inner"+name]()+num(this,"border"+tl+"Width")+num(this,"border"+br+"Width")+(margin?num(this,"margin"+tl)+num(this,"margin"+br):0);};});})(); \ No newline at end of file diff --git a/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.css b/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.css new file mode 100644 index 0000000..a54dd3b --- /dev/null +++ b/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.css @@ -0,0 +1,103 @@ +/** Slider **/ +.leaflet-control-zoomslider-wrap { + padding-top: 5px; + padding-bottom: 5px; + background-color: #fff; + border-bottom: 1px solid #ccc; +} +.leaflet-control-zoomslider-body { + width: 2px; + border: solid #fff; + border-width: 0px 9px 0px 9px; + background-color: black; + margin: 0 auto; +} +.leaflet-control-zoomslider-knob { + position: relative; + width: 12px; + height: 4px; + background-color: #efefef; + -webkit-border-radius: 2px; + border-radius: 2px; + border: 1px solid #000; + margin-left: -6px; +} +.leaflet-control-zoomslider-body:hover { + cursor: pointer; +} +.leaflet-control-zoomslider-knob:hover { + cursor: default; + cursor: -webkit-grab; + cursor: -moz-grab; +} + +.leaflet-dragging .leaflet-control-zoomslider, +.leaflet-dragging .leaflet-control-zoomslider-wrap, +.leaflet-dragging .leaflet-control-zoomslider-body, +.leaflet-dragging .leaflet-control-zoomslider a, +.leaflet-dragging .leaflet-control-zoomslider a.leaflet-control-zoomslider-disabled, +.leaflet-dragging .leaflet-control-zoomslider-knob:hover { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; +} + +/** Leaflet Zoom Styles **/ +.leaflet-container .leaflet-control-zoomslider { + margin-left: 10px; + margin-top: 10px; +} +.leaflet-control-zoomslider a { + width: 26px; + height: 26px; + text-align: center; + text-decoration: none; + color: black; + display: block; +} +.leaflet-control-zoomslider a:hover { + background-color: #f4f4f4; +} +.leaflet-control-zoomslider-in { + font: bold 18px 'Lucida Console', Monaco, monospace; +} +.leaflet-control-zoomslider-in:after{ + content:"+" +} +.leaflet-control-zoomslider-out { + font: bold 22px 'Lucida Console', Monaco, monospace; +} +.leaflet-control-zoomslider-out:after{ + content:"-" +} +.leaflet-control-zoomslider a.leaflet-control-zoomslider-disabled { + cursor: default; + color: #bbb; +} + +/* Touch */ +.leaflet-touch .leaflet-control-zoomslider-body { + background-position: 10px 0px; +} +.leaflet-touch .leaflet-control-zoomslider-knob { + width:16px; + margin-left: -1px; +} +.leaflet-touch .leaflet-control-zoomslider a { + width: 30px; + height: 30px; +} +.leaflet-touch .leaflet-control-zoomslider-in { + font-size: 24px; + line-height: 29px; +} +.leaflet-touch .leaflet-control-zoomslider-out { + font-size: 28px; + line-height: 30px; +} +.leaflet-touch .leaflet-control-zoomslider { + box-shadow: none; +} +.leaflet-touch .leaflet-control-zoomslider { + border: 4px solid rgba(0,0,0,0.3); +} diff --git a/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.ie.css b/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.ie.css new file mode 100644 index 0000000..4e81ea1 --- /dev/null +++ b/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.ie.css @@ -0,0 +1,16 @@ +/* IE6-7 specific hacks */ +.leaflet-control-zoomslider-wrap { + *width: 26px; +} +/* Fix IE6-divs having a too large min height */ +.leaflet-control-zoomslider-knob { + *overflow: hidden; +} + +/* Support for element:after { content: 'text' } */ +.leaflet-control-zoomslider-in { + *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '+'); +} +.leaflet-control-zoomslider-out { + *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = '-'); +} \ No newline at end of file diff --git a/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.js b/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.js new file mode 100644 index 0000000..557a0f3 --- /dev/null +++ b/BIN/MAP/leaflet/L.Control.Zoomslider/L.Control.Zoomslider.js @@ -0,0 +1,206 @@ +L.Control.Zoomslider = (function () { + + var Knob = L.Draggable.extend({ + initialize: function (element, stepHeight, knobHeight) { + L.Draggable.prototype.initialize.call(this, element, element); + this._element = element; + + this._stepHeight = stepHeight; + this._knobHeight = knobHeight; + + this.on('predrag', function () { + this._newPos.x = 0; + this._newPos.y = this._adjust(this._newPos.y); + }, this); + }, + + _adjust: function (y) { + var value = Math.round(this._toValue(y)); + value = Math.max(0, Math.min(this._maxValue, value)); + return this._toY(value); + }, + + // y = k*v + m + _toY: function (value) { + return this._k * value + this._m; + }, + // v = (y - m) / k + _toValue: function (y) { + return (y - this._m) / this._k; + }, + + setSteps: function (steps) { + var sliderHeight = steps * this._stepHeight; + this._maxValue = steps - 1; + + // conversion parameters + // the conversion is just a common linear function. + this._k = -this._stepHeight; + this._m = sliderHeight - (this._stepHeight + this._knobHeight) / 2; + }, + + setPosition: function (y) { + L.DomUtil.setPosition(this._element, + L.point(0, this._adjust(y))); + }, + + setValue: function (v) { + this.setPosition(this._toY(v)); + }, + + getValue: function () { + return this._toValue(L.DomUtil.getPosition(this._element).y); + } + }); + + var Zoomslider = L.Control.extend({ + options: { + position: 'topleft', + // Height of zoom-slider.png in px + stepHeight: 8, + // Height of the knob div in px (including border) + knobHeight: 6, + styleNS: 'leaflet-control-zoomslider' + }, + + onAdd: function (map) { + this._map = map; + this._ui = this._createUI(); + this._knob = new Knob(this._ui.knob, + this.options.stepHeight, + this.options.knobHeight); + + map .whenReady(this._initKnob, this) + .whenReady(this._initEvents, this) + .whenReady(this._updateSize, this) + .whenReady(this._updateKnobValue, this) + .whenReady(this._updateDisabled, this); + return this._ui.bar; + }, + + onRemove: function (map) { + map .off('zoomlevelschange', this._updateSize, this) + .off('zoomend zoomlevelschange', this._updateKnobValue, this) + .off('zoomend zoomlevelschange', this._updateDisabled, this); + }, + + _createUI: function () { + var ui = {}, + ns = this.options.styleNS; + + ui.bar = L.DomUtil.create('div', ns + ' leaflet-bar'), + ui.zoomIn = this._createZoomBtn('in', 'top', ui.bar), + ui.wrap = L.DomUtil.create('div', ns + '-wrap leaflet-bar-part', ui.bar), + ui.zoomOut = this._createZoomBtn('out', 'bottom', ui.bar), + ui.body = L.DomUtil.create('div', ns + '-body', ui.wrap), + ui.knob = L.DomUtil.create('div', ns + '-knob'); + + L.DomEvent.disableClickPropagation(ui.bar); + L.DomEvent.disableClickPropagation(ui.knob); + + return ui; + }, + _createZoomBtn: function (zoomDir, end, container) { + var classDef = this.options.styleNS + '-' + zoomDir + + ' leaflet-bar-part' + + ' leaflet-bar-part-' + end, + link = L.DomUtil.create('a', classDef, container); + + link.href = '#'; + link.title = 'Zoom ' + zoomDir; + + L.DomEvent.on(link, 'click', L.DomEvent.preventDefault); + + return link; + }, + + _initKnob: function () { + this._knob.enable(); + this._ui.body.appendChild(this._ui.knob); + }, + _initEvents: function (map) { + this._map + .on('zoomlevelschange', this._updateSize, this) + .on('zoomend zoomlevelschange', this._updateKnobValue, this) + .on('zoomend zoomlevelschange', this._updateDisabled, this); + + L.DomEvent.on(this._ui.body, 'click', this._onSliderClick, this); + L.DomEvent.on(this._ui.zoomIn, 'click', this._zoomIn, this); + L.DomEvent.on(this._ui.zoomOut, 'click', this._zoomOut, this); + + this._knob.on('dragend', this._updateMapZoom, this); + }, + + _onSliderClick: function (e) { + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e), + y = L.DomEvent.getMousePosition(first).y + - L.DomUtil.getViewportOffset(this._ui.body).y; // Cache this? + + this._knob.setPosition(y); + this._updateMapZoom(); + }, + + _zoomIn: function (e) { + this._map.zoomIn(e.shiftKey ? 3 : 1); + }, + _zoomOut: function (e) { + this._map.zoomOut(e.shiftKey ? 3 : 1); + }, + + _zoomLevels: function () { + var zoomLevels = this._map.getMaxZoom() - this._map.getMinZoom() + 1; + return zoomLevels < Infinity ? zoomLevels : 0; + }, + _toZoomLevel: function (value) { + return value + this._map.getMinZoom(); + }, + _toValue: function (zoomLevel) { + return zoomLevel - this._map.getMinZoom(); + }, + + _updateSize: function () { + var steps = this._zoomLevels(); + + this._ui.body.style.height = this.options.stepHeight * steps + 'px'; + this._knob.setSteps(steps); + }, + _updateMapZoom: function () { + this._map.setZoom(this._toZoomLevel(this._knob.getValue())); + }, + _updateKnobValue: function () { + this._knob.setValue(this._toValue(this._map.getZoom())); + }, + _updateDisabled: function () { + var zoomLevel = this._map.getZoom(), + className = this.options.styleNS + '-disabled'; + + L.DomUtil.removeClass(this._ui.zoomIn, className); + L.DomUtil.removeClass(this._ui.zoomOut, className); + + if (zoomLevel === this._map.getMinZoom()) { + L.DomUtil.addClass(this._ui.zoomOut, className); + } + if (zoomLevel === this._map.getMaxZoom()) { + L.DomUtil.addClass(this._ui.zoomIn, className); + } + } + }); + + return Zoomslider; +})(); + +L.Map.mergeOptions({ + zoomControl: false, + zoomsliderControl: true +}); + +L.Map.addInitHook(function () { + if (this.options.zoomsliderControl) { + this.zoomsliderControl = new L.Control.Zoomslider(); + this.addControl(this.zoomsliderControl); + } +}); + +L.control.zoomslider = function (options) { + return new L.Control.Zoomslider(options); +}; diff --git a/BIN/MAP/leaflet/images/layers-2x.png b/BIN/MAP/leaflet/images/layers-2x.png new file mode 100644 index 0000000..a2cf7f9 Binary files /dev/null and b/BIN/MAP/leaflet/images/layers-2x.png differ diff --git a/BIN/MAP/leaflet/images/layers.png b/BIN/MAP/leaflet/images/layers.png new file mode 100644 index 0000000..bca0a0e Binary files /dev/null and b/BIN/MAP/leaflet/images/layers.png differ diff --git a/BIN/MAP/leaflet/images/marker-icon-2x.png b/BIN/MAP/leaflet/images/marker-icon-2x.png new file mode 100644 index 0000000..0015b64 Binary files /dev/null and b/BIN/MAP/leaflet/images/marker-icon-2x.png differ diff --git a/BIN/MAP/leaflet/images/marker-icon.png b/BIN/MAP/leaflet/images/marker-icon.png new file mode 100644 index 0000000..e2e9f75 Binary files /dev/null and b/BIN/MAP/leaflet/images/marker-icon.png differ diff --git a/BIN/MAP/leaflet/images/marker-shadow.png b/BIN/MAP/leaflet/images/marker-shadow.png new file mode 100644 index 0000000..d1e773c Binary files /dev/null and b/BIN/MAP/leaflet/images/marker-shadow.png differ diff --git a/BIN/MAP/leaflet/images/zoom-in.png b/BIN/MAP/leaflet/images/zoom-in.png new file mode 100644 index 0000000..9f473d6 Binary files /dev/null and b/BIN/MAP/leaflet/images/zoom-in.png differ diff --git a/BIN/MAP/leaflet/images/zoom-out.png b/BIN/MAP/leaflet/images/zoom-out.png new file mode 100644 index 0000000..f0a5b5d Binary files /dev/null and b/BIN/MAP/leaflet/images/zoom-out.png differ diff --git a/BIN/MAP/leaflet/leaflet.css b/BIN/MAP/leaflet/leaflet.css new file mode 100644 index 0000000..1232550 --- /dev/null +++ b/BIN/MAP/leaflet/leaflet.css @@ -0,0 +1,478 @@ +/* required styles */ + +.leaflet-map-pane, +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow, +.leaflet-tile-pane, +.leaflet-tile-container, +.leaflet-overlay-pane, +.leaflet-shadow-pane, +.leaflet-marker-pane, +.leaflet-popup-pane, +.leaflet-overlay-pane svg, +.leaflet-zoom-box, +.leaflet-image-layer, +.leaflet-layer { + position: absolute; + left: 0; + top: 0; + } +.leaflet-container { + overflow: hidden; + -ms-touch-action: none; + } +.leaflet-tile, +.leaflet-marker-icon, +.leaflet-marker-shadow { + -webkit-user-select: none; + -moz-user-select: none; + user-select: none; + -webkit-user-drag: none; + } +.leaflet-marker-icon, +.leaflet-marker-shadow { + display: block; + } +/* map is broken in FF if you have max-width: 100% on tiles */ +.leaflet-container img { + max-width: none !important; + } +/* stupid Android 2 doesn't understand "max-width: none" properly */ +.leaflet-container img.leaflet-image-layer { + max-width: 15000px !important; + } +.leaflet-tile { + filter: inherit; + visibility: hidden; + } +.leaflet-tile-loaded { + visibility: inherit; + } +.leaflet-zoom-box { + width: 0; + height: 0; + } +/* workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=888319 */ +.leaflet-overlay-pane svg { + -moz-user-select: none; + } + +.leaflet-tile-pane { z-index: 2; } +.leaflet-objects-pane { z-index: 3; } +.leaflet-overlay-pane { z-index: 4; } +.leaflet-shadow-pane { z-index: 5; } +.leaflet-marker-pane { z-index: 6; } +.leaflet-popup-pane { z-index: 7; } + +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + + +/* control positioning */ + +.leaflet-control { + position: relative; + z-index: 7; + pointer-events: auto; + } +.leaflet-top, +.leaflet-bottom { + position: absolute; + z-index: 1000; + pointer-events: none; + } +.leaflet-top { + top: 0; + } +.leaflet-right { + right: 0; + } +.leaflet-bottom { + bottom: 0; + } +.leaflet-left { + left: 0; + } +.leaflet-control { + float: left; + clear: both; + } +.leaflet-right .leaflet-control { + float: right; + } +.leaflet-top .leaflet-control { + margin-top: 10px; + } +.leaflet-bottom .leaflet-control { + margin-bottom: 10px; + } +.leaflet-left .leaflet-control { + margin-left: 10px; + } +.leaflet-right .leaflet-control { + margin-right: 10px; + } + + +/* zoom and fade animations */ + +.leaflet-fade-anim .leaflet-tile, +.leaflet-fade-anim .leaflet-popup { + opacity: 0; + -webkit-transition: opacity 0.2s linear; + -moz-transition: opacity 0.2s linear; + -o-transition: opacity 0.2s linear; + transition: opacity 0.2s linear; + } +.leaflet-fade-anim .leaflet-tile-loaded, +.leaflet-fade-anim .leaflet-map-pane .leaflet-popup { + opacity: 1; + } + +.leaflet-zoom-anim .leaflet-zoom-animated { + -webkit-transition: -webkit-transform 0.25s cubic-bezier(0,0,0.25,1); + -moz-transition: -moz-transform 0.25s cubic-bezier(0,0,0.25,1); + -o-transition: -o-transform 0.25s cubic-bezier(0,0,0.25,1); + transition: transform 0.25s cubic-bezier(0,0,0.25,1); + } +.leaflet-zoom-anim .leaflet-tile, +.leaflet-pan-anim .leaflet-tile, +.leaflet-touching .leaflet-zoom-animated { + -webkit-transition: none; + -moz-transition: none; + -o-transition: none; + transition: none; + } + +.leaflet-zoom-anim .leaflet-zoom-hide { + visibility: hidden; + } + + +/* cursors */ + +.leaflet-clickable { + cursor: pointer; + } +.leaflet-container { + cursor: -webkit-grab; + cursor: -moz-grab; + } +.leaflet-popup-pane, +.leaflet-control { + cursor: auto; + } +.leaflet-dragging .leaflet-container, +.leaflet-dragging .leaflet-clickable { + cursor: move; + cursor: -webkit-grabbing; + cursor: -moz-grabbing; + } + + +/* visual tweaks */ + +.leaflet-container { + background: #ddd; + outline: 0; + } +.leaflet-container a { + color: #0078A8; + } +.leaflet-container a.leaflet-active { + outline: 2px solid orange; + } +.leaflet-zoom-box { + border: 2px dotted #38f; + background: rgba(255,255,255,0.5); + } + + +/* general typography */ +.leaflet-container { + font: 12px/1.5 "Helvetica Neue", Arial, Helvetica, sans-serif; + } + + +/* general toolbar styles */ + +.leaflet-bar { + box-shadow: 0 1px 5px rgba(0,0,0,0.65); + border-radius: 4px; + } +.leaflet-bar a, +.leaflet-bar a:hover { + background-color: #fff; + border-bottom: 1px solid #ccc; + width: 26px; + height: 26px; + line-height: 26px; + display: block; + text-align: center; + text-decoration: none; + color: black; + } +.leaflet-bar a, +.leaflet-control-layers-toggle { + background-position: 50% 50%; + background-repeat: no-repeat; + display: block; + } +.leaflet-bar a:hover { + background-color: #f4f4f4; + } +.leaflet-bar a:first-child { + border-top-left-radius: 4px; + border-top-right-radius: 4px; + } +.leaflet-bar a:last-child { + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; + border-bottom: none; + } +.leaflet-bar a.leaflet-disabled { + cursor: default; + background-color: #f4f4f4; + color: #bbb; + } + +.leaflet-touch .leaflet-bar a { + width: 30px; + height: 30px; + line-height: 30px; + } + + +/* zoom control */ + +.leaflet-control-zoom-in, +.leaflet-control-zoom-out { + font: bold 18px 'Lucida Console', Monaco, monospace; + text-indent: 1px; + } +.leaflet-control-zoom-out { + font-size: 20px; + } + +.leaflet-touch .leaflet-control-zoom-in { + font-size: 22px; + } +.leaflet-touch .leaflet-control-zoom-out { + font-size: 24px; + } + + +/* layers control */ + +.leaflet-control-layers { + box-shadow: 0 1px 5px rgba(0,0,0,0.4); + background: #fff; + border-radius: 5px; + } +.leaflet-control-layers-toggle { + background-image: url(images/layers.png); + width: 36px; + height: 36px; + } +.leaflet-retina .leaflet-control-layers-toggle { + background-image: url(images/layers-2x.png); + background-size: 26px 26px; + } +.leaflet-touch .leaflet-control-layers-toggle { + width: 44px; + height: 44px; + } +.leaflet-control-layers .leaflet-control-layers-list, +.leaflet-control-layers-expanded .leaflet-control-layers-toggle { + display: none; + } +.leaflet-control-layers-expanded .leaflet-control-layers-list { + display: block; + position: relative; + } +.leaflet-control-layers-expanded { + padding: 6px 10px 6px 6px; + color: #333; + background: #fff; + } +.leaflet-control-layers-selector { + margin-top: 2px; + position: relative; + top: 1px; + } +.leaflet-control-layers label { + display: block; + } +.leaflet-control-layers-separator { + height: 0; + border-top: 1px solid #ddd; + margin: 5px -10px 5px -6px; + } + + +/* attribution and scale controls */ + +.leaflet-container .leaflet-control-attribution { + background: #fff; + background: rgba(255, 255, 255, 0.7); + margin: 0; + } +.leaflet-control-attribution, +.leaflet-control-scale-line { + padding: 0 5px; + color: #333; + } +.leaflet-control-attribution a { + text-decoration: none; + } +.leaflet-control-attribution a:hover { + text-decoration: underline; + } +.leaflet-container .leaflet-control-attribution, +.leaflet-container .leaflet-control-scale { + font-size: 11px; + } +.leaflet-left .leaflet-control-scale { + margin-left: 5px; + } +.leaflet-bottom .leaflet-control-scale { + margin-bottom: 5px; + } +.leaflet-control-scale-line { + border: 2px solid #777; + border-top: none; + line-height: 1.1; + padding: 2px 5px 1px; + font-size: 11px; + white-space: nowrap; + overflow: hidden; + -moz-box-sizing: content-box; + box-sizing: content-box; + + background: #fff; + background: rgba(255, 255, 255, 0.5); + } +.leaflet-control-scale-line:not(:first-child) { + border-top: 2px solid #777; + border-bottom: none; + margin-top: -2px; + } +.leaflet-control-scale-line:not(:first-child):not(:last-child) { + border-bottom: 2px solid #777; + } + +.leaflet-touch .leaflet-control-attribution, +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + box-shadow: none; + } +.leaflet-touch .leaflet-control-layers, +.leaflet-touch .leaflet-bar { + border: 2px solid rgba(0,0,0,0.2); + background-clip: padding-box; + } + + +/* popup */ + +.leaflet-popup { + position: absolute; + text-align: center; + } +.leaflet-popup-content-wrapper { + padding: 1px; + text-align: left; + border-radius: 12px; + } +.leaflet-popup-content { + margin: 13px 19px; + line-height: 1.4; + } +.leaflet-popup-content p { + margin: 18px 0; + } +.leaflet-popup-tip-container { + margin: 0 auto; + width: 40px; + height: 20px; + position: relative; + overflow: hidden; + } +.leaflet-popup-tip { + width: 17px; + height: 17px; + padding: 1px; + + margin: -10px auto 0; + + -webkit-transform: rotate(45deg); + -moz-transform: rotate(45deg); + -ms-transform: rotate(45deg); + -o-transform: rotate(45deg); + transform: rotate(45deg); + } +.leaflet-popup-content-wrapper, +.leaflet-popup-tip { + background: white; + + box-shadow: 0 3px 14px rgba(0,0,0,0.4); + } +.leaflet-container a.leaflet-popup-close-button { + position: absolute; + top: 0; + right: 0; + padding: 4px 4px 0 0; + text-align: center; + width: 18px; + height: 14px; + font: 16px/14px Tahoma, Verdana, sans-serif; + color: #c3c3c3; + text-decoration: none; + font-weight: bold; + background: transparent; + } +.leaflet-container a.leaflet-popup-close-button:hover { + color: #999; + } +.leaflet-popup-scrolled { + overflow: auto; + border-bottom: 1px solid #ddd; + border-top: 1px solid #ddd; + } + +.leaflet-oldie .leaflet-popup-content-wrapper { + zoom: 1; + } +.leaflet-oldie .leaflet-popup-tip { + width: 24px; + margin: 0 auto; + + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + } +.leaflet-oldie .leaflet-popup-tip-container { + margin-top: -1px; + } + +.leaflet-oldie .leaflet-control-zoom, +.leaflet-oldie .leaflet-control-layers, +.leaflet-oldie .leaflet-popup-content-wrapper, +.leaflet-oldie .leaflet-popup-tip { + border: 1px solid #999; + } + + +/* div icon */ + +.leaflet-div-icon { + background: #fff; + border: 1px solid #666; + } diff --git a/BIN/MAP/leaflet/leaflet.ie.css b/BIN/MAP/leaflet/leaflet.ie.css new file mode 100644 index 0000000..6f776ca --- /dev/null +++ b/BIN/MAP/leaflet/leaflet.ie.css @@ -0,0 +1,55 @@ +.leaflet-vml-shape { + width: 1px; + height: 1px; + } +.lvml { + behavior: url(#default#VML); + display: inline-block; + position: absolute; + } + +.leaflet-control { + display: inline; + } + +.leaflet-popup-tip { + width: 21px; + _width: 27px; + margin: 0 auto; + _margin-top: -3px; + + filter: progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678); + -ms-filter: "progid:DXImageTransform.Microsoft.Matrix(M11=0.70710678, M12=0.70710678, M21=-0.70710678, M22=0.70710678)"; + } +.leaflet-popup-tip-container { + margin-top: -1px; + } +.leaflet-popup-content-wrapper, .leaflet-popup-tip { + border: 1px solid #999; + width:200px; + } +.leaflet-popup-content-wrapper { + zoom: 1; + } + +.leaflet-control-zoom, +.leaflet-control-layers { + border: 3px solid #999; + } +.leaflet-control-layers-toggle { + } +.leaflet-control-layers-expanded { + width:200px; +} +.leaflet-control-attribution, +.leaflet-control-layers, +.leaflet-control-scale-line { + background: white; + } +.leaflet-zoom-box { + filter: alpha(opacity=50); + } +.leaflet-control-attribution { + border-top: 1px solid #bbb; + border-left: 1px solid #bbb; + } diff --git a/BIN/MAP/leaflet/leaflet.ie.js b/BIN/MAP/leaflet/leaflet.ie.js new file mode 100644 index 0000000..5daf7a3 --- /dev/null +++ b/BIN/MAP/leaflet/leaflet.ie.js @@ -0,0 +1,93 @@ + + + + + + Page not found · GitHub Pages + + + + +
+ +

404

+

File not found

+ +

+ The site configured at this address does not + contain the requested file. +

+ +

+ If this is your site, make sure that the filename case matches the URL.
+ For root URLs (like http://example.com/) you must provide an + index.html file. +

+ +

+ Read the full documentation + for more information about using GitHub Pages. +

+ + + + + + +
+ + + diff --git a/BIN/MAP/leaflet/leaflet.js b/BIN/MAP/leaflet/leaflet.js new file mode 100644 index 0000000..e7d2be1 --- /dev/null +++ b/BIN/MAP/leaflet/leaflet.js @@ -0,0 +1,9 @@ +/* + Leaflet, a JavaScript library for mobile-friendly interactive maps. http://leafletjs.com + (c) 2010-2013, Vladimir Agafonkin + (c) 2010-2011, CloudMade +*/ +!function(t,e,i){var n=t.L,o={};o.version="0.7.2","object"==typeof module&&"object"==typeof module.exports?module.exports=o:"function"==typeof define&&define.amd&&define(o),o.noConflict=function(){return t.L=n,this},t.L=o,o.Util={extend:function(t){var e,i,n,o,s=Array.prototype.slice.call(arguments,1);for(i=0,n=s.length;n>i;i++){o=s[i]||{};for(e in o)o.hasOwnProperty(e)&&(t[e]=o[e])}return t},bind:function(t,e){var i=arguments.length>2?Array.prototype.slice.call(arguments,2):null;return function(){return t.apply(e,i||arguments)}},stamp:function(){var t=0,e="_leaflet_id";return function(i){return i[e]=i[e]||++t,i[e]}}(),invokeEach:function(t,e,i){var n,o;if("object"==typeof t){o=Array.prototype.slice.call(arguments,3);for(n in t)e.apply(i,[n,t[n]].concat(o));return!0}return!1},limitExecByInterval:function(t,e,i){var n,o;return function s(){var a=arguments;return n?void(o=!0):(n=!0,setTimeout(function(){n=!1,o&&(s.apply(i,a),o=!1)},e),void t.apply(i,a))}},falseFn:function(){return!1},formatNum:function(t,e){var i=Math.pow(10,e||5);return Math.round(t*i)/i},trim:function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")},splitWords:function(t){return o.Util.trim(t).split(/\s+/)},setOptions:function(t,e){return t.options=o.extend({},t.options,e),t.options},getParamString:function(t,e,i){var n=[];for(var o in t)n.push(encodeURIComponent(i?o.toUpperCase():o)+"="+encodeURIComponent(t[o]));return(e&&-1!==e.indexOf("?")?"&":"?")+n.join("&")},template:function(t,e){return t.replace(/\{ *([\w_]+) *\}/g,function(t,n){var o=e[n];if(o===i)throw new Error("No value provided for variable "+t);return"function"==typeof o&&(o=o(e)),o})},isArray:Array.isArray||function(t){return"[object Array]"===Object.prototype.toString.call(t)},emptyImageUrl:"data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs="},function(){function e(e){var i,n,o=["webkit","moz","o","ms"];for(i=0;it;t++)n._initHooks[t].call(this)}},e},o.Class.include=function(t){o.extend(this.prototype,t)},o.Class.mergeOptions=function(t){o.extend(this.prototype.options,t)},o.Class.addInitHook=function(t){var e=Array.prototype.slice.call(arguments,1),i="function"==typeof t?t:function(){this[t].apply(this,e)};this.prototype._initHooks=this.prototype._initHooks||[],this.prototype._initHooks.push(i)};var s="_leaflet_events";o.Mixin={},o.Mixin.Events={addEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d=this[s]=this[s]||{},p=i&&i!==this&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)r={action:e,context:i||this},h=t[n],p?(l=h+"_idx",u=l+"_len",c=d[l]=d[l]||{},c[p]||(c[p]=[],d[u]=(d[u]||0)+1),c[p].push(r)):(d[h]=d[h]||[],d[h].push(r));return this},hasEventListeners:function(t){var e=this[s];return!!e&&(t in e&&e[t].length>0||t+"_idx"in e&&e[t+"_idx_len"]>0)},removeEventListener:function(t,e,i){if(!this[s])return this;if(!t)return this.clearAllEventListeners();if(o.Util.invokeEach(t,this.removeEventListener,this,e,i))return this;var n,a,r,h,l,u,c,d,p,_=this[s],m=i&&i!==this&&o.stamp(i);for(t=o.Util.splitWords(t),n=0,a=t.length;a>n;n++)if(r=t[n],u=r+"_idx",c=u+"_len",d=_[u],e){if(h=m&&d?d[m]:_[r]){for(l=h.length-1;l>=0;l--)h[l].action!==e||i&&h[l].context!==i||(p=h.splice(l,1),p[0].action=o.Util.falseFn);i&&d&&0===h.length&&(delete d[m],_[c]--)}}else delete _[r],delete _[u],delete _[c];return this},clearAllEventListeners:function(){return delete this[s],this},fireEvent:function(t,e){if(!this.hasEventListeners(t))return this;var i,n,a,r,h,l=o.Util.extend({},e,{type:t,target:this}),u=this[s];if(u[t])for(i=u[t].slice(),n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context,l);r=u[t+"_idx"];for(h in r)if(i=r[h].slice())for(n=0,a=i.length;a>n;n++)i[n].action.call(i[n].context,l);return this},addOneTimeEventListener:function(t,e,i){if(o.Util.invokeEach(t,this.addOneTimeEventListener,this,e,i))return this;var n=o.bind(function(){this.removeEventListener(t,e,i).removeEventListener(t,n,i)},this);return this.addEventListener(t,e,i).addEventListener(t,n,i)}},o.Mixin.Events.on=o.Mixin.Events.addEventListener,o.Mixin.Events.off=o.Mixin.Events.removeEventListener,o.Mixin.Events.once=o.Mixin.Events.addOneTimeEventListener,o.Mixin.Events.fire=o.Mixin.Events.fireEvent,function(){var n="ActiveXObject"in t,s=n&&!e.addEventListener,a=navigator.userAgent.toLowerCase(),r=-1!==a.indexOf("webkit"),h=-1!==a.indexOf("chrome"),l=-1!==a.indexOf("phantom"),u=-1!==a.indexOf("android"),c=-1!==a.search("android [23]"),d=-1!==a.indexOf("gecko"),p=typeof orientation!=i+"",_=t.navigator&&t.navigator.msPointerEnabled&&t.navigator.msMaxTouchPoints&&!t.PointerEvent,m=t.PointerEvent&&t.navigator.pointerEnabled&&t.navigator.maxTouchPoints||_,f="devicePixelRatio"in t&&t.devicePixelRatio>1||"matchMedia"in t&&t.matchMedia("(min-resolution:144dpi)")&&t.matchMedia("(min-resolution:144dpi)").matches,g=e.documentElement,v=n&&"transition"in g.style,y="WebKitCSSMatrix"in t&&"m11"in new t.WebKitCSSMatrix&&!c,P="MozPerspective"in g.style,L="OTransition"in g.style,x=!t.L_DISABLE_3D&&(v||y||P||L)&&!l,w=!t.L_NO_TOUCH&&!l&&function(){var t="ontouchstart";if(m||t in g)return!0;var i=e.createElement("div"),n=!1;return i.setAttribute?(i.setAttribute(t,"return;"),"function"==typeof i[t]&&(n=!0),i.removeAttribute(t),i=null,n):!1}();o.Browser={ie:n,ielt9:s,webkit:r,gecko:d&&!r&&!t.opera&&!n,android:u,android23:c,chrome:h,ie3d:v,webkit3d:y,gecko3d:P,opera3d:L,any3d:x,mobile:p,mobileWebkit:p&&r,mobileWebkit3d:p&&y,mobileOpera:p&&t.opera,touch:w,msPointer:_,pointer:m,retina:f}}(),o.Point=function(t,e,i){this.x=i?Math.round(t):t,this.y=i?Math.round(e):e},o.Point.prototype={clone:function(){return new o.Point(this.x,this.y)},add:function(t){return this.clone()._add(o.point(t))},_add:function(t){return this.x+=t.x,this.y+=t.y,this},subtract:function(t){return this.clone()._subtract(o.point(t))},_subtract:function(t){return this.x-=t.x,this.y-=t.y,this},divideBy:function(t){return this.clone()._divideBy(t)},_divideBy:function(t){return this.x/=t,this.y/=t,this},multiplyBy:function(t){return this.clone()._multiplyBy(t)},_multiplyBy:function(t){return this.x*=t,this.y*=t,this},round:function(){return this.clone()._round()},_round:function(){return this.x=Math.round(this.x),this.y=Math.round(this.y),this},floor:function(){return this.clone()._floor()},_floor:function(){return this.x=Math.floor(this.x),this.y=Math.floor(this.y),this},distanceTo:function(t){t=o.point(t);var e=t.x-this.x,i=t.y-this.y;return Math.sqrt(e*e+i*i)},equals:function(t){return t=o.point(t),t.x===this.x&&t.y===this.y},contains:function(t){return t=o.point(t),Math.abs(t.x)<=Math.abs(this.x)&&Math.abs(t.y)<=Math.abs(this.y)},toString:function(){return"Point("+o.Util.formatNum(this.x)+", "+o.Util.formatNum(this.y)+")"}},o.point=function(t,e,n){return t instanceof o.Point?t:o.Util.isArray(t)?new o.Point(t[0],t[1]):t===i||null===t?t:new o.Point(t,e,n)},o.Bounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.Bounds.prototype={extend:function(t){return t=o.point(t),this.min||this.max?(this.min.x=Math.min(t.x,this.min.x),this.max.x=Math.max(t.x,this.max.x),this.min.y=Math.min(t.y,this.min.y),this.max.y=Math.max(t.y,this.max.y)):(this.min=t.clone(),this.max=t.clone()),this},getCenter:function(t){return new o.Point((this.min.x+this.max.x)/2,(this.min.y+this.max.y)/2,t)},getBottomLeft:function(){return new o.Point(this.min.x,this.max.y)},getTopRight:function(){return new o.Point(this.max.x,this.min.y)},getSize:function(){return this.max.subtract(this.min)},contains:function(t){var e,i;return t="number"==typeof t[0]||t instanceof o.Point?o.point(t):o.bounds(t),t instanceof o.Bounds?(e=t.min,i=t.max):e=i=t,e.x>=this.min.x&&i.x<=this.max.x&&e.y>=this.min.y&&i.y<=this.max.y},intersects:function(t){t=o.bounds(t);var e=this.min,i=this.max,n=t.min,s=t.max,a=s.x>=e.x&&n.x<=i.x,r=s.y>=e.y&&n.y<=i.y;return a&&r},isValid:function(){return!(!this.min||!this.max)}},o.bounds=function(t,e){return!t||t instanceof o.Bounds?t:new o.Bounds(t,e)},o.Transformation=function(t,e,i,n){this._a=t,this._b=e,this._c=i,this._d=n},o.Transformation.prototype={transform:function(t,e){return this._transform(t.clone(),e)},_transform:function(t,e){return e=e||1,t.x=e*(this._a*t.x+this._b),t.y=e*(this._c*t.y+this._d),t},untransform:function(t,e){return e=e||1,new o.Point((t.x/e-this._b)/this._a,(t.y/e-this._d)/this._c)}},o.DomUtil={get:function(t){return"string"==typeof t?e.getElementById(t):t},getStyle:function(t,i){var n=t.style[i];if(!n&&t.currentStyle&&(n=t.currentStyle[i]),(!n||"auto"===n)&&e.defaultView){var o=e.defaultView.getComputedStyle(t,null);n=o?o[i]:null}return"auto"===n?null:n},getViewportOffset:function(t){var i,n=0,s=0,a=t,r=e.body,h=e.documentElement;do{if(n+=a.offsetTop||0,s+=a.offsetLeft||0,n+=parseInt(o.DomUtil.getStyle(a,"borderTopWidth"),10)||0,s+=parseInt(o.DomUtil.getStyle(a,"borderLeftWidth"),10)||0,i=o.DomUtil.getStyle(a,"position"),a.offsetParent===r&&"absolute"===i)break;if("fixed"===i){n+=r.scrollTop||h.scrollTop||0,s+=r.scrollLeft||h.scrollLeft||0;break}if("relative"===i&&!a.offsetLeft){var l=o.DomUtil.getStyle(a,"width"),u=o.DomUtil.getStyle(a,"max-width"),c=a.getBoundingClientRect();("none"!==l||"none"!==u)&&(s+=c.left+a.clientLeft),n+=c.top+(r.scrollTop||h.scrollTop||0);break}a=a.offsetParent}while(a);a=t;do{if(a===r)break;n-=a.scrollTop||0,s-=a.scrollLeft||0,a=a.parentNode}while(a);return new o.Point(s,n)},documentIsLtr:function(){return o.DomUtil._docIsLtrCached||(o.DomUtil._docIsLtrCached=!0,o.DomUtil._docIsLtr="ltr"===o.DomUtil.getStyle(e.body,"direction")),o.DomUtil._docIsLtr},create:function(t,i,n){var o=e.createElement(t);return o.className=i,n&&n.appendChild(o),o},hasClass:function(t,e){if(t.classList!==i)return t.classList.contains(e);var n=o.DomUtil._getClass(t);return n.length>0&&new RegExp("(^|\\s)"+e+"(\\s|$)").test(n)},addClass:function(t,e){if(t.classList!==i)for(var n=o.Util.splitWords(e),s=0,a=n.length;a>s;s++)t.classList.add(n[s]);else if(!o.DomUtil.hasClass(t,e)){var r=o.DomUtil._getClass(t);o.DomUtil._setClass(t,(r?r+" ":"")+e)}},removeClass:function(t,e){t.classList!==i?t.classList.remove(e):o.DomUtil._setClass(t,o.Util.trim((" "+o.DomUtil._getClass(t)+" ").replace(" "+e+" "," ")))},_setClass:function(t,e){t.className.baseVal===i?t.className=e:t.className.baseVal=e},_getClass:function(t){return t.className.baseVal===i?t.className:t.className.baseVal},setOpacity:function(t,e){if("opacity"in t.style)t.style.opacity=e;else if("filter"in t.style){var i=!1,n="DXImageTransform.Microsoft.Alpha";try{i=t.filters.item(n)}catch(o){if(1===e)return}e=Math.round(100*e),i?(i.Enabled=100!==e,i.Opacity=e):t.style.filter+=" progid:"+n+"(opacity="+e+")"}},testProp:function(t){for(var i=e.documentElement.style,n=0;ni||i===e?e:t),new o.LatLng(this.lat,i)}},o.latLng=function(t,e){return t instanceof o.LatLng?t:o.Util.isArray(t)?"number"==typeof t[0]||"string"==typeof t[0]?new o.LatLng(t[0],t[1],t[2]):null:t===i||null===t?t:"object"==typeof t&&"lat"in t?new o.LatLng(t.lat,"lng"in t?t.lng:t.lon):e===i?null:new o.LatLng(t,e)},o.LatLngBounds=function(t,e){if(t)for(var i=e?[t,e]:t,n=0,o=i.length;o>n;n++)this.extend(i[n])},o.LatLngBounds.prototype={extend:function(t){if(!t)return this;var e=o.latLng(t);return t=null!==e?e:o.latLngBounds(t),t instanceof o.LatLng?this._southWest||this._northEast?(this._southWest.lat=Math.min(t.lat,this._southWest.lat),this._southWest.lng=Math.min(t.lng,this._southWest.lng),this._northEast.lat=Math.max(t.lat,this._northEast.lat),this._northEast.lng=Math.max(t.lng,this._northEast.lng)):(this._southWest=new o.LatLng(t.lat,t.lng),this._northEast=new o.LatLng(t.lat,t.lng)):t instanceof o.LatLngBounds&&(this.extend(t._southWest),this.extend(t._northEast)),this},pad:function(t){var e=this._southWest,i=this._northEast,n=Math.abs(e.lat-i.lat)*t,s=Math.abs(e.lng-i.lng)*t;return new o.LatLngBounds(new o.LatLng(e.lat-n,e.lng-s),new o.LatLng(i.lat+n,i.lng+s))},getCenter:function(){return new o.LatLng((this._southWest.lat+this._northEast.lat)/2,(this._southWest.lng+this._northEast.lng)/2)},getSouthWest:function(){return this._southWest},getNorthEast:function(){return this._northEast},getNorthWest:function(){return new o.LatLng(this.getNorth(),this.getWest())},getSouthEast:function(){return new o.LatLng(this.getSouth(),this.getEast())},getWest:function(){return this._southWest.lng},getSouth:function(){return this._southWest.lat},getEast:function(){return this._northEast.lng},getNorth:function(){return this._northEast.lat},contains:function(t){t="number"==typeof t[0]||t instanceof o.LatLng?o.latLng(t):o.latLngBounds(t);var e,i,n=this._southWest,s=this._northEast;return t instanceof o.LatLngBounds?(e=t.getSouthWest(),i=t.getNorthEast()):e=i=t,e.lat>=n.lat&&i.lat<=s.lat&&e.lng>=n.lng&&i.lng<=s.lng},intersects:function(t){t=o.latLngBounds(t);var e=this._southWest,i=this._northEast,n=t.getSouthWest(),s=t.getNorthEast(),a=s.lat>=e.lat&&n.lat<=i.lat,r=s.lng>=e.lng&&n.lng<=i.lng;return a&&r},toBBoxString:function(){return[this.getWest(),this.getSouth(),this.getEast(),this.getNorth()].join(",")},equals:function(t){return t?(t=o.latLngBounds(t),this._southWest.equals(t.getSouthWest())&&this._northEast.equals(t.getNorthEast())):!1},isValid:function(){return!(!this._southWest||!this._northEast)}},o.latLngBounds=function(t,e){return!t||t instanceof o.LatLngBounds?t:new o.LatLngBounds(t,e)},o.Projection={},o.Projection.SphericalMercator={MAX_LATITUDE:85.0511287798,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=t.lng*e,a=n*e;return a=Math.log(Math.tan(Math.PI/4+a/2)),new o.Point(s,a)},unproject:function(t){var e=o.LatLng.RAD_TO_DEG,i=t.x*e,n=(2*Math.atan(Math.exp(t.y))-Math.PI/2)*e;return new o.LatLng(n,i)}},o.Projection.LonLat={project:function(t){return new o.Point(t.lng,t.lat)},unproject:function(t){return new o.LatLng(t.y,t.x)}},o.CRS={latLngToPoint:function(t,e){var i=this.projection.project(t),n=this.scale(e);return this.transformation._transform(i,n)},pointToLatLng:function(t,e){var i=this.scale(e),n=this.transformation.untransform(t,i);return this.projection.unproject(n)},project:function(t){return this.projection.project(t)},scale:function(t){return 256*Math.pow(2,t)},getSize:function(t){var e=this.scale(t);return o.point(e,e)}},o.CRS.Simple=o.extend({},o.CRS,{projection:o.Projection.LonLat,transformation:new o.Transformation(1,0,-1,0),scale:function(t){return Math.pow(2,t)}}),o.CRS.EPSG3857=o.extend({},o.CRS,{code:"EPSG:3857",projection:o.Projection.SphericalMercator,transformation:new o.Transformation(.5/Math.PI,.5,-.5/Math.PI,.5),project:function(t){var e=this.projection.project(t),i=6378137;return e.multiplyBy(i)}}),o.CRS.EPSG900913=o.extend({},o.CRS.EPSG3857,{code:"EPSG:900913"}),o.CRS.EPSG4326=o.extend({},o.CRS,{code:"EPSG:4326",projection:o.Projection.LonLat,transformation:new o.Transformation(1/360,.5,-1/360,.5)}),o.Map=o.Class.extend({includes:o.Mixin.Events,options:{crs:o.CRS.EPSG3857,fadeAnimation:o.DomUtil.TRANSITION&&!o.Browser.android23,trackResize:!0,markerZoomAnimation:o.DomUtil.TRANSITION&&o.Browser.any3d},initialize:function(t,e){e=o.setOptions(this,e),this._initContainer(t),this._initLayout(),this._onResize=o.bind(this._onResize,this),this._initEvents(),e.maxBounds&&this.setMaxBounds(e.maxBounds),e.center&&e.zoom!==i&&this.setView(o.latLng(e.center),e.zoom,{reset:!0}),this._handlers=[],this._layers={},this._zoomBoundLayers={},this._tileLayersNum=0,this.callInitHooks(),this._addLayers(e.layers)},setView:function(t,e){return e=e===i?this.getZoom():e,this._resetView(o.latLng(t),this._limitZoom(e)),this},setZoom:function(t,e){return this._loaded?this.setView(this.getCenter(),t,{zoom:e}):(this._zoom=this._limitZoom(t),this)},zoomIn:function(t,e){return this.setZoom(this._zoom+(t||1),e)},zoomOut:function(t,e){return this.setZoom(this._zoom-(t||1),e)},setZoomAround:function(t,e,i){var n=this.getZoomScale(e),s=this.getSize().divideBy(2),a=t instanceof o.Point?t:this.latLngToContainerPoint(t),r=a.subtract(s).multiplyBy(1-1/n),h=this.containerPointToLatLng(s.add(r));return this.setView(h,e,{zoom:i})},fitBounds:function(t,e){e=e||{},t=t.getBounds?t.getBounds():o.latLngBounds(t);var i=o.point(e.paddingTopLeft||e.padding||[0,0]),n=o.point(e.paddingBottomRight||e.padding||[0,0]),s=this.getBoundsZoom(t,!1,i.add(n)),a=n.subtract(i).divideBy(2),r=this.project(t.getSouthWest(),s),h=this.project(t.getNorthEast(),s),l=this.unproject(r.add(h).divideBy(2).add(a),s);return s=e&&e.maxZoom?Math.min(e.maxZoom,s):s,this.setView(l,s,e)},fitWorld:function(t){return this.fitBounds([[-90,-180],[90,180]],t)},panTo:function(t,e){return this.setView(t,this._zoom,{pan:e})},panBy:function(t){return this.fire("movestart"),this._rawPanBy(o.point(t)),this.fire("move"),this.fire("moveend")},setMaxBounds:function(t){return t=o.latLngBounds(t),this.options.maxBounds=t,t?(this._loaded&&this._panInsideMaxBounds(),this.on("moveend",this._panInsideMaxBounds,this)):this.off("moveend",this._panInsideMaxBounds,this)},panInsideBounds:function(t,e){var i=this.getCenter(),n=this._limitCenter(i,this._zoom,t);return i.equals(n)?this:this.panTo(n,e)},addLayer:function(t){var e=o.stamp(t);return this._layers[e]?this:(this._layers[e]=t,!t.options||isNaN(t.options.maxZoom)&&isNaN(t.options.minZoom)||(this._zoomBoundLayers[e]=t,this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum++,this._tileLayersToLoad++,t.on("load",this._onTileLayerLoad,this)),this._loaded&&this._layerAdd(t),this)},removeLayer:function(t){var e=o.stamp(t);return this._layers[e]?(this._loaded&&t.onRemove(this),delete this._layers[e],this._loaded&&this.fire("layerremove",{layer:t}),this._zoomBoundLayers[e]&&(delete this._zoomBoundLayers[e],this._updateZoomLevels()),this.options.zoomAnimation&&o.TileLayer&&t instanceof o.TileLayer&&(this._tileLayersNum--,this._tileLayersToLoad--,t.off("load",this._onTileLayerLoad,this)),this):this},hasLayer:function(t){return t?o.stamp(t)in this._layers:!1},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},invalidateSize:function(t){if(!this._loaded)return this;t=o.extend({animate:!1,pan:!0},t===!0?{animate:!0}:t);var e=this.getSize();this._sizeChanged=!0,this._initialCenter=null;var i=this.getSize(),n=e.divideBy(2).round(),s=i.divideBy(2).round(),a=n.subtract(s);return a.x||a.y?(t.animate&&t.pan?this.panBy(a):(t.pan&&this._rawPanBy(a),this.fire("move"),t.debounceMoveend?(clearTimeout(this._sizeTimer),this._sizeTimer=setTimeout(o.bind(this.fire,this,"moveend"),200)):this.fire("moveend")),this.fire("resize",{oldSize:e,newSize:i})):this},addHandler:function(t,e){if(!e)return this;var i=this[t]=new e(this);return this._handlers.push(i),this.options[t]&&i.enable(),this},remove:function(){this._loaded&&this.fire("unload"),this._initEvents("off");try{delete this._container._leaflet}catch(t){this._container._leaflet=i}return this._clearPanes(),this._clearControlPos&&this._clearControlPos(),this._clearHandlers(),this},getCenter:function(){return this._checkIfLoaded(),this._initialCenter&&!this._moved()?this._initialCenter:this.layerPointToLatLng(this._getCenterLayerPoint())},getZoom:function(){return this._zoom},getBounds:function(){var t=this.getPixelBounds(),e=this.unproject(t.getBottomLeft()),i=this.unproject(t.getTopRight());return new o.LatLngBounds(e,i)},getMinZoom:function(){return this.options.minZoom===i?this._layersMinZoom===i?0:this._layersMinZoom:this.options.minZoom},getMaxZoom:function(){return this.options.maxZoom===i?this._layersMaxZoom===i?1/0:this._layersMaxZoom:this.options.maxZoom},getBoundsZoom:function(t,e,i){t=o.latLngBounds(t);var n,s=this.getMinZoom()-(e?1:0),a=this.getMaxZoom(),r=this.getSize(),h=t.getNorthWest(),l=t.getSouthEast(),u=!0;i=o.point(i||[0,0]);do s++,n=this.project(l,s).subtract(this.project(h,s)).add(i),u=e?n.x=s);return u&&e?null:e?s:s-1},getSize:function(){return(!this._size||this._sizeChanged)&&(this._size=new o.Point(this._container.clientWidth,this._container.clientHeight),this._sizeChanged=!1),this._size.clone()},getPixelBounds:function(){var t=this._getTopLeftPoint();return new o.Bounds(t,t.add(this.getSize()))},getPixelOrigin:function(){return this._checkIfLoaded(),this._initialTopLeftPoint},getPanes:function(){return this._panes},getContainer:function(){return this._container},getZoomScale:function(t){var e=this.options.crs;return e.scale(t)/e.scale(this._zoom)},getScaleZoom:function(t){return this._zoom+Math.log(t)/Math.LN2},project:function(t,e){return e=e===i?this._zoom:e,this.options.crs.latLngToPoint(o.latLng(t),e)},unproject:function(t,e){return e=e===i?this._zoom:e,this.options.crs.pointToLatLng(o.point(t),e)},layerPointToLatLng:function(t){var e=o.point(t).add(this.getPixelOrigin());return this.unproject(e)},latLngToLayerPoint:function(t){var e=this.project(o.latLng(t))._round();return e._subtract(this.getPixelOrigin())},containerPointToLayerPoint:function(t){return o.point(t).subtract(this._getMapPanePos())},layerPointToContainerPoint:function(t){return o.point(t).add(this._getMapPanePos())},containerPointToLatLng:function(t){var e=this.containerPointToLayerPoint(o.point(t));return this.layerPointToLatLng(e)},latLngToContainerPoint:function(t){return this.layerPointToContainerPoint(this.latLngToLayerPoint(o.latLng(t)))},mouseEventToContainerPoint:function(t){return o.DomEvent.getMousePosition(t,this._container)},mouseEventToLayerPoint:function(t){return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(t))},mouseEventToLatLng:function(t){return this.layerPointToLatLng(this.mouseEventToLayerPoint(t))},_initContainer:function(t){var e=this._container=o.DomUtil.get(t);if(!e)throw new Error("Map container not found.");if(e._leaflet)throw new Error("Map container is already initialized.");e._leaflet=!0},_initLayout:function(){var t=this._container;o.DomUtil.addClass(t,"leaflet-container"+(o.Browser.touch?" leaflet-touch":"")+(o.Browser.retina?" leaflet-retina":"")+(o.Browser.ielt9?" leaflet-oldie":"")+(this.options.fadeAnimation?" leaflet-fade-anim":""));var e=o.DomUtil.getStyle(t,"position");"absolute"!==e&&"relative"!==e&&"fixed"!==e&&(t.style.position="relative"),this._initPanes(),this._initControlPos&&this._initControlPos()},_initPanes:function(){var t=this._panes={};this._mapPane=t.mapPane=this._createPane("leaflet-map-pane",this._container),this._tilePane=t.tilePane=this._createPane("leaflet-tile-pane",this._mapPane),t.objectsPane=this._createPane("leaflet-objects-pane",this._mapPane),t.shadowPane=this._createPane("leaflet-shadow-pane"),t.overlayPane=this._createPane("leaflet-overlay-pane"),t.markerPane=this._createPane("leaflet-marker-pane"),t.popupPane=this._createPane("leaflet-popup-pane");var e=" leaflet-zoom-hide";this.options.markerZoomAnimation||(o.DomUtil.addClass(t.markerPane,e),o.DomUtil.addClass(t.shadowPane,e),o.DomUtil.addClass(t.popupPane,e))},_createPane:function(t,e){return o.DomUtil.create("div",t,e||this._panes.objectsPane)},_clearPanes:function(){this._container.removeChild(this._mapPane)},_addLayers:function(t){t=t?o.Util.isArray(t)?t:[t]:[];for(var e=0,i=t.length;i>e;e++)this.addLayer(t[e])},_resetView:function(t,e,i,n){var s=this._zoom!==e;n||(this.fire("movestart"),s&&this.fire("zoomstart")),this._zoom=e,this._initialCenter=t,this._initialTopLeftPoint=this._getNewTopLeftPoint(t),i?this._initialTopLeftPoint._add(this._getMapPanePos()):o.DomUtil.setPosition(this._mapPane,new o.Point(0,0)),this._tileLayersToLoad=this._tileLayersNum;var a=!this._loaded;this._loaded=!0,a&&(this.fire("load"),this.eachLayer(this._layerAdd,this)),this.fire("viewreset",{hard:!i}),this.fire("move"),(s||n)&&this.fire("zoomend"),this.fire("moveend",{hard:!i})},_rawPanBy:function(t){o.DomUtil.setPosition(this._mapPane,this._getMapPanePos().subtract(t))},_getZoomSpan:function(){return this.getMaxZoom()-this.getMinZoom()},_updateZoomLevels:function(){var t,e=1/0,n=-1/0,o=this._getZoomSpan();for(t in this._zoomBoundLayers){var s=this._zoomBoundLayers[t];isNaN(s.options.minZoom)||(e=Math.min(e,s.options.minZoom)),isNaN(s.options.maxZoom)||(n=Math.max(n,s.options.maxZoom))}t===i?this._layersMaxZoom=this._layersMinZoom=i:(this._layersMaxZoom=n,this._layersMinZoom=e),o!==this._getZoomSpan()&&this.fire("zoomlevelschange")},_panInsideMaxBounds:function(){this.panInsideBounds(this.options.maxBounds)},_checkIfLoaded:function(){if(!this._loaded)throw new Error("Set map center and zoom first.")},_initEvents:function(e){if(o.DomEvent){e=e||"on",o.DomEvent[e](this._container,"click",this._onMouseClick,this);var i,n,s=["dblclick","mousedown","mouseup","mouseenter","mouseleave","mousemove","contextmenu"];for(i=0,n=s.length;n>i;i++)o.DomEvent[e](this._container,s[i],this._fireMouseEvent,this);this.options.trackResize&&o.DomEvent[e](t,"resize",this._onResize,this)}},_onResize:function(){o.Util.cancelAnimFrame(this._resizeRequest),this._resizeRequest=o.Util.requestAnimFrame(function(){this.invalidateSize({debounceMoveend:!0})},this,!1,this._container)},_onMouseClick:function(t){!this._loaded||!t._simulated&&(this.dragging&&this.dragging.moved()||this.boxZoom&&this.boxZoom.moved())||o.DomEvent._skipped(t)||(this.fire("preclick"),this._fireMouseEvent(t))},_fireMouseEvent:function(t){if(this._loaded&&!o.DomEvent._skipped(t)){var e=t.type;if(e="mouseenter"===e?"mouseover":"mouseleave"===e?"mouseout":e,this.hasEventListeners(e)){"contextmenu"===e&&o.DomEvent.preventDefault(t);var i=this.mouseEventToContainerPoint(t),n=this.containerPointToLayerPoint(i),s=this.layerPointToLatLng(n);this.fire(e,{latlng:s,layerPoint:n,containerPoint:i,originalEvent:t})}}},_onTileLayerLoad:function(){this._tileLayersToLoad--,this._tileLayersNum&&!this._tileLayersToLoad&&this.fire("tilelayersload")},_clearHandlers:function(){for(var t=0,e=this._handlers.length;e>t;t++)this._handlers[t].disable()},whenReady:function(t,e){return this._loaded?t.call(e||this,this):this.on("load",t,e),this},_layerAdd:function(t){t.onAdd(this),this.fire("layeradd",{layer:t})},_getMapPanePos:function(){return o.DomUtil.getPosition(this._mapPane)},_moved:function(){var t=this._getMapPanePos();return t&&!t.equals([0,0])},_getTopLeftPoint:function(){return this.getPixelOrigin().subtract(this._getMapPanePos())},_getNewTopLeftPoint:function(t,e){var i=this.getSize()._divideBy(2);return this.project(t,e)._subtract(i)._round()},_latLngToNewLayerPoint:function(t,e,i){var n=this._getNewTopLeftPoint(i,e).add(this._getMapPanePos());return this.project(t,e)._subtract(n)},_getCenterLayerPoint:function(){return this.containerPointToLayerPoint(this.getSize()._divideBy(2))},_getCenterOffset:function(t){return this.latLngToLayerPoint(t).subtract(this._getCenterLayerPoint())},_limitCenter:function(t,e,i){if(!i)return t;var n=this.project(t,e),s=this.getSize().divideBy(2),a=new o.Bounds(n.subtract(s),n.add(s)),r=this._getBoundsOffset(a,i,e);return this.unproject(n.add(r),e)},_limitOffset:function(t,e){if(!e)return t;var i=this.getPixelBounds(),n=new o.Bounds(i.min.add(t),i.max.add(t));return t.add(this._getBoundsOffset(n,e))},_getBoundsOffset:function(t,e,i){var n=this.project(e.getNorthWest(),i).subtract(t.min),s=this.project(e.getSouthEast(),i).subtract(t.max),a=this._rebound(n.x,-s.x),r=this._rebound(n.y,-s.y);return new o.Point(a,r)},_rebound:function(t,e){return t+e>0?Math.round(t-e)/2:Math.max(0,Math.ceil(t))-Math.max(0,Math.floor(e))},_limitZoom:function(t){var e=this.getMinZoom(),i=this.getMaxZoom();return Math.max(e,Math.min(i,t))}}),o.map=function(t,e){return new o.Map(t,e)},o.Projection.Mercator={MAX_LATITUDE:85.0840591556,R_MINOR:6356752.314245179,R_MAJOR:6378137,project:function(t){var e=o.LatLng.DEG_TO_RAD,i=this.MAX_LATITUDE,n=Math.max(Math.min(i,t.lat),-i),s=this.R_MAJOR,a=this.R_MINOR,r=t.lng*e*s,h=n*e,l=a/s,u=Math.sqrt(1-l*l),c=u*Math.sin(h);c=Math.pow((1-c)/(1+c),.5*u);var d=Math.tan(.5*(.5*Math.PI-h))/c;return h=-s*Math.log(d),new o.Point(r,h)},unproject:function(t){for(var e,i=o.LatLng.RAD_TO_DEG,n=this.R_MAJOR,s=this.R_MINOR,a=t.x*i/n,r=s/n,h=Math.sqrt(1-r*r),l=Math.exp(-t.y/n),u=Math.PI/2-2*Math.atan(l),c=15,d=1e-7,p=c,_=.1;Math.abs(_)>d&&--p>0;)e=h*Math.sin(u),_=Math.PI/2-2*Math.atan(l*Math.pow((1-e)/(1+e),.5*h))-u,u+=_; +return new o.LatLng(u*i,a)}},o.CRS.EPSG3395=o.extend({},o.CRS,{code:"EPSG:3395",projection:o.Projection.Mercator,transformation:function(){var t=o.Projection.Mercator,e=t.R_MAJOR,i=.5/(Math.PI*e);return new o.Transformation(i,.5,-i,.5)}()}),o.TileLayer=o.Class.extend({includes:o.Mixin.Events,options:{minZoom:0,maxZoom:18,tileSize:256,subdomains:"abc",errorTileUrl:"",attribution:"",zoomOffset:0,opacity:1,unloadInvisibleTiles:o.Browser.mobile,updateWhenIdle:o.Browser.mobile},initialize:function(t,e){e=o.setOptions(this,e),e.detectRetina&&o.Browser.retina&&e.maxZoom>0&&(e.tileSize=Math.floor(e.tileSize/2),e.zoomOffset++,e.minZoom>0&&e.minZoom--,this.options.maxZoom--),e.bounds&&(e.bounds=o.latLngBounds(e.bounds)),this._url=t;var i=this.options.subdomains;"string"==typeof i&&(this.options.subdomains=i.split(""))},onAdd:function(t){this._map=t,this._animated=t._zoomAnimated,this._initContainer(),t.on({viewreset:this._reset,moveend:this._update},this),this._animated&&t.on({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||(this._limitedUpdate=o.Util.limitExecByInterval(this._update,150,this),t.on("move",this._limitedUpdate,this)),this._reset(),this._update()},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this._container.parentNode.removeChild(this._container),t.off({viewreset:this._reset,moveend:this._update},this),this._animated&&t.off({zoomanim:this._animateZoom,zoomend:this._endZoomAnim},this),this.options.updateWhenIdle||t.off("move",this._limitedUpdate,this),this._container=null,this._map=null},bringToFront:function(){var t=this._map._panes.tilePane;return this._container&&(t.appendChild(this._container),this._setAutoZIndex(t,Math.max)),this},bringToBack:function(){var t=this._map._panes.tilePane;return this._container&&(t.insertBefore(this._container,t.firstChild),this._setAutoZIndex(t,Math.min)),this},getAttribution:function(){return this.options.attribution},getContainer:function(){return this._container},setOpacity:function(t){return this.options.opacity=t,this._map&&this._updateOpacity(),this},setZIndex:function(t){return this.options.zIndex=t,this._updateZIndex(),this},setUrl:function(t,e){return this._url=t,e||this.redraw(),this},redraw:function(){return this._map&&(this._reset({hard:!0}),this._update()),this},_updateZIndex:function(){this._container&&this.options.zIndex!==i&&(this._container.style.zIndex=this.options.zIndex)},_setAutoZIndex:function(t,e){var i,n,o,s=t.children,a=-e(1/0,-1/0);for(n=0,o=s.length;o>n;n++)s[n]!==this._container&&(i=parseInt(s[n].style.zIndex,10),isNaN(i)||(a=e(a,i)));this.options.zIndex=this._container.style.zIndex=(isFinite(a)?a:0)+e(1,-1)},_updateOpacity:function(){var t,e=this._tiles;if(o.Browser.ielt9)for(t in e)o.DomUtil.setOpacity(e[t],this.options.opacity);else o.DomUtil.setOpacity(this._container,this.options.opacity)},_initContainer:function(){var t=this._map._panes.tilePane;if(!this._container){if(this._container=o.DomUtil.create("div","leaflet-layer"),this._updateZIndex(),this._animated){var e="leaflet-tile-container";this._bgBuffer=o.DomUtil.create("div",e,this._container),this._tileContainer=o.DomUtil.create("div",e,this._container)}else this._tileContainer=this._container;t.appendChild(this._container),this.options.opacity<1&&this._updateOpacity()}},_reset:function(t){for(var e in this._tiles)this.fire("tileunload",{tile:this._tiles[e]});this._tiles={},this._tilesToLoad=0,this.options.reuseTiles&&(this._unusedTiles=[]),this._tileContainer.innerHTML="",this._animated&&t&&t.hard&&this._clearBgBuffer(),this._initContainer()},_getTileSize:function(){var t=this._map,e=t.getZoom()+this.options.zoomOffset,i=this.options.maxNativeZoom,n=this.options.tileSize;return i&&e>i&&(n=Math.round(t.getZoomScale(e)/t.getZoomScale(i)*n)),n},_update:function(){if(this._map){var t=this._map,e=t.getPixelBounds(),i=t.getZoom(),n=this._getTileSize();if(!(i>this.options.maxZoom||in;n++)this._addTile(a[n],l);this._tileContainer.appendChild(l)}},_tileShouldBeLoaded:function(t){if(t.x+":"+t.y in this._tiles)return!1;var e=this.options;if(!e.continuousWorld){var i=this._getWrapTileNum();if(e.noWrap&&(t.x<0||t.x>=i.x)||t.y<0||t.y>=i.y)return!1}if(e.bounds){var n=e.tileSize,o=t.multiplyBy(n),s=o.add([n,n]),a=this._map.unproject(o),r=this._map.unproject(s);if(e.continuousWorld||e.noWrap||(a=a.wrap(),r=r.wrap()),!e.bounds.intersects([a,r]))return!1}return!0},_removeOtherTiles:function(t){var e,i,n,o;for(o in this._tiles)e=o.split(":"),i=parseInt(e[0],10),n=parseInt(e[1],10),(it.max.x||nt.max.y)&&this._removeTile(o)},_removeTile:function(t){var e=this._tiles[t];this.fire("tileunload",{tile:e,url:e.src}),this.options.reuseTiles?(o.DomUtil.removeClass(e,"leaflet-tile-loaded"),this._unusedTiles.push(e)):e.parentNode===this._tileContainer&&this._tileContainer.removeChild(e),o.Browser.android||(e.onload=null,e.src=o.Util.emptyImageUrl),delete this._tiles[t]},_addTile:function(t,e){var i=this._getTilePos(t),n=this._getTile();o.DomUtil.setPosition(n,i,o.Browser.chrome),this._tiles[t.x+":"+t.y]=n,this._loadTile(n,t),n.parentNode!==this._tileContainer&&e.appendChild(n)},_getZoomForUrl:function(){var t=this.options,e=this._map.getZoom();return t.zoomReverse&&(e=t.maxZoom-e),e+=t.zoomOffset,t.maxNativeZoom?Math.min(e,t.maxNativeZoom):e},_getTilePos:function(t){var e=this._map.getPixelOrigin(),i=this._getTileSize();return t.multiplyBy(i).subtract(e)},getTileUrl:function(t){return o.Util.template(this._url,o.extend({s:this._getSubdomain(t),z:t.z,x:t.x,y:t.y},this.options))},_getWrapTileNum:function(){var t=this._map.options.crs,e=t.getSize(this._map.getZoom());return e.divideBy(this._getTileSize())._floor()},_adjustTilePoint:function(t){var e=this._getWrapTileNum();this.options.continuousWorld||this.options.noWrap||(t.x=(t.x%e.x+e.x)%e.x),this.options.tms&&(t.y=e.y-t.y-1),t.z=this._getZoomForUrl()},_getSubdomain:function(t){var e=Math.abs(t.x+t.y)%this.options.subdomains.length;return this.options.subdomains[e]},_getTile:function(){if(this.options.reuseTiles&&this._unusedTiles.length>0){var t=this._unusedTiles.pop();return this._resetTile(t),t}return this._createTile()},_resetTile:function(){},_createTile:function(){var t=o.DomUtil.create("img","leaflet-tile");return t.style.width=t.style.height=this._getTileSize()+"px",t.galleryimg="no",t.onselectstart=t.onmousemove=o.Util.falseFn,o.Browser.ielt9&&this.options.opacity!==i&&o.DomUtil.setOpacity(t,this.options.opacity),o.Browser.mobileWebkit3d&&(t.style.WebkitBackfaceVisibility="hidden"),t},_loadTile:function(t,e){t._layer=this,t.onload=this._tileOnLoad,t.onerror=this._tileOnError,this._adjustTilePoint(e),t.src=this.getTileUrl(e),this.fire("tileloadstart",{tile:t,url:t.src})},_tileLoaded:function(){this._tilesToLoad--,this._animated&&o.DomUtil.addClass(this._tileContainer,"leaflet-zoom-animated"),this._tilesToLoad||(this.fire("load"),this._animated&&(clearTimeout(this._clearBgBufferTimer),this._clearBgBufferTimer=setTimeout(o.bind(this._clearBgBuffer,this),500)))},_tileOnLoad:function(){var t=this._layer;this.src!==o.Util.emptyImageUrl&&(o.DomUtil.addClass(this,"leaflet-tile-loaded"),t.fire("tileload",{tile:this,url:this.src})),t._tileLoaded()},_tileOnError:function(){var t=this._layer;t.fire("tileerror",{tile:this,url:this.src});var e=t.options.errorTileUrl;e&&(this.src=e),t._tileLoaded()}}),o.tileLayer=function(t,e){return new o.TileLayer(t,e)},o.TileLayer.WMS=o.TileLayer.extend({defaultWmsParams:{service:"WMS",request:"GetMap",version:"1.1.1",layers:"",styles:"",format:"image/jpeg",transparent:!1},initialize:function(t,e){this._url=t;var i=o.extend({},this.defaultWmsParams),n=e.tileSize||this.options.tileSize;i.width=i.height=e.detectRetina&&o.Browser.retina?2*n:n;for(var s in e)this.options.hasOwnProperty(s)||"crs"===s||(i[s]=e[s]);this.wmsParams=i,o.setOptions(this,e)},onAdd:function(t){this._crs=this.options.crs||t.options.crs,this._wmsVersion=parseFloat(this.wmsParams.version);var e=this._wmsVersion>=1.3?"crs":"srs";this.wmsParams[e]=this._crs.code,o.TileLayer.prototype.onAdd.call(this,t)},getTileUrl:function(t){var e=this._map,i=this.options.tileSize,n=t.multiplyBy(i),s=n.add([i,i]),a=this._crs.project(e.unproject(n,t.z)),r=this._crs.project(e.unproject(s,t.z)),h=this._wmsVersion>=1.3&&this._crs===o.CRS.EPSG4326?[r.y,a.x,a.y,r.x].join(","):[a.x,r.y,r.x,a.y].join(","),l=o.Util.template(this._url,{s:this._getSubdomain(t)});return l+o.Util.getParamString(this.wmsParams,l,!0)+"&BBOX="+h},setParams:function(t,e){return o.extend(this.wmsParams,t),e||this.redraw(),this}}),o.tileLayer.wms=function(t,e){return new o.TileLayer.WMS(t,e)},o.TileLayer.Canvas=o.TileLayer.extend({options:{async:!1},initialize:function(t){o.setOptions(this,t)},redraw:function(){this._map&&(this._reset({hard:!0}),this._update());for(var t in this._tiles)this._redrawTile(this._tiles[t]);return this},_redrawTile:function(t){this.drawTile(t,t._tilePoint,this._map._zoom)},_createTile:function(){var t=o.DomUtil.create("canvas","leaflet-tile");return t.width=t.height=this.options.tileSize,t.onselectstart=t.onmousemove=o.Util.falseFn,t},_loadTile:function(t,e){t._layer=this,t._tilePoint=e,this._redrawTile(t),this.options.async||this.tileDrawn(t)},drawTile:function(){},tileDrawn:function(t){this._tileOnLoad.call(t)}}),o.tileLayer.canvas=function(t){return new o.TileLayer.Canvas(t)},o.ImageOverlay=o.Class.extend({includes:o.Mixin.Events,options:{opacity:1},initialize:function(t,e,i){this._url=t,this._bounds=o.latLngBounds(e),o.setOptions(this,i)},onAdd:function(t){this._map=t,this._image||this._initImage(),t._panes.overlayPane.appendChild(this._image),t.on("viewreset",this._reset,this),t.options.zoomAnimation&&o.Browser.any3d&&t.on("zoomanim",this._animateZoom,this),this._reset()},onRemove:function(t){t.getPanes().overlayPane.removeChild(this._image),t.off("viewreset",this._reset,this),t.options.zoomAnimation&&t.off("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},setOpacity:function(t){return this.options.opacity=t,this._updateOpacity(),this},bringToFront:function(){return this._image&&this._map._panes.overlayPane.appendChild(this._image),this},bringToBack:function(){var t=this._map._panes.overlayPane;return this._image&&t.insertBefore(this._image,t.firstChild),this},setUrl:function(t){this._url=t,this._image.src=this._url},getAttribution:function(){return this.options.attribution},_initImage:function(){this._image=o.DomUtil.create("img","leaflet-image-layer"),this._map.options.zoomAnimation&&o.Browser.any3d?o.DomUtil.addClass(this._image,"leaflet-zoom-animated"):o.DomUtil.addClass(this._image,"leaflet-zoom-hide"),this._updateOpacity(),o.extend(this._image,{galleryimg:"no",onselectstart:o.Util.falseFn,onmousemove:o.Util.falseFn,onload:o.bind(this._onImageLoad,this),src:this._url})},_animateZoom:function(t){var e=this._map,i=this._image,n=e.getZoomScale(t.zoom),s=this._bounds.getNorthWest(),a=this._bounds.getSouthEast(),r=e._latLngToNewLayerPoint(s,t.zoom,t.center),h=e._latLngToNewLayerPoint(a,t.zoom,t.center)._subtract(r),l=r._add(h._multiplyBy(.5*(1-1/n)));i.style[o.DomUtil.TRANSFORM]=o.DomUtil.getTranslateString(l)+" scale("+n+") "},_reset:function(){var t=this._image,e=this._map.latLngToLayerPoint(this._bounds.getNorthWest()),i=this._map.latLngToLayerPoint(this._bounds.getSouthEast())._subtract(e);o.DomUtil.setPosition(t,e),t.style.width=i.x+"px",t.style.height=i.y+"px"},_onImageLoad:function(){this.fire("load")},_updateOpacity:function(){o.DomUtil.setOpacity(this._image,this.options.opacity)}}),o.imageOverlay=function(t,e,i){return new o.ImageOverlay(t,e,i)},o.Icon=o.Class.extend({options:{className:""},initialize:function(t){o.setOptions(this,t)},createIcon:function(t){return this._createIcon("icon",t)},createShadow:function(t){return this._createIcon("shadow",t)},_createIcon:function(t,e){var i=this._getIconUrl(t);if(!i){if("icon"===t)throw new Error("iconUrl not set in Icon options (see the docs).");return null}var n;return n=e&&"IMG"===e.tagName?this._createImg(i,e):this._createImg(i),this._setIconStyles(n,t),n},_setIconStyles:function(t,e){var i,n=this.options,s=o.point(n[e+"Size"]);i=o.point("shadow"===e?n.shadowAnchor||n.iconAnchor:n.iconAnchor),!i&&s&&(i=s.divideBy(2,!0)),t.className="leaflet-marker-"+e+" "+n.className,i&&(t.style.marginLeft=-i.x+"px",t.style.marginTop=-i.y+"px"),s&&(t.style.width=s.x+"px",t.style.height=s.y+"px")},_createImg:function(t,i){return i=i||e.createElement("img"),i.src=t,i},_getIconUrl:function(t){return o.Browser.retina&&this.options[t+"RetinaUrl"]?this.options[t+"RetinaUrl"]:this.options[t+"Url"]}}),o.icon=function(t){return new o.Icon(t)},o.Icon.Default=o.Icon.extend({options:{iconSize:[25,41],iconAnchor:[12,41],popupAnchor:[1,-34],shadowSize:[41,41]},_getIconUrl:function(t){var e=t+"Url";if(this.options[e])return this.options[e];o.Browser.retina&&"icon"===t&&(t+="-2x");var i=o.Icon.Default.imagePath;if(!i)throw new Error("Couldn't autodetect L.Icon.Default.imagePath, set it manually.");return i+"/marker-"+t+".png"}}),o.Icon.Default.imagePath=function(){var t,i,n,o,s,a=e.getElementsByTagName("script"),r=/[\/^]leaflet[\-\._]?([\w\-\._]*)\.js\??/;for(t=0,i=a.length;i>t;t++)if(n=a[t].src,o=n.match(r))return s=n.split(r)[0],(s?s+"/":"")+"images"}(),o.Marker=o.Class.extend({includes:o.Mixin.Events,options:{icon:new o.Icon.Default,title:"",alt:"",clickable:!0,draggable:!1,keyboard:!0,zIndexOffset:0,opacity:1,riseOnHover:!1,riseOffset:250},initialize:function(t,e){o.setOptions(this,e),this._latlng=o.latLng(t)},onAdd:function(t){this._map=t,t.on("viewreset",this.update,this),this._initIcon(),this.update(),this.fire("add"),t.options.zoomAnimation&&t.options.markerZoomAnimation&&t.on("zoomanim",this._animateZoom,this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){this.dragging&&this.dragging.disable(),this._removeIcon(),this._removeShadow(),this.fire("remove"),t.off({viewreset:this.update,zoomanim:this._animateZoom},this),this._map=null},getLatLng:function(){return this._latlng},setLatLng:function(t){return this._latlng=o.latLng(t),this.update(),this.fire("move",{latlng:this._latlng})},setZIndexOffset:function(t){return this.options.zIndexOffset=t,this.update(),this},setIcon:function(t){return this.options.icon=t,this._map&&(this._initIcon(),this.update()),this._popup&&this.bindPopup(this._popup),this},update:function(){if(this._icon){var t=this._map.latLngToLayerPoint(this._latlng).round();this._setPos(t)}return this},_initIcon:function(){var t=this.options,e=this._map,i=e.options.zoomAnimation&&e.options.markerZoomAnimation,n=i?"leaflet-zoom-animated":"leaflet-zoom-hide",s=t.icon.createIcon(this._icon),a=!1;s!==this._icon&&(this._icon&&this._removeIcon(),a=!0,t.title&&(s.title=t.title),t.alt&&(s.alt=t.alt)),o.DomUtil.addClass(s,n),t.keyboard&&(s.tabIndex="0"),this._icon=s,this._initInteraction(),t.riseOnHover&&o.DomEvent.on(s,"mouseover",this._bringToFront,this).on(s,"mouseout",this._resetZIndex,this);var r=t.icon.createShadow(this._shadow),h=!1;r!==this._shadow&&(this._removeShadow(),h=!0),r&&o.DomUtil.addClass(r,n),this._shadow=r,t.opacity<1&&this._updateOpacity();var l=this._map._panes;a&&l.markerPane.appendChild(this._icon),r&&h&&l.shadowPane.appendChild(this._shadow)},_removeIcon:function(){this.options.riseOnHover&&o.DomEvent.off(this._icon,"mouseover",this._bringToFront).off(this._icon,"mouseout",this._resetZIndex),this._map._panes.markerPane.removeChild(this._icon),this._icon=null},_removeShadow:function(){this._shadow&&this._map._panes.shadowPane.removeChild(this._shadow),this._shadow=null},_setPos:function(t){o.DomUtil.setPosition(this._icon,t),this._shadow&&o.DomUtil.setPosition(this._shadow,t),this._zIndex=t.y+this.options.zIndexOffset,this._resetZIndex()},_updateZIndex:function(t){this._icon.style.zIndex=this._zIndex+t},_animateZoom:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center).round();this._setPos(e)},_initInteraction:function(){if(this.options.clickable){var t=this._icon,e=["dblclick","mousedown","mouseover","mouseout","contextmenu"];o.DomUtil.addClass(t,"leaflet-clickable"),o.DomEvent.on(t,"click",this._onMouseClick,this),o.DomEvent.on(t,"keypress",this._onKeyPress,this);for(var i=0;is?(e.height=s+"px",o.DomUtil.addClass(t,a)):o.DomUtil.removeClass(t,a),this._containerWidth=this._container.offsetWidth},_updatePosition:function(){if(this._map){var t=this._map.latLngToLayerPoint(this._latlng),e=this._animated,i=o.point(this.options.offset);e&&o.DomUtil.setPosition(this._container,t),this._containerBottom=-i.y-(e?0:t.y),this._containerLeft=-Math.round(this._containerWidth/2)+i.x+(e?0:t.x),this._container.style.bottom=this._containerBottom+"px",this._container.style.left=this._containerLeft+"px"}},_zoomAnimation:function(t){var e=this._map._latLngToNewLayerPoint(this._latlng,t.zoom,t.center);o.DomUtil.setPosition(this._container,e)},_adjustPan:function(){if(this.options.autoPan){var t=this._map,e=this._container.offsetHeight,i=this._containerWidth,n=new o.Point(this._containerLeft,-e-this._containerBottom);this._animated&&n._add(o.DomUtil.getPosition(this._container));var s=t.layerPointToContainerPoint(n),a=o.point(this.options.autoPanPadding),r=o.point(this.options.autoPanPaddingTopLeft||a),h=o.point(this.options.autoPanPaddingBottomRight||a),l=t.getSize(),u=0,c=0;s.x+i+h.x>l.x&&(u=s.x+i-l.x+h.x),s.x-u-r.x<0&&(u=s.x-r.x),s.y+e+h.y>l.y&&(c=s.y+e-l.y+h.y),s.y-c-r.y<0&&(c=s.y-r.y),(u||c)&&t.fire("autopanstart").panBy([u,c])}},_onCloseButtonClick:function(t){this._close(),o.DomEvent.stop(t)}}),o.popup=function(t,e){return new o.Popup(t,e)},o.Map.include({openPopup:function(t,e,i){if(this.closePopup(),!(t instanceof o.Popup)){var n=t;t=new o.Popup(i).setLatLng(e).setContent(n)}return t._isOpen=!0,this._popup=t,this.addLayer(t)},closePopup:function(t){return t&&t!==this._popup||(t=this._popup,this._popup=null),t&&(this.removeLayer(t),t._isOpen=!1),this}}),o.Marker.include({openPopup:function(){return this._popup&&this._map&&!this._map.hasLayer(this._popup)&&(this._popup.setLatLng(this._latlng),this._map.openPopup(this._popup)),this},closePopup:function(){return this._popup&&this._popup._close(),this},togglePopup:function(){return this._popup&&(this._popup._isOpen?this.closePopup():this.openPopup()),this},bindPopup:function(t,e){var i=o.point(this.options.icon.options.popupAnchor||[0,0]);return i=i.add(o.Popup.prototype.options.offset),e&&e.offset&&(i=i.add(e.offset)),e=o.extend({offset:i},e),this._popupHandlersAdded||(this.on("click",this.togglePopup,this).on("remove",this.closePopup,this).on("move",this._movePopup,this),this._popupHandlersAdded=!0),t instanceof o.Popup?(o.setOptions(t,e),this._popup=t):this._popup=new o.Popup(e,this).setContent(t),this},setPopupContent:function(t){return this._popup&&this._popup.setContent(t),this},unbindPopup:function(){return this._popup&&(this._popup=null,this.off("click",this.togglePopup,this).off("remove",this.closePopup,this).off("move",this._movePopup,this),this._popupHandlersAdded=!1),this},getPopup:function(){return this._popup},_movePopup:function(t){this._popup.setLatLng(t.latlng)}}),o.LayerGroup=o.Class.extend({initialize:function(t){this._layers={};var e,i;if(t)for(e=0,i=t.length;i>e;e++)this.addLayer(t[e])},addLayer:function(t){var e=this.getLayerId(t);return this._layers[e]=t,this._map&&this._map.addLayer(t),this},removeLayer:function(t){var e=t in this._layers?t:this.getLayerId(t);return this._map&&this._layers[e]&&this._map.removeLayer(this._layers[e]),delete this._layers[e],this},hasLayer:function(t){return t?t in this._layers||this.getLayerId(t)in this._layers:!1},clearLayers:function(){return this.eachLayer(this.removeLayer,this),this},invoke:function(t){var e,i,n=Array.prototype.slice.call(arguments,1);for(e in this._layers)i=this._layers[e],i[t]&&i[t].apply(i,n);return this},onAdd:function(t){this._map=t,this.eachLayer(t.addLayer,t)},onRemove:function(t){this.eachLayer(t.removeLayer,t),this._map=null},addTo:function(t){return t.addLayer(this),this},eachLayer:function(t,e){for(var i in this._layers)t.call(e,this._layers[i]);return this},getLayer:function(t){return this._layers[t]},getLayers:function(){var t=[];for(var e in this._layers)t.push(this._layers[e]);return t},setZIndex:function(t){return this.invoke("setZIndex",t)},getLayerId:function(t){return o.stamp(t)}}),o.layerGroup=function(t){return new o.LayerGroup(t)},o.FeatureGroup=o.LayerGroup.extend({includes:o.Mixin.Events,statics:{EVENTS:"click dblclick mouseover mouseout mousemove contextmenu popupopen popupclose"},addLayer:function(t){return this.hasLayer(t)?this:("on"in t&&t.on(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.addLayer.call(this,t),this._popupContent&&t.bindPopup&&t.bindPopup(this._popupContent,this._popupOptions),this.fire("layeradd",{layer:t}))},removeLayer:function(t){return this.hasLayer(t)?(t in this._layers&&(t=this._layers[t]),t.off(o.FeatureGroup.EVENTS,this._propagateEvent,this),o.LayerGroup.prototype.removeLayer.call(this,t),this._popupContent&&this.invoke("unbindPopup"),this.fire("layerremove",{layer:t})):this},bindPopup:function(t,e){return this._popupContent=t,this._popupOptions=e,this.invoke("bindPopup",t,e)},openPopup:function(t){for(var e in this._layers){this._layers[e].openPopup(t);break}return this},setStyle:function(t){return this.invoke("setStyle",t)},bringToFront:function(){return this.invoke("bringToFront")},bringToBack:function(){return this.invoke("bringToBack")},getBounds:function(){var t=new o.LatLngBounds;return this.eachLayer(function(e){t.extend(e instanceof o.Marker?e.getLatLng():e.getBounds())}),t},_propagateEvent:function(t){t=o.extend({layer:t.target,target:this},t),this.fire(t.type,t)}}),o.featureGroup=function(t){return new o.FeatureGroup(t)},o.Path=o.Class.extend({includes:[o.Mixin.Events],statics:{CLIP_PADDING:function(){var e=o.Browser.mobile?1280:2e3,i=(e/Math.max(t.outerWidth,t.outerHeight)-1)/2;return Math.max(0,Math.min(.5,i))}()},options:{stroke:!0,color:"#0033ff",dashArray:null,lineCap:null,lineJoin:null,weight:5,opacity:.5,fill:!1,fillColor:null,fillOpacity:.2,clickable:!0},initialize:function(t){o.setOptions(this,t)},onAdd:function(t){this._map=t,this._container||(this._initElements(),this._initEvents()),this.projectLatlngs(),this._updatePath(),this._container&&this._map._pathRoot.appendChild(this._container),this.fire("add"),t.on({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},addTo:function(t){return t.addLayer(this),this},onRemove:function(t){t._pathRoot.removeChild(this._container),this.fire("remove"),this._map=null,o.Browser.vml&&(this._container=null,this._stroke=null,this._fill=null),t.off({viewreset:this.projectLatlngs,moveend:this._updatePath},this)},projectLatlngs:function(){},setStyle:function(t){return o.setOptions(this,t),this._container&&this._updateStyle(),this},redraw:function(){return this._map&&(this.projectLatlngs(),this._updatePath()),this}}),o.Map.include({_updatePathViewport:function(){var t=o.Path.CLIP_PADDING,e=this.getSize(),i=o.DomUtil.getPosition(this._mapPane),n=i.multiplyBy(-1)._subtract(e.multiplyBy(t)._round()),s=n.add(e.multiplyBy(1+2*t)._round());this._pathViewport=new o.Bounds(n,s)}}),o.Path.SVG_NS="http://www.w3.org/2000/svg",o.Browser.svg=!(!e.createElementNS||!e.createElementNS(o.Path.SVG_NS,"svg").createSVGRect),o.Path=o.Path.extend({statics:{SVG:o.Browser.svg},bringToFront:function(){var t=this._map._pathRoot,e=this._container;return e&&t.lastChild!==e&&t.appendChild(e),this},bringToBack:function(){var t=this._map._pathRoot,e=this._container,i=t.firstChild;return e&&i!==e&&t.insertBefore(e,i),this},getPathString:function(){},_createElement:function(t){return e.createElementNS(o.Path.SVG_NS,t)},_initElements:function(){this._map._initPathRoot(),this._initPath(),this._initStyle()},_initPath:function(){this._container=this._createElement("g"),this._path=this._createElement("path"),this.options.className&&o.DomUtil.addClass(this._path,this.options.className),this._container.appendChild(this._path)},_initStyle:function(){this.options.stroke&&(this._path.setAttribute("stroke-linejoin","round"),this._path.setAttribute("stroke-linecap","round")),this.options.fill&&this._path.setAttribute("fill-rule","evenodd"),this.options.pointerEvents&&this._path.setAttribute("pointer-events",this.options.pointerEvents),this.options.clickable||this.options.pointerEvents||this._path.setAttribute("pointer-events","none"),this._updateStyle()},_updateStyle:function(){this.options.stroke?(this._path.setAttribute("stroke",this.options.color),this._path.setAttribute("stroke-opacity",this.options.opacity),this._path.setAttribute("stroke-width",this.options.weight),this.options.dashArray?this._path.setAttribute("stroke-dasharray",this.options.dashArray):this._path.removeAttribute("stroke-dasharray"),this.options.lineCap&&this._path.setAttribute("stroke-linecap",this.options.lineCap),this.options.lineJoin&&this._path.setAttribute("stroke-linejoin",this.options.lineJoin)):this._path.setAttribute("stroke","none"),this.options.fill?(this._path.setAttribute("fill",this.options.fillColor||this.options.color),this._path.setAttribute("fill-opacity",this.options.fillOpacity)):this._path.setAttribute("fill","none")},_updatePath:function(){var t=this.getPathString();t||(t="M0 0"),this._path.setAttribute("d",t)},_initEvents:function(){if(this.options.clickable){(o.Browser.svg||!o.Browser.vml)&&o.DomUtil.addClass(this._path,"leaflet-clickable"),o.DomEvent.on(this._container,"click",this._onMouseClick,this);for(var t=["dblclick","mousedown","mouseover","mouseout","mousemove","contextmenu"],e=0;e';var i=t.firstChild;return i.style.behavior="url(#default#VML)",i&&"object"==typeof i.adj}catch(n){return!1}}(),o.Path=o.Browser.svg||!o.Browser.vml?o.Path:o.Path.extend({statics:{VML:!0,CLIP_PADDING:.02},_createElement:function(){try{return e.namespaces.add("lvml","urn:schemas-microsoft-com:vml"),function(t){return e.createElement("')}}catch(t){return function(t){return e.createElement("<"+t+' xmlns="urn:schemas-microsoft.com:vml" class="lvml">')}}}(),_initPath:function(){var t=this._container=this._createElement("shape");o.DomUtil.addClass(t,"leaflet-vml-shape"+(this.options.className?" "+this.options.className:"")),this.options.clickable&&o.DomUtil.addClass(t,"leaflet-clickable"),t.coordsize="1 1",this._path=this._createElement("path"),t.appendChild(this._path),this._map._pathRoot.appendChild(t)},_initStyle:function(){this._updateStyle()},_updateStyle:function(){var t=this._stroke,e=this._fill,i=this.options,n=this._container;n.stroked=i.stroke,n.filled=i.fill,i.stroke?(t||(t=this._stroke=this._createElement("stroke"),t.endcap="round",n.appendChild(t)),t.weight=i.weight+"px",t.color=i.color,t.opacity=i.opacity,t.dashStyle=i.dashArray?o.Util.isArray(i.dashArray)?i.dashArray.join(" "):i.dashArray.replace(/( *, *)/g," "):"",i.lineCap&&(t.endcap=i.lineCap.replace("butt","flat")),i.lineJoin&&(t.joinstyle=i.lineJoin)):t&&(n.removeChild(t),this._stroke=null),i.fill?(e||(e=this._fill=this._createElement("fill"),n.appendChild(e)),e.color=i.fillColor||i.color,e.opacity=i.fillOpacity):e&&(n.removeChild(e),this._fill=null)},_updatePath:function(){var t=this._container.style;t.display="none",this._path.v=this.getPathString()+" ",t.display=""}}),o.Map.include(o.Browser.svg||!o.Browser.vml?{}:{_initPathRoot:function(){if(!this._pathRoot){var t=this._pathRoot=e.createElement("div");t.className="leaflet-vml-container",this._panes.overlayPane.appendChild(t),this.on("moveend",this._updatePathViewport),this._updatePathViewport()}}}),o.Browser.canvas=function(){return!!e.createElement("canvas").getContext}(),o.Path=o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?o.Path:o.Path.extend({statics:{CANVAS:!0,SVG:!1},redraw:function(){return this._map&&(this.projectLatlngs(),this._requestUpdate()),this},setStyle:function(t){return o.setOptions(this,t),this._map&&(this._updateStyle(),this._requestUpdate()),this},onRemove:function(t){t.off("viewreset",this.projectLatlngs,this).off("moveend",this._updatePath,this),this.options.clickable&&(this._map.off("click",this._onClick,this),this._map.off("mousemove",this._onMouseMove,this)),this._requestUpdate(),this._map=null},_requestUpdate:function(){this._map&&!o.Path._updateRequest&&(o.Path._updateRequest=o.Util.requestAnimFrame(this._fireMapMoveEnd,this._map))},_fireMapMoveEnd:function(){o.Path._updateRequest=null,this.fire("moveend")},_initElements:function(){this._map._initPathRoot(),this._ctx=this._map._canvasCtx},_updateStyle:function(){var t=this.options;t.stroke&&(this._ctx.lineWidth=t.weight,this._ctx.strokeStyle=t.color),t.fill&&(this._ctx.fillStyle=t.fillColor||t.color)},_drawPath:function(){var t,e,i,n,s,a;for(this._ctx.beginPath(),t=0,i=this._parts.length;i>t;t++){for(e=0,n=this._parts[t].length;n>e;e++)s=this._parts[t][e],a=(0===e?"move":"line")+"To",this._ctx[a](s.x,s.y);this instanceof o.Polygon&&this._ctx.closePath()}},_checkIfEmpty:function(){return!this._parts.length},_updatePath:function(){if(!this._checkIfEmpty()){var t=this._ctx,e=this.options;this._drawPath(),t.save(),this._updateStyle(),e.fill&&(t.globalAlpha=e.fillOpacity,t.fill()),e.stroke&&(t.globalAlpha=e.opacity,t.stroke()),t.restore()}},_initEvents:function(){this.options.clickable&&(this._map.on("mousemove",this._onMouseMove,this),this._map.on("click",this._onClick,this))},_onClick:function(t){this._containsPoint(t.layerPoint)&&this.fire("click",t)},_onMouseMove:function(t){this._map&&!this._map._animatingZoom&&(this._containsPoint(t.layerPoint)?(this._ctx.canvas.style.cursor="pointer",this._mouseInside=!0,this.fire("mouseover",t)):this._mouseInside&&(this._ctx.canvas.style.cursor="",this._mouseInside=!1,this.fire("mouseout",t)))}}),o.Map.include(o.Path.SVG&&!t.L_PREFER_CANVAS||!o.Browser.canvas?{}:{_initPathRoot:function(){var t,i=this._pathRoot;i||(i=this._pathRoot=e.createElement("canvas"),i.style.position="absolute",t=this._canvasCtx=i.getContext("2d"),t.lineCap="round",t.lineJoin="round",this._panes.overlayPane.appendChild(i),this.options.zoomAnimation&&(this._pathRoot.className="leaflet-zoom-animated",this.on("zoomanim",this._animatePathZoom),this.on("zoomend",this._endPathZoom)),this.on("moveend",this._updateCanvasViewport),this._updateCanvasViewport())},_updateCanvasViewport:function(){if(!this._pathZooming){this._updatePathViewport();var t=this._pathViewport,e=t.min,i=t.max.subtract(e),n=this._pathRoot;o.DomUtil.setPosition(n,e),n.width=i.x,n.height=i.y,n.getContext("2d").translate(-e.x,-e.y)}}}),o.LineUtil={simplify:function(t,e){if(!e||!t.length)return t.slice();var i=e*e;return t=this._reducePoints(t,i),t=this._simplifyDP(t,i)},pointToSegmentDistance:function(t,e,i){return Math.sqrt(this._sqClosestPointOnSegment(t,e,i,!0))},closestPointOnSegment:function(t,e,i){return this._sqClosestPointOnSegment(t,e,i)},_simplifyDP:function(t,e){var n=t.length,o=typeof Uint8Array!=i+""?Uint8Array:Array,s=new o(n);s[0]=s[n-1]=1,this._simplifyDPStep(t,s,e,0,n-1);var a,r=[];for(a=0;n>a;a++)s[a]&&r.push(t[a]);return r},_simplifyDPStep:function(t,e,i,n,o){var s,a,r,h=0;for(a=n+1;o-1>=a;a++)r=this._sqClosestPointOnSegment(t[a],t[n],t[o],!0),r>h&&(s=a,h=r);h>i&&(e[s]=1,this._simplifyDPStep(t,e,i,n,s),this._simplifyDPStep(t,e,i,s,o))},_reducePoints:function(t,e){for(var i=[t[0]],n=1,o=0,s=t.length;s>n;n++)this._sqDist(t[n],t[o])>e&&(i.push(t[n]),o=n);return s-1>o&&i.push(t[s-1]),i},clipSegment:function(t,e,i,n){var o,s,a,r=n?this._lastCode:this._getBitCode(t,i),h=this._getBitCode(e,i);for(this._lastCode=h;;){if(!(r|h))return[t,e];if(r&h)return!1;o=r||h,s=this._getEdgeIntersection(t,e,o,i),a=this._getBitCode(s,i),o===r?(t=s,r=a):(e=s,h=a)}},_getEdgeIntersection:function(t,e,i,n){var s=e.x-t.x,a=e.y-t.y,r=n.min,h=n.max;return 8&i?new o.Point(t.x+s*(h.y-t.y)/a,h.y):4&i?new o.Point(t.x+s*(r.y-t.y)/a,r.y):2&i?new o.Point(h.x,t.y+a*(h.x-t.x)/s):1&i?new o.Point(r.x,t.y+a*(r.x-t.x)/s):void 0},_getBitCode:function(t,e){var i=0;return t.xe.max.x&&(i|=2),t.ye.max.y&&(i|=8),i},_sqDist:function(t,e){var i=e.x-t.x,n=e.y-t.y;return i*i+n*n},_sqClosestPointOnSegment:function(t,e,i,n){var s,a=e.x,r=e.y,h=i.x-a,l=i.y-r,u=h*h+l*l;return u>0&&(s=((t.x-a)*h+(t.y-r)*l)/u,s>1?(a=i.x,r=i.y):s>0&&(a+=h*s,r+=l*s)),h=t.x-a,l=t.y-r,n?h*h+l*l:new o.Point(a,r)}},o.Polyline=o.Path.extend({initialize:function(t,e){o.Path.prototype.initialize.call(this,e),this._latlngs=this._convertLatLngs(t)},options:{smoothFactor:1,noClip:!1},projectLatlngs:function(){this._originalPoints=[];for(var t=0,e=this._latlngs.length;e>t;t++)this._originalPoints[t]=this._map.latLngToLayerPoint(this._latlngs[t])},getPathString:function(){for(var t=0,e=this._parts.length,i="";e>t;t++)i+=this._getPathPartStr(this._parts[t]);return i},getLatLngs:function(){return this._latlngs},setLatLngs:function(t){return this._latlngs=this._convertLatLngs(t),this.redraw()},addLatLng:function(t){return this._latlngs.push(o.latLng(t)),this.redraw()},spliceLatLngs:function(){var t=[].splice.apply(this._latlngs,arguments);return this._convertLatLngs(this._latlngs,!0),this.redraw(),t},closestLayerPoint:function(t){for(var e,i,n=1/0,s=this._parts,a=null,r=0,h=s.length;h>r;r++)for(var l=s[r],u=1,c=l.length;c>u;u++){e=l[u-1],i=l[u];var d=o.LineUtil._sqClosestPointOnSegment(t,e,i,!0);n>d&&(n=d,a=o.LineUtil._sqClosestPointOnSegment(t,e,i))}return a&&(a.distance=Math.sqrt(n)),a},getBounds:function(){return new o.LatLngBounds(this.getLatLngs())},_convertLatLngs:function(t,e){var i,n,s=e?t:[];for(i=0,n=t.length;n>i;i++){if(o.Util.isArray(t[i])&&"number"!=typeof t[i][0])return;s[i]=o.latLng(t[i])}return s},_initEvents:function(){o.Path.prototype._initEvents.call(this)},_getPathPartStr:function(t){for(var e,i=o.Path.VML,n=0,s=t.length,a="";s>n;n++)e=t[n],i&&e._round(),a+=(n?"L":"M")+e.x+" "+e.y;return a},_clipPoints:function(){var t,e,i,n=this._originalPoints,s=n.length;if(this.options.noClip)return void(this._parts=[n]);this._parts=[];var a=this._parts,r=this._map._pathViewport,h=o.LineUtil;for(t=0,e=0;s-1>t;t++)i=h.clipSegment(n[t],n[t+1],r,t),i&&(a[e]=a[e]||[],a[e].push(i[0]),(i[1]!==n[t+1]||t===s-2)&&(a[e].push(i[1]),e++))},_simplifyPoints:function(){for(var t=this._parts,e=o.LineUtil,i=0,n=t.length;n>i;i++)t[i]=e.simplify(t[i],this.options.smoothFactor)},_updatePath:function(){this._map&&(this._clipPoints(),this._simplifyPoints(),o.Path.prototype._updatePath.call(this))}}),o.polyline=function(t,e){return new o.Polyline(t,e)},o.PolyUtil={},o.PolyUtil.clipPolygon=function(t,e){var i,n,s,a,r,h,l,u,c,d=[1,4,2,8],p=o.LineUtil;for(n=0,l=t.length;l>n;n++)t[n]._code=p._getBitCode(t[n],e);for(a=0;4>a;a++){for(u=d[a],i=[],n=0,l=t.length,s=l-1;l>n;s=n++)r=t[n],h=t[s],r._code&u?h._code&u||(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)):(h._code&u&&(c=p._getEdgeIntersection(h,r,u,e),c._code=p._getBitCode(c,e),i.push(c)),i.push(r));t=i}return t},o.Polygon=o.Polyline.extend({options:{fill:!0},initialize:function(t,e){o.Polyline.prototype.initialize.call(this,t,e),this._initWithHoles(t)},_initWithHoles:function(t){var e,i,n;if(t&&o.Util.isArray(t[0])&&"number"!=typeof t[0][0])for(this._latlngs=this._convertLatLngs(t[0]),this._holes=t.slice(1),e=0,i=this._holes.length;i>e;e++)n=this._holes[e]=this._convertLatLngs(this._holes[e]),n[0].equals(n[n.length-1])&&n.pop();t=this._latlngs,t.length>=2&&t[0].equals(t[t.length-1])&&t.pop()},projectLatlngs:function(){if(o.Polyline.prototype.projectLatlngs.call(this),this._holePoints=[],this._holes){var t,e,i,n;for(t=0,i=this._holes.length;i>t;t++)for(this._holePoints[t]=[],e=0,n=this._holes[t].length;n>e;e++)this._holePoints[t][e]=this._map.latLngToLayerPoint(this._holes[t][e])}},setLatLngs:function(t){return t&&o.Util.isArray(t[0])&&"number"!=typeof t[0][0]?(this._initWithHoles(t),this.redraw()):o.Polyline.prototype.setLatLngs.call(this,t)},_clipPoints:function(){var t=this._originalPoints,e=[];if(this._parts=[t].concat(this._holePoints),!this.options.noClip){for(var i=0,n=this._parts.length;n>i;i++){var s=o.PolyUtil.clipPolygon(this._parts[i],this._map._pathViewport);s.length&&e.push(s)}this._parts=e}},_getPathPartStr:function(t){var e=o.Polyline.prototype._getPathPartStr.call(this,t);return e+(o.Browser.svg?"z":"x")}}),o.polygon=function(t,e){return new o.Polygon(t,e)},function(){function t(t){return o.FeatureGroup.extend({initialize:function(t,e){this._layers={},this._options=e,this.setLatLngs(t)},setLatLngs:function(e){var i=0,n=e.length;for(this.eachLayer(function(t){n>i?t.setLatLngs(e[i++]):this.removeLayer(t)},this);n>i;)this.addLayer(new t(e[i++],this._options));return this},getLatLngs:function(){var t=[];return this.eachLayer(function(e){t.push(e.getLatLngs())}),t}})}o.MultiPolyline=t(o.Polyline),o.MultiPolygon=t(o.Polygon),o.multiPolyline=function(t,e){return new o.MultiPolyline(t,e)},o.multiPolygon=function(t,e){return new o.MultiPolygon(t,e)}}(),o.Rectangle=o.Polygon.extend({initialize:function(t,e){o.Polygon.prototype.initialize.call(this,this._boundsToLatLngs(t),e)},setBounds:function(t){this.setLatLngs(this._boundsToLatLngs(t))},_boundsToLatLngs:function(t){return t=o.latLngBounds(t),[t.getSouthWest(),t.getNorthWest(),t.getNorthEast(),t.getSouthEast()]}}),o.rectangle=function(t,e){return new o.Rectangle(t,e)},o.Circle=o.Path.extend({initialize:function(t,e,i){o.Path.prototype.initialize.call(this,i),this._latlng=o.latLng(t),this._mRadius=e},options:{fill:!0},setLatLng:function(t){return this._latlng=o.latLng(t),this.redraw()},setRadius:function(t){return this._mRadius=t,this.redraw()},projectLatlngs:function(){var t=this._getLngRadius(),e=this._latlng,i=this._map.latLngToLayerPoint([e.lat,e.lng-t]);this._point=this._map.latLngToLayerPoint(e),this._radius=Math.max(this._point.x-i.x,1)},getBounds:function(){var t=this._getLngRadius(),e=this._mRadius/40075017*360,i=this._latlng;return new o.LatLngBounds([i.lat-e,i.lng-t],[i.lat+e,i.lng+t])},getLatLng:function(){return this._latlng},getPathString:function(){var t=this._point,e=this._radius;return this._checkIfEmpty()?"":o.Browser.svg?"M"+t.x+","+(t.y-e)+"A"+e+","+e+",0,1,1,"+(t.x-.1)+","+(t.y-e)+" z":(t._round(),e=Math.round(e),"AL "+t.x+","+t.y+" "+e+","+e+" 0,23592600")},getRadius:function(){return this._mRadius},_getLatRadius:function(){return this._mRadius/40075017*360},_getLngRadius:function(){return this._getLatRadius()/Math.cos(o.LatLng.DEG_TO_RAD*this._latlng.lat)},_checkIfEmpty:function(){if(!this._map)return!1;var t=this._map._pathViewport,e=this._radius,i=this._point;return i.x-e>t.max.x||i.y-e>t.max.y||i.x+ei;i++)for(l=this._parts[i],n=0,r=l.length,s=r-1;r>n;s=n++)if((e||0!==n)&&(h=o.LineUtil.pointToSegmentDistance(t,l[s],l[n]),u>=h))return!0;return!1}}:{}),o.Polygon.include(o.Path.CANVAS?{_containsPoint:function(t){var e,i,n,s,a,r,h,l,u=!1;if(o.Polyline.prototype._containsPoint.call(this,t,!0))return!0;for(s=0,h=this._parts.length;h>s;s++)for(e=this._parts[s],a=0,l=e.length,r=l-1;l>a;r=a++)i=e[a],n=e[r],i.y>t.y!=n.y>t.y&&t.x<(n.x-i.x)*(t.y-i.y)/(n.y-i.y)+i.x&&(u=!u);return u}}:{}),o.Circle.include(o.Path.CANVAS?{_drawPath:function(){var t=this._point;this._ctx.beginPath(),this._ctx.arc(t.x,t.y,this._radius,0,2*Math.PI,!1)},_containsPoint:function(t){var e=this._point,i=this.options.stroke?this.options.weight/2:0;return t.distanceTo(e)<=this._radius+i}}:{}),o.CircleMarker.include(o.Path.CANVAS?{_updateStyle:function(){o.Path.prototype._updateStyle.call(this)}}:{}),o.GeoJSON=o.FeatureGroup.extend({initialize:function(t,e){o.setOptions(this,e),this._layers={},t&&this.addData(t)},addData:function(t){var e,i,n,s=o.Util.isArray(t)?t:t.features;if(s){for(e=0,i=s.length;i>e;e++)n=s[e],(n.geometries||n.geometry||n.features||n.coordinates)&&this.addData(s[e]);return this}var a=this.options;if(!a.filter||a.filter(t)){var r=o.GeoJSON.geometryToLayer(t,a.pointToLayer,a.coordsToLatLng,a);return r.feature=o.GeoJSON.asFeature(t),r.defaultOptions=r.options,this.resetStyle(r),a.onEachFeature&&a.onEachFeature(t,r),this.addLayer(r)}},resetStyle:function(t){var e=this.options.style;e&&(o.Util.extend(t.options,t.defaultOptions),this._setLayerStyle(t,e))},setStyle:function(t){this.eachLayer(function(e){this._setLayerStyle(e,t)},this)},_setLayerStyle:function(t,e){"function"==typeof e&&(e=e(t.feature)),t.setStyle&&t.setStyle(e)}}),o.extend(o.GeoJSON,{geometryToLayer:function(t,e,i,n){var s,a,r,h,l="Feature"===t.type?t.geometry:t,u=l.coordinates,c=[];switch(i=i||this.coordsToLatLng,l.type){case"Point":return s=i(u),e?e(t,s):new o.Marker(s);case"MultiPoint":for(r=0,h=u.length;h>r;r++)s=i(u[r]),c.push(e?e(t,s):new o.Marker(s));return new o.FeatureGroup(c);case"LineString":return a=this.coordsToLatLngs(u,0,i),new o.Polyline(a,n);case"Polygon":if(2===u.length&&!u[1].length)throw new Error("Invalid GeoJSON object.");return a=this.coordsToLatLngs(u,1,i),new o.Polygon(a,n);case"MultiLineString":return a=this.coordsToLatLngs(u,1,i),new o.MultiPolyline(a,n);case"MultiPolygon":return a=this.coordsToLatLngs(u,2,i),new o.MultiPolygon(a,n);case"GeometryCollection":for(r=0,h=l.geometries.length;h>r;r++)c.push(this.geometryToLayer({geometry:l.geometries[r],type:"Feature",properties:t.properties},e,i,n));return new o.FeatureGroup(c);default:throw new Error("Invalid GeoJSON object.")}},coordsToLatLng:function(t){return new o.LatLng(t[1],t[0],t[2])},coordsToLatLngs:function(t,e,i){var n,o,s,a=[];for(o=0,s=t.length;s>o;o++)n=e?this.coordsToLatLngs(t[o],e-1,i):(i||this.coordsToLatLng)(t[o]),a.push(n);return a},latLngToCoords:function(t){var e=[t.lng,t.lat];return t.alt!==i&&e.push(t.alt),e},latLngsToCoords:function(t){for(var e=[],i=0,n=t.length;n>i;i++)e.push(o.GeoJSON.latLngToCoords(t[i]));return e},getFeature:function(t,e){return t.feature?o.extend({},t.feature,{geometry:e}):o.GeoJSON.asFeature(e)},asFeature:function(t){return"Feature"===t.type?t:{type:"Feature",properties:{},geometry:t}}});var a={toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"Point",coordinates:o.GeoJSON.latLngToCoords(this.getLatLng())})}};o.Marker.include(a),o.Circle.include(a),o.CircleMarker.include(a),o.Polyline.include({toGeoJSON:function(){return o.GeoJSON.getFeature(this,{type:"LineString",coordinates:o.GeoJSON.latLngsToCoords(this.getLatLngs())})}}),o.Polygon.include({toGeoJSON:function(){var t,e,i,n=[o.GeoJSON.latLngsToCoords(this.getLatLngs())];if(n[0].push(n[0][0]),this._holes)for(t=0,e=this._holes.length;e>t;t++)i=o.GeoJSON.latLngsToCoords(this._holes[t]),i.push(i[0]),n.push(i);return o.GeoJSON.getFeature(this,{type:"Polygon",coordinates:n})}}),function(){function t(t){return function(){var e=[];return this.eachLayer(function(t){e.push(t.toGeoJSON().geometry.coordinates)}),o.GeoJSON.getFeature(this,{type:t,coordinates:e})}}o.MultiPolyline.include({toGeoJSON:t("MultiLineString")}),o.MultiPolygon.include({toGeoJSON:t("MultiPolygon")}),o.LayerGroup.include({toGeoJSON:function(){var e,i=this.feature&&this.feature.geometry,n=[];if(i&&"MultiPoint"===i.type)return t("MultiPoint").call(this);var s=i&&"GeometryCollection"===i.type;return this.eachLayer(function(t){t.toGeoJSON&&(e=t.toGeoJSON(),n.push(s?e.geometry:o.GeoJSON.asFeature(e)))}),s?o.GeoJSON.getFeature(this,{geometries:n,type:"GeometryCollection"}):{type:"FeatureCollection",features:n}}})}(),o.geoJson=function(t,e){return new o.GeoJSON(t,e)},o.DomEvent={addListener:function(t,e,i,n){var s,a,r,h=o.stamp(i),l="_leaflet_"+e+h;return t[l]?this:(s=function(e){return i.call(n||t,e||o.DomEvent._getEvent())},o.Browser.pointer&&0===e.indexOf("touch")?this.addPointerListener(t,e,s,h):(o.Browser.touch&&"dblclick"===e&&this.addDoubleTapListener&&this.addDoubleTapListener(t,s,h),"addEventListener"in t?"mousewheel"===e?(t.addEventListener("DOMMouseScroll",s,!1),t.addEventListener(e,s,!1)):"mouseenter"===e||"mouseleave"===e?(a=s,r="mouseenter"===e?"mouseover":"mouseout",s=function(e){return o.DomEvent._checkMouse(t,e)?a(e):void 0},t.addEventListener(r,s,!1)):"click"===e&&o.Browser.android?(a=s,s=function(t){return o.DomEvent._filterClick(t,a)},t.addEventListener(e,s,!1)):t.addEventListener(e,s,!1):"attachEvent"in t&&t.attachEvent("on"+e,s),t[l]=s,this))},removeListener:function(t,e,i){var n=o.stamp(i),s="_leaflet_"+e+n,a=t[s];return a?(o.Browser.pointer&&0===e.indexOf("touch")?this.removePointerListener(t,e,n):o.Browser.touch&&"dblclick"===e&&this.removeDoubleTapListener?this.removeDoubleTapListener(t,n):"removeEventListener"in t?"mousewheel"===e?(t.removeEventListener("DOMMouseScroll",a,!1),t.removeEventListener(e,a,!1)):"mouseenter"===e||"mouseleave"===e?t.removeEventListener("mouseenter"===e?"mouseover":"mouseout",a,!1):t.removeEventListener(e,a,!1):"detachEvent"in t&&t.detachEvent("on"+e,a),t[s]=null,this):this},stopPropagation:function(t){return t.stopPropagation?t.stopPropagation():t.cancelBubble=!0,o.DomEvent._skipped(t),this},disableScrollPropagation:function(t){var e=o.DomEvent.stopPropagation;return o.DomEvent.on(t,"mousewheel",e).on(t,"MozMousePixelScroll",e)},disableClickPropagation:function(t){for(var e=o.DomEvent.stopPropagation,i=o.Draggable.START.length-1;i>=0;i--)o.DomEvent.on(t,o.Draggable.START[i],e);return o.DomEvent.on(t,"click",o.DomEvent._fakeStop).on(t,"dblclick",e)},preventDefault:function(t){return t.preventDefault?t.preventDefault():t.returnValue=!1,this},stop:function(t){return o.DomEvent.preventDefault(t).stopPropagation(t)},getMousePosition:function(t,e){if(!e)return new o.Point(t.clientX,t.clientY);var i=e.getBoundingClientRect();return new o.Point(t.clientX-i.left-e.clientLeft,t.clientY-i.top-e.clientTop)},getWheelDelta:function(t){var e=0;return t.wheelDelta&&(e=t.wheelDelta/120),t.detail&&(e=-t.detail/3),e},_skipEvents:{},_fakeStop:function(t){o.DomEvent._skipEvents[t.type]=!0},_skipped:function(t){var e=this._skipEvents[t.type];return this._skipEvents[t.type]=!1,e},_checkMouse:function(t,e){var i=e.relatedTarget;if(!i)return!0;try{for(;i&&i!==t;)i=i.parentNode}catch(n){return!1}return i!==t},_getEvent:function(){var e=t.event;if(!e)for(var i=arguments.callee.caller;i&&(e=i.arguments[0],!e||t.Event!==e.constructor);)i=i.caller;return e},_filterClick:function(t,e){var i=t.timeStamp||t.originalEvent.timeStamp,n=o.DomEvent._lastClick&&i-o.DomEvent._lastClick;return n&&n>100&&1e3>n||t.target._simulatedClick&&!t._simulated?void o.DomEvent.stop(t):(o.DomEvent._lastClick=i,e(t))}},o.DomEvent.on=o.DomEvent.addListener,o.DomEvent.off=o.DomEvent.removeListener,o.Draggable=o.Class.extend({includes:o.Mixin.Events,statics:{START:o.Browser.touch?["touchstart","mousedown"]:["mousedown"],END:{mousedown:"mouseup",touchstart:"touchend",pointerdown:"touchend",MSPointerDown:"touchend"},MOVE:{mousedown:"mousemove",touchstart:"touchmove",pointerdown:"touchmove",MSPointerDown:"touchmove"}},initialize:function(t,e){this._element=t,this._dragStartTarget=e||t},enable:function(){if(!this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.on(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!0}},disable:function(){if(this._enabled){for(var t=o.Draggable.START.length-1;t>=0;t--)o.DomEvent.off(this._dragStartTarget,o.Draggable.START[t],this._onDown,this);this._enabled=!1,this._moved=!1}},_onDown:function(t){if(this._moved=!1,!(t.shiftKey||1!==t.which&&1!==t.button&&!t.touches||(o.DomEvent.stopPropagation(t),o.Draggable._disabled||(o.DomUtil.disableImageDrag(),o.DomUtil.disableTextSelection(),this._moving)))){var i=t.touches?t.touches[0]:t;this._startPoint=new o.Point(i.clientX,i.clientY),this._startPos=this._newPos=o.DomUtil.getPosition(this._element),o.DomEvent.on(e,o.Draggable.MOVE[t.type],this._onMove,this).on(e,o.Draggable.END[t.type],this._onUp,this)}},_onMove:function(t){if(t.touches&&t.touches.length>1)return void(this._moved=!0);var i=t.touches&&1===t.touches.length?t.touches[0]:t,n=new o.Point(i.clientX,i.clientY),s=n.subtract(this._startPoint);(s.x||s.y)&&(o.DomEvent.preventDefault(t),this._moved||(this.fire("dragstart"),this._moved=!0,this._startPos=o.DomUtil.getPosition(this._element).subtract(s),o.DomUtil.addClass(e.body,"leaflet-dragging"),o.DomUtil.addClass(t.target||t.srcElement,"leaflet-drag-target")),this._newPos=this._startPos.add(s),this._moving=!0,o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updatePosition,this,!0,this._dragStartTarget))},_updatePosition:function(){this.fire("predrag"),o.DomUtil.setPosition(this._element,this._newPos),this.fire("drag")},_onUp:function(t){o.DomUtil.removeClass(e.body,"leaflet-dragging"),o.DomUtil.removeClass(t.target||t.srcElement,"leaflet-drag-target");for(var i in o.Draggable.MOVE)o.DomEvent.off(e,o.Draggable.MOVE[i],this._onMove).off(e,o.Draggable.END[i],this._onUp);o.DomUtil.enableImageDrag(),o.DomUtil.enableTextSelection(),this._moved&&this._moving&&(o.Util.cancelAnimFrame(this._animRequest),this.fire("dragend",{distance:this._newPos.distanceTo(this._startPos)})),this._moving=!1}}),o.Handler=o.Class.extend({initialize:function(t){this._map=t},enable:function(){this._enabled||(this._enabled=!0,this.addHooks())},disable:function(){this._enabled&&(this._enabled=!1,this.removeHooks())},enabled:function(){return!!this._enabled}}),o.Map.mergeOptions({dragging:!0,inertia:!o.Browser.android23,inertiaDeceleration:3400,inertiaMaxSpeed:1/0,inertiaThreshold:o.Browser.touch?32:18,easeLinearity:.25,worldCopyJump:!1}),o.Map.Drag=o.Handler.extend({addHooks:function(){if(!this._draggable){var t=this._map;this._draggable=new o.Draggable(t._mapPane,t._container),this._draggable.on({dragstart:this._onDragStart,drag:this._onDrag,dragend:this._onDragEnd},this),t.options.worldCopyJump&&(this._draggable.on("predrag",this._onPreDrag,this),t.on("viewreset",this._onViewReset,this),t.whenReady(this._onViewReset,this))}this._draggable.enable()},removeHooks:function(){this._draggable.disable()},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){var t=this._map;t._panAnim&&t._panAnim.stop(),t.fire("movestart").fire("dragstart"),t.options.inertia&&(this._positions=[],this._times=[])},_onDrag:function(){if(this._map.options.inertia){var t=this._lastTime=+new Date,e=this._lastPos=this._draggable._newPos;this._positions.push(e),this._times.push(t),t-this._times[0]>200&&(this._positions.shift(),this._times.shift())}this._map.fire("move").fire("drag")},_onViewReset:function(){var t=this._map.getSize()._divideBy(2),e=this._map.latLngToLayerPoint([0,0]);this._initialWorldOffset=e.subtract(t).x,this._worldWidth=this._map.project([0,180]).x},_onPreDrag:function(){var t=this._worldWidth,e=Math.round(t/2),i=this._initialWorldOffset,n=this._draggable._newPos.x,o=(n-e+i)%t+e-i,s=(n+e+i)%t-e-i,a=Math.abs(o+i)i.inertiaThreshold||!this._positions[0];if(e.fire("dragend",t),s)e.fire("moveend");else{var a=this._lastPos.subtract(this._positions[0]),r=(this._lastTime+n-this._times[0])/1e3,h=i.easeLinearity,l=a.multiplyBy(h/r),u=l.distanceTo([0,0]),c=Math.min(i.inertiaMaxSpeed,u),d=l.multiplyBy(c/u),p=c/(i.inertiaDeceleration*h),_=d.multiplyBy(-p/2).round();_.x&&_.y?(_=e._limitOffset(_,e.options.maxBounds),o.Util.requestAnimFrame(function(){e.panBy(_,{duration:p,easeLinearity:h,noMoveStart:!0})})):e.fire("moveend")}}}),o.Map.addInitHook("addHandler","dragging",o.Map.Drag),o.Map.mergeOptions({doubleClickZoom:!0}),o.Map.DoubleClickZoom=o.Handler.extend({addHooks:function(){this._map.on("dblclick",this._onDoubleClick,this)},removeHooks:function(){this._map.off("dblclick",this._onDoubleClick,this)},_onDoubleClick:function(t){var e=this._map,i=e.getZoom()+(t.originalEvent.shiftKey?-1:1);"center"===e.options.doubleClickZoom?e.setZoom(i):e.setZoomAround(t.containerPoint,i)}}),o.Map.addInitHook("addHandler","doubleClickZoom",o.Map.DoubleClickZoom),o.Map.mergeOptions({scrollWheelZoom:!0}),o.Map.ScrollWheelZoom=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"mousewheel",this._onWheelScroll,this),o.DomEvent.on(this._map._container,"MozMousePixelScroll",o.DomEvent.preventDefault),this._delta=0},removeHooks:function(){o.DomEvent.off(this._map._container,"mousewheel",this._onWheelScroll),o.DomEvent.off(this._map._container,"MozMousePixelScroll",o.DomEvent.preventDefault)},_onWheelScroll:function(t){var e=o.DomEvent.getWheelDelta(t);this._delta+=e,this._lastMousePos=this._map.mouseEventToContainerPoint(t),this._startTime||(this._startTime=+new Date);var i=Math.max(40-(+new Date-this._startTime),0);clearTimeout(this._timer),this._timer=setTimeout(o.bind(this._performZoom,this),i),o.DomEvent.preventDefault(t),o.DomEvent.stopPropagation(t)},_performZoom:function(){var t=this._map,e=this._delta,i=t.getZoom();e=e>0?Math.ceil(e):Math.floor(e),e=Math.max(Math.min(e,4),-4),e=t._limitZoom(i+e)-i,this._delta=0,this._startTime=null,e&&("center"===t.options.scrollWheelZoom?t.setZoom(i+e):t.setZoomAround(this._lastMousePos,i+e))}}),o.Map.addInitHook("addHandler","scrollWheelZoom",o.Map.ScrollWheelZoom),o.extend(o.DomEvent,{_touchstart:o.Browser.msPointer?"MSPointerDown":o.Browser.pointer?"pointerdown":"touchstart",_touchend:o.Browser.msPointer?"MSPointerUp":o.Browser.pointer?"pointerup":"touchend",addDoubleTapListener:function(t,i,n){function s(t){var e;if(o.Browser.pointer?(_.push(t.pointerId),e=_.length):e=t.touches.length,!(e>1)){var i=Date.now(),n=i-(r||i);h=t.touches?t.touches[0]:t,l=n>0&&u>=n,r=i}}function a(t){if(o.Browser.pointer){var e=_.indexOf(t.pointerId);if(-1===e)return;_.splice(e,1)}if(l){if(o.Browser.pointer){var n,s={};for(var a in h)n=h[a],s[a]="function"==typeof n?n.bind(h):n;h=s}h.type="dblclick",i(h),r=null}}var r,h,l=!1,u=250,c="_leaflet_",d=this._touchstart,p=this._touchend,_=[];t[c+d+n]=s,t[c+p+n]=a;var m=o.Browser.pointer?e.documentElement:t;return t.addEventListener(d,s,!1),m.addEventListener(p,a,!1),o.Browser.pointer&&m.addEventListener(o.DomEvent.POINTER_CANCEL,a,!1),this},removeDoubleTapListener:function(t,i){var n="_leaflet_";return t.removeEventListener(this._touchstart,t[n+this._touchstart+i],!1),(o.Browser.pointer?e.documentElement:t).removeEventListener(this._touchend,t[n+this._touchend+i],!1),o.Browser.pointer&&e.documentElement.removeEventListener(o.DomEvent.POINTER_CANCEL,t[n+this._touchend+i],!1),this}}),o.extend(o.DomEvent,{POINTER_DOWN:o.Browser.msPointer?"MSPointerDown":"pointerdown",POINTER_MOVE:o.Browser.msPointer?"MSPointerMove":"pointermove",POINTER_UP:o.Browser.msPointer?"MSPointerUp":"pointerup",POINTER_CANCEL:o.Browser.msPointer?"MSPointerCancel":"pointercancel",_pointers:[],_pointerDocumentListener:!1,addPointerListener:function(t,e,i,n){switch(e){case"touchstart":return this.addPointerListenerStart(t,e,i,n);case"touchend":return this.addPointerListenerEnd(t,e,i,n);case"touchmove":return this.addPointerListenerMove(t,e,i,n);default:throw"Unknown touch event type"}},addPointerListenerStart:function(t,i,n,s){var a="_leaflet_",r=this._pointers,h=function(t){o.DomEvent.preventDefault(t);for(var e=!1,i=0;i1))&&(this._moved||(o.DomUtil.addClass(e._mapPane,"leaflet-touching"),e.fire("movestart").fire("zoomstart"),this._moved=!0),o.Util.cancelAnimFrame(this._animRequest),this._animRequest=o.Util.requestAnimFrame(this._updateOnMove,this,!0,this._map._container),o.DomEvent.preventDefault(t))}},_updateOnMove:function(){var t=this._map,e=this._getScaleOrigin(),i=t.layerPointToLatLng(e),n=t.getScaleZoom(this._scale);t._animateZoom(i,n,this._startCenter,this._scale,this._delta)},_onTouchEnd:function(){if(!this._moved||!this._zooming)return void(this._zooming=!1);var t=this._map;this._zooming=!1,o.DomUtil.removeClass(t._mapPane,"leaflet-touching"),o.Util.cancelAnimFrame(this._animRequest),o.DomEvent.off(e,"touchmove",this._onTouchMove).off(e,"touchend",this._onTouchEnd);var i=this._getScaleOrigin(),n=t.layerPointToLatLng(i),s=t.getZoom(),a=t.getScaleZoom(this._scale)-s,r=a>0?Math.ceil(a):Math.floor(a),h=t._limitZoom(s+r),l=t.getZoomScale(h)/this._scale;t._animateZoom(n,h,i,l)},_getScaleOrigin:function(){var t=this._centerOffset.subtract(this._delta).divideBy(this._scale);return this._startCenter.add(t)}}),o.Map.addInitHook("addHandler","touchZoom",o.Map.TouchZoom),o.Map.mergeOptions({tap:!0,tapTolerance:15}),o.Map.Tap=o.Handler.extend({addHooks:function(){o.DomEvent.on(this._map._container,"touchstart",this._onDown,this)},removeHooks:function(){o.DomEvent.off(this._map._container,"touchstart",this._onDown,this)},_onDown:function(t){if(t.touches){if(o.DomEvent.preventDefault(t),this._fireClick=!0,t.touches.length>1)return this._fireClick=!1,void clearTimeout(this._holdTimeout);var i=t.touches[0],n=i.target;this._startPos=this._newPos=new o.Point(i.clientX,i.clientY),n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.addClass(n,"leaflet-active"),this._holdTimeout=setTimeout(o.bind(function(){this._isTapValid()&&(this._fireClick=!1,this._onUp(),this._simulateEvent("contextmenu",i))},this),1e3),o.DomEvent.on(e,"touchmove",this._onMove,this).on(e,"touchend",this._onUp,this)}},_onUp:function(t){if(clearTimeout(this._holdTimeout),o.DomEvent.off(e,"touchmove",this._onMove,this).off(e,"touchend",this._onUp,this),this._fireClick&&t&&t.changedTouches){var i=t.changedTouches[0],n=i.target;n&&n.tagName&&"a"===n.tagName.toLowerCase()&&o.DomUtil.removeClass(n,"leaflet-active"),this._isTapValid()&&this._simulateEvent("click",i)}},_isTapValid:function(){return this._newPos.distanceTo(this._startPos)<=this._map.options.tapTolerance},_onMove:function(t){var e=t.touches[0];this._newPos=new o.Point(e.clientX,e.clientY)},_simulateEvent:function(i,n){var o=e.createEvent("MouseEvents");o._simulated=!0,n.target._simulatedClick=!0,o.initMouseEvent(i,!0,!0,t,1,n.screenX,n.screenY,n.clientX,n.clientY,!1,!1,!1,!1,0,null),n.target.dispatchEvent(o)}}),o.Browser.touch&&!o.Browser.pointer&&o.Map.addInitHook("addHandler","tap",o.Map.Tap),o.Map.mergeOptions({boxZoom:!0}),o.Map.BoxZoom=o.Handler.extend({initialize:function(t){this._map=t,this._container=t._container,this._pane=t._panes.overlayPane,this._moved=!1},addHooks:function(){o.DomEvent.on(this._container,"mousedown",this._onMouseDown,this)},removeHooks:function(){o.DomEvent.off(this._container,"mousedown",this._onMouseDown),this._moved=!1},moved:function(){return this._moved},_onMouseDown:function(t){return this._moved=!1,!t.shiftKey||1!==t.which&&1!==t.button?!1:(o.DomUtil.disableTextSelection(),o.DomUtil.disableImageDrag(),this._startLayerPoint=this._map.mouseEventToLayerPoint(t),void o.DomEvent.on(e,"mousemove",this._onMouseMove,this).on(e,"mouseup",this._onMouseUp,this).on(e,"keydown",this._onKeyDown,this))},_onMouseMove:function(t){this._moved||(this._box=o.DomUtil.create("div","leaflet-zoom-box",this._pane),o.DomUtil.setPosition(this._box,this._startLayerPoint),this._container.style.cursor="crosshair",this._map.fire("boxzoomstart"));var e=this._startLayerPoint,i=this._box,n=this._map.mouseEventToLayerPoint(t),s=n.subtract(e),a=new o.Point(Math.min(n.x,e.x),Math.min(n.y,e.y));o.DomUtil.setPosition(i,a),this._moved=!0,i.style.width=Math.max(0,Math.abs(s.x)-4)+"px",i.style.height=Math.max(0,Math.abs(s.y)-4)+"px"},_finish:function(){this._moved&&(this._pane.removeChild(this._box),this._container.style.cursor=""),o.DomUtil.enableTextSelection(),o.DomUtil.enableImageDrag(),o.DomEvent.off(e,"mousemove",this._onMouseMove).off(e,"mouseup",this._onMouseUp).off(e,"keydown",this._onKeyDown)},_onMouseUp:function(t){this._finish();var e=this._map,i=e.mouseEventToLayerPoint(t);if(!this._startLayerPoint.equals(i)){var n=new o.LatLngBounds(e.layerPointToLatLng(this._startLayerPoint),e.layerPointToLatLng(i));e.fitBounds(n),e.fire("boxzoomend",{boxZoomBounds:n})}},_onKeyDown:function(t){27===t.keyCode&&this._finish()}}),o.Map.addInitHook("addHandler","boxZoom",o.Map.BoxZoom),o.Map.mergeOptions({keyboard:!0,keyboardPanOffset:80,keyboardZoomOffset:1}),o.Map.Keyboard=o.Handler.extend({keyCodes:{left:[37],right:[39],down:[40],up:[38],zoomIn:[187,107,61,171],zoomOut:[189,109,173]},initialize:function(t){this._map=t,this._setPanOffset(t.options.keyboardPanOffset),this._setZoomOffset(t.options.keyboardZoomOffset)},addHooks:function(){var t=this._map._container;-1===t.tabIndex&&(t.tabIndex="0"),o.DomEvent.on(t,"focus",this._onFocus,this).on(t,"blur",this._onBlur,this).on(t,"mousedown",this._onMouseDown,this),this._map.on("focus",this._addHooks,this).on("blur",this._removeHooks,this)},removeHooks:function(){this._removeHooks();var t=this._map._container;o.DomEvent.off(t,"focus",this._onFocus,this).off(t,"blur",this._onBlur,this).off(t,"mousedown",this._onMouseDown,this),this._map.off("focus",this._addHooks,this).off("blur",this._removeHooks,this)},_onMouseDown:function(){if(!this._focused){var i=e.body,n=e.documentElement,o=i.scrollTop||n.scrollTop,s=i.scrollLeft||n.scrollLeft;this._map._container.focus(),t.scrollTo(s,o)}},_onFocus:function(){this._focused=!0,this._map.fire("focus")},_onBlur:function(){this._focused=!1,this._map.fire("blur")},_setPanOffset:function(t){var e,i,n=this._panKeys={},o=this.keyCodes;for(e=0,i=o.left.length;i>e;e++)n[o.left[e]]=[-1*t,0];for(e=0,i=o.right.length;i>e;e++)n[o.right[e]]=[t,0];for(e=0,i=o.down.length;i>e;e++)n[o.down[e]]=[0,t];for(e=0,i=o.up.length;i>e;e++)n[o.up[e]]=[0,-1*t]},_setZoomOffset:function(t){var e,i,n=this._zoomKeys={},o=this.keyCodes;for(e=0,i=o.zoomIn.length;i>e;e++)n[o.zoomIn[e]]=t;for(e=0,i=o.zoomOut.length;i>e;e++)n[o.zoomOut[e]]=-t},_addHooks:function(){o.DomEvent.on(e,"keydown",this._onKeyDown,this)},_removeHooks:function(){o.DomEvent.off(e,"keydown",this._onKeyDown,this)},_onKeyDown:function(t){var e=t.keyCode,i=this._map;if(e in this._panKeys){if(i._panAnim&&i._panAnim._inProgress)return;i.panBy(this._panKeys[e]),i.options.maxBounds&&i.panInsideBounds(i.options.maxBounds)}else{if(!(e in this._zoomKeys))return;i.setZoom(i.getZoom()+this._zoomKeys[e])}o.DomEvent.stop(t)}}),o.Map.addInitHook("addHandler","keyboard",o.Map.Keyboard),o.Handler.MarkerDrag=o.Handler.extend({initialize:function(t){this._marker=t},addHooks:function(){var t=this._marker._icon;this._draggable||(this._draggable=new o.Draggable(t,t)),this._draggable.on("dragstart",this._onDragStart,this).on("drag",this._onDrag,this).on("dragend",this._onDragEnd,this),this._draggable.enable(),o.DomUtil.addClass(this._marker._icon,"leaflet-marker-draggable")},removeHooks:function(){this._draggable.off("dragstart",this._onDragStart,this).off("drag",this._onDrag,this).off("dragend",this._onDragEnd,this),this._draggable.disable(),o.DomUtil.removeClass(this._marker._icon,"leaflet-marker-draggable")},moved:function(){return this._draggable&&this._draggable._moved},_onDragStart:function(){this._marker.closePopup().fire("movestart").fire("dragstart")},_onDrag:function(){var t=this._marker,e=t._shadow,i=o.DomUtil.getPosition(t._icon),n=t._map.layerPointToLatLng(i);e&&o.DomUtil.setPosition(e,i),t._latlng=n,t.fire("move",{latlng:n}).fire("drag")},_onDragEnd:function(t){this._marker.fire("moveend").fire("dragend",t)}}),o.Control=o.Class.extend({options:{position:"topright"},initialize:function(t){o.setOptions(this,t)},getPosition:function(){return this.options.position},setPosition:function(t){var e=this._map;return e&&e.removeControl(this),this.options.position=t,e&&e.addControl(this),this},getContainer:function(){return this._container},addTo:function(t){this._map=t;var e=this._container=this.onAdd(t),i=this.getPosition(),n=t._controlCorners[i];return o.DomUtil.addClass(e,"leaflet-control"),-1!==i.indexOf("bottom")?n.insertBefore(e,n.firstChild):n.appendChild(e),this},removeFrom:function(t){var e=this.getPosition(),i=t._controlCorners[e];return i.removeChild(this._container),this._map=null,this.onRemove&&this.onRemove(t),this},_refocusOnMap:function(){this._map&&this._map.getContainer().focus()}}),o.control=function(t){return new o.Control(t)},o.Map.include({addControl:function(t){return t.addTo(this),this},removeControl:function(t){return t.removeFrom(this),this},_initControlPos:function(){function t(t,s){var a=i+t+" "+i+s;e[t+s]=o.DomUtil.create("div",a,n)}var e=this._controlCorners={},i="leaflet-",n=this._controlContainer=o.DomUtil.create("div",i+"control-container",this._container);t("top","left"),t("top","right"),t("bottom","left"),t("bottom","right")},_clearControlPos:function(){this._container.removeChild(this._controlContainer)}}),o.Control.Zoom=o.Control.extend({options:{position:"topleft",zoomInText:"+",zoomInTitle:"Zoom in",zoomOutText:"-",zoomOutTitle:"Zoom out"},onAdd:function(t){var e="leaflet-control-zoom",i=o.DomUtil.create("div",e+" leaflet-bar");return this._map=t,this._zoomInButton=this._createButton(this.options.zoomInText,this.options.zoomInTitle,e+"-in",i,this._zoomIn,this),this._zoomOutButton=this._createButton(this.options.zoomOutText,this.options.zoomOutTitle,e+"-out",i,this._zoomOut,this),this._updateDisabled(),t.on("zoomend zoomlevelschange",this._updateDisabled,this),i},onRemove:function(t){t.off("zoomend zoomlevelschange",this._updateDisabled,this)},_zoomIn:function(t){this._map.zoomIn(t.shiftKey?3:1)},_zoomOut:function(t){this._map.zoomOut(t.shiftKey?3:1)},_createButton:function(t,e,i,n,s,a){var r=o.DomUtil.create("a",i,n);r.innerHTML=t,r.href="#",r.title=e;var h=o.DomEvent.stopPropagation;return o.DomEvent.on(r,"click",h).on(r,"mousedown",h).on(r,"dblclick",h).on(r,"click",o.DomEvent.preventDefault).on(r,"click",s,a).on(r,"click",this._refocusOnMap,a),r},_updateDisabled:function(){var t=this._map,e="leaflet-disabled";o.DomUtil.removeClass(this._zoomInButton,e),o.DomUtil.removeClass(this._zoomOutButton,e),t._zoom===t.getMinZoom()&&o.DomUtil.addClass(this._zoomOutButton,e),t._zoom===t.getMaxZoom()&&o.DomUtil.addClass(this._zoomInButton,e)}}),o.Map.mergeOptions({zoomControl:!0}),o.Map.addInitHook(function(){this.options.zoomControl&&(this.zoomControl=new o.Control.Zoom,this.addControl(this.zoomControl))}),o.control.zoom=function(t){return new o.Control.Zoom(t)},o.Control.Attribution=o.Control.extend({options:{position:"bottomright",prefix:'Leaflet'},initialize:function(t){o.setOptions(this,t),this._attributions={}},onAdd:function(t){this._container=o.DomUtil.create("div","leaflet-control-attribution"),o.DomEvent.disableClickPropagation(this._container);for(var e in t._layers)t._layers[e].getAttribution&&this.addAttribution(t._layers[e].getAttribution());return t.on("layeradd",this._onLayerAdd,this).on("layerremove",this._onLayerRemove,this),this._update(),this._container},onRemove:function(t){t.off("layeradd",this._onLayerAdd).off("layerremove",this._onLayerRemove)},setPrefix:function(t){return this.options.prefix=t,this._update(),this},addAttribution:function(t){return t?(this._attributions[t]||(this._attributions[t]=0),this._attributions[t]++,this._update(),this):void 0},removeAttribution:function(t){return t?(this._attributions[t]&&(this._attributions[t]--,this._update()),this):void 0},_update:function(){if(this._map){var t=[];for(var e in this._attributions)this._attributions[e]&&t.push(e);var i=[];this.options.prefix&&i.push(this.options.prefix),t.length&&i.push(t.join(", ")),this._container.innerHTML=i.join(" | ")}},_onLayerAdd:function(t){t.layer.getAttribution&&this.addAttribution(t.layer.getAttribution())},_onLayerRemove:function(t){t.layer.getAttribution&&this.removeAttribution(t.layer.getAttribution())}}),o.Map.mergeOptions({attributionControl:!0}),o.Map.addInitHook(function(){this.options.attributionControl&&(this.attributionControl=(new o.Control.Attribution).addTo(this))}),o.control.attribution=function(t){return new o.Control.Attribution(t)},o.Control.Scale=o.Control.extend({options:{position:"bottomleft",maxWidth:100,metric:!0,imperial:!0,updateWhenIdle:!1},onAdd:function(t){this._map=t;var e="leaflet-control-scale",i=o.DomUtil.create("div",e),n=this.options;return this._addScales(n,e,i),t.on(n.updateWhenIdle?"moveend":"move",this._update,this),t.whenReady(this._update,this),i},onRemove:function(t){t.off(this.options.updateWhenIdle?"moveend":"move",this._update,this)},_addScales:function(t,e,i){t.metric&&(this._mScale=o.DomUtil.create("div",e+"-line",i)),t.imperial&&(this._iScale=o.DomUtil.create("div",e+"-line",i))},_update:function(){var t=this._map.getBounds(),e=t.getCenter().lat,i=6378137*Math.PI*Math.cos(e*Math.PI/180),n=i*(t.getNorthEast().lng-t.getSouthWest().lng)/180,o=this._map.getSize(),s=this.options,a=0;o.x>0&&(a=n*(s.maxWidth/o.x)),this._updateScales(s,a)},_updateScales:function(t,e){t.metric&&e&&this._updateMetric(e),t.imperial&&e&&this._updateImperial(e)},_updateMetric:function(t){var e=this._getRoundNum(t);this._mScale.style.width=this._getScaleWidth(e/t)+"px",this._mScale.innerHTML=1e3>e?e+" m":e/1e3+" km"},_updateImperial:function(t){var e,i,n,o=3.2808399*t,s=this._iScale;o>5280?(e=o/5280,i=this._getRoundNum(e),s.style.width=this._getScaleWidth(i/e)+"px",s.innerHTML=i+" mi"):(n=this._getRoundNum(o),s.style.width=this._getScaleWidth(n/o)+"px",s.innerHTML=n+" ft")},_getScaleWidth:function(t){return Math.round(this.options.maxWidth*t)-10},_getRoundNum:function(t){var e=Math.pow(10,(Math.floor(t)+"").length-1),i=t/e;return i=i>=10?10:i>=5?5:i>=3?3:i>=2?2:1,e*i}}),o.control.scale=function(t){return new o.Control.Scale(t)},o.Control.Layers=o.Control.extend({options:{collapsed:!0,position:"topright",autoZIndex:!0},initialize:function(t,e,i){o.setOptions(this,i),this._layers={},this._lastZIndex=0,this._handlingClick=!1;for(var n in t)this._addLayer(t[n],n);for(n in e)this._addLayer(e[n],n,!0)},onAdd:function(t){return this._initLayout(),this._update(),t.on("layeradd",this._onLayerChange,this).on("layerremove",this._onLayerChange,this),this._container},onRemove:function(t){t.off("layeradd",this._onLayerChange).off("layerremove",this._onLayerChange)},addBaseLayer:function(t,e){return this._addLayer(t,e),this._update(),this},addOverlay:function(t,e){return this._addLayer(t,e,!0),this._update(),this},removeLayer:function(t){var e=o.stamp(t);return delete this._layers[e],this._update(),this},_initLayout:function(){var t="leaflet-control-layers",e=this._container=o.DomUtil.create("div",t);e.setAttribute("aria-haspopup",!0),o.Browser.touch?o.DomEvent.on(e,"click",o.DomEvent.stopPropagation):o.DomEvent.disableClickPropagation(e).disableScrollPropagation(e);var i=this._form=o.DomUtil.create("form",t+"-list");if(this.options.collapsed){o.Browser.android||o.DomEvent.on(e,"mouseover",this._expand,this).on(e,"mouseout",this._collapse,this);var n=this._layersLink=o.DomUtil.create("a",t+"-toggle",e);n.href="#",n.title="Layers",o.Browser.touch?o.DomEvent.on(n,"click",o.DomEvent.stop).on(n,"click",this._expand,this):o.DomEvent.on(n,"focus",this._expand,this),o.DomEvent.on(i,"click",function(){setTimeout(o.bind(this._onInputClick,this),0)},this),this._map.on("click",this._collapse,this)}else this._expand();this._baseLayersList=o.DomUtil.create("div",t+"-base",i),this._separator=o.DomUtil.create("div",t+"-separator",i),this._overlaysList=o.DomUtil.create("div",t+"-overlays",i),e.appendChild(i)},_addLayer:function(t,e,i){var n=o.stamp(t);this._layers[n]={layer:t,name:e,overlay:i},this.options.autoZIndex&&t.setZIndex&&(this._lastZIndex++,t.setZIndex(this._lastZIndex))},_update:function(){if(this._container){this._baseLayersList.innerHTML="",this._overlaysList.innerHTML="";var t,e,i=!1,n=!1;for(t in this._layers)e=this._layers[t],this._addItem(e),n=n||e.overlay,i=i||!e.overlay;this._separator.style.display=n&&i?"":"none"}},_onLayerChange:function(t){var e=this._layers[o.stamp(t.layer)];if(e){this._handlingClick||this._update();var i=e.overlay?"layeradd"===t.type?"overlayadd":"overlayremove":"layeradd"===t.type?"baselayerchange":null;i&&this._map.fire(i,e)}},_createRadioElement:function(t,i){var n='t;t++)e=n[t],i=this._layers[e.layerId],e.checked&&!this._map.hasLayer(i.layer)?this._map.addLayer(i.layer):!e.checked&&this._map.hasLayer(i.layer)&&this._map.removeLayer(i.layer);this._handlingClick=!1,this._refocusOnMap()},_expand:function(){o.DomUtil.addClass(this._container,"leaflet-control-layers-expanded")},_collapse:function(){this._container.className=this._container.className.replace(" leaflet-control-layers-expanded","")}}),o.control.layers=function(t,e,i){return new o.Control.Layers(t,e,i)},o.PosAnimation=o.Class.extend({includes:o.Mixin.Events,run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._newPos=e,this.fire("start"),t.style[o.DomUtil.TRANSITION]="all "+(i||.25)+"s cubic-bezier(0,0,"+(n||.5)+",1)",o.DomEvent.on(t,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),o.DomUtil.setPosition(t,e),o.Util.falseFn(t.offsetWidth),this._stepTimer=setInterval(o.bind(this._onStep,this),50)},stop:function(){this._inProgress&&(o.DomUtil.setPosition(this._el,this._getPos()),this._onTransitionEnd(),o.Util.falseFn(this._el.offsetWidth))},_onStep:function(){var t=this._getPos();return t?(this._el._leaflet_pos=t,void this.fire("step")):void this._onTransitionEnd()},_transformRe:/([-+]?(?:\d*\.)?\d+)\D*, ([-+]?(?:\d*\.)?\d+)\D*\)/,_getPos:function(){var e,i,n,s=this._el,a=t.getComputedStyle(s);if(o.Browser.any3d){if(n=a[o.DomUtil.TRANSFORM].match(this._transformRe),!n)return;e=parseFloat(n[1]),i=parseFloat(n[2])}else e=parseFloat(a.left),i=parseFloat(a.top);return new o.Point(e,i,!0)},_onTransitionEnd:function(){o.DomEvent.off(this._el,o.DomUtil.TRANSITION_END,this._onTransitionEnd,this),this._inProgress&&(this._inProgress=!1,this._el.style[o.DomUtil.TRANSITION]="",this._el._leaflet_pos=this._newPos,clearInterval(this._stepTimer),this.fire("step").fire("end"))}}),o.Map.include({setView:function(t,e,n){if(e=e===i?this._zoom:this._limitZoom(e),t=this._limitCenter(o.latLng(t),e,this.options.maxBounds),n=n||{},this._panAnim&&this._panAnim.stop(),this._loaded&&!n.reset&&n!==!0){n.animate!==i&&(n.zoom=o.extend({animate:n.animate},n.zoom),n.pan=o.extend({animate:n.animate},n.pan));var s=this._zoom!==e?this._tryAnimatedZoom&&this._tryAnimatedZoom(t,e,n.zoom):this._tryAnimatedPan(t,n.pan);if(s)return clearTimeout(this._sizeTimer),this}return this._resetView(t,e),this},panBy:function(t,e){if(t=o.point(t).round(),e=e||{},!t.x&&!t.y)return this;if(this._panAnim||(this._panAnim=new o.PosAnimation,this._panAnim.on({step:this._onPanTransitionStep,end:this._onPanTransitionEnd},this)),e.noMoveStart||this.fire("movestart"),e.animate!==!1){o.DomUtil.addClass(this._mapPane,"leaflet-pan-anim");var i=this._getMapPanePos().subtract(t);this._panAnim.run(this._mapPane,i,e.duration||.25,e.easeLinearity)}else this._rawPanBy(t),this.fire("move").fire("moveend");return this},_onPanTransitionStep:function(){this.fire("move")},_onPanTransitionEnd:function(){o.DomUtil.removeClass(this._mapPane,"leaflet-pan-anim"),this.fire("moveend")},_tryAnimatedPan:function(t,e){var i=this._getCenterOffset(t)._floor();return(e&&e.animate)===!0||this.getSize().contains(i)?(this.panBy(i,e),!0):!1}}),o.PosAnimation=o.DomUtil.TRANSITION?o.PosAnimation:o.PosAnimation.extend({run:function(t,e,i,n){this.stop(),this._el=t,this._inProgress=!0,this._duration=i||.25,this._easeOutPower=1/Math.max(n||.5,.2),this._startPos=o.DomUtil.getPosition(t),this._offset=e.subtract(this._startPos),this._startTime=+new Date,this.fire("start"),this._animate()},stop:function(){this._inProgress&&(this._step(),this._complete())},_animate:function(){this._animId=o.Util.requestAnimFrame(this._animate,this),this._step()},_step:function(){var t=+new Date-this._startTime,e=1e3*this._duration;e>t?this._runFrame(this._easeOut(t/e)):(this._runFrame(1),this._complete())},_runFrame:function(t){var e=this._startPos.add(this._offset.multiplyBy(t));o.DomUtil.setPosition(this._el,e),this.fire("step")},_complete:function(){o.Util.cancelAnimFrame(this._animId),this._inProgress=!1,this.fire("end")},_easeOut:function(t){return 1-Math.pow(1-t,this._easeOutPower)}}),o.Map.mergeOptions({zoomAnimation:!0,zoomAnimationThreshold:4}),o.DomUtil.TRANSITION&&o.Map.addInitHook(function(){this._zoomAnimated=this.options.zoomAnimation&&o.DomUtil.TRANSITION&&o.Browser.any3d&&!o.Browser.android23&&!o.Browser.mobileOpera,this._zoomAnimated&&o.DomEvent.on(this._mapPane,o.DomUtil.TRANSITION_END,this._catchTransitionEnd,this)}),o.Map.include(o.DomUtil.TRANSITION?{_catchTransitionEnd:function(t){this._animatingZoom&&t.propertyName.indexOf("transform")>=0&&this._onZoomTransitionEnd()},_nothingToAnimate:function(){return!this._container.getElementsByClassName("leaflet-zoom-animated").length},_tryAnimatedZoom:function(t,e,i){if(this._animatingZoom)return!0;if(i=i||{},!this._zoomAnimated||i.animate===!1||this._nothingToAnimate()||Math.abs(e-this._zoom)>this.options.zoomAnimationThreshold)return!1;var n=this.getZoomScale(e),o=this._getCenterOffset(t)._divideBy(1-1/n),s=this._getCenterLayerPoint()._add(o);return i.animate===!0||this.getSize().contains(o)?(this.fire("movestart").fire("zoomstart"),this._animateZoom(t,e,s,n,null,!0),!0):!1},_animateZoom:function(t,e,i,n,s,a){this._animatingZoom=!0,o.DomUtil.addClass(this._mapPane,"leaflet-zoom-anim"),this._animateToCenter=t,this._animateToZoom=e,o.Draggable&&(o.Draggable._disabled=!0),this.fire("zoomanim",{center:t,zoom:e,origin:i,scale:n,delta:s,backwards:a})},_onZoomTransitionEnd:function(){this._animatingZoom=!1,o.DomUtil.removeClass(this._mapPane,"leaflet-zoom-anim"),this._resetView(this._animateToCenter,this._animateToZoom,!0,!0),o.Draggable&&(o.Draggable._disabled=!1)}}:{}),o.TileLayer.include({_animateZoom:function(t){this._animating||(this._animating=!0,this._prepareBgBuffer());var e=this._bgBuffer,i=o.DomUtil.TRANSFORM,n=t.delta?o.DomUtil.getTranslateString(t.delta):e.style[i],s=o.DomUtil.getScaleString(t.scale,t.origin);e.style[i]=t.backwards?s+" "+n:n+" "+s},_endZoomAnim:function(){var t=this._tileContainer,e=this._bgBuffer;t.style.visibility="",t.parentNode.appendChild(t),o.Util.falseFn(e.offsetWidth),this._animating=!1},_clearBgBuffer:function(){var t=this._map;!t||t._animatingZoom||t.touchZoom._zooming||(this._bgBuffer.innerHTML="",this._bgBuffer.style[o.DomUtil.TRANSFORM]="")},_prepareBgBuffer:function(){var t=this._tileContainer,e=this._bgBuffer,i=this._getLoadedTilesPercentage(e),n=this._getLoadedTilesPercentage(t);return e&&i>.5&&.5>n?(t.style.visibility="hidden",void this._stopLoadingImages(t)):(e.style.visibility="hidden",e.style[o.DomUtil.TRANSFORM]="",this._tileContainer=e,e=this._bgBuffer=t,this._stopLoadingImages(e),void clearTimeout(this._clearBgBufferTimer))},_getLoadedTilesPercentage:function(t){var e,i,n=t.getElementsByTagName("img"),o=0;for(e=0,i=n.length;i>e;e++)n[e].complete&&o++;return o/i},_stopLoadingImages:function(t){var e,i,n,s=Array.prototype.slice.call(t.getElementsByTagName("img"));for(e=0,i=s.length;i>e;e++)n=s[e],n.complete||(n.onload=o.Util.falseFn,n.onerror=o.Util.falseFn,n.src=o.Util.emptyImageUrl,n.parentNode.removeChild(n))}}),o.Map.include({_defaultLocateOptions:{watch:!1,setView:!1,maxZoom:1/0,timeout:1e4,maximumAge:0,enableHighAccuracy:!1},locate:function(t){if(t=this._locateOptions=o.extend(this._defaultLocateOptions,t),!navigator.geolocation)return this._handleGeolocationError({code:0,message:"Geolocation not supported."}),this;var e=o.bind(this._handleGeolocationResponse,this),i=o.bind(this._handleGeolocationError,this);return t.watch?this._locationWatchId=navigator.geolocation.watchPosition(e,i,t):navigator.geolocation.getCurrentPosition(e,i,t),this},stopLocate:function(){return navigator.geolocation&&navigator.geolocation.clearWatch(this._locationWatchId),this._locateOptions&&(this._locateOptions.setView=!1),this},_handleGeolocationError:function(t){var e=t.code,i=t.message||(1===e?"permission denied":2===e?"position unavailable":"timeout");this._locateOptions.setView&&!this._loaded&&this.fitWorld(),this.fire("locationerror",{code:e,message:"Geolocation error: "+i+"."})},_handleGeolocationResponse:function(t){var e=t.coords.latitude,i=t.coords.longitude,n=new o.LatLng(e,i),s=180*t.coords.accuracy/40075017,a=s/Math.cos(o.LatLng.DEG_TO_RAD*e),r=o.latLngBounds([e-s,i-a],[e+s,i+a]),h=this._locateOptions;if(h.setView){var l=Math.min(this.getBoundsZoom(r),h.maxZoom);this.setView(n,l)}var u={latlng:n,bounds:r,timestamp:t.timestamp};for(var c in t.coords)"number"==typeof t.coords[c]&&(u[c]=t.coords[c]);this.fire("locationfound",u)}})}(window,document); \ No newline at end of file diff --git a/BIN/MAP/leaflet/leaflet.label.css b/BIN/MAP/leaflet/leaflet.label.css new file mode 100644 index 0000000..6f20324 --- /dev/null +++ b/BIN/MAP/leaflet/leaflet.label.css @@ -0,0 +1,41 @@ +.leaflet-label { + background: rgba(255, 255, 255, 0.61); + background-clip: padding-box; + border-color: rgba(0,55,255,0.15); + border-radius: 2px; + border-style: solid; + border-width: 1px; + color: #111; + display: block; + font: 11px/11px Arial, sans-serif; + padding: 2px 2px; + position: absolute; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + pointer-events: none; + white-space: nowrap; + top: 24px; + left: -22px; +} + +.aprs-clear{ + background: none; +} + +.aprs-usericon{ + display: block; + position:absolute; + left:-8px; + top:-6px; + width:24px; + height:24px; +} + +.aprs-hdg +{ + position:absolute; + left:-4px; + top:-4px; +} \ No newline at end of file diff --git a/BIN/MAP/leaflet/leaflet.label.js b/BIN/MAP/leaflet/leaflet.label.js new file mode 100644 index 0000000..b918ca5 --- /dev/null +++ b/BIN/MAP/leaflet/leaflet.label.js @@ -0,0 +1,9 @@ +/* + Leaflet.label, a plugin that adds labels to markers and vectors for Leaflet powered maps. + (c) 2012-2013, Jacob Toye, Smartrak + + https://github.com/Leaflet/Leaflet.label + http://leafletjs.com + https://github.com/jacobtoye +*/ +(function(){L.labelVersion="0.2.2-dev",L.Label=L.Class.extend({includes:L.Mixin.Events,options:{className:"",clickable:!1,direction:"right",noHide:!1,offset:[12,-15],opacity:1,zoomAnimation:!0},initialize:function(t,e){L.setOptions(this,t),this._source=e,this._animated=L.Browser.any3d&&this.options.zoomAnimation,this._isOpen=!1},onAdd:function(t){this._map=t,this._pane=this._source instanceof L.Marker?t._panes.markerPane:t._panes.popupPane,this._container||this._initLayout(),this._pane.appendChild(this._container),this._initInteraction(),this._update(),this.setOpacity(this.options.opacity),t.on("moveend",this._onMoveEnd,this).on("viewreset",this._onViewReset,this),this._animated&&t.on("zoomanim",this._zoomAnimation,this),L.Browser.touch&&!this.options.noHide&&L.DomEvent.on(this._container,"click",this.close,this)},onRemove:function(t){this._pane.removeChild(this._container),t.off({zoomanim:this._zoomAnimation,moveend:this._onMoveEnd,viewreset:this._onViewReset},this),this._removeInteraction(),this._map=null},setLatLng:function(t){return this._latlng=L.latLng(t),this._map&&this._updatePosition(),this},setContent:function(t){return this._previousContent=this._content,this._content=t,this._updateContent(),this},close:function(){var t=this._map;t&&(L.Browser.touch&&!this.options.noHide&&L.DomEvent.off(this._container,"click",this.close),t.removeLayer(this))},updateZIndex:function(t){this._zIndex=t,this._container&&this._zIndex&&(this._container.style.zIndex=t)},setOpacity:function(t){this.options.opacity=t,this._container&&L.DomUtil.setOpacity(this._container,t)},_initLayout:function(){this._container=L.DomUtil.create("div","leaflet-label "+this.options.className+" leaflet-zoom-animated"),this.updateZIndex(this._zIndex)},_update:function(){this._map&&(this._container.style.visibility="hidden",this._updateContent(),this._updatePosition(),this._container.style.visibility="")},_updateContent:function(){this._content&&this._map&&this._prevContent!==this._content&&"string"==typeof this._content&&(this._container.innerHTML=this._content,this._prevContent=this._content,this._labelWidth=this._container.offsetWidth)},_updatePosition:function(){var t=this._map.latLngToLayerPoint(this._latlng);this._setPosition(t)},_setPosition:function(t){var e=this._map,i=this._container,n=e.latLngToContainerPoint(e.getCenter()),o=e.layerPointToContainerPoint(t),s=this.options.direction,a=this._labelWidth,l=L.point(this.options.offset);"right"===s||"auto"===s&&o.xi;i++)L.DomEvent.on(t,e[i],this._fireMouseEvent,this)}},_removeInteraction:function(){if(this.options.clickable){var t=this._container,e=["dblclick","mousedown","mouseover","mouseout","contextmenu"];L.DomUtil.removeClass(t,"leaflet-clickable"),L.DomEvent.off(t,"click",this._onMouseClick,this);for(var i=0;e.length>i;i++)L.DomEvent.off(t,e[i],this._fireMouseEvent,this)}},_onMouseClick:function(t){this.hasEventListeners(t.type)&&L.DomEvent.stopPropagation(t),this.fire(t.type,{originalEvent:t})},_fireMouseEvent:function(t){this.fire(t.type,{originalEvent:t}),"contextmenu"===t.type&&this.hasEventListeners(t.type)&&L.DomEvent.preventDefault(t),"mousedown"!==t.type?L.DomEvent.stopPropagation(t):L.DomEvent.preventDefault(t)}}),L.BaseMarkerMethods={showLabel:function(){return this.label&&this._map&&(this.label.setLatLng(this._latlng),this._map.showLabel(this.label)),this},hideLabel:function(){return this.label&&this.label.close(),this},setLabelNoHide:function(t){this._labelNoHide!==t&&(this._labelNoHide=t,t?(this._removeLabelRevealHandlers(),this.showLabel()):(this._addLabelRevealHandlers(),this.hideLabel()))},bindLabel:function(t,e){var i=this.options.icon?this.options.icon.options.labelAnchor:this.options.labelAnchor,n=L.point(i)||L.point(0,0);return n=n.add(L.Label.prototype.options.offset),e&&e.offset&&(n=n.add(e.offset)),e=L.Util.extend({offset:n},e),this._labelNoHide=e.noHide,this.label||(this._labelNoHide||this._addLabelRevealHandlers(),this.on("remove",this.hideLabel,this).on("move",this._moveLabel,this).on("add",this._onMarkerAdd,this),this._hasLabelHandlers=!0),this.label=new L.Label(e,this).setContent(t),this},unbindLabel:function(){return this.label&&(this.hideLabel(),this.label=null,this._hasLabelHandlers&&(this._labelNoHide||this._removeLabelRevealHandlers(),this.off("remove",this.hideLabel,this).off("move",this._moveLabel,this).off("add",this._onMarkerAdd,this)),this._hasLabelHandlers=!1),this},updateLabelContent:function(t){this.label&&this.label.setContent(t)},getLabel:function(){return this.label},_onMarkerAdd:function(){this._labelNoHide&&this.showLabel()},_addLabelRevealHandlers:function(){this.on("mouseover",this.showLabel,this).on("mouseout",this.hideLabel,this),L.Browser.touch&&this.on("click",this.showLabel,this)},_removeLabelRevealHandlers:function(){this.off("mouseover",this.showLabel,this).off("mouseout",this.hideLabel,this),L.Browser.touch&&this.off("click",this.showLabel,this)},_moveLabel:function(t){this.label.setLatLng(t.latlng)}},L.Icon.Default.mergeOptions({labelAnchor:new L.Point(9,-20)}),L.Marker.mergeOptions({icon:new L.Icon.Default}),L.Marker.include(L.BaseMarkerMethods),L.Marker.include({_originalUpdateZIndex:L.Marker.prototype._updateZIndex,_updateZIndex:function(t){var e=this._zIndex+t;this._originalUpdateZIndex(t),this.label&&this.label.updateZIndex(e)},_originalSetOpacity:L.Marker.prototype.setOpacity,setOpacity:function(t,e){this.options.labelHasSemiTransparency=e,this._originalSetOpacity(t)},_originalUpdateOpacity:L.Marker.prototype._updateOpacity,_updateOpacity:function(){var t=0===this.options.opacity?0:1;this._originalUpdateOpacity(),this.label&&this.label.setOpacity(this.options.labelHasSemiTransparency?this.options.opacity:t)},_originalSetLatLng:L.Marker.prototype.setLatLng,setLatLng:function(t){return this.label&&!this._labelNoHide&&this.hideLabel(),this._originalSetLatLng(t)}}),L.CircleMarker.mergeOptions({labelAnchor:new L.Point(0,0)}),L.CircleMarker.include(L.BaseMarkerMethods),L.Path.include({bindLabel:function(t,e){return this.label&&this.label.options===e||(this.label=new L.Label(e,this)),this.label.setContent(t),this._showLabelAdded||(this.on("mouseover",this._showLabel,this).on("mousemove",this._moveLabel,this).on("mouseout remove",this._hideLabel,this),L.Browser.touch&&this.on("click",this._showLabel,this),this._showLabelAdded=!0),this},unbindLabel:function(){return this.label&&(this._hideLabel(),this.label=null,this._showLabelAdded=!1,this.off("mouseover",this._showLabel,this).off("mousemove",this._moveLabel,this).off("mouseout remove",this._hideLabel,this)),this},updateLabelContent:function(t){this.label&&this.label.setContent(t)},_showLabel:function(t){this.label.setLatLng(t.latlng),this._map.showLabel(this.label)},_moveLabel:function(t){this.label.setLatLng(t.latlng)},_hideLabel:function(){this.label.close()}}),L.Map.include({showLabel:function(t){return this.addLayer(t)}}),L.FeatureGroup.include({clearLayers:function(){return this.unbindLabel(),this.eachLayer(this.removeLayer,this),this},bindLabel:function(t,e){return this.invoke("bindLabel",t,e)},unbindLabel:function(){return this.invoke("unbindLabel")},updateLabelContent:function(t){this.invoke("updateLabelContent",t)}})})(this,document); \ No newline at end of file diff --git a/BIN/MAP/leaflet/zoomslider.js b/BIN/MAP/leaflet/zoomslider.js new file mode 100644 index 0000000..34e6fb6 --- /dev/null +++ b/BIN/MAP/leaflet/zoomslider.js @@ -0,0 +1,166 @@ +L.Control.Zoomslider = L.Control.extend({ + options: { + position: 'topleft', + // height in px of zoom-slider.png + stepHeight: 9 + }, + + onAdd: function (map) { + var className = 'leaflet-control-zoomslider', + container = L.DomUtil.create('div', className); + + this._createButton('Zoom in', className + '-in' + , container, map.zoomIn , map); + this._createSlider(className + '-slider', container, map); + this._createButton('Zoom out', className + '-out' + , container, map.zoomOut, map); + + this._map = map; + + this._map.on('zoomend', this._snapToSliderValue, this); + + this._snapToSliderValue(); + return container; + }, + + onRemove: function(map){ + map.off('zoomend', this._snapToSliderValue); + }, + + _createSlider: function (className, container, map) { + var zoomLevels = 19 - map.getMinZoom(); + this._sliderHeight = this.options.stepHeight * zoomLevels; + + var wrapper = L.DomUtil.create('div', className + '-wrap', container); + wrapper.style.height = (this._sliderHeight + 5) + "px"; + var slider = L.DomUtil.create('div', className, wrapper); + this._knob = L.DomUtil.create('div', className + '-knob', slider); + + this._draggable = this._createDraggable(); + this._draggable.enable(); + + L.DomEvent + .on(slider, 'click', L.DomEvent.stopPropagation) + .on(slider, 'click', L.DomEvent.preventDefault) + .on(slider, 'click', this._onSliderClick, this); + + return slider; + }, + + _createButton: function (title, className, container, fn, context) { + var link = L.DomUtil.create('a', className, container); + link.href = '#'; + link.title = title; + + L.DomEvent + .on(link, 'click', L.DomEvent.stopPropagation) + .on(link, 'click', L.DomEvent.preventDefault) + .on(link, 'click', fn, context); + + return link; + }, + + _createDraggable: function() { + L.DomUtil.setPosition(this._knob, new L.Point(0, 0)); + L.DomEvent + .on(this._knob + , L.Draggable.START + , L.DomEvent.stopPropagation) + .on(this._knob, 'click', L.DomEvent.stopPropagation); + + var bounds = new L.Bounds( + new L.Point(0, 0), + new L.Point(0, this._sliderHeight) + ); + var draggable = new L.BoundedDraggable(this._knob, + this._knob, + bounds) + .on('drag', this._snap, this) + .on('dragend', this._setZoom, this); + + return draggable; + }, + + _snap : function(){ + this._snapToSliderValue(this._posToSliderValue()); + }, + _setZoom: function() { + this._map.setZoom(this._toZoomLevel(this._posToSliderValue())); + }, + + _onSliderClick: function(e){ + var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e); + var offset = first.offsetY + ? first.offsetY + : L.DomEvent.getMousePosition(first).y + - L.DomUtil.getViewportOffset(this._knob).y; + var value = this._posToSliderValue(offset - this._knob.offsetHeight / 2); + this._snapToSliderValue(value); + this._map.setZoom(this._toZoomLevel(value)); + }, + + _posToSliderValue: function(pos) { + pos = isNaN(pos) + ? L.DomUtil.getPosition(this._knob).y + : pos + return Math.round( (this._sliderHeight - pos) / this.options.stepHeight); + }, + + _snapToSliderValue: function(sliderValue) { + if(this._knob) { + sliderValue = isNaN(sliderValue) + ? this._getSliderValue() + : sliderValue; + var y = this._sliderHeight + - (sliderValue * this.options.stepHeight); + L.DomUtil.setPosition(this._knob, new L.Point(0, y)); + } + }, + _toZoomLevel: function(sliderValue) { + return sliderValue + this._map.getMinZoom(); + }, + _toSliderValue: function(zoomLevel) { + return zoomLevel - this._map.getMinZoom(); + }, + _getSliderValue: function(){ + return this._toSliderValue(this._map.getZoom()); + } +}); + +L.Map.mergeOptions({ + zoomControl: false, + zoomsliderControl: true +}); + +L.Map.addInitHook(function () { + if (this.options.zoomsliderControl) { + this.zoomsliderControl = new L.Control.Zoomslider(); + this.addControl(this.zoomsliderControl); + } +}); + +L.control.zoomslider = function (options) { + return new L.Control.Zoomslider(options); +}; + + +L.BoundedDraggable = L.Draggable.extend({ + initialize: function(element, dragStartTarget, bounds) { + L.Draggable.prototype.initialize.call(this, element, dragStartTarget); + this._bounds = bounds; + this.on('predrag', function() { + if(!this._bounds.contains(this._newPos)){ + this._newPos = this._fitPoint(this._newPos); + } + }, this); + }, + _fitPoint: function(point){ + var closest = new L.Point( + Math.min(point.x, this._bounds.max.x), + Math.min(point.y, this._bounds.max.y) + ); + closest.x = Math.max(closest.x, this._bounds.min.x); + closest.y = Math.max(closest.y, this._bounds.min.y); + return closest; + } +}); diff --git a/BIN/MAP/mapmerger.js b/BIN/MAP/mapmerger.js new file mode 100644 index 0000000..427a70b --- /dev/null +++ b/BIN/MAP/mapmerger.js @@ -0,0 +1,139 @@ +// author: dkxce@mail.ru // + + Math.sinh = Math.sinh || function(x) + { + var y = Math.exp(x); + return (y - 1 / y) / 2; + } + + function MapMerger(wi, he) + { + this.width = wi; + this.height = he; + + //this.url = 'http://mts0.google.com/vt/lyrs=m@177000000&hl=ru&src=app&x={x}&s=&y={y}&z={z}&s=Ga'; // Google + this.url = 'http://tile.openstreetmap.org/{z}/{x}/{y}.png'; // Mapnik + //this.url = 'http://tile.xn--pnvkarte-m4a.de/tilegen/{z}/{x}/{y}.png'; // Opnvkarte + + this.iconSymbol = '/>'; + this.iconAngle = 0; + this.iconTitle = ''; + this.iconHREF = '#'; + }; + + MapMerger.prototype.InitIcon = function(symbol, angle, title, href) + { + this.iconSymbol = symbol; + this.iconAngle = angle; + this.iconTitle = title; + this.iconHREF = href; + }; + + MapMerger.prototype.GetTileXYFromLatLon = function(lat, lon, zoom) + { + var x = Math.floor((lon + 180.0) / 360.0 * Math.pow(2.0, zoom)); + var y = Math.floor((1.0 - Math.log(Math.tan(lat * Math.PI / 180.0) + 1.0 / Math.cos(lat * Math.PI / 180.0)) / Math.PI) / 2.0 * Math.pow(2.0, zoom)); + return {'x':x,'y':y}; + }; + + MapMerger.prototype.GetLatLonFromTileXY = function(x, y, zoom) + { + var lon = ((x / Math.pow(2.0, zoom) * 360.0) - 180.0); + var n = Math.PI - ((2.0 * Math.PI * y) / Math.pow(2.0, zoom)); + var lat = (180.0 / Math.PI * Math.atan(Math.sinh(n))); + return {'lat':lat,'lon':lon}; + }; + + MapMerger.prototype.GetTile = function (x, y, z) + { + return this.url.replace('{x}',x).replace('{y}',y).replace('{z}',z); + }; + + MapMerger.prototype.SymbolToImage = function(symb) + { + var prose = 'primary'; + var label = ''; + if(symb.length == 2) + { + if(symb[0] == '\\') + prose = 'secondary'; + else if ((symb[0] != '/') && (("#&0>AW^_acnsuvz").indexOf(symb[1]) >= 0)) + { + prose = "secondary"; + label = symb[0].toString(); + if (("#0A^cv").indexOf(symb[1]) >= 0) + { label = "" + label + ""; }; + }; + symb = symb.substr(1); + }; + var symbtable = '!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~'; + var idd = symbtable.indexOf(symb); + if(idd < 0) idd = 14; + var itop = Math.floor(idd / 16) * 24; + var ileft = (idd % 16) * 24; + return ['background:url(../v/images/'+prose+'.png) -'+ileft+'px -'+itop+'px no-repeat;',label]; + } + + MapMerger.prototype.CalcGeo = function () + { + var center = this.GetTileXYFromLatLon(this.mLat, this.mLon, this.mZoom); + this.Center_TileX = center.x; + this.Center_TileY = center.y; + + var topleft = this.GetLatLonFromTileXY(center.x, center.y, this.mZoom); + var bottomright = this.GetLatLonFromTileXY(center.x + 1, center.y + 1, this.mZoom); + + this.LonPerTile = 360.0 / (Math.pow(2, this.mZoom)); + this.LonPerPixel = this.LonPerTile / 256; + + this.Center_Tile_XPos = Math.floor(this.width / 2 - (256.0 * ((this.mLon - topleft.lon) / (bottomright.lon - topleft.lon)))); + this.Center_Tile_YPos = Math.floor(this.height / 2 - (256.0 * ((this.mLat - topleft.lat) / (bottomright.lat - topleft.lat)))); + }; + + MapMerger.prototype.GetMap = function(lat, lon, zoom) + { + this.mLat = lat; + this.mLon = lon; + this.mZoom = zoom; + this.CalcGeo(); + + var ddv = '
'; + + var drawx = this.Center_Tile_XPos; + var drawy = this.Center_Tile_YPos; + + var x = this.Center_TileX; + var y = this.Center_TileY; + + while (drawx >= 0) { drawx -= 256; x--; }; + while (drawy >= 0) { drawy -= 256; y--; }; + + var curdrawx; + var curx; + while (drawy < this.height) + { + curdrawx = drawx; + curx = x; + while (curdrawx < this.width) + { + try { + var url = this.GetTile(curx, y, this.mZoom); + ddv += '
'; + } catch (ex) { }; + curdrawx += 256; + curx++; + }; + drawy += 256; + y++; + }; + + // place icon // + var smbl = this.SymbolToImage(this.iconSymbol); + var title = "Icon"; + ddv += '
 ' + smbl[1] + ' 
'; + ddv += '
'; + // -- // + + ddv += '
'; + return ddv; + }; \ No newline at end of file diff --git a/BIN/SimpleAPRSserver.exe b/BIN/SimpleAPRSserver.exe new file mode 100644 index 0000000..8cf9438 Binary files /dev/null and b/BIN/SimpleAPRSserver.exe differ diff --git a/BIN/SimpleAPRSserver.pdb b/BIN/SimpleAPRSserver.pdb new file mode 100644 index 0000000..598afd8 Binary files /dev/null and b/BIN/SimpleAPRSserver.pdb differ diff --git a/BIN/SimpleAPRSserver.vshost.exe b/BIN/SimpleAPRSserver.vshost.exe new file mode 100644 index 0000000..ce3f102 Binary files /dev/null and b/BIN/SimpleAPRSserver.vshost.exe differ diff --git a/BIN/SimpleAPRSserver_service_edit.cmd b/BIN/SimpleAPRSserver_service_edit.cmd new file mode 100644 index 0000000..5525d2c --- /dev/null +++ b/BIN/SimpleAPRSserver_service_edit.cmd @@ -0,0 +1 @@ +@nssm32.exe edit "SimpleAPRSserver" \ No newline at end of file diff --git a/BIN/SimpleAPRSserver_service_install.cmd b/BIN/SimpleAPRSserver_service_install.cmd new file mode 100644 index 0000000..4d363de --- /dev/null +++ b/BIN/SimpleAPRSserver_service_install.cmd @@ -0,0 +1,2 @@ +@nssm32.exe install "SimpleAPRSserver" "%CD%\SimpleAPRSserver.exe" +@nssm32.exe edit "SimpleAPRSserver" \ No newline at end of file diff --git a/BIN/SimpleAPRSserver_service_remove.cmd b/BIN/SimpleAPRSserver_service_remove.cmd new file mode 100644 index 0000000..2658737 --- /dev/null +++ b/BIN/SimpleAPRSserver_service_remove.cmd @@ -0,0 +1 @@ +@nssm32.exe remove "SimpleAPRSserver" \ No newline at end of file diff --git a/BIN/SimpleAPRSserver_service_start.cmd b/BIN/SimpleAPRSserver_service_start.cmd new file mode 100644 index 0000000..dd6e963 --- /dev/null +++ b/BIN/SimpleAPRSserver_service_start.cmd @@ -0,0 +1 @@ +@nssm32.exe start "SimpleAPRSserver" \ No newline at end of file diff --git a/BIN/SimpleAPRSserver_service_stop.cmd b/BIN/SimpleAPRSserver_service_stop.cmd new file mode 100644 index 0000000..73ba496 --- /dev/null +++ b/BIN/SimpleAPRSserver_service_stop.cmd @@ -0,0 +1 @@ +@nssm32.exe stop "SimpleAPRSserver" \ No newline at end of file diff --git a/BIN/clients.txt b/BIN/clients.txt new file mode 100644 index 0000000..15ac717 --- /dev/null +++ b/BIN/clients.txt @@ -0,0 +1,6 @@ +Windows: + UI-View32 + +Android: + OruxMaps + APRSDroid \ No newline at end of file diff --git a/BIN/config.xml b/BIN/config.xml new file mode 100644 index 0000000..28c3b42 --- /dev/null +++ b/BIN/config.xml @@ -0,0 +1,54 @@ + + + + SimpleAPRSServer + 12015 + 50 + + + 80 + + + 0 + 1 + + + 1 + 0 + + + 0 + 0 + 0 + + + 1 + 1440 + + + UNKNOWN + ANONYMOUS + + + 0 + + 127.0.0.1 + 192.168.*.* + ^10.0.0?[0-9]?\d.\d{1,3}$ + + + + + 0 + + *-*-*-*-*-* + + + + + 1 + 1 + 1 + 1 + 1 + \ No newline at end of file diff --git a/BIN/readme.md b/BIN/readme.md new file mode 100644 index 0000000..e032bff --- /dev/null +++ b/BIN/readme.md @@ -0,0 +1,73 @@ +Это простой APRS сервер, который +позволяет обмениваться данными между +всеми подключенными к нему клиентами. +Позволяет кэшировать и хранить последние +полученные координаты и выдавать их при +входящем подключении. + +Сервер имеет HTTP интерфейс с картой и +статистикой. + +Поддерживается AIS протокол. + +Поддерживает стандартные фильтры клиентов: + r/lat/lon/dist -- Range filter + p/aa/bb/cc -- Prefix filter + b/call1/call2 -- Budlist filter + o/call1/call2 -- Object filter + os/call1/call2 -- Strict Object filter + t/poimqstuw/call/km -- Type filter + s/pri/alt/over -- Symbol filter + d/digi1/digi2 -- Digipeater filter + a/latN/lonW/latS/lonE -- Area filter + e/call1/call2 -- Entry station filter + g/call1/call2 -- Group Message filter + u/call1/call2 -- Unproto filter + m/dist -- My Range filter + f/user/dist -- Friend Range filter + +Клиентский софт: + YAAC + + Windows: + UI-View32 + + Android: + OruxMaps + APRSDroid + +=============================================== + +This is the Simple APRS Server +wich send received data to all +connected users. + +Has HTTP interface with live Map. + +Support AIS protocol. + +Supporting client filters: + r/lat/lon/dist -- Range filter + p/aa/bb/cc -- Prefix filter + b/call1/call2 -- Budlist filter + o/call1/call2 -- Object filter + os/call1/call2 -- Strict Object filter + t/poimqstuw/call/km -- Type filter + s/pri/alt/over -- Symbol filter + d/digi1/digi2 -- Digipeater filter + a/latN/lonW/latS/lonE -- Area filter + e/call1/call2 -- Entry station filter + g/call1/call2 -- Group Message filter + u/call1/call2 -- Unproto filter + m/dist -- My Range filter + f/user/dist -- Friend Range filter + +Client Software: + YAAC + + Windows: + UI-View32 + + Android: + OruxMaps + APRSDroid diff --git a/FilterRegex.txt b/FilterRegex.txt new file mode 100644 index 0000000..54a1ab1 --- /dev/null +++ b/FilterRegex.txt @@ -0,0 +1,14 @@ +r/lat/lon/dist - (?:^|\s)r/(?[\d\.\-]+)/(?[\d\.\-]+)/(?[\d]+) +p/aa/bb/cc - (?:^|\s)p(?:/(?[^\/\s\r\n]+))+ +b/call1/call2 - (?:^|\s)b(?:/(?[^\/\s\r\n]+))+ +o/obj1/obj2 - (?:^|\s)o(?:/(?[^\/\s\r\n]+))+ +os/obj1/obj2 - (?:^|\s)os(?:/(?[^\/\r\n]{3,9}))+$ +t/poimqstunw - (?:^|\s)t/(?[poimqstunw]+)(?:/(?[^\/\s\r\n]+)/(?[\d]+))? +s/pri/alt/over - (?:^|\s)s/(?[^\/\s\r\n]*)(?:/(?[^\/\s\r\n]*)(?:/(?[^\/\s\r\n]+))?)? +d/digi1/digi2 - (?:^|\s)d(?:/(?[^\/\s\r\n]+))+ +a/latN/lonW/latS/lonE - (?:^|\s)a/(?[\d\.\-]+)/(?[\d\.\-]+)/(?[\d\.\-]+)/(?[\d\.\-]+) +e/call1/call1 - (?:^|\s)e(?:/(?[^\/\s\r\n]+))+ +g/call1/call1 - (?:^|\s)g(?:/(?[^\/\s\r\n]+))+ +u/call1/call1 - (?:^|\s)u(?:/(?[^\/\s\r\n]+))+ +m/dist - (?:^|\s)m/(?[\d]+) +f/call/dist - (?:^|\s)f/(?[^\/\s\r\n]+)/(?[\d]+) \ No newline at end of file diff --git a/SimpleAPRSserver.sln b/SimpleAPRSserver.sln new file mode 100644 index 0000000..bdd0a53 --- /dev/null +++ b/SimpleAPRSserver.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 9.00 +# Visual Studio 2005 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleAPRSserver", "SimpleAPRSserver\SimpleAPRSserver.csproj", "{E0BBC088-37CD-4FA1-BC60-808FF8BF3EBC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E0BBC088-37CD-4FA1-BC60-808FF8BF3EBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E0BBC088-37CD-4FA1-BC60-808FF8BF3EBC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E0BBC088-37CD-4FA1-BC60-808FF8BF3EBC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E0BBC088-37CD-4FA1-BC60-808FF8BF3EBC}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/SimpleAPRSserver/AIS.cs b/SimpleAPRSserver/AIS.cs new file mode 100644 index 0000000..2372171 --- /dev/null +++ b/SimpleAPRSserver/AIS.cs @@ -0,0 +1,1389 @@ +/******************************************* +* * +* Simple AIS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.IO; +using System.Threading; +using System.Net; +using System.Net.Sockets; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Serialization; + +namespace SimpleAPRSserver +{ + // Ship Types + public enum ShipType : int + { + Default = 0, + WIG_AllShips = 20, + Fishing = 30, + Towing = 31, + TowingBig = 32, + DredgingOrUnderwater = 33, + Diving = 34, + Military = 35, + Sailing = 36, + PleasureCraft = 37, + HighSpeedCraft_All = 40, + HighSpeedCraft_A = 41, + HighSpeedCraft_B = 42, + HighSpeedCraft_NoInfo = 49, + PilotVessel = 50, + SearchRescue = 51, + Tug = 52, + PortTender = 53, + MedicalTransport = 58, + Passenger_All = 60, + Passenger_A = 61, + Passenger_B = 62, + Passenger_NoInfo = 69, + Cargo_All = 70, + Cargo_A = 71, + Cargo_B = 72, + Cargo_NoInfo = 79, + Tanker_All = 80, + Tanker_A = 81, + Tanker_B = 82, + Tanker_NoInfo = 89 + } + + // Code to/from AIS packet + public class AISTransCoder + { + public class AISPacket + { + public byte[] OriginalData = null; + public string OriginalText = null; + public string OriginalFrame = null; + + public uint PacketType = 0; + public bool HasData = false; + public bool Valid = false; + + public PositionReportClassA PositionReportA { get { if ((PacketType == 1) || (PacketType == 2) || (PacketType == 3)) return PositionReportClassA.FromAIS(OriginalData); else return null; } } + public PositionReportClassAExt PositionReportAext { get { if (PacketType == 5) return PositionReportClassAExt.FromAIS(OriginalData); else return null; } } + public PositionReportClassB PositionReportB { get { if (PacketType == 18) return PositionReportClassB.FromAIS(OriginalData); else return null; } } + public PositionReportClassBE PositionReportBext { get { if (PacketType == 19) return PositionReportClassBE.FromAIS(OriginalData); else return null; } } + public CNBAsentense SentenseA { get { if ((PacketType == 1) || (PacketType == 2) || (PacketType == 3)) return CNBAsentense.FromAIS(OriginalData); else return null; } } + public StaVoyData SentenseStaVoyData { get { if (PacketType == 5) return StaVoyData.FromAIS(OriginalData); else return null; } } + public AIVDMSentense SentenseAIVDM { get { if (PacketType == 5) return AIVDMSentense.FromAIS(OriginalData); else return null; } } + public SafetyRelatedBroadcastMessage SafetyMessage { get { if (PacketType == 14) return SafetyRelatedBroadcastMessage.FromAIS(OriginalData); else return null; } } + public CNBBsentense SentenseB { get { if (PacketType == 18) return CNBBsentense.FromAIS(OriginalData); else return null; } } + public CNBBEsentense SentenseSB { get { if (PacketType == 19) return CNBBEsentense.FromAIS(OriginalData); else return null; } } + public StaticDataReport StaticReport { get { if (PacketType == 24) return StaticDataReport.FromAIS(OriginalData); else return null; } } + + public object Result + { + get + { + switch (PacketType) + { + case 01: + case 02: + case 03: return this.PositionReportA; + case 05: return this.PositionReportAext; + case 14: return this.SafetyMessage; + case 18: return this.PositionReportB; + case 19: return this.PositionReportBext; + case 24: return this.StaticReport; + default: return null; + }; + } + } + + // Get Normal Data from Unpacked AIS text + public static AISPacket FromUnpackedData(byte[] unpackedData, uint packetType) + { + AISPacket res = new AISPacket(); + res.PacketType = packetType; + res.OriginalData = unpackedData; + res.OriginalText = null; + res.HasData = res.Result != null; + res.Valid = (packetType > 0) && (unpackedData != null) && (unpackedData.Length > 0); + return res; + } + + // Get Normal Data from AIS Frame text + public static AISPacket FromPacketFrame(string PacketFrame) + { + AISPacket res = new AISPacket(); + res.OriginalFrame = PacketFrame; + res.Valid = AISTransCoder.ValidatePacket(PacketFrame, out res.OriginalText, out res.OriginalData, out res.PacketType); + if (res.Valid) res.HasData = res.Result != null; + return res; + } + } + + // desc http://www.bosunsmate.org/ais + // test http://ais.tbsalling.dk/ + // test http://www.aggsoft.com/ais-decoder.htm + // api http://catb.org/gpsd/AIVDM.html + + // Orux Decode: AIS 1,2,3,5,18,19,24 ( http://www.oruxmaps.com/foro/viewtopic.php?t=1627 ) + // http://wiki.openseamap.org/wiki/OruxMaps + + // AIS Message Types: + // 01 - Position Report with SOTDMA + // 02 - Position Report with SOTDMA + // 03 - Position Report with ITDMA + // 05 - Static and Voyage Related Dat;; http://www.navcen.uscg.gov/?pageName=AISMessagesAStatic + // 14 - SafetyRelatedBroadcastMessage + // 18 - Standard Class B CS Position Report + // 19 - Extended Class B CS Position Report + // 24 - Static Data Report + + // Check Valid AIS Packet + public static bool ValidatePacket(string packet, out string command, out byte[] unpacked, out uint packetType) + { + command = null; + unpacked = null; + packetType = 0; + + if (String.IsNullOrEmpty(packet)) return false; + if (!packet.StartsWith("!AIVD")) return false; + + string chsum = Checksum(packet.Substring(0, packet.Length - 3)); + if (packet.Substring(packet.Length - 2) != chsum) return false; + + string checksum = packet.Substring(packet.Length - 2); + command = packet.Remove(packet.Length - 5); + command = command.Substring(command.LastIndexOf(",") + 1); + + unpacked = AISTransCoder.UnpackAisEncoding(command); + packetType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpacked, 0, 6); + + return true; + } + + // Get Checksum + public static string Checksum(string sentence) + { + int iFrom = 0; + if (sentence.IndexOf('$') == 0) iFrom++; + if (sentence.IndexOf('!') == 0) iFrom++; + int iTo = sentence.Length; + if (sentence.LastIndexOf('*') == (sentence.Length - 3)) + iTo = sentence.IndexOf('*'); + int checksum = Convert.ToByte(sentence[iFrom]); + for (int i = iFrom + 1; i < iTo; i++) + checksum ^= Convert.ToByte(sentence[i]); + return checksum.ToString("X2"); + } + + // Unpack from 6-bit AIS string to 8-bit normal array + public static byte[] UnpackAisEncoding(string s) + { + return UnpackAisEncoding(Encoding.UTF8.GetBytes(s)); + } + + // Unpack from 6-bit AIS chars array to 8-bit normal array + private static byte[] UnpackAisEncoding(byte[] data) + { + int outputLen = ((data.Length * 6) + 7) / 8; + byte[] result = new byte[outputLen]; + + // We are always combining two input bytes into one or two output bytes. + // This happens in three phases. The phases are + // 0 == 6,2 (six bits of the current source byte, plus 2 bits of the next) + // 1 == 4,4 + // 2 == 2,6; + int iSrcByte = 0; + byte nextByte = ConvertSixBit(data[iSrcByte]); + for (int iDstByte = 0; iDstByte < outputLen; ++iDstByte) + { + byte currByte = nextByte; + if (iSrcByte < data.Length - 1) + nextByte = ConvertSixBit(data[++iSrcByte]); + else + nextByte = 0; + + // iDstByte % 3 is the 'phase' we are in and determins the shifting pattern to use + switch (iDstByte % 3) + { + case 0: + // 11111122 2222xxxx + result[iDstByte] = (byte)((currByte << 2) | (nextByte >> 4)); + break; + case 1: + // 11112222 22xxxxxx + result[iDstByte] = (byte)((currByte << 4) | (nextByte >> 2)); + break; + case 2: + // 11222222 xxxxxxxx + result[iDstByte] = (byte)((currByte << 6) | (nextByte)); + // There are now no remainder bits, so we need to eat another input byte + if (iSrcByte < data.Length - 1) + nextByte = ConvertSixBit(data[++iSrcByte]); + else + nextByte = 0; + break; + } + } + + return result; + } + + // Pack from 8-bit normal array to 6-bit AIS string + public static string EnpackAisToString(byte[] ba) + { + return Encoding.UTF8.GetString(EnpackAisEncoding(ba)); + } + + // Pack from 8-bit normal array to 6-bit AIS chars array + private static byte[] EnpackAisEncoding(byte[] ba) + { + List res = new List(); + for (int i = 0; i < ba.Length; i++) + { + int val = 0; + int val2 = 0; + switch (i % 3) + { + case 0: + val = (byte)((ba[i] >> 2) & 0x3F); + break; + case 1: + val = (byte)((ba[i - 1] & 0x03) << 4) | (byte)((ba[i] & 0xF0) >> 4); + break; + case 2: + val = (byte)((ba[i - 1] & 0x0F) << 2) | (byte)((ba[i] & 0xC0) >> 6); + val2 = (byte)((ba[i] & 0x3F)) + 48; + if (val2 > 87) val2 += 8; + break; + }; + val += 48; + if (val > 87) val += 8; + res.Add((byte)val); + if ((i % 3) == 2) res.Add((byte)val2); + }; + return res.ToArray(); + } + + // Get Strring From Unpacked Bytes + public static string GetAisString(byte[] source, int start, int len) + { + string key = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?"; + int l = key.Length; + string val = ""; + for (int i = 0; i < len; i += 6) + { + byte c = (byte)(GetBitsAsSignedInt(source, start + i, 6) & 0x3F); + val += key[c]; + }; + return val.Trim(); + } + + // Set String To Unpacked Bytes + public static void SetAisString(byte[] source, int start, int len, string val) + { + if (val == null) val = ""; + string key = "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_ !\"#$%&'()*+,-./0123456789:;<=>?;"; + int strlen = len / 6; + if (val.Length > strlen) val = val.Substring(0, strlen); + while (val.Length < strlen) val += " "; + int s = 0; + for (int i = 0; i < len; i += 6, s++) + { + byte c = (byte)key.IndexOf(val[s]); + SetBitsAsSignedInt(source, start + i, 6, c); + }; + } + + // Get Int32 From Unpacked Bytes + public static int GetBitsAsSignedInt(byte[] source, int start, int len) + { + int value = GetBitsAsUnsignedInt(source, start, len); + if ((value & (1 << (len - 1))) != 0) + { + // perform 32 bit sign extension + for (int i = len; i < 32; ++i) + { + value |= (1 << i); + } + }; + return value; + } + + // Set Int32 To Unpacked Bytes + public static void SetBitsAsSignedInt(byte[] source, int start, int len, int val) + { + int value = val; + if (value < 0) + { + value = ~value; + for (int i = len; i < 32; ++i) + { + value |= (1 << i); + }; + } + SetBitsAsUnsignedInt(source, start, len, val); + } + + // Get UInt32 From Unpacked Bytes + public static int GetBitsAsUnsignedInt(byte[] source, int start, int len) + { + int result = 0; + + for (int i = start; i < (start + len); ++i) + { + int iByte = i / 8; + int iBit = 7 - (i % 8); + result = result << 1 | (((source[iByte] & (1 << iBit)) != 0) ? 1 : 0); + } + + return result; + } + + // Set UInt32 To Unpacked Bytes + public static void SetBitsAsUnsignedInt(byte[] source, int start, int len, int val) + { + int bit = len - 1; + for (int i = start; i < (start + len); ++i, --bit) + { + int iByte = i / 8; + int iBit = 7 - (i % 8); + byte mask = (byte)(0xFF - (byte)(1 << iBit)); + byte bitm = (byte)~mask; + byte b = (byte)(((val >> bit) & 0x01) << iBit); + source[iByte] = (byte)((source[iByte] & mask) | b); + } + } + + private static byte ConvertSixBit(byte b) + { + byte result = (byte)(b - 48); + if (result > 39) + result -= 8; + return result; + } + } + + // 1, 2, 3 -- Violet + // Position Report with SOTDMA + // Position Report with ITDMA + public class PositionReportClassA + { + public bool valid = false; + public byte length = 168; + + private uint pType = 1; + private uint pRepeat = 0; + + public bool Accuracy = false; + public uint MMSI; + private uint Status = 15; + private int Turn = 0; + public uint Speed = 0; + public double Lon = 0; + public double Lat = 0; + public double Course = 0; + public ushort Heading = 0; + private uint Second = 60; + private uint Maneuver = 0; + private uint Radio = 0; + + public static PositionReportClassA FromAIS(byte[] unpackedBytes) + { + PositionReportClassA res = new PositionReportClassA(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if ((res.pType < 1) || (res.pType > 3)) return res; + + res.valid = true; + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.Status = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 38, 4); + res.Turn = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 42, 8); + res.Speed = (uint)(AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 50, 10) / 10 * 1.852); + res.Accuracy = (byte)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 60, 1) == 1 ? true : false; + res.Lon = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 61, 28) / 600000.0; + res.Lat = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 89, 27) / 600000.0; + res.Course = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 116, 12) / 10.0; + res.Heading = (ushort)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 128, 9); + res.Second = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 137, 6); + res.Maneuver = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 143, 2); + res.Radio = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 149, 19); + return res; + } + + public static PositionReportClassA FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static PositionReportClassA FromBuddie(APRSData.Buddie buddie) + { + PositionReportClassA res = new PositionReportClassA(); + res.Accuracy = buddie.PositionIsValid; + res.MMSI = APRSData.Buddie.MMSI(buddie.name); + res.Turn = 0; + res.Speed = (uint)buddie.speed; + res.Lon = buddie.lon; + res.Lat = buddie.lat; + res.Course = (ushort)buddie.course; + res.Heading = (ushort)buddie.course; + return res; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[21]; + if ((pType < 0) || (pType > 3)) pType = 3; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); // type + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); // repeat (no) + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); // mmsi + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 4, (int)Status); // status (default) + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 42, 8, (int)Turn); // turn (off) + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 50, 10, (int)(Speed / 1.852 * 10)); // speed + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 60, 1, Accuracy ? 1 : 0); // FixOk + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 61, 28, (int)(Lon * 600000)); // lon + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 89, 27, (int)(Lat * 600000)); // lat + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 116, 12, (int)(Course * 10)); // course + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 128, 9, (int)Heading); // heading + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 137, 6, (int)Second); // timestamp (not available (default)) + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 143, 2, (int)Maneuver); // no Maneuver + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 149, 19, (int)Radio); // no Maneuver + return unpackedBytes; + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 5 + // Static and Voyage Related Data + public class PositionReportClassAExt + { + private const short length = 424; + + private uint pType = 5; + private uint pRepeat = 0; + public uint MMSI; + public uint IMOShipID; + public string CallSign; + public string VesselName; + public int ShipType = 0; + public string Destination = ""; + + public static PositionReportClassAExt FromAIS(byte[] unpackedBytes) + { + PositionReportClassAExt res = new PositionReportClassAExt(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 5) return res; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.IMOShipID = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 40, 30); + res.CallSign = AISTransCoder.GetAisString(unpackedBytes, 70, 42); + res.VesselName = AISTransCoder.GetAisString(unpackedBytes, 112, 120); + res.ShipType = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 232, 8); + res.Destination = AISTransCoder.GetAisString(unpackedBytes, 302, 120); + + return res; + } + + public static PositionReportClassAExt FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static PositionReportClassAExt FromBuddie(APRSData.Buddie buddie) + { + PositionReportClassAExt res = new PositionReportClassAExt(); + res.CallSign = res.VesselName = buddie.name; + res.Destination = DateTime.Now.ToString("HHmmss ddMMyy"); + res.ShipType = 0; + res.MMSI = res.IMOShipID = APRSData.Buddie.MMSI(buddie.name); + return res; + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[54]; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, 5); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, 0); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 2, 0); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 40, 30, (int)IMOShipID); + AISTransCoder.SetAisString(unpackedBytes, 70, 42, CallSign); + AISTransCoder.SetAisString(unpackedBytes, 112, 120, VesselName); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 232, 8, (int)ShipType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 240, 9, 4); //A + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 249, 9, 1); //B + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 258, 6, 1); //C + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 264, 6, 2); //D + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 270, 4, 1); //PostFix + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 274, 4, DateTime.UtcNow.Month); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 278, 5, DateTime.UtcNow.Day); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 283, 5, DateTime.UtcNow.Hour); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 288, 6, DateTime.UtcNow.Minute); + AISTransCoder.SetAisString(unpackedBytes, 302, 120, Destination); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 422, 1, 0); + return unpackedBytes; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 18 -- Green + // Standard Class B CS Position Report + public class PositionReportClassB + { + public bool valid = false; + public int length = 168; + + private uint pType = 18; + private uint pRepeat = 0; + + public bool Accuracy; + public uint MMSI; + public uint Speed; + public double Lon; + public double Lat; + public double Course = 0; + public ushort Heading = 0; + private uint Second = 60; + + public static PositionReportClassB FromAIS(byte[] unpackedBytes) + { + PositionReportClassB res = new PositionReportClassB(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 18) return res; + + res.valid = true; + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.Speed = (uint)(AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 46, 10) / 10 * 1.852); + res.Accuracy = (byte)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 56, 1) == 1 ? true : false; + res.Lon = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 57, 28) / 600000.0; + res.Lat = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 85, 27) / 600000.0; + res.Course = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 112, 12) / 10.0; + res.Heading = (ushort)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 124, 9); + res.Second = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 133, 6); + return res; + } + + public static PositionReportClassB FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static PositionReportClassB FromBuddie(APRSData.Buddie buddie) + { + PositionReportClassB res = new PositionReportClassB(); + res.Accuracy = buddie.PositionIsValid; + res.MMSI = APRSData.Buddie.MMSI(buddie.name); + res.Speed = (ushort)buddie.speed; + res.Lon = buddie.lon; + res.Lat = buddie.lat; + res.Course = (ushort)buddie.course; + res.Heading = (ushort)buddie.course; + res.Speed = (uint)buddie.speed; + return res; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[21]; + pType = 18; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); // type + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 46, 10, (int)(Speed / 1.852 * 10)); // speed + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 56, 1, Accuracy ? 1 : 0); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 57, 28, (int)(Lon * 600000)); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 85, 27, (int)(Lat * 600000)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 112, 12, (int)(Course * 10.0)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 124, 9, Heading); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 133, 6, 60); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 142, 1, 1); + return unpackedBytes; + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 19 -- Green + // Extended Class B CS Position Report + public class PositionReportClassBE + { + public bool valid = false; + public int length = 312; + + private uint pType = 19; + private uint pRepeat = 0; + + public bool Accuracy; + public uint MMSI; + public uint Speed; + public double Lon; + public double Lat; + public double Course = 0; + public ushort Heading = 0; + private uint Second = 60; + public string Name; + public int ShipType = 31; + + public static PositionReportClassBE FromAIS(byte[] unpackedBytes) + { + PositionReportClassBE res = new PositionReportClassBE(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 19) return res; + + res.valid = true; + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.Speed = (uint)(AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 46, 10) / 10 * 1.852); + res.Accuracy = (byte)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 56, 1) == 1 ? true : false; + res.Lon = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 57, 28) / 600000.0; + res.Lat = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 85, 27) / 600000.0; + res.Course = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 112, 12) / 10.0; + res.Heading = (ushort)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 124, 9); + res.Second = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 133, 6); + res.Name = AISTransCoder.GetAisString(unpackedBytes, 143, 120); + res.ShipType = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 263, 8); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 271, 9, 4); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 280, 9, 1); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 289, 6, 1); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 295, 6, 2); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 301, 4, 1); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 306, 6, 1); + return res; + } + + public static PositionReportClassBE FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static PositionReportClassBE FromBuddie(APRSData.Buddie buddie) + { + PositionReportClassBE res = new PositionReportClassBE(); + res.Accuracy = buddie.PositionIsValid; + res.MMSI = APRSData.Buddie.MMSI(buddie.name); + res.Speed = (uint)buddie.speed; + res.Lon = buddie.lon; + res.Lat = buddie.lat; + res.Course = (ushort)buddie.course; + res.Heading = (ushort)buddie.course; + res.Name = buddie.name; + return res; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[39]; + pType = 19; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); // type + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 46, 10, (int)(Speed / 1.852 * 10)); // speed + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 56, 1, Accuracy ? 1 : 0); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 57, 28, (int)(Lon * 600000)); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 85, 27, (int)(Lat * 600000)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 112, 12, (int)(Course * 10.0)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 124, 9, Heading); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 133, 6, 60); + AISTransCoder.SetAisString(unpackedBytes, 143, 120, Name); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 263, 8, ShipType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 301, 4, 1); + return unpackedBytes; + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 1, 2, 3 -- Violet + // Position Report with SOTDMA + // Position Report with ITDMA + public class CNBAsentense + { + private const byte length = 168; + + private uint pType = 1; // packet type + private uint pRepeat = 0; // repeat + public uint MMSI; // mmsi + private uint NavigationStatus = 15; // status + private int ROT = 0; // turn + public uint SOG = 0; // speed + public bool Accuracy = false; // FixOk + public double Longitude = 0; // lon + public double Latitude = 0; // lat + public double COG = 0; // course + public ushort HDG = 0; // heading + private uint TimeStamp = 60; + private uint ManeuverIndicator = 1; + private uint RadioStatus = 0; + + public static CNBAsentense FromAIS(byte[] unpackedBytes) + { + CNBAsentense res = new CNBAsentense(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if ((res.pType < 1) || (res.pType > 3)) return null; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); // + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); // + res.NavigationStatus = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 38, 4); + res.ROT = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 42, 8); + res.SOG = (uint)(AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 50, 10) / 10 * 1.852); + res.Accuracy = (byte)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 60, 1) == 1 ? true : false; + res.Longitude = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 61, 28) / 600000.0; + res.Latitude = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 89, 27) / 600000.0; + res.COG = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 116, 12) / 10.0; + res.HDG = (ushort)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 128, 9); + res.TimeStamp = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 137, 6); + res.ManeuverIndicator = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 143, 2); + res.RadioStatus = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 149, 19); + return res; + } + + public static CNBAsentense FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static CNBAsentense FromBuddie(APRSData.Buddie buddie) + { + CNBAsentense res = new CNBAsentense(); + res.Accuracy = buddie.PositionIsValid; + res.Latitude = buddie.lat; + res.Longitude = buddie.lon; + res.COG = res.HDG = (ushort)buddie.course; + res.SOG = (uint)buddie.speed; + res.MMSI = APRSData.Buddie.MMSI(buddie.name); + return res; + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[21]; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); // type + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); // repeat + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 4, (int)NavigationStatus); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 42, 8, (int)ROT); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 50, 10, (int)(SOG / 1.852 * 10)); // speed + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 60, 1, Accuracy ? 1 : 0); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 61, 28, (int)(Longitude * 600000)); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 89, 27, (int)(Latitude * 600000)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 116, 12, (int)(COG * 10)); // course + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 128, 9, (int)HDG); // heading + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 137, 6, (int)TimeStamp); // timestamp (not available (default)) + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 143, 2, (int)ManeuverIndicator); // no Maneuver + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 149, 19, (int)RadioStatus); + return unpackedBytes; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 5 + // Static and Voyage Related Dat + public class StaVoyData + { + public bool valid = false; + public int length = 424; + + private uint pType = 5; + private uint pRepeat = 0; + + public uint MMSI; + private int AISv = 0; + public uint ShipNo; + public string Callsign; + public string Name; + public int ShipType = 31; + private int Posfixt = 1; + public string Destination = ""; + + public static StaVoyData FromAIS(byte[] unpackedBytes) + { + StaVoyData res = new StaVoyData(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 5) return res; + res.valid = true; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.AISv = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 38, 2); + res.ShipNo = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 40, 30); + res.Callsign = AISTransCoder.GetAisString(unpackedBytes, 70, 42); + res.Name = AISTransCoder.GetAisString(unpackedBytes, 112, 120); + res.ShipType = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 232, 8); //30 - fishing, 31 - towing; 34 - diving; 36 - sailing; 37 - pleasure craft; + // 40 - hi speed; 50 - pilot vessel; 52 - tug; 60/69 - passenger; 70/79 - cargo; 80/89 - tanker + res.Posfixt = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 270, 4); + res.Destination = AISTransCoder.GetAisString(unpackedBytes, 302, 120); + + return res; + } + + public static StaVoyData FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[54]; + pType = 5; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 2, (int)AISv); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 40, 30, (int)ShipNo); + AISTransCoder.SetAisString(unpackedBytes, 70, 42, Callsign); + AISTransCoder.SetAisString(unpackedBytes, 112, 120, Name); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 232, 8, (int)ShipType); //30 - fishing, 31 - towing; 34 - diving; 36 - sailing; 37 - pleasure craft; + // 40 - hi speed; 50 - pilot vessel; 52 - tug; 60/69 - passenger; 70/79 - cargo; 80/89 - tanker + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 240, 9, 4); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 249, 9, 1); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 258, 6, 1); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 264, 6, 2); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 270, 4, (int)Posfixt); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 274, 4, DateTime.UtcNow.Month); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 278, 5, DateTime.UtcNow.Day); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 283, 5, DateTime.UtcNow.Hour); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 288, 6, DateTime.UtcNow.Minute); + AISTransCoder.SetAisString(unpackedBytes, 302, 120, Destination); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 422, 1, 0); + return unpackedBytes; + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 5 + // Static and Voyage Related Data + public class AIVDMSentense + { + private const short length = 424; + + private uint pType = 5; + private uint pRepeat = 0; + + public uint MMSI; + public uint IMOShipID; + public string CallSign; + public string VesselName; + public int ShipType = 0; + public string Destination = ""; + + public static AIVDMSentense FromAIS(byte[] unpackedBytes) + { + AIVDMSentense res = new AIVDMSentense(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 5) return null; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); // + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.IMOShipID = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 40, 30); + res.CallSign = AISTransCoder.GetAisString(unpackedBytes, 70, 42); + res.VesselName = AISTransCoder.GetAisString(unpackedBytes, 112, 120); + res.ShipType = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 232, 8); + res.Destination = AISTransCoder.GetAisString(unpackedBytes, 302, 120); + + return res; + } + + public static AIVDMSentense FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static AIVDMSentense FromBuddie(APRSData.Buddie buddie) + { + AIVDMSentense res = new AIVDMSentense(); + res.CallSign = res.VesselName = buddie.name; + res.Destination = DateTime.Now.ToString("HHmmss ddMMyy"); + res.ShipType = 0; + res.MMSI = res.IMOShipID = APRSData.Buddie.MMSI(buddie.name); + return res; + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[54]; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 2, 0); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 40, 30, (int)IMOShipID); + AISTransCoder.SetAisString(unpackedBytes, 70, 42, CallSign); + AISTransCoder.SetAisString(unpackedBytes, 112, 120, VesselName); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 232, 8, (int)ShipType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 240, 9, 4); //A + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 249, 9, 1); //B + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 258, 6, 1); //C + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 264, 6, 2); //D + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 270, 4, 1); //PostFix + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 274, 4, DateTime.UtcNow.Month); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 278, 5, DateTime.UtcNow.Day); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 283, 5, DateTime.UtcNow.Hour); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 288, 6, DateTime.UtcNow.Minute); + AISTransCoder.SetAisString(unpackedBytes, 302, 120, Destination); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 422, 1, 0); + return unpackedBytes; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 14 + // SafetyRelatedBroadcastMessage + public class SafetyRelatedBroadcastMessage + { + private uint pType = 14; + private uint pRepeat = 0; + + public string Message = "PING"; + public uint MMSI = 0; + + public SafetyRelatedBroadcastMessage() { } + public SafetyRelatedBroadcastMessage(string Message) { this.Message = Message; } + public SafetyRelatedBroadcastMessage(string Message, uint MMSI) { this.Message = Message; this.MMSI = MMSI; } + + public byte[] ToAIS(string text) + { + string sftv = text; + byte[] unpackedBytes = new byte[5 + (int)(sftv.Length / 8.0 * 6.0 + 1)]; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); //MMSI + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 2, 0); + AISTransCoder.SetAisString(unpackedBytes, 40, sftv.Length * 6, sftv); + return unpackedBytes; + } + + public static SafetyRelatedBroadcastMessage FromAIS(byte[] unpackedBytes) + { + SafetyRelatedBroadcastMessage res = new SafetyRelatedBroadcastMessage(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 14) return res; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + int strlen = (int)((unpackedBytes.Length - 5 - 1) * 8.0 / 6.0); + res.Message = AISTransCoder.GetAisString(unpackedBytes, 40, strlen * 6); + + return res; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS(Message)); + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 18 -- Green + // Standard Class B CS Position Report + public class CNBBsentense + { + private const byte length = 168; + + private uint pType = 18; + private uint pRepeat = 0; + + public uint MMSI; + public uint SOG; // speed + public bool Accuracy; + public double Longitude; + public double Latitude; + public double COG = 0; // course + public ushort HDG = 0; // heading + private uint TimeStamp = 60; + + public static CNBBsentense FromAIS(byte[] unpackedBytes) + { + CNBBsentense res = new CNBBsentense(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 18) return null; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); // + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.SOG = (uint)(AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 46, 10) / 10 * 1.852); + res.Accuracy = (byte)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 56, 1) == 1 ? true : false; + res.Longitude = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 57, 28) / 600000.0; + res.Latitude = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 85, 27) / 600000.0; + res.COG = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 112, 12) / 10.0; + res.HDG = (ushort)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 124, 9); + res.TimeStamp = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 133, 6); + return res; + } + + public static CNBBsentense FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static CNBBsentense FromBuddie(APRSData.Buddie buddie) + { + CNBBsentense res = new CNBBsentense(); + res.Accuracy = buddie.PositionIsValid; + res.COG = res.HDG = (ushort)buddie.speed; + res.Latitude = buddie.lat; + res.Longitude = buddie.lon; + res.SOG = (uint)buddie.speed; + res.MMSI = APRSData.Buddie.MMSI(buddie.name); + return res; + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[21]; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); // type + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 46, 10, (int)(SOG / 1.852 * 10)); // speed + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 56, 1, Accuracy ? 1 : 0); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 57, 28, (int)(Longitude * 600000)); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 85, 27, (int)(Latitude * 600000)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 112, 12, (int)(COG * 10.0)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 124, 9, HDG); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 133, 6, 60); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 142, 1, 1); + return unpackedBytes; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 19 -- Green + // Extended Class B CS Position Report + public class CNBBEsentense + { + private const short length = 312; + + private uint pType = 19; + private uint pRepeat = 0; + + public uint MMSI; + public uint SOG; // speed + public bool Accuracy; + public double Longitude; + public double Latitude; + public double COG = 0; // course + public ushort HDG = 0; // heading + private uint Timestamp = 60; + public string VesselName; + public int ShipType = 0; + + public static CNBBEsentense FromAIS(byte[] unpackedBytes) + { + CNBBEsentense res = new CNBBEsentense(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 19) return null; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); // + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.SOG = (uint)(AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 46, 10) / 10 * 1.852); + res.Accuracy = (byte)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 56, 1) == 1 ? true : false; + res.Longitude = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 57, 28) / 600000.0; + res.Latitude = AISTransCoder.GetBitsAsSignedInt(unpackedBytes, 85, 27) / 600000.0; + res.COG = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 112, 12) / 10.0; + res.HDG = (ushort)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 124, 9); + res.Timestamp = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 133, 6); + res.VesselName = AISTransCoder.GetAisString(unpackedBytes, 143, 120); + res.ShipType = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 263, 8); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 271, 9, 4); // A + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 280, 9, 1); // B + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 289, 6, 1); // C + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 295, 6, 2); // D + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 301, 4, 1); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 306, 6, 1); + return res; + } + + public static CNBBEsentense FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static CNBBEsentense FromBuddie(APRSData.Buddie buddie) + { + CNBBEsentense res = new CNBBEsentense(); + res.Accuracy = buddie.PositionIsValid; + res.COG = res.HDG = (ushort)buddie.course; + res.Latitude = buddie.lat; + res.Longitude = buddie.lon; + res.SOG = (uint)buddie.speed; + res.VesselName = buddie.name; + res.MMSI = APRSData.Buddie.MMSI(buddie.name); + return res; + } + + public byte[] ToAIS() + { + byte[] unpackedBytes = new byte[39]; + + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); // type + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 46, 10, (int)(SOG / 1.852 * 10)); // speed + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 56, 1, Accuracy ? 1 : 0); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 57, 28, (int)(Longitude * 600000)); + AISTransCoder.SetBitsAsSignedInt(unpackedBytes, 85, 27, (int)(Latitude * 600000)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 112, 12, (int)(COG * 10.0)); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 124, 9, HDG); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 133, 6, 60); + AISTransCoder.SetAisString(unpackedBytes, 143, 120, VesselName); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 263, 8, ShipType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 301, 4, 1); + return unpackedBytes; + } + + public override string ToString() + { + return AISTransCoder.EnpackAisToString(ToAIS()); + } + + public string ToPacketFrame() + { + string s = this.ToString(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string Frame { get { return this.ToString(); } } + public string PacketFrame { get { return this.ToPacketFrame(); } } + } + + // 24 + // Static Data Report + public class StaticDataReport + { + private const int length = 168; + + private uint pType = 24; + private uint pRepeat = 0; + + public uint MMSI; + public string VesselName; + public int ShipType = 0; + public uint IMOShipID; + public string CallSign; + + public static StaticDataReport FromAIS(byte[] unpackedBytes) + { + StaticDataReport res = new StaticDataReport(); + res.pType = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 0, 6); + if (res.pType != 24) return res; + + res.pRepeat = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 6, 2); + res.MMSI = (uint)AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 8, 30); + res.VesselName = AISTransCoder.GetAisString(unpackedBytes, 40, 120); + res.ShipType = AISTransCoder.GetBitsAsUnsignedInt(unpackedBytes, 40, 8); + res.CallSign = AISTransCoder.GetAisString(unpackedBytes, 90, 42); + return res; + } + + public static StaticDataReport FromAIS(string ais) + { + byte[] unp = AISTransCoder.UnpackAisEncoding(ais); + return FromAIS(unp); + } + + public static StaticDataReport FromBuddie(APRSData.Buddie buddie) + { + StaticDataReport res = new StaticDataReport(); + res.VesselName = res.CallSign = buddie.name; + res.MMSI = res.IMOShipID = APRSData.Buddie.MMSI(buddie.name); + return res; + } + + public override string ToString() + { + return ToStringA(); + } + + public byte[] ToAISa() + { + byte[] unpackedBytes = new byte[21]; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, (int)pType); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, (int)pRepeat); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 2, 0); // partA + AISTransCoder.SetAisString(unpackedBytes, 40, 120, VesselName); + return unpackedBytes; + } + + public string ToStringA() + { + return AISTransCoder.EnpackAisToString(ToAISa()); + } + + public byte[] ToAISb() + { + byte[] unpackedBytes = new byte[21]; + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 0, 6, 24); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 6, 2, 0); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 8, 30, (int)MMSI); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 38, 2, 1); // partB + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 40, 8, (int)ShipType); + AISTransCoder.SetAisString(unpackedBytes, 90, 42, CallSign); + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 132, 9, 4); // A + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 141, 9, 1); // B + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 150, 6, 1); // C + AISTransCoder.SetBitsAsUnsignedInt(unpackedBytes, 156, 6, 2); // D + return unpackedBytes; + } + + public string ToStringB() + { + return AISTransCoder.EnpackAisToString(ToAISb()); + } + + public string ToPacketFrameA() + { + string s = this.ToStringA(); + s = "!AIVDM,1,1,,A," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + + public string ToPacketFrameB() + { + string s = this.ToStringA(); + s = "!AIVDM,1,1,,B," + s + ",0"; + s += "*" + AISTransCoder.Checksum(s); + return s; + } + } +} diff --git a/SimpleAPRSserver/AISServer.cs b/SimpleAPRSserver/AISServer.cs new file mode 100644 index 0000000..0320268 --- /dev/null +++ b/SimpleAPRSserver/AISServer.cs @@ -0,0 +1,202 @@ +/******************************************* +* * +* Simple AIS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.IO; +using System.Threading; +using System.Net; +using System.Net.Sockets; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml; +using System.Xml.Serialization; + +namespace SimpleAPRSserver +{ + public class AISServer : SimpleServersPBAuth.ThreadedTCPServer + { + private APRSServer aprsServer = null; + public string ServerName = "SimpleAISServer"; + + public AISServer(APRSServer aprsServer) : base() { this.aprsServer = aprsServer; } + public AISServer(APRSServer aprsServer, int Port) : base(Port) { this.aprsServer = aprsServer; } + public AISServer(APRSServer aprsServer, IPAddress IP, int Port) : base(IP, Port) { this.aprsServer = aprsServer; } + ~AISServer() { this.Dispose(); } + + private Mutex acMutex = new Mutex(); + private Dictionary aisClients = new Dictionary(); + + protected override void GetClient(TcpClient Client, ulong clientID) + { + try { GetAISClientConnected(Client, clientID); } + catch { }; + + int rCounter = 0; + bool loop = true; + while (loop) + { + try + { + string line = ""; + int bRead = -1; + int posCRLF = -1; + int receivedBytes = 0; + + if (Client.Available > 0) + while (((bRead = Client.GetStream().ReadByte()) >= 0)) // doesn't work correct + { + receivedBytes++; + line += (char)bRead; // standard symbol + + if ((receivedBytes == 1) && (line != "!")) { line = ""; break; }; + if ((receivedBytes == 2) && (line != "!A")) { line = ""; break; }; + if ((receivedBytes == 3) && (line != "!AI")) { line = ""; break; }; + if ((receivedBytes == 4) && (line != "!AIV")) { line = ""; break; }; + if ((receivedBytes == 5) && (line != "!AIVD")) { line = ""; break; }; + + if (bRead == 0x0A) posCRLF = line.IndexOf("\n"); + if (posCRLF >= 0 || line.Length > 1024) { break; }; + }; + if (!String.IsNullOrEmpty(line)) + GetAISClientData(Client, clientID, line.Trim()); + } + catch { }; + + if (!isRunning) loop = false; + + if (rCounter >= 600) // 30s ping + { + try + { + if (!IsConnected(Client)) break; + SafetyRelatedBroadcastMessage sbm = new SafetyRelatedBroadcastMessage("#PING, " + ServerName.ToUpper() + " " + APRSServer.GetVersion().ToUpper()); + string frm = sbm.ToPacketFrame() + "\r\n"; + byte[] ret = Encoding.ASCII.GetBytes(frm); + Client.GetStream().Write(ret, 0, ret.Length); + Client.GetStream().Flush(); + rCounter = 0; + } + catch { loop = false; }; + }; + System.Threading.Thread.Sleep(50); + rCounter++; + }; + + try { GetAISClientDisconnected(Client, clientID); } + catch { }; + } + + protected virtual void GetAISClientConnected(TcpClient Client, ulong clientID) + { + acMutex.WaitOne(); + aisClients.Add(clientID, Client); + acMutex.ReleaseMutex(); + + SafetyRelatedBroadcastMessage sbm = new SafetyRelatedBroadcastMessage("#WELCOME TO " + ServerName.ToUpper() + " " + APRSServer.GetVersion().ToUpper()); + string frm = sbm.ToPacketFrame() + "\r\n"; + byte[] ret = Encoding.ASCII.GetBytes(frm); + Client.GetStream().Write(ret, 0, ret.Length); + Client.GetStream().Flush(); + + PassBuds(Client); + } + + protected virtual void GetAISClientDisconnected(TcpClient Client, ulong clientID) + { + acMutex.WaitOne(); + aisClients.Remove(clientID); + acMutex.ReleaseMutex(); + } + + protected virtual void GetAISClientData(TcpClient Client, ulong clientID, string line) + { + AISTransCoder.AISPacket pRec = AISTransCoder.AISPacket.FromPacketFrame(line); + if (!pRec.Valid) + return; + else + if ((pRec.SafetyMessage != null) && (!String.IsNullOrEmpty(pRec.SafetyMessage.Message)) && (pRec.SafetyMessage.Message.StartsWith("#"))) + return; + + // Broadcast + if(aprsServer.AISBetween) + Broadcast(line, clientID); + } + + protected virtual void Broadcast(string message) + { + Broadcast(message, ulong.MaxValue); + } + + protected virtual void Broadcast(string message, ulong instedOf) + { + string toSend = message; + if (!toSend.EndsWith("\n")) toSend += "\r\n"; + byte[] packet = System.Text.Encoding.ASCII.GetBytes(toSend); + + acMutex.WaitOne(); + foreach (ulong key in aisClients.Keys) + { + if (key == instedOf) continue; + try + { + aisClients[key].GetStream().Write(packet, 0, packet.Length); + aisClients[key].GetStream().Flush(); + } + catch { }; + }; + acMutex.ReleaseMutex(); + } + + public void Broadcast(APRSData.Buddie bud) + { + PositionReportClassA a = PositionReportClassA.FromBuddie(bud); + string ln1 = "!AIVDM,1,1,,A," + a.ToString() + ",0"; + PositionReportClassAExt ae = PositionReportClassAExt.FromBuddie(bud); + string frm = a.ToPacketFrame() + "\r\n" + ae.ToPacketFrame() + "\r\n"; + Broadcast(frm); + } + + // Send Buddies to client + private void PassBuds(TcpClient Client) + { + if (!aprsServer.StoreGPSInMemory) return; + aprsServer.ClearBuds(); + + aprsServer.budsMutex.WaitOne(); + if (aprsServer.BUDs.Count > 0) + for (int i = 0; i < aprsServer.BUDs.Count; i++) + { + PositionReportClassB a = PositionReportClassB.FromBuddie(aprsServer.BUDs[i]); + string frm = a.ToPacketFrame() + "\r\n"; + byte[] toSend = System.Text.Encoding.ASCII.GetBytes(frm); + try + { + Client.GetStream().Write(toSend, 0, toSend.Length); + Client.GetStream().Flush(); + } + catch { }; + }; + aprsServer.budsMutex.ReleaseMutex(); + } + + // Get Connected Clients + public string[] GetClients() + { + List res = new List(); + acMutex.WaitOne(); + foreach(ulong Key in aisClients.Keys) + { + IPEndPoint ipp = (IPEndPoint)aisClients[Key].Client.RemoteEndPoint; + res.Add(ipp.Address.ToString() + ":" + ipp.Port.ToString()); + }; + acMutex.ReleaseMutex(); + return res.ToArray(); + } + } + +} diff --git a/SimpleAPRSserver/APRSData.cs b/SimpleAPRSserver/APRSData.cs new file mode 100644 index 0000000..b36f9fd --- /dev/null +++ b/SimpleAPRSserver/APRSData.cs @@ -0,0 +1,386 @@ +/******************************************* +* * +* Simple APRS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.Text.RegularExpressions; +using System.IO; +using System.Web; +using System.Xml; +using System.Xml.Serialization; +using System.Security; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using System.Reflection; + +namespace SimpleAPRSserver +{ + public class APRSData + { + public static int CallsignChecksum(string callsign) + { + if (callsign == null) return 99999; + if (callsign.Length == 0) return 99999; + if (callsign.Length > 10) return 99999; + + int stophere = callsign.IndexOf("-"); + if (stophere > 0) callsign = callsign.Substring(0, stophere); + string realcall = callsign.ToUpper(); + while (realcall.Length < 10) realcall += " "; + + // initialize hash + int hash = 0x73e2; + int i = 0; + int len = realcall.Length; + + // hash callsign two bytes at a time + while (i < len) + { + hash ^= (int)(realcall.Substring(i, 1))[0] << 8; + hash ^= (int)(realcall.Substring(i + 1, 1))[0]; + i += 2; + } + // mask off the high bit so number is always positive + return hash & 0x7fff; + } + + public static bool ParseAPRSRoute(string line, out string callsign, out string route, out string packet) + { + callsign = ""; route = ""; packet = ""; + if (line.IndexOf("#") == 0) return false; // comment packet + int fChr = line.IndexOf(">"); + if (fChr <= 1) return false; // invalid packet + int sChr = line.IndexOf(":"); + if (sChr < fChr) return false; // invalid packet + + callsign = line.Substring(0, fChr); + route = line.Substring(fChr + 1, sChr - fChr - 1); + packet = line.Substring(sChr + 1); + return true; + } + + public static Buddie ParseAPRSPacket(string line) + { + if (line.IndexOf("#") == 0) return null; // comment packet + + // Valid APRS? + int fChr = line.IndexOf(">"); + if (fChr <= 1) return null; // invalid packet + int sChr = line.IndexOf(":"); + if (sChr < fChr) return null; // invalid packet + + string callsign = line.Substring(0, fChr); + string pckroute = line.Substring(fChr + 1, sChr - fChr - 1); + string packet = line.Substring(sChr); + + if (packet.Length < 2) return null; // invalid packet + + Buddie b = new Buddie(callsign, 0, 0, 0, 0); + b.APRS = line; + + + switch (packet[1]) + { + /* Object */ + case ';': + int sk0 = Math.Max(packet.IndexOf("*", 2, 10), packet.IndexOf("_", 2, 10)); + if (sk0 < 0) return null; + string obj_name = packet.Substring(2, sk0 - 2).Trim(); + if (packet.IndexOf("*") > 0) + return ParseAPRSPacket(obj_name + ">" + pckroute + ":@" + packet.Substring(sk0 + 1)); // set object name as callsign and packet as position + break; + + /* Item Report Format */ + case ')': + int sk1 = Math.Max(packet.IndexOf("!", 2, 10), packet.IndexOf("_", 2, 10)); + if (sk1 < 0) return null; + string rep_name = packet.Substring(2, sk1 - 2).Trim(); + if (packet.IndexOf("!") > 0) + return ParseAPRSPacket(rep_name + ">" + pckroute + ":@" + packet.Substring(sk1 + 1)); // set object name as callsign and packet as position + break; + + /* Positions Reports */ + case '!': // Positions with no time, no APRS + case '=': // Position with no time, but APRS + case '/': // Position with time, no APRS + case '@': // Position with time and APRS + { + string pos = packet.Substring(2); + if (pos[0] == '!') break; // Raw Weather Data + + DateTime received = DateTime.UtcNow; + if (pos[0] != '/') // not compressed data firsts + { + switch (packet[8]) + { + case 'z': // zulu ddHHmm time + received = new DateTime(DateTime.Now.Year, DateTime.Now.Month, int.Parse(packet.Substring(2, 2)), + int.Parse(packet.Substring(4, 2)), int.Parse(packet.Substring(6, 2)), 0, DateTimeKind.Utc); + pos = packet.Substring(9); + break; + case '/': // local ddHHmm time + received = new DateTime(DateTime.Now.Year, DateTime.Now.Month, int.Parse(packet.Substring(2, 2)), + int.Parse(packet.Substring(4, 2)), int.Parse(packet.Substring(6, 2)), 0, DateTimeKind.Local); + pos = packet.Substring(9); + break; + case 'h': // HHmmss time + received = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, + int.Parse(packet.Substring(2, 2)), int.Parse(packet.Substring(4, 2)), int.Parse(packet.Substring(6, 2)), DateTimeKind.Local); + pos = packet.Substring(9); + break; + }; + }; + b.last = received; + + string aftertext = ""; + char prim_or_sec = '/'; + char symbol = '>'; + + if (pos[0] == '/') // compressed data YYYYXXXXcsT + { + string yyyy = pos.Substring(1, 4); + b.lat = 90 - (((byte)yyyy[0] - 33) * Math.Pow(91, 3) + ((byte)yyyy[1] - 33) * Math.Pow(91, 2) + ((byte)yyyy[2] - 33) * 91 + ((byte)yyyy[3] - 33)) / 380926; + string xxxx = pos.Substring(5, 4); + b.lon = -180 + (((byte)xxxx[0] - 33) * Math.Pow(91, 3) + ((byte)xxxx[1] - 33) * Math.Pow(91, 2) + ((byte)xxxx[2] - 33) * 91 + ((byte)xxxx[3] - 33)) / 190463; + symbol = pos[9]; + string cmpv = pos.Substring(10, 2); + int addIfWeather = 0; + if (cmpv[0] == '_') // with weather report + { + symbol = '_'; + cmpv = pos.Substring(11, 2); + addIfWeather = 1; + }; + if (cmpv[0] != ' ') // ' ' - no data + { + int cmpt = ((byte)pos[12 + addIfWeather] - 33); + if (((cmpt & 0x18) == 0x18) && (cmpv[0] != '{') && (cmpv[0] != '|')) // RMC sentence with course & speed + { + b.course = (short)(((byte)cmpv[0] - 33) * 4); + b.speed = (short)(((int)Math.Pow(1.08, ((byte)cmpv[1] - 33)) - 1) * 1.852); + }; + }; + aftertext = pos.Substring(13 + addIfWeather); + b.iconSymbol = "/" + symbol.ToString(); + } + else // not compressed + { + if (pos.Substring(0, 18).Contains(" ")) return null; // nearest degree + + b.lat = double.Parse(pos.Substring(2, 5), System.Globalization.CultureInfo.InvariantCulture); + b.lat = double.Parse(pos.Substring(0, 2), System.Globalization.CultureInfo.InvariantCulture) + b.lat / 60; + if (pos[7] == 'S') b.lat *= -1; + + b.lon = double.Parse(pos.Substring(12, 5), System.Globalization.CultureInfo.InvariantCulture); + b.lon = double.Parse(pos.Substring(9, 3), System.Globalization.CultureInfo.InvariantCulture) + b.lon / 60; + if (pos[17] == 'W') b.lon *= -1; + + prim_or_sec = pos[8]; + symbol = pos[18]; + aftertext = pos.Substring(19); + + b.iconSymbol = prim_or_sec.ToString() + symbol.ToString(); + }; + + // course/speed or course/speed/bearing/NRQ + if ((symbol != '_') && (aftertext.Length >= 7) && (aftertext[3] == '/')) // course/speed 000/000 + { + short.TryParse(aftertext.Substring(0, 3), out b.course); + short.TryParse(aftertext.Substring(4, 3), out b.speed); + aftertext = aftertext.Remove(0, 7); + }; + + b.Comment = aftertext.Trim(); + + }; + break; + /* All Other */ + default: + // + break; + }; + if (line.IndexOf(":>") > 0) b.Status = line.Substring(line.IndexOf(":>") + 2); + return b; + } + + public class Buddie + { + public static Regex BuddieNameRegex = new Regex("^([A-Z0-9]{3,9})$"); + public static Regex BuddieCallSignRegex = new Regex(@"^([A-Z0-9\-]{3,9})$"); + public static string symbolAny = "123456789ABCDEFGHJKLMNOPRSTUVWXYZ";//"/*//C/F/M/P/U/X/Y/Z/[/a/b/e/f/j/k/p/s/u/v\\O\\j\\k\\u\\v/0/1/2/3/4/5/6/7/8/9/'/O"; + public static int symbolAnyLength = 33;//40; + + public static bool IsNullIcon(string symbol) + { + return (symbol == null) || (symbol == String.Empty) || (symbol == "//"); + } + + public bool Verified = false; + public bool Owner = false; + private string qConstruct { get { + if (Verified && Owner) return ",qAC"; + if ((!Verified) && Owner) return ",qAX"; + if (Verified && (!Owner)) return ",qAO"; + if ((!Verified) && (!Owner)) return ",qAo"; + return ""; + } } + + public string name; + public double lat; + public double lon; + /// + /// Speed in kmph; + /// mph = kmph * 0.62137119; + /// knots = kmph / 1.852; + /// mps = kmps / 3.6 + /// + public short speed; + public short course; + public uint alt; + public string APRS = ""; + public string iconSymbol = "//"; + private string _comment = ""; + public string Status = ""; + public DateTime last; + + public byte[] APRSData { get { return String.IsNullOrEmpty(APRS) ? null : System.Text.Encoding.ASCII.GetBytes(APRS + "\r\n"); } set { APRS = (value == null) || (value.Length == 0) ? "" : System.Text.Encoding.ASCII.GetString(value).Replace("\r", "").Replace("\n", ""); } } + + public string Comment + { + get + { + return _comment; + } + set + { + _comment = value; + Regex rx = new Regex(@"/A=(?\d+)", RegexOptions.IgnoreCase); + Match mx = rx.Match(_comment); + if (mx.Success) alt = uint.Parse(mx.Groups[1].Value); + } + } + + public bool PositionIsValid + { + get { return (lat != 0) && (lon != 0); } + } + + public Buddie(string name, double lat, double lon, short speed, short course) + { + this.name = name.ToUpper(); + this.lat = lat; + this.lon = lon; + this.speed = speed; + this.course = course; + this.last = DateTime.UtcNow; + } + + public void SetAPRSNoDate() + { + + APRS = + name + ">APRS,TCPIP*" + qConstruct + ":=" + // Position without timestamp + APRS message + Math.Truncate(lat).ToString("00") + ((lat - Math.Truncate(lat)) * 60).ToString("00.00").Replace(",", ".") + + (lat > 0 ? "N" : "S") + + iconSymbol[0] + + Math.Truncate(lon).ToString("000") + ((lon - Math.Truncate(lon)) * 60).ToString("00.00").Replace(",", ".") + + (lon > 0 ? "E" : "W") + + iconSymbol[1] + + course.ToString("000") + "/" + Math.Truncate(speed / 1.852).ToString("000") + + ((this.Comment != null) && (this.Comment != String.Empty) ? " " + this.Comment : "") + + "\r\n"; + APRSData = Encoding.ASCII.GetBytes(APRS); + } + + public void SetAPRSWithDate() + { + APRS = + // ddHHmmz or HHmmssh + name + ">APRS,TCPIP*" + qConstruct + ":@"; // Position with timestamp + APRS message + if (DateTime.UtcNow.Subtract(this.last).TotalHours <= 23.5) + APRS += this.last.ToString("HHmmss") + "h"; + else + APRS += this.last.ToString("ddHHmm") + "z"; + APRS += + Math.Truncate(lat).ToString("00") + ((lat - Math.Truncate(lat)) * 60).ToString("00.00").Replace(",", ".") + + (lat > 0 ? "N" : "S") + + iconSymbol[0] + + Math.Truncate(lon).ToString("000") + ((lon - Math.Truncate(lon)) * 60).ToString("00.00").Replace(",", ".") + + (lon > 0 ? "E" : "W") + + iconSymbol[1] + + course.ToString("000") + "/" + Math.Truncate(speed / 1.852).ToString("000") + + ((this.Comment != null) && (this.Comment != String.Empty) ? " " + this.Comment : "") + + "\r\n"; + APRSData = Encoding.ASCII.GetBytes(APRS); + } + + public string GetWebSocketText() + { + return String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0:HH:mm:ss dd.MM.yyyy} UTC {1} >> {2:000.0000000} {3:000.0000000} {4:00000.00} {5} {6} {7}\r\n", new object[] { last, name, lat, lon, alt, course, speed, iconSymbol }); + } + + public override string ToString() + { + return String.Format("{0} >> {1} {2} {3}/{4} {5}", new object[] { name, lat, lon, speed, course, iconSymbol }); + } + + public static int Hash(string name) + { + string upname = name == null ? "" : name; + int stophere = upname.IndexOf("-"); + if (stophere > 0) upname = upname.Substring(0, stophere); + while (upname.Length < 9) upname += " "; + + int hash = 0x2017; + int i = 0; + while (i < 9) + { + hash ^= (int)(upname.Substring(i, 1))[0] << 16; + hash ^= (int)(upname.Substring(i + 1, 1))[0] << 8; + hash ^= (int)(upname.Substring(i + 2, 1))[0]; + i += 3; + }; + return hash & 0x7FFFFF; + } + + public static uint MMSI(string name) + { + string upname = name == null ? "" : name; + while (upname.Length < 9) upname += " "; + int hash = 2017; + int i = 0; + while (i < 9) + { + hash ^= (int)(upname.Substring(i, 1))[0] << 16; + hash ^= (int)(upname.Substring(i + 1, 1))[0] << 8; + hash ^= (int)(upname.Substring(i + 2, 1))[0]; + i += 3; + }; + return (uint)(hash & 0xFFFFFF); + } + + public void FillFrom(Buddie b) + { + this.name = b.name; + this.lat = b.lat; + this.lon = b.lon; + this.speed = b.speed; + this.course = b.course; + this.alt = b.alt; + if (!String.IsNullOrEmpty(b.APRS)) this.APRS = b.APRS; + if (!IsNullIcon(b.iconSymbol)) this.iconSymbol = b.iconSymbol; + if (!String.IsNullOrEmpty(b._comment)) this._comment = b._comment; + if (!String.IsNullOrEmpty(b.Status)) this.Status = b.Status; + this.last = b.last; + } + } + } +} diff --git a/SimpleAPRSserver/APRSServer.cs b/SimpleAPRSserver/APRSServer.cs new file mode 100644 index 0000000..0ccbd35 --- /dev/null +++ b/SimpleAPRSserver/APRSServer.cs @@ -0,0 +1,632 @@ +/******************************************* +* * +* Simple APRS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.Text.RegularExpressions; +using System.IO; +using System.Web; +using System.Xml; +using System.Xml.Serialization; +using System.Security; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using System.Reflection; + +namespace SimpleAPRSserver +{ + public class APRSServer : SimpleServersPBAuth.ThreadedHttpServer + { + public static string Build = "HTTP+AIS"; + + public bool OnlyValidPasswordUsers = false; + public bool PassDataOnlyValidUsers = false; + public bool PassDataOnlyLoggedUser = false; + public bool StoreGPSInMemory = false; + public bool OutConfigToConsole = true; + public bool OutAPRStoConsole = true; + public bool OutConnectionsToConsole = true; + public bool OutBroadcastsMessages = false; + public bool OutBuddiesCount = false; + public int StoreGPSMaxTime = 1440; // in sec + public int HTTPServer = 0; // 80 + public int AISServer = 0; // 1080 + public bool AISBetween = false; + public bool EnableClientFilter = false; + public bool PassBackAPRSPackets = false; + public List banlist = new List(); + // support remote servers ? + + private Mutex lpMutex = new Mutex(); + private List lastPackets = new List(30); + public string[] LastAPRSPackets + { + get + { + if (lastPackets.Count == 0) return new string[0]; + lpMutex.WaitOne(); + string[] res = lastPackets.ToArray(); + lpMutex.ReleaseMutex(); + return res; + } + } + + private HttpAPRSServer httpServer = null; + private AISServer aisServer = null; + + internal Mutex aprsMutex = new Mutex(); + internal List aprsClients = new List(); + + internal Mutex lastgeoMutex = new Mutex(); + internal Dictionary LastPositions = new Dictionary(); + + internal Mutex budsMutex = new Mutex(); + internal List BUDs = new List(); + + public APRSServer() : base() { InitConfig(); } + public APRSServer(int Port) : base(Port) { InitConfig(); } + public APRSServer(IPAddress IP, int Port) : base(IP, Port) { InitConfig(); } + ~APRSServer() { this.Dispose(); } + + private void InitConfig() + { + this.ServerName = "SimpleAPRSserver"; + this.ListenPort = 14580; + this.ListenIPAllow = new string[0]; + string fName = SimpleServersPBAuth.TTCPServer.GetCurrentDir() + @"\config.xml"; + XmlDocument xd = new XmlDocument(); + xd.Load(fName); + XmlNodeList nl = xd.SelectSingleNode("config").ChildNodes; + if ((nl.Count > 0) && OutConfigToConsole) Console.WriteLine("Loading config from `config.xml`..."); + foreach (XmlNode nn in nl) + { + string name = nn.Name; + string val = nn.ChildNodes.Count == 0 ? null : nn.ChildNodes[0].InnerText; + if (!String.IsNullOrEmpty(val)) + { + if (name == "Ban") + { + banlist.Add(val.ToUpper()); + if (OutConfigToConsole) + Console.WriteLine(" Ban: " + val.ToUpper()); + continue; + }; + FieldInfo fi = this.GetType().GetField(name); + if ((fi != null) && (fi.IsPublic)) + { + if (OutConfigToConsole) + Console.WriteLine(" {0}: {1}", name, val); + if (fi.FieldType == typeof(int)) + fi.SetValue(this, int.Parse(val)); + if (fi.FieldType == typeof(string)) + fi.SetValue(this, val); + if (fi.FieldType == typeof(bool)) + fi.SetValue(this, val == "1"); + }; + if (fi == null) + foreach (PropertyInfo prop in this.GetType().GetProperties()) + if (prop.Name == name) + { + if (OutConfigToConsole) + Console.WriteLine(" {0}: {1}", name, val); + if (prop.PropertyType == typeof(string)) + prop.SetValue(this, val, null); + if (prop.PropertyType == typeof(int)) + prop.SetValue(this, int.Parse(val), null); + if (prop.PropertyType == typeof(ushort)) + prop.SetValue(this, ushort.Parse(val), null); + if (prop.PropertyType == typeof(bool)) + prop.SetValue(this, val == "1", null); + if (prop.PropertyType == typeof(SimpleServersPBAuth.ThreadedTCPServer.Mode)) + prop.SetValue(this, (SimpleServersPBAuth.ThreadedTCPServer.Mode)int.Parse(val), null); + if (prop.PropertyType == typeof(string[])) + { + List lst = new List((string[])prop.GetValue(this, null)); + lst.Add(val); + prop.SetValue(this, lst.ToArray(), null); + }; + }; + }; + }; + if ((nl.Count > 0) && OutConfigToConsole) Console.WriteLine(""); + + this.ServerName = this.ServerName.Replace(" ", "_"); + + if (HTTPServer > 0) + { + httpServer = new HttpAPRSServer(this, HTTPServer); + httpServer.ServerName = this.ServerName; + httpServer.AllowBrowseFiles = true; + httpServer.ListenIPMode = this.ListenIPMode; + httpServer.ListenIPAllow = this.ListenIPAllow; + httpServer.ListenIPDeny = this.ListenIPDeny; + httpServer.ListenMacMode = this.ListenMacMode; + httpServer.ListenMacAllow = this.ListenMacAllow; + httpServer.ListenMacDeny = this.ListenMacDeny; + httpServer.MaxClients = this.MaxClients; + }; + + if (AISServer > 0) + { + aisServer = new AISServer(this, AISServer); + aisServer.ServerName = this.ServerName.ToUpper(); + aisServer.ListenIPMode = this.ListenIPMode; + aisServer.ListenIPAllow = this.ListenIPAllow; + aisServer.ListenIPDeny = this.ListenIPDeny; + aisServer.ListenMacMode = this.ListenMacMode; + aisServer.ListenMacAllow = this.ListenMacAllow; + aisServer.ListenMacDeny = this.ListenMacDeny; + aisServer.MaxClients = this.MaxClients; + }; + } + + public bool AISRunning { get { return aisServer == null ? false : aisServer.Running; } } + public ulong AISAlive { get { return aisServer == null ? 0 : aisServer.ClientsAlive; } } + public ulong AISCounter { get { return aisServer == null ? 0 : aisServer.ClientsCounter; } } + public string[] AISClients { get { return aisServer == null ? new string[0] : aisServer.GetClients(); } } + public int AISPort { get { return aisServer == null ? 0 : aisServer.ServerPort; } } + + public override void Start() + { + base.Start(); + Console.WriteLine("ServerName: {0}", this.ServerName); + Console.WriteLine(" APRS Started at: {0}:{1}", this.ServerIP.ToString(), this.ServerPort); + if (httpServer != null) + { + httpServer.Start(); + Console.WriteLine(" HTTP Started at: {0}:{1}", httpServer.ServerIP.ToString(), httpServer.ServerPort); + }; + if (aisServer != null) + { + aisServer.Start(); + Console.WriteLine(" AIS Started at: {0}:{1}", httpServer.ServerIP.ToString(), aisServer.ServerPort); + }; + Console.WriteLine(); + } + + public override void Stop() + { + base.Stop(); + if (httpServer != null) httpServer.Stop(); + if (aisServer != null) aisServer.Stop(); + httpServer = null; + } + + // Get Client, threaded + protected override void GetClient(TcpClient Client, ulong clientID) + { + int bRead = -1; + int posCRLF = -1; + int receivedBytes = 0; + string receivedText = ""; + + // APRS Server Welcome + byte[] toSend = System.Text.Encoding.ASCII.GetBytes("# " + ServerName + " v" + GetVersion() + "\r\n"); + Send(Client.GetStream(), toSend); + + //while ((Client.Available > 0) && ((bRead = Client.GetStream().ReadByte()) >= 0)) // doesn't work correct + while ((bRead = Client.GetStream().ReadByte()) >= 0) + { + receivedBytes++; + receivedText += (char)bRead; + + if ((receivedBytes == 1) && (receivedText != "u")) return; + if ((receivedBytes == 2) && (receivedText != "us")) return; + if ((receivedBytes == 3) && (receivedText != "use")) return; + if ((receivedBytes == 4) && (receivedText != "user")) return; + + if (bRead == 0x0A) posCRLF = receivedText.IndexOf("\n"); // End of single packet + if (posCRLF >= 0 || receivedText.Length > 2048) { break; }; // BAD CLIENT + }; + + GetAPRSClient(Client, clientID, receivedText); + } + + // Get APRS client, threaded + private void GetAPRSClient(TcpClient Client, ulong clientID, string firstPacket) + { + string loginstring = firstPacket.Replace("\r", "").Replace("\n", ""); + string res = "# logresp user unverified, server " + ServerName.ToUpper() + " v" + GetVersion(); + string re2 = ""; + + Match rm = Regex.Match(loginstring, @"^user\s([\w\-]{3,})\spass\s([\d\-]+)\svers\s([\w\d\-.]+)(?:\s([\w\d\-.\+]+))?"); + if (!rm.Success) return; // not valid + + string callsign = rm.Groups[1].Value.ToUpper(); + if (banlist.Contains(callsign)) return; + + string password = rm.Groups[2].Value; + string software = ""; + string version = ""; + try { software = rm.Groups[3].Value; } catch { }; + try { version = rm.Groups[4].Value; } catch { }; + string doptext = loginstring.Substring(rm.Groups[0].Value.Length).Trim(); + + int psw = -1; + if (!int.TryParse(password, out psw)) return; + + ClientData cd = new ClientData(Client, clientID); + cd.user = callsign; + cd.SoftNam = software; + cd.SoftVer = version; + if (cd.user.Contains("-")) cd.user = cd.user.Substring(0, cd.user.IndexOf("-")); // remove SSID + + if (EnableClientFilter && (doptext.IndexOf("filter ") >= 0)) + { + string fres = cd.SetFilter(doptext.Substring(doptext.IndexOf("filter") + 7), cd.user); + if(!String.IsNullOrEmpty(fres)) re2 = "# filter '" + fres + "' is active\r\n"; + }; + + // check for valid HAM user or for valid OruxPalsServer user + if ((psw == APRSData.CallsignChecksum(callsign)) || (psw == APRSData.Buddie.Hash(callsign))) + { + cd.validated = true; + res = "# logresp " + callsign + " verified, server " + ServerName + " v" + GetVersion() + "\r\n"; + byte[] ret = Encoding.ASCII.GetBytes(res); + try { Send(cd.stream, ret); } + catch { }; + } + else + { + cd.validated = false; + res = "# logresp " + callsign + " unverified, server " + ServerName + " v" + GetVersion() + "\r\n"; + byte[] ret = Encoding.ASCII.GetBytes(res); + try { Send(cd.stream, ret); } + catch { }; + if (OnlyValidPasswordUsers) return; + }; + if (re2 != "") + { + byte[] ret = Encoding.ASCII.GetBytes(re2); + try { Send(cd.stream, ret); } + catch { }; + }; + + GetAPRSClient(cd); + } + + // Get APRS client, threaded + private void GetAPRSClient(ClientData cd) + { + aprsMutex.WaitOne(); + aprsClients.Add(cd); + aprsMutex.ReleaseMutex(); + + if (OutConnectionsToConsole) + Console.WriteLine("APRS client connected from: {0}:{1} as {2} via {4}, total {3}", ((IPEndPoint)cd.client.Client.RemoteEndPoint).Address.ToString(), ((IPEndPoint)cd.client.Client.RemoteEndPoint).Port, cd.user, aprsClients.Count, cd.SoftNam + " " + cd.SoftVer); + + PassBuds(cd); + + if ((OutBroadcastsMessages) && (BUDs.Count > 0)) + Console.WriteLine("Pass {0} buddies to APRS {1}:{2}", BUDs.Count, ((IPEndPoint)cd.client.Client.RemoteEndPoint).Address.ToString(), ((IPEndPoint)cd.client.Client.RemoteEndPoint).Port); + + int rxCount = 0; + int rxAvailable = 0; + byte[] rxBuffer = new byte[65536]; + bool loop = true; + int rCounter = 0; + string rxText = ""; + while (loop) + { + try { rxAvailable = cd.client.Available; } + catch { break; }; + + // Read Incoming Data + while (rxAvailable > 0) + { + try { rxAvailable -= (rxCount = cd.stream.Read(rxBuffer, 0, rxBuffer.Length > rxAvailable ? rxAvailable : rxBuffer.Length)); } + catch { break; }; + if (rxCount > 0) rxText += Encoding.ASCII.GetString(rxBuffer, 0, rxCount); + }; + + // Read Packet + if ((rxText != "") && (rxText.IndexOf("\n") > 0)) + { + OnAPRSData(cd, rxText); + rxText = ""; + }; + + if (!isRunning) loop = false; + if (rCounter >= 600) // 30s ping + { + try + { + if (!IsConnected(cd.client)) break; + byte[] ping = System.Text.Encoding.ASCII.GetBytes("#ping; server " + ServerName + " v" + GetVersion() + "\r\n"); + Send(cd.stream, ping); + rCounter = 0; + } + catch { loop = false; }; + }; + System.Threading.Thread.Sleep(50); + rCounter++; + }; + + aprsMutex.WaitOne(); + for (int i = 0; i < aprsClients.Count; i++) + if (aprsClients[i].id == cd.id) + { + aprsClients.RemoveAt(i); + break; + }; + aprsMutex.ReleaseMutex(); + + if (OutConnectionsToConsole) + Console.WriteLine("APRS client disconnected from: {0}:{1} as {2}, total {3}", ((IPEndPoint)cd.client.Client.RemoteEndPoint).Address.ToString(), ((IPEndPoint)cd.client.Client.RemoteEndPoint).Port, cd.user, aprsClients.Count); + } + + // On APRS User Data // they can upload data to server + private void OnAPRSData(ClientData cd, string line) + { + line = line.Trim(); + if (String.IsNullOrEmpty(line)) return; + + if (OutAPRStoConsole) + Console.WriteLine("APRS from {0}:{1}:: {2}", cd.IP, ((IPEndPoint)cd.client.Client.RemoteEndPoint).Port, line.Replace("\r", "").Replace("\n", "")); + + UpdateLastPackets(line, cd); + + // COMMENT STRING + if (line.IndexOf("#") == 0) + { + string filter = ""; + if (line.IndexOf("filter") > 0) filter = line.Substring(line.IndexOf("filter")); + // filter ... active + if (EnableClientFilter && (filter != "")) + { + string fres = cd.SetFilter(filter.Substring(7), cd.user); + string resp = "# filter '" + fres + "' is active\r\n"; + byte[] bts = Encoding.ASCII.GetBytes(resp); + try { Send(cd.stream, bts); } + catch { } + }; + return; + }; + + // Ping Packet + if (line.IndexOf(">online") > 0) return; + + // if no pass any incoming data from user with bad password + if (PassDataOnlyValidUsers && (!cd.validated)) return; + + // Broadcast to APRS + bool broadcasted = false; + // Broadcast packets only if packet sender is a logged aprs user, + if (PassDataOnlyLoggedUser) + { + if (line.StartsWith(cd.user + ">") || line.StartsWith(cd.user + "-")) + { + broadcasted = true; + BroadcastAPRS(line, cd, (long)cd.id); + }; + } + else + { + broadcasted = true; + BroadcastAPRS(line, cd, (long)cd.id); + }; + + // if Not Broadcasted -> No Store + if (!broadcasted) return; + + // PARSE NORMAL PACKET + cd.lastBuddie = null; + try + { + cd.lastBuddie = APRSData.ParseAPRSPacket(line); + cd.lastBuddie.Verified = cd.validated; + cd.lastBuddie.Owner = cd.user == cd.lastBuddie.name; + } + catch { }; + + if (cd.lastBuddie == null) return; // Bad Data + if ((!cd.validated) && (!cd.lastBuddie.PositionIsValid)) return; // No pass nonGPS data from not validated users + + // Update Buddies (Last User Info) + UpdateBUDs(cd.lastBuddie); + + // Broadcast GEO + if (cd.lastBuddie.PositionIsValid) + { + // Update stored positions + UpdateLastPos(cd.lastBuddie); + + // Broadcast to AIS + BroadcastAIS(cd.lastBuddie); + + // Broadcast to HTTP + BroadcastHTTP(cd.lastBuddie); + }; + } + + private void UpdateLastPackets(string packet, ClientData cd) + { + lpMutex.WaitOne(); + if (lastPackets.Count == 30) lastPackets.RemoveAt(29); + lastPackets.Insert(0,String.Format("{1:yyyy-MM-dd HH:mm:ss} UTC: {0} from {4} - {2}:{3}", packet, DateTime.UtcNow, cd.IP, cd.Port, cd.user)); + lpMutex.ReleaseMutex(); + } + + private void UpdateLastPos(APRSData.Buddie bud) + { + lastgeoMutex.WaitOne(); + if (LastPositions.ContainsKey(bud.name)) + { + LastPositions[bud.name].lon = bud.lat; + LastPositions[bud.name].lon = bud.lon; + } + else + LastPositions.Add(bud.name, new ClientAPRSFilter.GeoPos(bud.lat, bud.lon)); + lastgeoMutex.ReleaseMutex(); + } + + // Update Memory + private void UpdateBUDs(APRSData.Buddie bud) + { + if (!StoreGPSInMemory) return; + + int rmvd = 0; + budsMutex.WaitOne(); + bool ex = false; + if (BUDs.Count > 0) + for (int i = BUDs.Count - 1; i >= 0; i--) + { + double ttlm = DateTime.UtcNow.Subtract(BUDs[i].last).TotalMinutes; + if (ttlm >= StoreGPSMaxTime) + { + rmvd++; + BUDs.RemoveAt(i); + continue; + }; + if (BUDs[i].name == bud.name) + { + ex = true; + BUDs[i].FillFrom(bud); + break; + }; + }; + if ((rmvd > 0) && (OutBuddiesCount)) + Console.WriteLine("Removed {0} buddies, total {1}", rmvd, BUDs.Count); + if (!ex) + { + BUDs.Add(bud); + if (OutBuddiesCount) + Console.WriteLine("Added 1 buddies, total {0}", BUDs.Count); + }; + budsMutex.ReleaseMutex(); + } + + // Send Buddies to client + private void PassBuds(ClientData cd) + { + if (!StoreGPSInMemory) return; + ClearBuds(); + + budsMutex.WaitOne(); + if (BUDs.Count > 0) + for (int i = 0; i < BUDs.Count; i++) + { + BUDs[i].SetAPRSWithDate(); + lastgeoMutex.WaitOne(); + bool pass = !EnableClientFilter ? true : cd.PassFilter(BUDs[i].APRS, BUDs[i], LastPositions); + lastgeoMutex.ReleaseMutex(); + if (pass) try { Send(cd.stream, BUDs[i].APRSData); } + catch { }; + }; + budsMutex.ReleaseMutex(); + } + + // Clear Old Buddies + internal void ClearBuds() + { + int rmvd = 0; + budsMutex.WaitOne(); + if (BUDs.Count > 0) + for (int i = BUDs.Count - 1; i >= 0; i--) + if (DateTime.UtcNow.Subtract(BUDs[i].last).TotalMinutes >= StoreGPSMaxTime) + { + BUDs.RemoveAt(i); + rmvd++; + }; + if ((rmvd > 0) && (OutBuddiesCount)) + Console.WriteLine("Removed {0} buddies, total {1}", rmvd, BUDs.Count); + budsMutex.ReleaseMutex(); + } + + private string PlaceQAConstruct(string message, ClientData cReq) + { + string csign, rt, pckt; + if (APRSData.ParseAPRSRoute(message, out csign, out rt, out pckt)) + { + rt = (new Regex(@"qA\w,?", RegexOptions.None)).Replace(rt, "").Trim(new char[] { ',' }); + string qAdd = ""; + if (cReq.validated && (cReq.user == csign)) qAdd = "qAC"; + if ((!cReq.validated) && (cReq.user == csign)) qAdd = "qAX"; + if (cReq.validated && (cReq.user != csign)) qAdd = "qAO"; + if ((!cReq.validated) && (cReq.user != csign)) qAdd = "qAo"; + message = csign + ">"; + if (qAdd != "") + { + int pos = Math.Max(rt.IndexOf("APRS"), rt.IndexOf("PIP*")); + if (pos >= 0) + message += rt.Insert(pos + 4, "," + qAdd); + else + message += qAdd + (rt.Length > 0 ? "," : "") + rt; + }; + message += ":" + pckt; + }; + return message; + } + + // Send message to all aprs clients + public void BroadcastAPRS(string message, ClientData cReq) + { + BroadcastAPRS(message, cReq, -1); + } + + // Send message to all aprs clients + public void BroadcastAPRS(string message, ClientData cReq, long instedOf) + { + // place qA construct + message = PlaceQAConstruct(message, cReq); + string tosend = message.EndsWith("\n") ? message : message + "\r\n"; + byte[] msg = System.Text.Encoding.ASCII.GetBytes(tosend); + + aprsMutex.WaitOne(); + if (aprsClients.Count > 0) + { + if (OutBroadcastsMessages) + Console.WriteLine("Broadcast APRS: {0}", tosend.Replace("\r", "").Replace("\n", "")); + for (int i = 0; i < aprsClients.Count; i++) + { + if (!PassBackAPRSPackets) + if (instedOf != -1) + if (aprsClients[i].id == (ulong)instedOf) + continue; + lastgeoMutex.WaitOne(); + bool pass = !EnableClientFilter ? true : aprsClients[i].PassFilter(message, cReq.lastBuddie, LastPositions); + lastgeoMutex.ReleaseMutex(); + if (pass) try { Send(aprsClients[i].stream, msg); } + catch { }; + }; + }; + aprsMutex.ReleaseMutex(); + } + + // Send message to all WebSocket clients + public void BroadcastHTTP(APRSData.Buddie bud) + { + if (httpServer == null) return; + httpServer.Broadcast(bud); + } + + public void BroadcastAIS(APRSData.Buddie bud) + { + if (aisServer == null) return; + aisServer.Broadcast(bud); + } + + // Write to Net Stream + private static void Send(Stream stream, byte[] data) + { + stream.Write(data, 0, data.Length); + stream.Flush(); + } + + // Get Server Version + public static string GetVersion() + { + System.Reflection.Assembly assembly = System.Reflection.Assembly.GetExecutingAssembly(); + System.Diagnostics.FileVersionInfo fvi = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location); + return fvi.FileVersion; + } + } +} diff --git a/SimpleAPRSserver/ClientAPRSFilter.cs b/SimpleAPRSserver/ClientAPRSFilter.cs new file mode 100644 index 0000000..bc59f06 --- /dev/null +++ b/SimpleAPRSserver/ClientAPRSFilter.cs @@ -0,0 +1,690 @@ +/******************************************* +* * +* Simple APRS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.Text.RegularExpressions; +using System.IO; +using System.Web; +using System.Xml; +using System.Xml.Serialization; +using System.Security; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using System.Reflection; + +namespace SimpleAPRSserver +{ + public class ClientAPRSFilter + { + public class GeoPos + { + public double lat; + public double lon; + public GeoPos(double lat, double lon) { this.lat = lat; this.lon = lon; } + public override string ToString() + { + return String.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} {1}", lat, lon); + } + + public static uint GetLengthMeters(double StartLat, double StartLong, double EndLat, double EndLong, bool radians) + { + double D2R = Math.PI / 180; + if (radians) D2R = 1; + double dDistance = Double.MinValue; + double dLat1InRad = StartLat * D2R; + double dLong1InRad = StartLong * D2R; + double dLat2InRad = EndLat * D2R; + double dLong2InRad = EndLong * D2R; + + double dLongitude = dLong2InRad - dLong1InRad; + double dLatitude = dLat2InRad - dLat1InRad; + + // Intermediate result a. + double a = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) + + Math.Cos(dLat1InRad) * Math.Cos(dLat2InRad) * + Math.Pow(Math.Sin(dLongitude / 2.0), 2.0); + + // Intermediate result c (great circle distance in Radians). + double c = 2.0 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1.0 - a)); + + const double kEarthRadiusKms = 6378137.0000; + dDistance = kEarthRadiusKms * c; + + return (uint)Math.Round(dDistance); + } + + public static double GetLengthKm(double StartLat, double StartLong, double EndLat, double EndLong, bool radians) + { + return (double)GetLengthMeters(StartLat, StartLong, EndLat, EndLong, radians) / 1000.0; + } + } + + private const string R_r = @"(?:^|\s)r/(?[\d\.\-]+)/(?[\d\.\-]+)/(?[\d]+)"; + private const string R_p = @"(?:^|\s)p(?:/(?[^\/\s\r\n]+))+"; + private const string R_b = @"(?:^|\s)b(?:/(?[^\/\s\r\n]+))+"; + private const string R_o = @"(?:^|\s)o(?:/(?[^\/\s\r\n]+))+"; + private const string R_os = @"(?:^|\s)os(?:/(?[^\/\r\n]{3,9}))+$"; + private const string R_t = @"(?:^|\s)t/(?[poimqstunw]+)(?:/(?[^\/\s\r\n]+)/(?[\d]+))?"; + private const string R_s = @"(?:^|\s)s/(?[^\/\s\r\n]*)(?:/(?[^\/\s\r\n]*)(?:/(?[^\/\s\r\n]+))?)?"; + private const string R_d = @"(?:^|\s)d(?:/(?[^\/\s\r\n]+))+"; + private const string R_a = @"(?:^|\s)a/(?[\d\.\-]+)/(?[\d\.\-]+)/(?[\d\.\-]+)/(?[\d\.\-]+)"; + private const string R_e = @"(?:^|\s)e(?:/(?[^\/\s\r\n]+))+"; + private const string R_g = @"(?:^|\s)g(?:/(?[^\/\s\r\n]+))+"; + private const string R_u = @"(?:^|\s)u(?:/(?[^\/\s\r\n]+))+"; + private const string R_m = @"(?:^|\s)m/(?[\d]+)"; + private const string R_f = @"(?:^|\s)f/(?[^\/\s\r\n]+)/(?[\d]+)"; + + private List list = new List(); + public int Count { get { return list.Count; } } + + private string filter = ""; + public string Filter { get { return filter; } } + + private string user = "UNKNOWN"; + public string User { get { return user; } } + + public ClientAPRSFilter(string filter, string user) + { + this.filter = filter; + if (!String.IsNullOrEmpty(this.filter)) this.filter = this.filter.Trim(new char[] { '\r', '\n' }); + if (!String.IsNullOrEmpty(user)) this.user = user; + Init(); + } + + private void Init() + { + if (String.IsNullOrEmpty(filter)) return; + + F f = null; + try { if ((f = new F_r(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_p(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_b(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_o(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_os(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_t(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_s(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_d(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_a(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_e(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_g(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_u(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_m(filter, this)).Sucess) list.Add(f); } + catch { }; + try { if ((f = new F_f(filter, this)).Sucess) list.Add(f); } + catch { }; + } + + public bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (list.Count == 0) return true; + foreach (F f in list) + { + try + { + if (f.Pass(APRS, buddie, LastPositions)) return true; + } + catch { }; + }; + return false; + } + + public override string ToString() + { + string res = ""; + if (list.Count > 0) + { + foreach(F f in list) res += (res.Length > 0 ? " " : "") + f.filter; + return res; + } + else + return filter; + } + + public class F // Filter Prototype + { + public ClientAPRSFilter parent; + public string filter; + public string f_str; + public Regex f_reg; + public MatchCollection mc; + public F(string regExp, ClientAPRSFilter parent) { this.parent = parent; this.f_str = regExp; this.f_reg = new Regex(this.f_str, RegexOptions.None); } + protected void Init(string filter) + { + this.filter = filter; + this.mc = this.f_reg.Matches(filter); + if (Sucess) + { + this.filter = ""; + foreach (Match mx in mc) + this.filter += (this.filter.Length > 0 ? " " : "") + mx.Value.Trim(); + }; + } + public bool Sucess { get { return this.mc.Count > 0; } } + public virtual bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) { return true; } + } + public class F_r : F // r/lat/lon/dist -- Range filter + { + public double[] lat; + public double[] lon; + public int[] dist; + public F_r(string filter, ClientAPRSFilter parent) + : base(R_r, parent) + { + Init(filter); + if (!Sucess) return; + lat = new double[mc.Count]; + lon = new double[mc.Count]; + dist = new int[mc.Count]; + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + lat[i] = double.Parse(mx.Groups["lat"].Value, System.Globalization.CultureInfo.InvariantCulture); + lon[i] = double.Parse(mx.Groups["lon"].Value, System.Globalization.CultureInfo.InvariantCulture); + dist[i] = int.Parse(mx.Groups["dist"].Value, System.Globalization.CultureInfo.InvariantCulture); + }; + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (buddie == null) return false; + if (!buddie.PositionIsValid) return false; + for (int i = 0; i < lat.Length; i++) + if (GeoPos.GetLengthKm(buddie.lat, buddie.lon, lat[i], lon[i], false) <= dist[i]) + return true; + return false; + } + } + public class F_p : F // p/aa/bb/cc -- Prefix filter + { + public string[] pass; + public F_p(string filter, ClientAPRSFilter parent) + : base(R_p, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["call"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + foreach (string p in pass) + { + Regex rx = new Regex(@"(?:^|\s)" + p + @"[\w\-]+>", RegexOptions.IgnoreCase); + if (rx.Match(APRS).Success) return true; + }; + return false; + } + } + public class F_b : F // b/call1/call2 -- Budlist filter + { + public string[] pass; + public F_b(string filter, ClientAPRSFilter parent) + : base(R_b, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["exact"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + foreach (string p in pass) + { + Regex rx = new Regex(@"(?:^|\s)" + p.Replace("*", @"[^\>,\s]*") + @"\>", RegexOptions.None); + if (rx.Match(APRS).Success) return true; + }; + return false; + } + } + public class F_o : F // o/call1/call2 -- Object filter + { + public string[] pass; + public F_o(string filter, ClientAPRSFilter parent) + : base(R_o, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["obj"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + + string csign, rt, pckt; + if (!APRSData.ParseAPRSRoute(APRS, out csign, out rt, out pckt)) pckt = APRS; + + foreach (string p in pass) + { + Regex rx = new Regex(@"(?:^|\s)" + ";" + p.Replace("*", @"[^\*_\s\>,]*") + @"\s*[\*_]", RegexOptions.None); + if (rx.Match(pckt).Success) return true; + rx = new Regex(@"(?:^|\s)" + @"\)" + p.Replace("*", @"[^!_\s\>,]*") + @"\s*[!_]", RegexOptions.None); + if (rx.Match(pckt).Success) return true; + }; + return false; + } + } + public class F_os : F // os/call1/call2 -- Strict Object filter + { + public string[] pass; + public F_os(string filter, ClientAPRSFilter parent) + : base(R_os, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["strict"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + + string csign, rt, pckt; + if (!APRSData.ParseAPRSRoute(APRS, out csign, out rt, out pckt)) pckt = APRS; + + foreach (string p in pass) + { + string srep = @"(?:^|\s)" + ";" + p.Replace(" ", @"\s").Replace("*", @"[^\*_\>,]*") + @"\s*[\*_]"; + Regex rx = new Regex(srep, RegexOptions.None); + if (rx.Match(pckt).Success) return true; + srep = @"(?:^|\s)" + @"\)" + p.Replace(" ", @"\s").Replace("*", @"[^!_\>,]*") + @"\s*[!_]"; + rx = new Regex(srep, RegexOptions.None); + if (rx.Match(pckt).Success) return true; + }; + return false; + } + } + public class F_t : F // t/poimqstuw/call/km -- Type filter + { + public string[] types; + public string[] calls; + public int[] dists; + public F_t(string filter, ClientAPRSFilter parent) + : base(R_t, parent) + { + Init(filter); + if (!Sucess) return; + types = new string[mc.Count]; + calls = new string[mc.Count]; + dists = new int[mc.Count]; + for (int i = 0; i < mc.Count; i++) + { + types[i] = mc[i].Groups["type"].Value; + calls[i] = mc[i].Groups["call"].Value; + if (!String.IsNullOrEmpty(mc[i].Groups["dist"].Value)) dists[i] = int.Parse(mc[i].Groups["dist"].Value); + }; + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (types == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + for (int i = 0; i < types.Length; i++) + { + string csign, rt, pckt; + if (!APRSData.ParseAPRSRoute(APRS, out csign, out rt, out pckt)) pckt = APRS; + + bool pass = false; + if (types[i].IndexOf("p") >= 0) // position + { + if (pckt.StartsWith(";")) pass = true; + else + if (pckt.StartsWith(")")) pass = true; + else + if ((new Regex(@"^[^\s\>]+>[^\s\:]+:[!=/@]", RegexOptions.None)).Match(APRS).Success) pass = true; + }; + if ((!pass) && (types[i].IndexOf("o") >= 0) && (pckt.StartsWith(";"))) pass = true; // objects + if ((!pass) && (types[i].IndexOf("i") >= 0) && (pckt.StartsWith(")"))) pass = true; // items + if ((!pass) && (types[i].IndexOf("m") >= 0) && (pckt.StartsWith(":"))) pass = true; // messages + if ((!pass) && (types[i].IndexOf("q") >= 0) && (pckt.StartsWith("?"))) pass = true; // queries + if ((!pass) && (types[i].IndexOf("s") >= 0) && (pckt.StartsWith(">"))) pass = true; // status + if ((!pass) && (types[i].IndexOf("t") >= 0) && (pckt.StartsWith("T#"))) pass = true; // telemetry + if ((!pass) && (types[i].IndexOf("u") >= 0) && (pckt.StartsWith("{"))) pass = true; // user-defined + if ((!pass) && (types[i].IndexOf("n") >= 0) && (pckt.StartsWith(":NWS"))) pass = true; // NWS + if ((!pass) && (types[i].IndexOf("w") >= 0) && (pckt.StartsWith("!") || pckt.StartsWith("#") || pckt.StartsWith("$") || pckt.StartsWith("*"))) pass = true; // weather + if (pass && (!String.IsNullOrEmpty(calls[i])) && (buddie != null) && (!String.IsNullOrEmpty(buddie.name))) + { + if (buddie.name != calls[i]) pass = false; // not specified user + if (pass && (!buddie.PositionIsValid)) pass = false; // bad position + if (pass && (LastPositions == null)) pass = false; // no stored positions + if (pass && !LastPositions.ContainsKey(buddie.name)) pass = false; // no stored positions + if (pass && (GeoPos.GetLengthKm(buddie.lat, buddie.lon, LastPositions[buddie.name].lat, LastPositions[buddie.name].lon, false) > dists[i])) pass = false; // too far + }; + if (pass) return true; + }; + return false; + } + } + public class F_s : F // s/pri/alt/over -- Symbol filter + { + public string[] pri; + public string[] alt; + public string[] over; + public F_s(string filter, ClientAPRSFilter parent) + : base(R_s, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + pri = new string[mc.Count]; + alt = new string[mc.Count]; + over = new string[mc.Count]; + for (int i = 0; i < mc.Count; i++) + { + if (!String.IsNullOrEmpty(mc[i].Groups["pri"].Value)) pri[i] = mc[i].Groups["pri"].Value; else pri[i] = ""; + if (!String.IsNullOrEmpty(mc[i].Groups["alt"].Value)) alt[i] = mc[i].Groups["alt"].Value; else alt[i] = ""; + if (!String.IsNullOrEmpty(mc[i].Groups["over"].Value)) over[i] = mc[i].Groups["over"].Value; else over[i] = ""; + }; + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (String.IsNullOrEmpty(APRS)) return false; + if (buddie == null) return false; + if (String.IsNullOrEmpty(buddie.iconSymbol)) return false; + if (buddie.iconSymbol.Length != 2) return false; + for (int i = 0; i < pri.Length; i++) + { + if (over[i] == "") + { + if ((buddie.iconSymbol[0] == '/') && (pri[i].IndexOf(buddie.iconSymbol[1]) >= 0)) return true; + if ((buddie.iconSymbol[0] == '\\') && (alt[i].IndexOf(buddie.iconSymbol[1]) >= 0)) return true; + } + else + { + if ((over[i].IndexOf(buddie.iconSymbol[0]) >= 0) && (alt[i].IndexOf(buddie.iconSymbol[1]) >= 0)) return true; + }; + }; + return false; + } + } + public class F_d : F // d/digi1/digi2 -- Digipeater filter + { + public string[] pass; + public F_d(string filter, ClientAPRSFilter parent) + : base(R_d, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["call"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + foreach (string p in pass) + { + Regex rx = new Regex(@">\S*,?(?" + p.Replace("*", @"[^\>,\s]*") + @"),?[^\w\-\*\s]", RegexOptions.None); + if (rx.Match(APRS).Success) return true; + }; + return false; + } + } + public class F_a : F // a/latN/lonW/latS/lonE -- Area filter + { + public double[] top, left, bottom, right; + public F_a(string filter, ClientAPRSFilter parent) + : base(R_a, parent) + { + Init(filter); + if (!Sucess) return; + top = new double[mc.Count]; + left = new double[mc.Count]; + bottom = new double[mc.Count]; + right = new double[mc.Count]; + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + top[i] = double.Parse(mx.Groups["top"].Value, System.Globalization.CultureInfo.InvariantCulture); + left[i] = double.Parse(mx.Groups["left"].Value, System.Globalization.CultureInfo.InvariantCulture); + bottom[i] = double.Parse(mx.Groups["bottom"].Value, System.Globalization.CultureInfo.InvariantCulture); + right[i] = double.Parse(mx.Groups["right"].Value, System.Globalization.CultureInfo.InvariantCulture); + }; + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (buddie == null) return false; + if (!buddie.PositionIsValid) return false; + for (int i = 0; i < left.Length; i++) + if ((buddie.lat <= top[i]) && (buddie.lat >= bottom[i]) && (buddie.lon >= left[i]) && (buddie.lon <= right[i])) + return true; + return false; + } + } + public class F_e : F // e/call1/call2 -- Entry station filter + { + public string[] pass; + public F_e(string filter, ClientAPRSFilter parent) + : base(R_e, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["ssid"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + foreach (string p in pass) + { + Regex rx = new Regex(@"[>,]" + p.Replace("*", @"[\w-]*") + @"[^\w-\s]", RegexOptions.None); + if (rx.Match(APRS).Success) return true; + }; + return false; + } + } + public class F_g : F // g/call1/call2 -- Group Message filter + { + public string[] pass; + public F_g(string filter, ClientAPRSFilter parent) + : base(R_g, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["ssid"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + + string csign, rt, pckt; + if (!APRSData.ParseAPRSRoute(APRS, out csign, out rt, out pckt)) pckt = APRS; + + foreach (string p in pass) + { + Regex rx = new Regex(@"(?:^|\s):" + p.Replace("*", @"[\w-]*") + @":", RegexOptions.None); + if (rx.Match(pckt).Success) return true; + }; + return false; + } + } + public class F_u : F // u/call1/call2 -- Unproto filter + { + public string[] pass; + public F_u(string filter, ClientAPRSFilter parent) + : base(R_u, parent) + { + Init(filter); + if (!Sucess) return; + List strs = new List(); + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + CaptureCollection cc = mx.Groups["ssid"].Captures; + if (cc.Count > 0) + foreach (Capture c in cc) + strs.Add(c.Value); + }; + pass = strs.ToArray(); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (pass == null) return false; + if (String.IsNullOrEmpty(APRS)) return false; + + string csign, rt, pckt; + if (!APRSData.ParseAPRSRoute(APRS, out csign, out rt, out pckt)) return false; + + foreach (string p in pass) + { + Regex rx = new Regex(@":;" + p.Replace("*", @"[\w-]*") + @"[^\w-\s]", RegexOptions.None); + if (rx.Match(pckt).Success) return true; + }; + return false; + } + } + public class F_m : F // m/dist -- My Range filter + { + public int dist; + public F_m(string filter, ClientAPRSFilter parent) + : base(R_m, parent) + { + Init(filter); + if (!Sucess) return; + for (int i = 0; i < mc.Count; i++) + dist = int.Parse(mc[i].Groups["dist"].Value, System.Globalization.CultureInfo.InvariantCulture); + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (buddie == null) return false; + if (!buddie.PositionIsValid) return false; + if (LastPositions == null) return false; + if (LastPositions.Count == 0) return false; + if (!LastPositions.ContainsKey(parent.User)) return false; + if (GeoPos.GetLengthKm(buddie.lat, buddie.lon, LastPositions[parent.User].lat, LastPositions[parent.User].lon, false) <= dist) return true; + return false; + } + } + public class F_f : F // f/user/dist -- Friend Range filter + { + public string[] user; + public int[] dist; + public F_f(string filter, ClientAPRSFilter parent) + : base(R_f, parent) + { + Init(filter); + if (!Sucess) return; + user = new string[mc.Count]; + dist = new int[mc.Count]; + for (int i = 0; i < mc.Count; i++) + { + Match mx = mc[i]; + user[i] = mx.Groups["call"].Value; + dist[i] = int.Parse(mx.Groups["dist"].Value, System.Globalization.CultureInfo.InvariantCulture); + }; + } + public override bool Pass(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (!Sucess) return false; + if (buddie == null) return false; + if (!buddie.PositionIsValid) return false; + if (LastPositions == null) return false; + if (LastPositions.Count == 0) return false; + for (int i = 0; i < user.Length; i++) + { + if (LastPositions.ContainsKey(user[i])) + if (GeoPos.GetLengthKm(buddie.lat, buddie.lon, LastPositions[user[i]].lat, LastPositions[user[i]].lon, false) <= dist[i]) + return true; + }; + return false; + } + } + } +} diff --git a/SimpleAPRSserver/ClientData.cs b/SimpleAPRSserver/ClientData.cs new file mode 100644 index 0000000..fbb7cb7 --- /dev/null +++ b/SimpleAPRSserver/ClientData.cs @@ -0,0 +1,69 @@ +/******************************************* +* * +* Simple APRS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.Text.RegularExpressions; +using System.IO; +using System.Web; +using System.Xml; +using System.Xml.Serialization; +using System.Security; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using System.Reflection; + + +namespace SimpleAPRSserver +{ + public class ClientData + { + public TcpClient client; + public DateTime connected; + public ulong id; + public Stream stream; + public string SoftNam = ""; + public string SoftVer = ""; + + public string user = "UNKNOWN"; + public bool validated; + + public ClientAPRSFilter filter = null; + public APRSData.Buddie lastBuddie = null; + + public string IP { get { return ((IPEndPoint)this.client.Client.RemoteEndPoint).Address.ToString(); } } + public int Port { get { return ((IPEndPoint)this.client.Client.RemoteEndPoint).Port; } } + + public ClientData(TcpClient client, ulong clientID) + { + this.id = clientID; + this.connected = DateTime.UtcNow; + this.client = client; + this.validated = false; + this.stream = client.GetStream(); + } + + public string SetFilter(string filter, string user) + { + this.filter = new ClientAPRSFilter(filter, user); + if((this.filter== null) || (this.filter.Count == 0)) + return "NOT DEFINED"; + else + return this.filter.ToString(); + } + + public bool PassFilter(string APRS, APRSData.Buddie buddie, Dictionary LastPositions) + { + if (filter == null) return true; + return filter.Pass(APRS, buddie, LastPositions); + } + } +} diff --git a/SimpleAPRSserver/HttpAPRSServer.cs b/SimpleAPRSserver/HttpAPRSServer.cs new file mode 100644 index 0000000..6e973a2 --- /dev/null +++ b/SimpleAPRSserver/HttpAPRSServer.cs @@ -0,0 +1,214 @@ +/******************************************* +* * +* Simple APRS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.Text.RegularExpressions; +using System.IO; +using System.Web; +using System.Xml; +using System.Xml.Serialization; +using System.Security; +using System.Security.Cryptography; +using System.Runtime.InteropServices; +using System.Reflection; + +namespace SimpleAPRSserver +{ + public class HttpAPRSServer : SimpleServersPBAuth.ThreadedHttpServer + { + private APRSServer aprsServer = null; + private Mutex wsMutex = new Mutex(); + private List wsClients = new List(); + + public HttpAPRSServer(APRSServer aprsServer) : base(80) { this.aprsServer = aprsServer; } + public HttpAPRSServer(APRSServer aprsServer, int Port) : base(Port) { this.aprsServer = aprsServer; } + public HttpAPRSServer(APRSServer aprsServer, IPAddress IP, int Port) : base(IP, Port) { this.aprsServer = aprsServer; } + ~HttpAPRSServer() { this.Dispose(); } + + /// + /// Get HTTP Client Request, threaded (thread per client) + /// -- connection to client will be closed after return -- + /// -- do not call base method if response any data -- + /// + /// + protected override void GetClientRequest(ClientRequest Request) + { + if (Request.Query.StartsWith("/statistics")) + { + OutStatistics(Request); + return; + }; + if (!HttpClientWebSocketInit(Request, false)) + PassFileToClientByRequest(Request, GetCurrentDir() + @"\MAP\"); + } + + // Statistics Page + private void OutStatistics(ClientRequest clReq) + { + string text = "

" + ServerName + " v" + APRSServer.GetVersion() + "

"; + text += "Statistics:
\r\n
\r\n"; + text += String.Format("     Current TCP connections: {0}
\r\n", new object[] { this.ClientsAlive + aprsServer.ClientsAlive + aprsServer.AISAlive }); + if (aprsServer.AISRunning) + { + text += String.Format("     Current AIS connections: {0} at port {1}
\r\n", new object[] { aprsServer.AISAlive, aprsServer.AISPort }); + if (aprsServer.AISAlive > 0) + { + for (int i = 0; i < aprsServer.AISClients.Length; i++) + text += String.Format("         - {0}
\r\n", aprsServer.AISClients[i]); + }; + } + text += String.Format("     Current APRS connections: {0} at port {1}
\r\n", new object[] { aprsServer.ClientsAlive, aprsServer.ServerPort }); + if (aprsServer.aprsClients.Count > 0) + { + aprsServer.aprsMutex.WaitOne(); + for (int i = 0; i < aprsServer.aprsClients.Count; i++) + text += String.Format("         - {0}:{1} as {2} {3} via {4}
\r\n", new object[] { ((IPEndPoint)aprsServer.aprsClients[i].client.Client.RemoteEndPoint).Address.ToString(), ((IPEndPoint)aprsServer.aprsClients[i].client.Client.RemoteEndPoint).Port, aprsServer.aprsClients[i].user, aprsServer.aprsClients[i].validated ? " - passw ok" : "", aprsServer.aprsClients[i].SoftNam + " " + aprsServer.aprsClients[i].SoftVer }); + aprsServer.aprsMutex.ReleaseMutex(); + }; + text += String.Format("     Current WebSocket connections: {0} at port {1}
\r\n", new object[] { wsClients.Count, this.ServerPort }); + if (wsClients.Count > 0) + { + wsMutex.WaitOne(); + for (int i = 0; i < wsClients.Count; i++) + text += String.Format("         - {0}:{1}
\r\n", new object[] { ((IPEndPoint)wsClients[i].Client.Client.RemoteEndPoint).Address.ToString(), ((IPEndPoint)wsClients[i].Client.Client.RemoteEndPoint).Port }); + wsMutex.ReleaseMutex(); + }; + if (aprsServer.StoreGPSInMemory) + { + aprsServer.ClearBuds(); + text += String.Format("     Current Buddies in Memory: {0}, max alive {1} m
\r\n", new object[] { aprsServer.BUDs.Count, aprsServer.StoreGPSMaxTime }); + if (aprsServer.BUDs.Count > 0) + { + aprsServer.budsMutex.WaitOne(); + for (int i = 0; i < aprsServer.BUDs.Count; i++) + text += String.Format(System.Globalization.CultureInfo.InvariantCulture, + "         - {0}, last {1:HH:mm:ss dd.MM.yyyy} >> {2} {3} - {4}; alive {5:0.} m
\r\n", new object[] { aprsServer.BUDs[i].name, aprsServer.BUDs[i].last, aprsServer.BUDs[i].lat, aprsServer.BUDs[i].lon, aprsServer.BUDs[i].iconSymbol, DateTime.UtcNow.Subtract(aprsServer.BUDs[i].last).TotalMinutes }); + aprsServer.budsMutex.ReleaseMutex(); + }; + } + else + text += "     No Store GPS in Memory
\r\n"; + text += "
\r\n"; + text += String.Format("     Total TCP Clients Counter: {0}
\r\n", this.ClientsCounter + aprsServer.ClientsCounter + aprsServer.AISCounter); + text += String.Format("     Total AIS Clients Counter: {0}
\r\n", aprsServer.AISCounter); + text += String.Format("     Total APRS Clients Counter: {0}
\r\n", aprsServer.ClientsCounter); + text += String.Format("     Total HTTP Clients Counter: {0}
\r\n", this.ClientsCounter); + text += "

"; + text += "Last APRS Packets:
"; + string[] lap = aprsServer.LastAPRSPackets; + if ((lap != null) && (lap.Length > 0)) + foreach (string lp in lap) + text += "   " + lp + "
"; + text += "
"; + text += String.Format("Report time: {0:HH:mm:ss dd.MM.yyyy} UTC", DateTime.UtcNow); + text += "

MAP"; + HttpClientSendText(clReq.Client, text); + } + + // On WebSocket Client Connected + protected override void OnWebSocketClientConnected(ClientRequest clientRequest) + { + wsMutex.WaitOne(); + wsClients.Add(clientRequest); + wsMutex.ReleaseMutex(); + + byte[] ba = GetWebSocketFrameFromString("Welcome to " + ServerName); + clientRequest.Client.GetStream().Write(ba, 0, ba.Length); + clientRequest.Client.GetStream().Flush(); + + if (aprsServer.OutConnectionsToConsole) + Console.WriteLine("WebSocket connected from: {0}:{1}, total {2}", clientRequest.RemoteIP, ((IPEndPoint)clientRequest.Client.Client.RemoteEndPoint).Port, wsClients.Count); + + PassBuds(clientRequest); + + if ((aprsServer.OutBroadcastsMessages) && (aprsServer.BUDs.Count > 0)) + Console.WriteLine("Passed {0} buddies to WS {1}:{2}", aprsServer.BUDs.Count, clientRequest.RemoteIP, ((IPEndPoint)clientRequest.Client.Client.RemoteEndPoint).Port); + } + + // On WebSocket Client Disconnected + protected override void OnWebSocketClientDisconnected(ClientRequest clientRequest) + { + wsMutex.WaitOne(); + for (int i = 0; i < wsClients.Count; i++) + if (wsClients[i].clientID == clientRequest.clientID) + { + wsClients.RemoveAt(i); + break; + }; + wsMutex.ReleaseMutex(); + + if (aprsServer.OutConnectionsToConsole) + Console.WriteLine("WebSocket disconnected from: {0}:{1}, total {2}", clientRequest.RemoteIP, ((IPEndPoint)clientRequest.Client.Client.RemoteEndPoint).Port, wsClients.Count); + } + + // On WebSocket Client Incoming Data + protected override void OnWebSocketClientData(ClientRequest clientRequest, byte[] data) + { + try + { + string fws = GetStringFromWebSocketFrame(data, data.Length); + if (String.IsNullOrEmpty(fws)) return; + + string tws = fws + " ok"; + byte[] toSend = GetWebSocketFrameFromString(tws); + clientRequest.Client.GetStream().Write(toSend, 0, toSend.Length); + clientRequest.Client.GetStream().Flush(); + } + catch { }; + } + + // Send Buddies to client + private void PassBuds(ClientRequest cr) + { + if (!aprsServer.StoreGPSInMemory) return; + aprsServer.ClearBuds(); + + aprsServer.budsMutex.WaitOne(); + if (aprsServer.BUDs.Count > 0) + for (int i = 0; i < aprsServer.BUDs.Count; i++) + { + string text = aprsServer.BUDs[i].GetWebSocketText() + "\r\n"; + byte[] toSend = GetWebSocketFrameFromString(text); + try + { + cr.Client.GetStream().Write(toSend, 0, toSend.Length); + cr.Client.GetStream().Flush(); + } + catch { }; + }; + aprsServer.budsMutex.ReleaseMutex(); + } + + // Send Packet to All WebSocket Clients + public void Broadcast(APRSData.Buddie bud) + { + string text = bud.GetWebSocketText() + "\r\n"; + byte[] toSend = GetWebSocketFrameFromString(text); + wsMutex.WaitOne(); + if (wsClients.Count > 0) + { + if (aprsServer.OutBroadcastsMessages) + Console.WriteLine("Broadcast WS: {0}", text.Replace("\r", "").Replace("\n", "")); + for (int i = 0; i < wsClients.Count; i++) + { + try + { + wsClients[i].Client.GetStream().Write(toSend, 0, toSend.Length); + wsClients[i].Client.GetStream().Flush(); + } + catch { }; + }; + }; + wsMutex.ReleaseMutex(); + } + } +} \ No newline at end of file diff --git a/SimpleAPRSserver/Program.cs b/SimpleAPRSserver/Program.cs new file mode 100644 index 0000000..1bfa42a --- /dev/null +++ b/SimpleAPRSserver/Program.cs @@ -0,0 +1,35 @@ +/******************************************* +* * +* Simple APRS Server by milokz@gmail.com * +* * +*******************************************/ + +using System; +using System.Reflection; +using System.Collections; +using System.Collections.Generic; +using System.Text; + +namespace SimpleAPRSserver +{ + class Program + { + static void Main(string[] args) + { + Console.WriteLine("********************************************"); + Console.WriteLine("* *"); + Console.WriteLine("* Simple APRS Server by milokz@gmail.com *"); + Console.WriteLine("* *"); + Console.WriteLine("********************************************"); + Console.WriteLine("Version " + APRSServer.GetVersion() + " " + APRSServer.Build); + Console.WriteLine(""); + + APRSServer server = new APRSServer(); + server.Start(); + Console.WriteLine("Type exit to Exit:"); + while (true) if(Console.ReadLine() == "exit") break; + Console.WriteLine("exiting..."); + server.Stop(); + } + } +} diff --git a/SimpleAPRSserver/Properties/AssemblyInfo.cs b/SimpleAPRSserver/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..f81c806 --- /dev/null +++ b/SimpleAPRSserver/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Simple HAM APRS Server")] +[assembly: AssemblyDescription("Radio HAM Software")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("milokz@gmail.com")] +[assembly: AssemblyProduct("SimpleAPRSserver")] +[assembly: AssemblyCopyright("milokz@gmail.com")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("4bdae04c-c749-4db3-8c03-6ede8c352b0b")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +[assembly: AssemblyVersion("0.0.1.15")] +[assembly: AssemblyFileVersion("0.0.1.15")] diff --git a/SimpleAPRSserver/SimpleAPRSserver.csproj b/SimpleAPRSserver/SimpleAPRSserver.csproj new file mode 100644 index 0000000..3d556f9 --- /dev/null +++ b/SimpleAPRSserver/SimpleAPRSserver.csproj @@ -0,0 +1,57 @@ + + + Debug + AnyCPU + 8.0.50727 + 2.0 + {E0BBC088-37CD-4FA1-BC60-808FF8BF3EBC} + Exe + Properties + SimpleAPRSserver + SimpleAPRSserver + + + true + full + false + ..\BIN\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + ..\BIN\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/SimpleAPRSserver/SimpleServersPBAuth.cs b/SimpleAPRSserver/SimpleServersPBAuth.cs new file mode 100644 index 0000000..de7aa97 --- /dev/null +++ b/SimpleAPRSserver/SimpleServersPBAuth.cs @@ -0,0 +1,3847 @@ +/*********************************************/ +/* */ +/* */ +/* Author Milok Zbrozek (milokz@gmail.com) */ +/* */ +/* */ +/* Simple TCP/UDP/HTTP Servers Class */ +/* last moidified 09.08.2021 */ +/* version 0.5B */ +/* */ +/* */ +/* Supports: */ +/* */ +/* - Browsing files and directories */ +/* - index.html */ +/* - CGI-BIN */ +/* - IP & Mac filters (* and Regex ^$) */ +/* */ +/*********************************************/ + + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using System.Net.Sockets; +using System.Net; +using System.Threading; +using System.Text.RegularExpressions; +using System.IO; +using System.Web; +using System.Xml; +using System.Xml.Serialization; +using System.Security; +using System.Security.Cryptography; +using System.Runtime.InteropServices; + +namespace SimpleServersPBAuth +{ + public enum ServerState + { + ssStopped = 0, + ssStarting = 1, + ssRunning = 2, + ssStopping = 3 + } + + /// + /// + /// + public interface IServer + { + void Start(); + void Stop(); + ServerState GetState(); + int GetServerPort(); + Exception GetLastError(); + } + + /// + /// TCP + /// + public abstract class TTCPServer : IServer, IDisposable + { + protected Thread mainThread = null; + protected TcpListener mainListener = null; + + /// + /// Listen IP Address + /// + protected IPAddress ListenIP = IPAddress.Any; + /// + /// Listen IP Address + /// + public IPAddress ServerIP { get { return ListenIP; } } + + /// + /// Listen Port Number + /// + protected int ListenPort = 5000; + /// + /// Listen Port Number + /// + public int ServerPort { get { return ListenPort; } set { if (isRunning) throw new Exception("Server is running"); ListenPort = value; } } + /// + /// Listen Port Number + /// + /// + public int GetServerPort() { return ListenPort; } + + /// + /// Server is running or not + /// + protected bool isRunning = false; + /// + /// Server is running or not + /// + public bool Running { get { return isRunning; } } + /// + /// Get Servers State + /// + /// + public ServerState GetState() { if (isRunning) return ServerState.ssRunning; else return ServerState.ssStopped; } + + /// + /// Last Thread Error + /// + protected Exception LastError = null; + /// + /// Last Thread Error + /// + public Exception GetLastError() { return LastError; } + + /// + /// Last Error Time + /// + protected DateTime LastErrTime = DateTime.MaxValue; + /// + /// Last Error Time + /// + public DateTime LastErrorTime { get { return LastErrTime; } } + + /// + /// Get Total Error Count + /// + protected uint ErrorsCounter = 0; + /// + /// Get Total Error Count + /// + public uint GetErrorsCount { get { return ErrorsCounter; } } + + /// + /// Total Clients Counter + /// + protected ulong counter = 0; + /// + /// Total Clients Counter + /// + public ulong ClientsCounter { get { return counter; } } + + /// + /// Connected clients counter + /// + protected ulong alive = 0; + /// + /// Connected clients counter + /// + public ulong ClientsAlive { get { return alive; } } + + /// + /// Client Read Timeout in seconds, default 10 + /// + protected int readTimeout = 10; // 10 sec + /// + /// Client Read Timeout in seconds, default 10 + /// + public int ReadTimeout { get { return readTimeout; } set { readTimeout = value; } } + + public TTCPServer() { } + public TTCPServer(int Port) { this.ListenPort = Port; } + public TTCPServer(IPAddress IP, int Port) { this.ListenIP = IP; this.ListenPort = Port; } + + /// + /// Start Server + /// + public virtual void Start() { } + + /// + /// Stop Server + /// + public virtual void Stop() { } + + /// + /// Stop Server and Dipose + /// + public virtual void Dispose() { this.Stop(); } + + /// + /// Accept TCP Client + /// + /// + /// true - connect, false - ignore + protected virtual bool AcceptClient(TcpClient client) { return true; } + + /// + /// Get Client Connection + /// + /// + /// Client Number + protected virtual void GetClient(TcpClient Client, ulong clientID) { } + + /// + /// On Error + /// + /// + /// Client Number + /// + protected virtual void onError(TcpClient Client, ulong clientID, Exception error) { throw error; } + + /// + /// Is Connection Alive? + /// + /// + /// + public static bool IsConnected(TcpClient Client) + { + if (!Client.Connected) return false; + if (Client.Client.Poll(0, SelectMode.SelectRead)) + { + byte[] buff = new byte[1]; + try + { + if (Client.Client.Receive(buff, SocketFlags.Peek) == 0) + return false; + } + catch + { + return false; + }; + }; + return true; + } + + /// + /// Get Client HTTP Headers + /// + /// client header + /// header list + public static IDictionary GetClientHeaders(string Header) + { + if (String.IsNullOrEmpty(Header)) return null; + + Dictionary clHeaders = new Dictionary(); + Regex rx = new Regex(@"([\w-]+): (.*)", RegexOptions.IgnoreCase); + try + { + MatchCollection mc = rx.Matches(Header); + foreach (Match mx in mc) + { + string val = mx.Groups[2].Value.Trim(); + if (!clHeaders.ContainsKey(mx.Groups[1].Value)) + clHeaders.Add(mx.Groups[1].Value, val); + else + clHeaders[mx.Groups[1].Value] += val; + }; + } + catch { }; + return clHeaders; + } + + /// + /// Get client HTTP query + /// + /// header + /// server:ip + /// server page + /// string params + /// list params + /// query string + public static string GetClientQuery(string Header, out string host, out string page, out string sParameters, out IDictionary lParameters) + { + host = null; + Regex rx = new Regex(@"Host: (.*)", RegexOptions.IgnoreCase); + Match mx = rx.Match(Header); + if (mx.Success) host = mx.Groups[1].Value.Trim(); + + page = null; + lParameters = null; + sParameters = null; + if (String.IsNullOrEmpty(Header)) return null; + + string query = ""; + rx = new Regex("^(?:PUT|POST|GET) (.*) HTTP", RegexOptions.IgnoreCase); // "^(?:PUT|POST|GET) (.*) HTTP" or @"^(?:PUT|POST|GET) (\/?[\w\.?=%&=\-@/$,]*) HTTP" + mx = rx.Match(Header); + if (mx.Success) query = UrlUnescape(mx.Groups[1].Value); + if (query != null) + { + rx = new Regex(@"^(?[\[\]+!_\(\)\s\w\.=%=\-@/$,]*)?", RegexOptions.None); + mx = rx.Match(query); + if (mx.Success) page = mx.Groups["page"].Value; + + rx = new Regex(@"(?:[\?&](.*))", RegexOptions.None); + mx = rx.Match(query); + if (mx.Success) sParameters = mx.Groups[1].Value; + + rx = new Regex(@"([\?&]((?[^&=]+)=(?[^&=]+)))", RegexOptions.None); + MatchCollection mc = rx.Matches(query); + if (mc.Count > 0) + { + lParameters = new Dictionary(); + foreach (Match m in mc) + { + string n = m.Groups["name"].Value; + string v = m.Groups["value"].Value; + if (lParameters.ContainsKey(n)) + lParameters[n] += "," + v; + else + lParameters.Add(n, v); + }; + }; + }; + return query; + } + + /// + /// Get client GET/POST Query Parameters + /// + /// + /// + public static IDictionary GetClientParams(string query) + { + if (String.IsNullOrEmpty(query)) return null; + Dictionary lParameters = new Dictionary(); + + Regex rx = new Regex(@"([\?&]*((?[^&=]+)=(?[^&=]+)))", RegexOptions.None); + MatchCollection mc = rx.Matches(query); + if (mc.Count > 0) + { + lParameters = new Dictionary(); + foreach (Match m in mc) + { + string n = UrlUnescape(m.Groups["name"].Value); + string v = UrlUnescape(m.Groups["value"].Value); + if (lParameters.ContainsKey(n)) + lParameters[n] += "," + v; + else + lParameters.Add(n, v); + }; + }; + return lParameters; + } + + /// + /// Dictionary has cas-ignored key + /// + /// dictionary + /// key to find + /// has key + public static bool DictHasKeyIgnoreCase(IDictionary dict, string key) + { + if (dict == null) return false; + if (dict.Count == 0) return false; + foreach (string k in dict.Keys) + if (string.Compare(k, key, true) == 0) + return true; + return false; + } + + /// + /// Get Dictionary Value by Key ignoring case + /// + /// dictionary + /// key to find + /// key value + public static string DictGetKeyIgnoreCase(IDictionary dict, string key) + { + if (dict == null) return null; + if (dict.Count == 0) return null; + foreach (string k in dict.Keys) + if (string.Compare(k, key, true) == 0) + return dict[k]; + return null; + } + + /// + /// Encode string to base64 + /// + /// + /// + public static string Base64Encode(string plainText) + { + byte[] plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return System.Convert.ToBase64String(plainTextBytes); + } + + /// + /// Decode base64 to string + /// + /// + /// + public static string Base64Decode(string base64EncodedData) + { + byte[] base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData); + return System.Text.Encoding.UTF8.GetString(base64EncodedBytes); + } + + /// + /// Code string with key + /// + /// + /// + /// + public static string CodeInString(string clearText, string EncryptionKey) + { + byte[] clearBytes = Encoding.Unicode.GetBytes(clearText); + using (System.Security.Cryptography.SymmetricAlgorithm encryptor = System.Security.Cryptography.SymmetricAlgorithm.Create()) + { + Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); + encryptor.Key = pdb.GetBytes(32); + encryptor.IV = pdb.GetBytes(16); + using (MemoryStream ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateEncryptor(), CryptoStreamMode.Write)) + { + cs.Write(clearBytes, 0, clearBytes.Length); + cs.Close(); + } + clearText = Convert.ToBase64String(ms.ToArray()); + } + } + return clearText; + } + + /// + /// decode string with key + /// + /// + /// + /// + public static string CodeOutString(string cipherText, string EncryptionKey) + { + cipherText = cipherText.Replace(" ", "+"); + byte[] cipherBytes = Convert.FromBase64String(cipherText); + using (System.Security.Cryptography.SymmetricAlgorithm encryptor = System.Security.Cryptography.SymmetricAlgorithm.Create()) + { + Rfc2898DeriveBytes pdb = new Rfc2898DeriveBytes(EncryptionKey, new byte[] { 0x49, 0x76, 0x61, 0x6e, 0x20, 0x4d, 0x65, 0x64, 0x76, 0x65, 0x64, 0x65, 0x76 }); + encryptor.Key = pdb.GetBytes(32); + encryptor.IV = pdb.GetBytes(16); + using (MemoryStream ms = new MemoryStream()) + { + using (CryptoStream cs = new CryptoStream(ms, encryptor.CreateDecryptor(), CryptoStreamMode.Write)) + { + cs.Write(cipherBytes, 0, cipherBytes.Length); + cs.Close(); + } + cipherText = Encoding.Unicode.GetString(ms.ToArray()); + } + } + return cipherText; + } + + public static string ToFileSize(double value) + { + string[] suffixes = { "bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB" }; + for (int i = 0; i < suffixes.Length; i++) + { + if (value <= (Math.Pow(1024, i + 1))) + { + return ThreeNonZeroDigits(value / + Math.Pow(1024, i)) + + " " + suffixes[i]; + }; + }; + return ThreeNonZeroDigits(value / Math.Pow(1024, suffixes.Length - 1)) + " " + suffixes[suffixes.Length - 1]; + } + + private static string ThreeNonZeroDigits(double value) + { + if (value >= 100) + { + // No digits after the decimal. + return value.ToString("0,0"); + } + else if (value >= 10) + { + // One digit after the decimal. + return value.ToString("0.0"); + } + else + { + // Two digits after the decimal. + return value.ToString("0.00"); + } + } + + protected static char Get1251Char(byte b) + { + return (System.Text.Encoding.GetEncoding(1251).GetString(new byte[] { b }, 0, 1))[0]; + } + + public static string UrlEscape(string str) + { + return System.Uri.EscapeDataString(str.Replace("+", "%2B")); + } + + public static string UrlUnescape(string str) + { + return System.Uri.UnescapeDataString(str).Replace("%2B", "+"); + } + + /// + /// , + /// + /// \ + public static string GetCurrentDir() + { + string fname = System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase.ToString(); + fname = fname.Replace("file:///", ""); + fname = fname.Replace("/", @"\"); + fname = fname.Substring(0, fname.LastIndexOf(@"\") + 1); + return fname; + } + + public static string GetMemeType(string fileExt) + { + // https://snipp.ru/handbk/mime-list + switch (fileExt) + { + case ".pdf": return "application/pdf"; + case ".djvu": return "image/vnd.djvu"; + case ".zip": return "application/zip"; + case ".doc": return "application/msword"; + case ".docx": return "application/msword"; + case ".mp3": return "audio/mpeg"; + case ".m3u": return "audio/x-mpegurl"; + case ".wav": return "audio/x-wav"; + case ".gif": return "image/gif"; + case ".bmp": return "image/bmp"; + case ".psd": return "image/vnd.adobe.photoshop"; + case ".jpg": return "image/jpeg"; + case ".jpeg": return "image/jpeg"; + case ".png": return "image/png"; + case ".svg": return "image/svg"; + case ".tiff": return "image/tiff"; + case ".css": return "text/css"; + case ".csv": return "text/csv"; + case ".html": return "text/html"; + case ".htmlx": return "text/html"; + case ".dhtml": return "text/html"; + case ".xhtml": return "text/html"; + case ".js": return "application/javascript"; + case ".json": return "application/json"; + case ".txt": return "text/plain"; + case ".md": return "text/plain"; + case ".php": return "text/php"; + case ".xml": return "text/xml"; + case ".mpg": return "video/mpeg"; + case ".mpeg": return "video/mpeg"; + case ".mp4": return "video/mp4"; + case ".ogg": return "video/ogg"; + case ".avi": return "video/x-msvideo"; + case ".rar": return "application/x-rar-compresse"; + default: return "application/octet-stream"; + }; + } + + [DllImport("iphlpapi.dll", ExactSpelling = true)] + private static extern int SendARP(int DestIP, int SrcIP, [Out] byte[] pMacAddr, ref int PhyAddrLen); + + public static string GetMacAddressByIP(string ip) + { + IPAddress ip_adr = IPAddress.Parse(ip); + byte[] ab = new byte[6]; + int len = ab.Length; + int r = SendARP(ip_adr.GetHashCode(), 0, ab, ref len); + return BitConverter.ToString(ab, 0, 6); + } + } + + /// + /// TCP- + /// + public class SingledTCPServer: TTCPServer + { + /// + /// Close connection when GetClient method completes + /// + protected bool _closeOnGetClientCompleted = true; + + /// + /// Close connection when GetClient method completes + /// + public bool CloseConnectionOnGetClientCompleted { get { return _closeOnGetClientCompleted; } set { _closeOnGetClientCompleted = value; } } + + public SingledTCPServer() { } + public SingledTCPServer(int Port) { this.ListenPort = Port; } + public SingledTCPServer(IPAddress IP, int Port) { this.ListenIP = IP; this.ListenPort = Port; } + ~SingledTCPServer() { this.Dispose(); } + + public override void Start() + { + if (isRunning) throw new Exception("Server Already Running!"); + + try + { + mainListener = new TcpListener(this.ListenIP, this.ListenPort); + mainListener.Start(); + } + catch (Exception ex) + { + LastError = ex; + ErrorsCounter++; + throw ex; + }; + + mainThread = new Thread(MainThread); + mainThread.Start(); + } + + public override void Stop() + { + if (!isRunning) return; + + isRunning = false; + + if (mainListener != null) mainListener.Stop(); + mainListener = null; + + mainThread.Join(); + mainThread = null; + } + + private void MainThread() + { + isRunning = true; + while (isRunning) + { + try + { + TcpClient client = mainListener.AcceptTcpClient(); + if (client == null) continue; + + if (!AcceptClient(client)) + { + client.Client.Close(); + client.Close(); + continue; + }; + + ulong id = 0; + try + { + alive++; + client.GetStream().ReadTimeout = this.readTimeout * 1000; + GetClient(client, id = this.counter++); + } + catch (Exception ex) + { + try + { + ErrorsCounter++; + LastError = ex; + onError(client, id, ex); + } + catch { }; + }; + if(_closeOnGetClientCompleted) + CloseClient(client, id); + } + catch (Exception ex) + { + LastError = ex; + ErrorsCounter++; + }; + Thread.Sleep(1); + }; + } + + protected override void GetClient(TcpClient Client, ulong clientID) + { + // + // do something + // + + if (!this._closeOnGetClientCompleted) CloseClient(Client, clientID); + } + + protected void CloseClient(TcpClient Client, ulong clientID) + { + try + { + alive--; + Client.Client.Close(); + Client.Close(); + } + catch { }; + } + } + + /// + /// TCP-, + /// + public class SingledTextTCPServer : SingledTCPServer + { + public SingledTextTCPServer() : base() { } + public SingledTextTCPServer(int Port) : base(Port) { } + public SingledTextTCPServer(IPAddress IP, int Port) : base(IP, Port) { } + ~SingledTextTCPServer() { this.Dispose(); } + + protected bool _OnlyHTTP = false; + public virtual bool OnlyHTTPClients { get { return _OnlyHTTP; } set { _OnlyHTTP = value; } } + protected uint _MaxHeaderSize = 4096; + public uint MaxClientHeaderSize { get { return _MaxHeaderSize; } set { _MaxHeaderSize = value; } } + protected uint _MaxBodySize = 65536; + public uint MaxClientBodySize { get { return _MaxBodySize; } set { _MaxBodySize = value; } } + protected Encoding _responseEnc = Encoding.GetEncoding(1251); + public Encoding ResponseEncoding { get { return _responseEnc; } set { _responseEnc = value; } } + protected Encoding _requestEnc = Encoding.GetEncoding(1251); + public Encoding RequestEncoding { get { return _requestEnc; } set { _requestEnc = value; } } + + protected override void GetClient(TcpClient Client, ulong clientID) + { + Regex CR = new Regex(@"Content-Length: (\d+)", RegexOptions.IgnoreCase); + + string Request = ""; + string Header = null; + List Body = new List(); + + int bRead = -1; + int posCRLF = -1; + int receivedBytes = 0; + int contentLength = 0; + + // Get Header + //while ((Client.Available > 0) && ((bRead = Client.GetStream().ReadByte()) >= 0)) // doesn't work correct + while ((bRead = Client.GetStream().ReadByte()) >= 0) + { + receivedBytes++; + Body.Add((byte)bRead); + + // Check GET or POST + if (_OnlyHTTP && (receivedBytes == 1)) + if ((bRead != 0x47) && (bRead != 0x50)) + { + if (!this._closeOnGetClientCompleted) CloseClient(Client, clientID); + return; + }; + + Request += (char)bRead; // standard symbol + if (bRead == 0x0A) posCRLF = Request.IndexOf("\r\n\r\n"); // get body start index + if (posCRLF >= 0 || Request.Length > _MaxHeaderSize) { break; }; // GET ONLY + }; + + bool valid = (posCRLF > 0); + if ((!valid) && _OnlyHTTP) + { + if (!this._closeOnGetClientCompleted) CloseClient(Client, clientID); + return; + }; + + // Get Body + if(valid) + { + Body.Clear(); + Header = Request; + Match mx = CR.Match(Request); + if (mx.Success) contentLength = int.Parse(mx.Groups[1].Value); + int total2read = posCRLF + 4 + contentLength; + while ((receivedBytes < total2read) && ((bRead = Client.GetStream().ReadByte()) >= 0)) + { + receivedBytes++; + Body.Add((byte)bRead); + + string rcvd = _requestEnc.GetString(new byte[] { (byte)bRead }, 0, 1); + Request += rcvd; + if (Request.Length > _MaxBodySize) { break; }; + }; + }; + + GetClientRequest(Client, clientID, Request, Header, Body.ToArray()); + } + + /// + /// Get Client with Request Text Data + /// + /// socket + /// number + /// Request + /// Header + /// Body + /// Body + protected virtual void GetClientRequest(TcpClient Client, ulong clientID, string Request, string Header, byte[] Body) + { + string proto = "tcp://" + Client.Client.RemoteEndPoint.ToString() + "/text/"; + + // + // do something + // + + if (!this._closeOnGetClientCompleted) CloseClient(Client, clientID); + } + } + + /// + /// HTTP- + /// + public class SingledHttpServer : SingledTextTCPServer + { + public SingledHttpServer() : base(80) { this._closeOnGetClientCompleted = true; this._OnlyHTTP = true; } + public SingledHttpServer(int Port) : base(Port) { this._closeOnGetClientCompleted = true; this._OnlyHTTP = true; } + public SingledHttpServer(IPAddress IP, int Port) : base(IP, Port) { this._closeOnGetClientCompleted = true; this._OnlyHTTP = true; } + ~SingledHttpServer() { this.Dispose(); } + + protected Mutex _h_mutex = new Mutex(); + protected Dictionary _headers = new Dictionary(); + public Dictionary Headers + { + get + { + _h_mutex.WaitOne(); + Dictionary res = new Dictionary(); + foreach (KeyValuePair kvp in _headers) + res.Add(kvp.Key, kvp.Value); + _h_mutex.ReleaseMutex(); + return res; + } + set + { + _h_mutex.WaitOne(); + _headers.Clear(); + foreach (KeyValuePair kvp in value) + _headers.Add(kvp.Key, kvp.Value); + _h_mutex.ReleaseMutex(); + } + } + + public virtual void HttpClientSendError(TcpClient Client, int Code, Dictionary dopHeaders) + { + // "200 OK" + // HttpStatusCode - HTTP/1.1 + string CodeStr = Code.ToString() + " " + ((HttpStatusCode)Code).ToString(); + // HTML- + string Html = "

" + CodeStr + "

"; + // : , . - + string Str = "HTTP/1.1 " + CodeStr + "\r\n"; + this._h_mutex.WaitOne(); + foreach (KeyValuePair kvp in this._headers) + Str += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + this._h_mutex.ReleaseMutex(); + if (dopHeaders != null) + foreach (KeyValuePair kvp in dopHeaders) + Str += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + Str += "Content-type: text/html\r\nContent-Length: " + Html.Length.ToString() + "\r\n\r\n" + Html; + // + byte[] Buffer = Encoding.GetEncoding(1251).GetBytes(Str); + // + Client.GetStream().Write(Buffer, 0, Buffer.Length); + Client.GetStream().Flush(); + Client.Client.Close(); + Client.Close(); + } + public virtual void HttpClientSendError(TcpClient Client, int Code) + { + HttpClientSendError(Client, Code, null); + } + public virtual void HttpClientSendText(TcpClient Client, string Text, Dictionary dopHeaders) + { + // HTML- + string body = "" + Text + ""; + // : , . - + string header = "HTTP/1.1 200\r\n"; + + this._h_mutex.WaitOne(); + foreach (KeyValuePair kvp in this._headers) + header += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + this._h_mutex.ReleaseMutex(); + + if (dopHeaders != null) + foreach (KeyValuePair kvp in dopHeaders) + header += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + + byte[] bData = _responseEnc.GetBytes(body); + if (!DictHasKeyIgnoreCase(dopHeaders, "Content-type")) + header += "Content-type: text/html\r\n"; + if (!DictHasKeyIgnoreCase(dopHeaders, "Content-Length")) + header += "Content-Length: " + bData.Length.ToString() + "\r\n"; + header += "\r\n"; + + List response = new List(); + response.AddRange(Encoding.GetEncoding(1251).GetBytes(header)); + response.AddRange(bData); + + Client.GetStream().Write(response.ToArray(), 0, response.Count); + Client.GetStream().Flush(); + Client.Client.Close(); + Client.Close(); + } + public virtual void HttpClientSendText(TcpClient Client, string Text) + { + HttpClientSendText(Client, Text, null); + } + + protected override void GetClientRequest(TcpClient Client, ulong clientID, string Request, string Header, byte[] Body) + { + IDictionary clHeaders = GetClientHeaders(Header); + string page, host, inline; + IDictionary parameters; + string query = GetClientQuery(Header, out host, out page, out inline, out parameters); + HttpClientSendError(Client, 501); + if (!this._closeOnGetClientCompleted) CloseClient(Client, clientID); + } + } + + #region SimpleUDP + /// + /// UDP- + /// + public class SimpleUDPServer : IServer, IDisposable + { + private Thread mainThread = null; + private Socket udpSocket = null; + private IPAddress ListenIP = IPAddress.Any; + private int ListenPort = 5000; + private bool isRunning = false; + private int _bufferSize = 4096; + protected Exception LastError = null; + protected uint ErrorsCounter = 0; + + public SimpleUDPServer() { } + public SimpleUDPServer(int Port) { this.ListenPort = Port; } + public SimpleUDPServer(IPAddress IP, int Port) { this.ListenIP = IP; this.ListenPort = Port; } + ~SimpleUDPServer() { Dispose(); } + + public bool Running { get { return isRunning; } } + public ServerState GetState() { if (isRunning) return ServerState.ssRunning; else return ServerState.ssStopped; } + public IPAddress ServerIP { get { return ListenIP; } } + public int ServerPort { get { return ListenPort; } } + public int GetServerPort() { return ListenPort; } + public int BufferSize { get { return _bufferSize; } set { _bufferSize = value; } } + public Exception GetLastError() { return LastError; } + public uint GetErrorsCount { get { return ErrorsCounter; } } + + public void MainThread() + { + isRunning = true; + + IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0); + EndPoint Remote = (EndPoint)(sender); + + //byte[] data1 = Encoding.GetEncoding(1251).GetBytes("Hello"); + //udpSocket.SendTo(data1, data1.Length, SocketFlags.None, new IPEndPoint(IPAddress.Parse("127.0.0.1"), this.ListenPort)); + + while (isRunning) + { + try + { + byte[] data = new byte[_bufferSize]; + int recv = udpSocket.ReceiveFrom(data, ref Remote); + if (recv > 0) ReceiveBuff(Remote, data, recv); + } + catch (Exception ex) + { + try + { + ErrorsCounter++; + LastError = ex; + onError(ex); + } + catch { }; + }; + Thread.Sleep(1); + }; + } + + public virtual void Stop() + { + if (!isRunning) return; + isRunning = false; + + udpSocket.Close(); + mainThread.Join(); + + udpSocket = null; + mainThread = null; + } + + public virtual void Start() + { + if (isRunning) throw new Exception("Server Already Running!"); + + try + { + udpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); + IPEndPoint ipep = new IPEndPoint(this.ListenIP, this.ListenPort); + udpSocket.Bind(ipep); + } + catch (Exception ex) + { + ErrorsCounter++; + LastError = ex; + throw ex; + }; + + mainThread = new Thread(MainThread); + mainThread.Start(); + } + + public virtual void Dispose() { Stop(); } + + protected virtual void onError(Exception ex) + { + + } + + protected virtual void ReceiveBuff(EndPoint Client, byte[] data, int length) + { + // do anything + } + } + + /// + /// UDP-, + /// + public class SimpleTextUDPServer : SimpleUDPServer + { + public SimpleTextUDPServer() : base() { } + public SimpleTextUDPServer(int Port) : base(Port) { } + public SimpleTextUDPServer(IPAddress IP, int Port) : base(IP, Port) { } + ~SimpleTextUDPServer() { this.Dispose(); } + + protected override void ReceiveBuff(EndPoint Client, byte[] data, int length) + { + string Request = System.Text.Encoding.GetEncoding(1251).GetString(data, 0, length); + ReceiveData(Client, Request); + } + + protected virtual void ReceiveData(EndPoint Client, string Request) + { + string proto = "udp://" + Client.ToString() + "/text/"; + // do anything + } + } + #endregion + + /// + /// TCP- + /// + public class ThreadedTCPServer : TTCPServer + { + private class ClientTCPSInfo + { + public ulong id; + public TcpClient client; + public Thread thread; + + public ClientTCPSInfo(TcpClient client, Thread thread) + { + this.client = client; + this.thread = thread; + } + } + + /// + /// Working Mode + /// + public enum Mode: byte + { + /// + /// Allow all connections + /// + NoRules = 0, + /// + /// Allow only specified connections + /// + AllowWhiteList = 1, + /// + /// Allow all but black list + /// + DenyBlackList = 2 + } + + /// + /// Started + /// + private DateTime _started = DateTime.MinValue; + /// + /// Started + /// + public DateTime Started { get { return isRunning ? _started : DateTime.MinValue; } } + + /// + /// Started + /// + private DateTime _stopped = DateTime.MaxValue; + /// + /// Started + /// + public DateTime Stopped { get { return isRunning ? DateTime.MaxValue : _stopped; } } + + /// + /// Server IP Mode + /// + private Mode ipmode = Mode.NoRules; + /// + /// Server Access by IP Rules Mode + /// + public Mode ListenIPMode { get { return ipmode; } set { ipmode = value; } } + + private Mutex iplistmutex = new Mutex(); + /// + /// IP white list (supporting *: 192.168.10.*) (Regex, if starts from ^ or ends with $) + /// + private List ipwhitelist = new List(new string[] { "127.0.0.1", "192.168.*.*", @"^10.0.0?[0-9]?\d.\d{1,3}$" }); + /// + /// IP white List (supporting *: 192.168.10.*) (Regex, if starts from ^ or ends with $) + /// + public string[] ListenIPAllow + { + get + { + iplistmutex.WaitOne(); + string[] res = ipwhitelist.ToArray(); + iplistmutex.ReleaseMutex(); + return res; + } + set + { + iplistmutex.WaitOne(); + ipwhitelist.Clear(); + if (value != null) + ipwhitelist.AddRange(value); + iplistmutex.ReleaseMutex(); + } + } + + /// + /// IP black list (supporting *: 192.168.10.*) (Regex, if starts from ^ or ends with $) + /// + private List ipblacklist = new List(); + /// + /// IP black list (supporting *: 192.168.10.*) (Regex, if starts from ^ or ends with $) + /// + public string[] ListenIPDeny + { + get + { + iplistmutex.WaitOne(); + string[] res = ipblacklist.ToArray(); + iplistmutex.ReleaseMutex(); + return res; + } + set + { + iplistmutex.WaitOne(); + ipblacklist.Clear(); + if (value != null) + ipblacklist.AddRange(value); + iplistmutex.ReleaseMutex(); + } + } + + /// + /// Server Mac Mode + /// + private Mode macmode = Mode.NoRules; + /// + /// Server Access by IP Rules Mode + /// + public Mode ListenMacMode { get { return macmode; } set { macmode = value; } } + + private Mutex maclistmutex = new Mutex(); + /// + /// Mac Address White List (XX-XX-XX-XX-XX-XX) (supporting *: XX-XX-XX-XX-*-XX) (Regex, if starts from ^ or ends with $) + /// + private List macwhitelist = new List(); + /// + /// Mac Address White List (XX-XX-XX-XX-XX-XX) (supporting *: XX-XX-XX-XX-*-XX) (Regex, if starts from ^ or ends with $) + /// + public string[] ListenMacAllow + { + get + { + maclistmutex.WaitOne(); + string[] res = macwhitelist.ToArray(); + maclistmutex.ReleaseMutex(); + return res; + } + set + { + maclistmutex.WaitOne(); + macwhitelist.Clear(); + if (value != null) + macwhitelist.AddRange(value); + if (macwhitelist.Count > 0) + for (int i = 0; i < macwhitelist.Count; i++) + macwhitelist[i] = macwhitelist[i].ToUpper(); + maclistmutex.ReleaseMutex(); + } + } + /// + /// Mac Address Black List (XX-XX-XX-XX-XX-XX) (supporting *: XX-XX-XX-XX-*-XX) (Regex, if starts from ^ or ends with $) + /// + private List macblacklist = new List(); + /// + /// Mac Address Black List (XX-XX-XX-XX-XX-XX) (supporting *: XX-XX-XX-XX-*-XX) (Regex, if starts from ^ or ends with $) + /// + public string[] ListenMacDeny + { + get + { + maclistmutex.WaitOne(); + string[] res = macblacklist.ToArray(); + maclistmutex.ReleaseMutex(); + return res; + } + set + { + maclistmutex.WaitOne(); + macblacklist.Clear(); + if (value != null) + macblacklist.AddRange(value); + if (macblacklist.Count > 0) + for (int i = 0; i < macblacklist.Count; i++) + macblacklist[i] = macblacklist[i].ToUpper(); + maclistmutex.ReleaseMutex(); + } + } + + + /// + /// Max Clients Count + /// + private ushort maxClients = 50; + /// + /// Max connected clients count + /// + public ushort MaxClients { get { return maxClients; } set { maxClients = value; } } + + /// + /// Abort client connection on stop + /// + private bool abortOnStop = false; + /// + /// Abort client connections on stop + /// + public bool AbortOnStop { get { return abortOnStop; } set { abortOnStop = value; } } + + /// + /// Mutex for client dictionary + /// + private Mutex stack = new Mutex(); + /// + /// Client dictionary + /// + private Dictionary clients = new Dictionary(); + /// + /// Currect connected clients + /// + public KeyValuePair[] Clients + { + get + { + this.stack.WaitOne(); + List> res = new List>(); + foreach (KeyValuePair kvp in this.clients) + res.Add(new KeyValuePair(kvp.Key, kvp.Value.client)); + this.stack.ReleaseMutex(); + return res.ToArray(); + } + } + + public ThreadedTCPServer() { } + public ThreadedTCPServer(int Port) { this.ListenPort = Port; } + public ThreadedTCPServer(IPAddress IP, int Port) { this.ListenIP = IP; this.ListenPort = Port; } + ~ThreadedTCPServer() { Dispose(); } + + private bool AllowedByIPRules(TcpClient client) + { + if (ipmode != Mode.NoRules) + { + string remoteIP = ((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString(); + iplistmutex.WaitOne(); + if (ipmode == Mode.AllowWhiteList) + { + if ((ipwhitelist != null) && (ipwhitelist.Count > 0)) + { + foreach (string ip in ipwhitelist) + if ((ip.Contains("*") || ip.StartsWith("^") || ip.EndsWith("$"))) + { + string nip = ip.StartsWith("^") || ip.EndsWith("$") ? ip : ip.Replace(".", @"\.").Replace("*", @"\d{1,3}"); + Regex ex = new Regex(nip, RegexOptions.None); + if (ex.Match(remoteIP).Success) + return true; + }; + }; + if ((ipwhitelist == null) || (ipwhitelist.Count == 0) || (!ipwhitelist.Contains(remoteIP))) + { + iplistmutex.ReleaseMutex(); + return false; + }; + } + else + { + if ((ipblacklist != null) && (ipblacklist.Count > 0) && ipblacklist.Contains(remoteIP)) + { + iplistmutex.ReleaseMutex(); + return false; + }; + if ((ipblacklist != null) && (ipblacklist.Count > 0)) + { + foreach (string ip in ipblacklist) + if ((ip.Contains("*") || ip.StartsWith("^") || ip.EndsWith("$"))) + { + string nip = ip.StartsWith("^") || ip.EndsWith("$") ? ip : ip.Replace(".", @"\.").Replace("*", @"\d{1,3}"); + Regex ex = new Regex(nip, RegexOptions.None); + if (ex.Match(remoteIP).Success) + return false; + }; + }; + }; + iplistmutex.ReleaseMutex(); + }; + return true; + } + + private bool AllowedByMacRules(TcpClient client) + { + if (macmode != Mode.NoRules) + { + string remoteMac = GetMacAddressByIP(((IPEndPoint)client.Client.RemoteEndPoint).Address.ToString().ToUpper()); + maclistmutex.WaitOne(); + if (macmode == Mode.AllowWhiteList) + { + if ((macwhitelist != null) && (macwhitelist.Count > 0)) + { + foreach (string mac in macwhitelist) + if ((mac.Contains("*") || mac.StartsWith("^") || mac.EndsWith("$"))) + { + string nmac = mac.StartsWith("^") || mac.EndsWith("$") ? mac : mac.Replace(".", @"\.").Replace("*", @"\w{2}}"); + Regex ex = new Regex(nmac, RegexOptions.None); + if (ex.Match(remoteMac).Success) + return true; + }; + }; + if ((macwhitelist == null) || (macwhitelist.Count == 0) || (!macwhitelist.Contains(remoteMac))) + { + maclistmutex.ReleaseMutex(); + return false; + }; + } + else + { + if ((macblacklist != null) && (macblacklist.Count > 0) && macblacklist.Contains(remoteMac)) + { + maclistmutex.ReleaseMutex(); + return false; + }; + if ((macblacklist != null) && (macblacklist.Count > 0)) + { + foreach (string mac in macblacklist) + if ((mac.Contains("*") || mac.StartsWith("^") || mac.EndsWith("$"))) + { + string nmac = mac.StartsWith("^") || mac.EndsWith("$") ? mac : mac.Replace(".", @"\.").Replace("*", @"\w{2}}"); + Regex ex = new Regex(nmac, RegexOptions.None); + if (ex.Match(remoteMac).Success) + return false; + }; + }; + }; + maclistmutex.ReleaseMutex(); + }; + return true; + } + + /// + /// Calls at server main listening thread not for each client + /// -- connection will be closed after return -- + /// + /// + protected virtual void GetBlockedClient(TcpClient Client) + { + // do nothing + // close after return + } + + private void MainThread() + { + _started = DateTime.Now; + isRunning = true; + while (isRunning) + { + try + { + bool allowed = false; + TcpClient client = mainListener.AcceptTcpClient(); + if (!AcceptClient(client)) + { + client.Client.Close(); + client.Close(); + continue; + }; + + allowed = AllowedByIPRules(client) && AllowedByMacRules(client); + if (!allowed) + { + try + { + GetBlockedClient(client); + client.Client.Close(); + client.Close(); + } + catch { }; + continue; + }; + + if (this.maxClients < 2) // single-threaded + { + RunClientThread(new ClientTCPSInfo(client, null)); + } + else // multi-threaded + { + while ((this.alive >= this.maxClients) && isRunning) // wait for any closed thread + System.Threading.Thread.Sleep(5); + if (isRunning) + { + Thread thr = new Thread(RunThreaded); + thr.Start(new ClientTCPSInfo(client, thr)); + }; + }; + } + catch { }; + Thread.Sleep(1); + }; + } + + private void RunThreaded(object client) + { + RunClientThread((ClientTCPSInfo)client); + } + + private void RunClientThread(ClientTCPSInfo Client) + { + this.alive++; + Client.id = this.counter++; + this.stack.WaitOne(); + this.clients.Add(Client.id, Client); + this.stack.ReleaseMutex(); + try + { + Client.client.GetStream().ReadTimeout = this.readTimeout * 1000; + GetClient(Client.client, Client.id); + } + catch (Exception ex) + { + LastError = ex; + LastErrTime = DateTime.Now; + ErrorsCounter++; + onError(Client.client, Client.id, ex); + } + finally + { + try { Client.client.GetStream().Flush(); } catch { }; + try + { + Client.client.Client.Close(); + Client.client.Close(); + } + catch { }; + }; + + this.stack.WaitOne(); + if (this.clients.ContainsKey(Client.id)) + this.clients.Remove(Client.id); + this.stack.ReleaseMutex(); + this.alive--; + } + + /// + /// Start Server + /// + public override void Start() + { + if (isRunning) throw new Exception("Server Already Running!"); + + try + { + mainListener = new TcpListener(this.ListenIP, this.ListenPort); + mainListener.Start(); + } + catch (Exception ex) + { + LastError = ex; + LastErrTime = DateTime.Now; + ErrorsCounter++; + throw ex; + }; + + mainThread = new Thread(MainThread); + mainThread.Start(); + } + + /// + /// Stop Server + /// + public override void Stop() + { + if (!isRunning) return; + + isRunning = false; + + if (this.abortOnStop) + { + this.stack.WaitOne(); + try + { + foreach (KeyValuePair kvp in this.clients) + { + try { if (kvp.Value.thread != null) kvp.Value.thread.Abort(); } + catch { }; + try { kvp.Value.client.Client.Close(); } + catch { }; + try { kvp.Value.client.Close(); } + catch { }; + }; + this.clients.Clear(); + } + catch { }; + this.stack.ReleaseMutex(); + }; + + _stopped = DateTime.Now; + + if (mainListener != null) mainListener.Stop(); + mainListener = null; + + mainThread.Join(); + mainThread = null; + } + + /// + /// Get Client, threaded + /// + /// + /// + protected override void GetClient(TcpClient Client, ulong id) + { + // loop something + // connection will be close after return + } + } + + public class ClientExample4Bytes + { + public ClientExample4Bytes() + { + System.Net.Sockets.TcpClient client = new System.Net.Sockets.TcpClient(); + client.Connect("127.0.0.1", 8011); + + // write + List buff = new List(); + buff.AddRange(System.Text.Encoding.GetEncoding(1251).GetBytes("PROTOBUF+4")); + buff.Add(0); buff.Add(0); buff.Add(0); buff.Add(0); + client.GetStream().Write(buff.ToArray(), 0, buff.Count); + client.GetStream().Flush(); + + // read + byte[] incb = new byte[14]; + int count = client.GetStream().Read(incb, 0, incb.Length); + string prefix = System.Text.Encoding.GetEncoding(1251).GetString(incb, 0, 10); + if (prefix != "PROTOBUF+4") + { + int length = System.BitConverter.ToInt32(incb, 10); + incb = new byte[length]; + client.GetStream().Read(incb, 0, incb.Length); + }; + } + } + + /// + /// Protobuf- + /// + public class Threaded4BytesTCPServer : ThreadedTCPServer + { + //[ProtoContract] + //public class Config + //{ + // [ProtoMember(1)] + // public string inputFileName = ""; + // [ProtoMember(2)] + // public string outputFileName = AppDomain.CurrentDomain.BaseDirectory.Trim('\\') + @"\SHAPES\default.dbf"; + // [ProtoMember(3)] + // public byte selector = 1; + // [ProtoMember(4)] + // public bool onlyHasName = false; + // [ProtoMember(5)] + // public Dictionary onlyWithTags = new Dictionary(); + // [ProtoMember(7)] + // public DateTime onlyMdfAfter = DateTime.MinValue; + // [ProtoMember(10)] + // public List onlyVersion = new List(); + //}; + // ProtoBuf.Serializer.Serialize(Stream, object); + // ProtoBuf.Serializer.Deserialize(new MemoryStream(data)); + + public Threaded4BytesTCPServer() : base() { } + public Threaded4BytesTCPServer(int Port) : base(Port) { } + public Threaded4BytesTCPServer(IPAddress IP, int Port) : base(IP, Port) { } + ~Threaded4BytesTCPServer() { this.Dispose(); } + + protected string _prefix = "PROTOBUF+4"; + public string MessagePrefix { get { return _prefix; } set { _prefix = value; } } + + /// + /// Get Client, threaded + /// + /// + /// + protected override void GetClient(TcpClient Client, ulong id) + { + try + { + // PROTOBUF + 0x00 0x00 0x00 0x00 // 4 bytes length of 1-st block data (BigEndian) // 0x04000000 means 4; 0x00000004 means 67108864 + + byte[] buff = new byte[this._prefix.Length + 4]; + Client.GetStream().Read(buff, 0, buff.Length); + string prefix = System.Text.Encoding.GetEncoding(1251).GetString(buff, 0, this._prefix.Length); + if (prefix != this._prefix) return; + + int length = System.BitConverter.ToInt32(buff, this._prefix.Length); + buff = new byte[length]; + Client.GetStream().Read(buff, 0, buff.Length); + GetClientData(Client, id, buff); + } + catch (Exception ex) + { + onError(Client, id, ex); + }; + } + + /// + /// Get Client with 1st block of data + /// + /// + /// + /// + protected virtual void GetClientData(TcpClient Client, ulong id, byte[] data) + { + // Write No Data To Client + + byte[] prfx = System.Text.Encoding.GetEncoding(1251).GetBytes(this._prefix); + Client.GetStream().Write(prfx, 0, prfx.Length); + byte[] buff = System.Text.Encoding.GetEncoding(1251).GetBytes("Hello " + DateTime.Now.ToString()); + Client.GetStream().Write(BitConverter.GetBytes(buff.Length), 0, 4); + Client.GetStream().Write(buff, 0, buff.Length); + Client.GetStream().Flush(); + + // connection will be close after return + } + + protected override void onError(TcpClient Client, ulong id, Exception error) + { + + } + + private static void sample() + { + SimpleServersPBAuth.Threaded4BytesTCPServer srv = new SimpleServersPBAuth.Threaded4BytesTCPServer(8011); + srv.ReadTimeout = 30; + srv.Start(); + // + System.Threading.Thread.Sleep(10000); + // + srv.Stop(); + srv.Dispose(); + } + } + + /// + /// TCP-, + /// + public class ThreadedTextTCPServer : ThreadedTCPServer + { + public ThreadedTextTCPServer() : base() { } + public ThreadedTextTCPServer(int Port) : base(Port) { } + public ThreadedTextTCPServer(IPAddress IP, int Port) : base(IP, Port) { } + ~ThreadedTextTCPServer() { this.Dispose(); } + + protected bool _OnlyHTTP = false; + public virtual bool OnlyHTTPClients { get { return _OnlyHTTP; } set { _OnlyHTTP = value; } } + protected ushort _MaxHeaderSize = 4096; + public ushort MaxClientHeaderSize { get { return _MaxHeaderSize; } set { _MaxHeaderSize = value; } } + protected uint _MaxBodySize = 65536; + public uint MaxClientBodySize { get { return _MaxBodySize; } set { _MaxBodySize = value; } } + protected Encoding _responseEnc = Encoding.GetEncoding(1251); + public Encoding ResponseEncoding { get { return _responseEnc; } set { _responseEnc = value; } } + protected Encoding _requestEnc = Encoding.GetEncoding(1251); + public Encoding RequestEncoding { get { return _requestEnc; } set { _requestEnc = value; } } + + /// + /// Get Client, threaded + /// + /// + /// + protected override void GetClient(TcpClient Client, ulong clientID) + { + Regex CR = new Regex(@"Content-Length: (\d+)", RegexOptions.IgnoreCase); + + string Request = ""; + string Header = null; + List Body = new List(); + + int bRead = -1; + int posCRLF = -1; + int receivedBytes = 0; + int contentLength = 0; + + // Get Header + //while ((Client.Available > 0) && ((bRead = Client.GetStream().ReadByte()) >= 0)) // doesn't work correct + while ((bRead = Client.GetStream().ReadByte()) >= 0) + { + receivedBytes++; + Body.Add((byte)bRead); + + // Check GET or POST + if (_OnlyHTTP && (receivedBytes == 1)) + if ((bRead != 0x47) && (bRead != 0x50)) + return; + + Request += (char)bRead; // standard symbol + if (bRead == 0x0A) posCRLF = Request.IndexOf("\r\n\r\n"); // get body start index + if (posCRLF >= 0 || Request.Length > _MaxHeaderSize) { break; }; // GET ONLY + }; + + bool valid = (posCRLF > 0); + if ((!valid) && _OnlyHTTP) return; + + // Get Body + if (valid) + { + Body.Clear(); + Header = Request; + Match mx = CR.Match(Request); + if (mx.Success) contentLength = int.Parse(mx.Groups[1].Value); + int total2read = posCRLF + 4 + contentLength; + while ((receivedBytes < total2read) && ((bRead = Client.GetStream().ReadByte()) >= 0)) + { + receivedBytes++; + Body.Add((byte)bRead); + + string rcvd = _requestEnc.GetString(new byte[] { (byte)bRead }, 0, 1); + Request += rcvd; + if (Request.Length > _MaxBodySize) { break; }; + }; + }; + + GetClientRequest(Client, clientID, Request, Header, Body.ToArray()); + } + + /// + /// Get Client with Request Text Data + /// + /// socket + /// number + /// Request + /// Header + /// Body + /// Body + protected virtual void GetClientRequest(TcpClient Client, ulong clientID, string Request, string Header, byte[] Body) + { + // + // loop something + // connection will be close after return + // + } + } + + /// + /// HTTP- + /// + public class ThreadedHttpServer : ThreadedTCPServer + { + public ThreadedHttpServer() : base(80) { Init(); } + public ThreadedHttpServer(int Port) : base(Port) { Init(); } + public ThreadedHttpServer(IPAddress IP, int Port) : base(IP, Port) { Init(); } + ~ThreadedHttpServer() { this.Dispose(); } + + protected void Init() + { + _headers.Add("Server-Name", _serverName); + //_headers.Add("Connection", "close"); + } + + /// + /// Skip Files Extentions to Browse and download + /// + public string[] AllowNotFileExt = new string[] { ".exe", ".dll", ".cmd", ".bat", ".lib", ".crypt", }; + + /// + /// Server Name + /// + public string ServerName + { + get { return _serverName; } + set + { + _serverName = value; + _headers_mutex.WaitOne(); + if (_headers.ContainsKey("Server-Name")) + _headers["Server-Name"] = _serverName; + else + _headers.Add("Server-Name", _serverName); + _headers_mutex.ReleaseMutex(); + } + } + protected string _serverName = "SimpleServersPBAuth Basic HttpServer v0.2B"; + + /// + /// Allow to connect only HTTP clients + /// + public virtual bool OnlyHTTPClients { get { return _OnlyHTTP; } set { _OnlyHTTP = value; } } + protected bool _OnlyHTTP = true; + + /// + /// Max Http Header Client Size + /// + public uint MaxClientHeaderSize { get { return _MaxHeaderSize; } set { _MaxHeaderSize = value; } } + protected uint _MaxHeaderSize = 4096; + + /// + /// Max Http Body Client Size + /// + public uint MaxClientBodySize { get { return _MaxBodySize; } set { _MaxBodySize = value; } } + protected uint _MaxBodySize = 65536; + + /// + /// Max File Size to Download + /// + public long AllowBrowseDownloadMaxSize { get { return _MaxFileDownloadSize; } set { _MaxFileDownloadSize = value; } } + protected long _MaxFileDownloadSize = 1024 * 1024 * 40; // 40 MB + + /// + /// Default Server Response Encoding + /// + public Encoding ResponseEncoding { get { return _responseEnc; } set { _responseEnc = value; } } + protected Encoding _responseEnc = Encoding.GetEncoding(1251); + + /// + /// Default Client Request Encoding + /// + public Encoding RequestEncoding { get { return _requestEnc; } set { _requestEnc = value; } } + protected Encoding _requestEnc = Encoding.GetEncoding(1251); + + /// + /// Send Error to Denied IP Clients + /// + public bool ListenIPDeniedSendError { get { return _sendlockedError; } set { _sendlockedError = value; } } + protected bool _sendlockedError = false; + + /// + /// Error Code for Denied IP Clients + /// + public int ListenIPDeniedErrorCode { get { return _sendlockedErrCode; } set { _sendlockedErrCode = value; } } + protected int _sendlockedErrCode = 423; // Locked + + /// + /// Server Response Main Headers + /// + public Dictionary Headers + { + get + { + _headers_mutex.WaitOne(); + Dictionary res = new Dictionary(); + foreach (KeyValuePair kvp in _headers) + res.Add(kvp.Key, kvp.Value); + _headers_mutex.ReleaseMutex(); + return res; + } + set + { + _headers_mutex.WaitOne(); + _headers.Clear(); + foreach (KeyValuePair kvp in value) + _headers.Add(kvp.Key, kvp.Value); + if (_headers.ContainsKey("Server-Name")) + _headers["Server-Name"] = _serverName; + else + _headers.Add("Server-Name", _serverName); + _headers_mutex.ReleaseMutex(); + } + } + protected Dictionary _headers = new Dictionary(); + protected Mutex _headers_mutex = new Mutex(); + + /// + /// Home Directory For File Listing + /// + public string HomeDirectory { get { return _baseDir; } set { _baseDir = value; } } + protected string _baseDir = null; + + /// + /// Allow File Download + /// + public bool AllowBrowseDownloads { get { return _allowGetFiles; } set { _allowGetFiles = value; } } + protected bool _allowGetFiles = false; + + /// + /// Allow Browse Directory for Files + /// + public bool AllowBrowseFiles { get { return _allowGetDirs; } set { if (value) _allowGetFiles = true; _allowGetDirs = value; } } + protected bool _allowGetDirs = false; + + /// + /// Allow Browse Directory for Big Files (over AllowBrowseDownloadMaxSize) + /// + public bool AllowBrowseBigFiles { get { return _allowGetDirs; } set { if (value) _allowGetFiles = true; _allowGetDirs = value; } } + protected bool _allowListBigFiles = true; + + /// + /// Allow Browse Directory for Directories + /// + public bool AllowBrowseDirectories { get { return _allowListDirs; } set { if (value) _allowGetFiles = true; _allowListDirs = value; } } + protected bool _allowListDirs = false; + + /// + /// User:password for Authorizated users + /// + public Dictionary AuthentificationCredintals = new Dictionary(); + /// + /// Server Requires Authorization + /// + public bool AuthentificationRequired { get { return _authRequired; } set { _authRequired = value; } } + private bool _authRequired = false; + + /// + /// Get Client, threaded + /// + /// + /// + protected override void GetClient(TcpClient Client, ulong clientID) + { + Regex CR = new Regex(@"Content-Length: (\d+)", RegexOptions.IgnoreCase); + + string Request = ""; + string Header = null; + List Body = new List(); + + int bRead = -1; + int posCRLF = -1; + int receivedBytes = 0; + int contentLength = 0; + + try + { + // Get Header + //while ((Client.Available > 0) && ((bRead = Client.GetStream().ReadByte()) >= 0)) // doesn't work correct + while ((bRead = Client.GetStream().ReadByte()) >= 0) + { + receivedBytes++; + Body.Add((byte)bRead); + + // Check GET or POST + if (_OnlyHTTP && (receivedBytes == 1)) + if ((bRead != 0x47) && (bRead != 0x50)) + { + onBadClient(Client, clientID, Body.ToArray()); + return; + }; + + Request += (char)bRead; // standard symbol + if (bRead == 0x0A) posCRLF = Request.IndexOf("\r\n\r\n"); // get body start index + if (posCRLF >= 0 || Request.Length > _MaxHeaderSize) { break; }; // GET ONLY + }; + + if (Request.Length > _MaxHeaderSize) + { + // Header too long + HttpClientSendError(Client, 414, "414 Header Too Long"); + return; + }; + + bool valid = (posCRLF > 0); + if ((!valid) && _OnlyHTTP) + { + onBadClient(Client, clientID, Body.ToArray()); + return; + }; + + if (_authRequired && (AuthentificationCredintals.Count > 0)) + { + bool accept = false; + string sa = "Authorization:"; + if (Request.IndexOf(sa) > 0) + { + int iofcl = Request.IndexOf(sa); + sa = Request.Substring(iofcl + sa.Length, Request.IndexOf("\r", iofcl + sa.Length) - iofcl - sa.Length).Trim(); + if (sa.StartsWith("Basic")) + { + sa = Base64Decode(sa.Substring(6)); + string[] up = sa.Split(new char[] { ':' }, 2); + if (AuthentificationCredintals.ContainsKey(up[0]) && AuthentificationCredintals[up[0]] == up[1]) + accept = true; + }; + }; + if (!accept) + { + Dictionary dh = new Dictionary(); + dh.Add("WWW-Authenticate", "Basic realm=\"Authentification required\""); + HttpClientSendError(Client, 401, dh); // 401 Unauthorized + return; + }; + }; + + // Get Body + if (valid) + { + Body.Clear(); + Header = Request; + Match mx = CR.Match(Request); + if (mx.Success) contentLength = int.Parse(mx.Groups[1].Value); + int total2read = posCRLF + 4 + contentLength; + while ((receivedBytes < total2read) && ((bRead = Client.GetStream().ReadByte()) >= 0)) + { + receivedBytes++; + Body.Add((byte)bRead); + + string rcvd = _requestEnc.GetString(new byte[] { (byte)bRead }, 0, 1); + Request += rcvd; + if (Request.Length > _MaxBodySize) + { + // Body too long + HttpClientSendError(Client, 413, "413 Payload Too Large"); + return; + }; + }; + }; + + GetClientRequest(Client, clientID, Request, Header, Body.ToArray()); + } + catch (Exception ex) + { + LastError = ex; + LastErrTime = DateTime.Now; + ErrorsCounter++; + onError(Client, clientID, ex); + }; + } + + /// + /// Send Response with Error Code To Client + /// + /// + /// Status Code + /// Response Headers + protected virtual void HttpClientSendError(TcpClient Client, int Code, Dictionary dopHeaders) + { + string CodeStr = Code.ToString() + " " + ((HttpStatusCode)Code).ToString(); + string body = "

" + CodeStr + "

"; + HttpClientSendData(Client, _responseEnc.GetBytes(body), dopHeaders, Code, "text/html"); + } + /// + /// Send Response with Error Code To Client + /// + /// + /// Status Code + protected virtual void HttpClientSendError(TcpClient Client, int Code) + { + HttpClientSendError(Client, Code, (Dictionary)null); + } + /// + /// Send Response with Error Code To Client + /// + /// + /// Status Code + /// Body Text + protected virtual void HttpClientSendError(TcpClient Client, int Code, string Text) + { + HttpClientSendData(Client, _responseEnc.GetBytes(Text), null, Code, "text/html"); + } + /// + /// Send Response Text Status 200 To Client + /// + /// + /// Body Text + /// Response Headers + protected virtual void HttpClientSendText(TcpClient Client, string Text, IDictionary dopHeaders) + { + string body = "" + Text + ""; + HttpClientSendData(Client, _responseEnc.GetBytes(body), dopHeaders, 200, "text/html"); + } + /// + /// Send Response Text Status 200 To Client + /// + /// + /// Body Text + protected virtual void HttpClientSendText(TcpClient Client, string Text) + { + HttpClientSendText(Client, Text, null); + } + + /// + /// Send Response Data Status 200 To Client + /// + /// + /// + protected virtual void HttpClientSendData(TcpClient Client, byte[] body) + { + HttpClientSendData(Client, body, null, 200, "text/html"); + } + /// + /// Send Response Data Status 200 To Client + /// + /// + /// + /// Response Headers + protected virtual void HttpClientSendData(TcpClient Client, byte[] body, IDictionary dopHeaders) + { + HttpClientSendData(Client, body, dopHeaders, 200, "text/html"); + } + /// + /// Send Response Data To Client + /// + /// + /// + /// Response Headers + /// Status Code + protected virtual void HttpClientSendData(TcpClient Client, byte[] body, IDictionary dopHeaders, int ResponseCode) + { + HttpClientSendData(Client, body, dopHeaders, ResponseCode, "text/html"); + } + /// + /// Send Response Data To Client + /// + /// + /// + /// Response Headers + /// Content Type + protected virtual void HttpClientSendData(TcpClient Client, byte[] body, IDictionary dopHeaders, string ContentType) + { + HttpClientSendData(Client, body, dopHeaders, 200, ContentType); + } + /// + /// Send Response Data To Client + /// + /// + /// + /// Response Headers + /// Status Code + /// Content Type + protected virtual void HttpClientSendData(TcpClient Client, byte[] body, IDictionary dopHeaders, int ResponseCode, string ContentType) + { + string header = "HTTP/1.1 " + ResponseCode.ToString() + "\r\n"; + + string val = null; + if ((val = DictGetKeyIgnoreCase(dopHeaders, "Status")) != null) header = "HTTP/1.1 " + val + "\r\n"; + + // Main Headers + this._headers_mutex.WaitOne(); + foreach (KeyValuePair kvp in this._headers) + header += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + this._headers_mutex.ReleaseMutex(); + + // Dop Headers + if (dopHeaders != null) + foreach (KeyValuePair kvp in dopHeaders) + header += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + + if (!DictHasKeyIgnoreCase(dopHeaders, "Content-type")) + header += "Content-type: " + ContentType + "\r\n"; + if (!DictHasKeyIgnoreCase(dopHeaders, "Content-Length")) + header += "Content-Length: " + body.Length.ToString() + "\r\n"; + header += "\r\n"; + + List response = new List(); + response.AddRange(Encoding.GetEncoding(1251).GetBytes(header)); + response.AddRange(body); + + Client.GetStream().Write(response.ToArray(), 0, response.Count); + Client.GetStream().Flush(); + //Client.Client.Close(); + //Client.Close(); + } + /// + /// Send File To Client + /// + /// + /// File Full Path + /// Response Headers + /// Status Code + /// Content Type + protected virtual void HttpClientSendFile(TcpClient Client, string fileName, Dictionary dopHeaders, int ResponseCode, string ContentType) + { + FileInfo fi = new FileInfo(fileName); + + string header = "HTTP/1.1 " + ResponseCode.ToString() + "\r\n"; + + // Main Headers + this._headers_mutex.WaitOne(); + foreach (KeyValuePair kvp in this._headers) + header += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + this._headers_mutex.ReleaseMutex(); + + // Dop Headers + if (dopHeaders != null) + foreach (KeyValuePair kvp in dopHeaders) + header += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + + if (String.IsNullOrEmpty(ContentType)) + ContentType = GetMemeType(fi.Extension.ToLower()); + if (!DictHasKeyIgnoreCase(dopHeaders, "Content-type")) + header += "Content-type: " + ContentType + "\r\n"; + if (!DictHasKeyIgnoreCase(dopHeaders, "Content-Length")) + header += "Content-Length: " + fi.Length.ToString() + "\r\n"; + header += "\r\n"; + + List response = new List(); + response.AddRange(Encoding.GetEncoding(1251).GetBytes(header)); + Client.GetStream().Write(response.ToArray(), 0, response.Count); + + // copy + byte[] buff = new byte[65536]; + int bRead = 0; + FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read); + while (fs.Position < fs.Length) + { + bRead = fs.Read(buff, 0, buff.Length); + Client.GetStream().Write(buff, 0, bRead); + }; + fs.Close(); + // + + Client.GetStream().Flush(); + //Client.Client.Close(); + //Client.Close(); + } + + /// + /// Get HTTP Client Request, threaded + /// + /// + /// + /// + /// + /// + protected virtual void GetClientRequest(TcpClient Client, ulong clientID, string Request, string Header, byte[] Body) + { + IDictionary clHeaders = GetClientHeaders(Header); + string page, host, inline, query; + IDictionary parameters; + ClientRequest cl = ClientRequest.FromServer(this); + + try + { + query = GetClientQuery(Header, out host, out page, out inline, out parameters); + + cl.Client = Client; + cl.clientID = clientID; + cl.OriginRequest = Request; + cl.OriginHeader = Header; + cl.BodyData = Body; + cl.Query = query; + cl.Page = page; + cl.Host = host; + cl.QueryParams = parameters; + cl.QueryInline = inline; + cl.Headers = clHeaders; + } + catch (Exception ex) + { + + }; + + GetClientRequest(cl); + // connection will be close after return + } + /// + /// Get HTTP Client Request, threaded + /// + /// + protected virtual void GetClientRequest(ClientRequest Request) + { + HttpClientSendError(Request.Client, 501); + // connection will be close after return + } + /// + /// Call on error in GetClient & GetClientRequest + /// + /// + /// + /// + protected override void onError(TcpClient Client, ulong id, Exception error) + { + // no base.onError - no throw exception + } + /// + /// Call on Bad Client (Invalid HTTP Request) + /// + /// + /// + /// + protected virtual void onBadClient(TcpClient Client, ulong id, byte[] Request) + { + // do nothing + } + + /// + /// Pass File or Browse Folder(s) to HTTP Client by Request + /// + /// + protected virtual void PassFileToClientByRequest(ClientRequest Request) + { + PassFileToClientByRequest(Request, _baseDir, null); + } + /// + /// Pass File or Browse Folder(s) to HTTP Client by Request + /// + /// + /// Home Directory with files to Browse + protected virtual void PassFileToClientByRequest(ClientRequest Request, string HomeDirectory) + { + PassFileToClientByRequest(Request, HomeDirectory, null); + } + /// + /// Pass File or Browse Folder(s) to HTTP Client by Request + /// + /// + /// Home Directory with files to Browse + /// Sub Path to Extract from URL Path + protected virtual void PassFileToClientByRequest(ClientRequest Request, string HomeDirectory, string subPath) + { + if (String.IsNullOrEmpty(HomeDirectory)) { HttpClientSendError(Request.Client, 403); return; }; + if (!_allowGetFiles) { HttpClientSendError(Request.Client, 403); return; }; + if (String.IsNullOrEmpty(Request.Query)) { HttpClientSendError(Request.Client, 400); return; }; + if (String.IsNullOrEmpty(Request.Page)) { HttpClientSendError(Request.Client, 403); return; }; + if ((Request.QueryParams != null) && (Request.QueryParams.Count > 0)) { HttpClientSendError(Request.Client, 400); return; }; + + string path = Request.Page; + if (!String.IsNullOrEmpty(subPath)) + { + int i = path.IndexOf(subPath); + if (i >= 0) path = path.Remove(i, subPath.Length); + }; + path = path.Replace("/", @"\"); + if (path.IndexOf("/./") >= 0) { HttpClientSendError(Request.Client, 400); return; }; + if (path.IndexOf("/../") >= 0) { HttpClientSendError(Request.Client, 400); return; }; + if (path.IndexOf("/.../") >= 0) { HttpClientSendError(Request.Client, 400); return; }; + path = HomeDirectory + @"\" + path; + while (path.IndexOf(@"\\") > 0) path = path.Replace(@"\\", @"\"); + string fName = System.IO.Path.GetFileName(path); + string dName = System.IO.Path.GetDirectoryName(path); + if ((String.IsNullOrEmpty(dName)) && (String.IsNullOrEmpty(fName)) && (path.EndsWith(@":\")) && (Path.IsPathRooted(path))) dName = path; + if (!String.IsNullOrEmpty(fName)) + { + if (!File.Exists(path)) + { + HttpClientSendError(Request.Client, 404); + return; + } + else + { + List disallowExt = new List(AllowNotFileExt); + FileInfo fi = new FileInfo(path); + string fExt = fi.Extension.ToLower(); + if (disallowExt.Contains(fExt)) + { + HttpClientSendError(Request.Client, 403); + return; + } + else + { + if (fi.Length > _MaxFileDownloadSize) + HttpClientSendError(Request.Client, 509, String.Format("509 File is too big - {0}, limit - {1}", ToFileSize(fi.Length), ToFileSize(_MaxFileDownloadSize))); + else + HttpClientSendFile(Request.Client, path, null, 200, null); + return; + }; + }; + } + else if (!String.IsNullOrEmpty(dName)) + { + if (!Directory.Exists(path)) + { + HttpClientSendError(Request.Client, 404); + return; + } + else + { + // load default file + { + List files = new List(Directory.GetFiles(path, "index.*", SearchOption.TopDirectoryOnly)); + foreach (string file in files) + { + string fExt = Path.GetExtension(file); + if (fExt == ".html") { HttpClientSendFile(Request.Client, file, null, 200, null); return; }; + if (fExt == ".dhtml") { HttpClientSendFile(Request.Client, file, null, 200, null); return; }; + if (fExt == ".htmlx") { HttpClientSendFile(Request.Client, file, null, 200, null); return; }; + if (fExt == ".xhtml") { HttpClientSendFile(Request.Client, file, null, 200, null); return; }; + if (fExt == ".txt") { HttpClientSendFile(Request.Client, file, null, 200, null); return; }; + }; + }; + if (!_allowGetDirs) + { + HttpClientSendError(Request.Client, 403); + return; + } + else + { + string html = ""; + if (_allowListDirs) + { + html += String.Format(" {0}
\n\r", ".."); + string[] dirs = Directory.GetDirectories(path); + if (dirs != null) Array.Sort(dirs); + foreach (string dir in dirs) + { + DirectoryInfo di = new DirectoryInfo(dir); + if ((di.Attributes & FileAttributes.Hidden) > 0) continue; + string sPath = dir.Substring(dir.LastIndexOf(@"\") + 1); + html += String.Format("{0}
\n\r", sPath, UrlEscape(sPath)); + }; + }; + { + List disallowExt = new List(AllowNotFileExt); + string[] files = Directory.GetFiles(path, "*", SearchOption.TopDirectoryOnly); + if (files != null) Array.Sort(files); + foreach (string file in files) + { + FileInfo fi = new FileInfo(file); + if (disallowExt.Contains(fi.Extension.ToLower())) continue; + if ((fi.Attributes & FileAttributes.Hidden) > 0) continue; + if ((!_allowListBigFiles) && (fi.Length > _MaxFileDownloadSize)) continue; + string sPath = Path.GetFileName(file); + html += String.Format("{0} - {2}, MDF: {3}
\n\r", sPath, UrlEscape(sPath), ToFileSize(fi.Length), fi.LastWriteTime); + }; + }; + html += ""; + HttpClientSendText(Request.Client, html); + return; + }; + }; + }; + HttpClientSendError(Request.Client, 400); + } + + /// + /// Call CGI-BIN file and Send Response to HTTP Client + /// + /// + /// CGI-BIN File Full Path + /// Command Line Arguments + protected virtual void PassCGIBinResultToClientByRequest(ClientRequest Request, string exeFile, string cmdLineArgs) + { + string pVal = null; + Dictionary parameters = new Dictionary(); + if ((pVal = DictGetKeyIgnoreCase(Request.Headers, "content-type")) != null) parameters.Add("CONTENT_TYPE", pVal); + if ((pVal = DictGetKeyIgnoreCase(Request.Headers, "content-length")) != null) + parameters.Add("CONTENT_LENGTH", pVal); + else + parameters.Add("CONTENT_LENGTH", Request.BodyData == null ? "0" : Request.BodyData.Length.ToString()); + parameters.Add("SERVER_PORT", ServerPort.ToString()); + parameters.Add("PATH_INFO", Request.Page); + parameters.Add("REQUEST_URI", Request.Query); + if (!String.IsNullOrEmpty(Request.QueryInline)) parameters.Add("QUERY_STRING", Request.QueryInline); + parameters.Add("REMOTE_HOST", Request.RemoteIP); + parameters.Add("REMOTE_ADDR", Request.RemoteIP); + if (!String.IsNullOrEmpty(Request.Authorization)) parameters.Add("AUTH_TYPE", "Basic"); + parameters.Add("REMOTE_USER", Request.User); + if ((pVal = DictGetKeyIgnoreCase(Request.Headers, "accept")) != null) parameters.Add("HTTP_ACCEPT", pVal); + parameters.Add("HTTP_USER_AGENT", Request.UserAgent); + parameters.Add("HTTP_REFERER", Request.Referer); + if ((pVal = DictGetKeyIgnoreCase(Request.Headers, "cookie")) != null) parameters.Add("HTTP_COOKIE", Request.User); + parameters.Add("SERVER_NAME", _serverName); + + CGIBINCaller.Response resp = CGIBINCaller.Call(exeFile, Request.BodyData, parameters, cmdLineArgs); + if (resp == null) + HttpClientSendError(Request.Client, 523, "523 Origin Is Unreachable"); + else + HttpClientSendData(Request.Client, resp.Content, String.IsNullOrEmpty(resp.Header) ? null : GetClientHeaders(resp.Header)); + } + + /// + /// Calls on Blocked by IP Client + /// + /// + protected override void GetBlockedClient(TcpClient Client) + { + if (_sendlockedError) + { + if ((_sendlockedErrCode == 0) || ((_sendlockedErrCode == 423))) + HttpClientSendError(Client, 423, "423 Locked"); + else + HttpClientSendError(Client, _sendlockedErrCode); + }; + } + + /// + /// Initialize Web Socket Connection + /// + /// + /// true if ok + protected virtual bool HttpClientWebSocketInit(ClientRequest clientRequest, bool sendErrorIfFail) + { + try + { + string swk = Regex.Match(clientRequest.OriginRequest, "Sec-WebSocket-Key: (.*)").Groups[1].Value.Trim(); + if(string.IsNullOrEmpty(swk)) + { + if(sendErrorIfFail) + HttpClientSendError(clientRequest.Client, 417, "417 Expectation Failed"); + return false; + }; + + string swka = swk + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + byte[] swkaSha1 = System.Security.Cryptography.SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(swka)); + string swkaSha1Base64 = Convert.ToBase64String(swkaSha1); + + byte[] response = Encoding.UTF8.GetBytes( + "HTTP/1.1 101 Switching Protocols\r\n" + + "Connection: Upgrade\r\n" + + "Upgrade: websocket\r\n" + + "Sec-WebSocket-Accept: " + swkaSha1Base64 + "\r\n\r\n"); + clientRequest.Client.GetStream().Write(response, 0, response.Length); + clientRequest.Client.GetStream().Flush(); + } + catch + { + if (sendErrorIfFail) + HttpClientSendError(clientRequest.Client, 417, "417 Expectation Failed"); + return false; + }; + + GetClientWebSocket(clientRequest); + return true; + } + + /// + /// Get WebSocket Connection + /// + /// + protected virtual void GetClientWebSocket(ClientRequest clientRequest) + { + try { OnWebSocketClientConnected(clientRequest); } + catch { }; + try { if (!clientRequest.Client.Connected) return; } + catch { return; }; + + int rxCount = 0; + int rxAvailable = 0; + byte[] rxBuffer = new byte[65536]; + List rx8Buffer = new List(); + bool loop = true; + int rCounter = 0; + while (loop) + { + try { rxAvailable = clientRequest.Client.Available; } + catch { break; }; + + // Read Incoming Data + while (rxAvailable > 0) + { + try { rxAvailable -= (rxCount = clientRequest.Client.GetStream().Read(rxBuffer, 0, rxBuffer.Length > rxAvailable ? rxAvailable : rxBuffer.Length)); } + catch { break; }; + if (rxCount > 0) + { + byte[] b2a = new byte[rxCount]; + Array.Copy(rxBuffer, b2a, rxCount); + rx8Buffer.AddRange(b2a); + }; + }; + + if (rx8Buffer.Count > 0) + { + OnWebSocketClientData(clientRequest, rx8Buffer.ToArray()); + rx8Buffer.Clear(); + rCounter = 0; + }; + + if (!isRunning) loop = false; + if (rCounter >= 200) // 10s + { + try + { + if (!IsConnected(clientRequest.Client)) loop = false; + rCounter = 0; + } + catch { loop = false; }; + }; + System.Threading.Thread.Sleep(50); + rCounter++; + }; + // loop // + + try { OnWebSocketClientDisconnected(clientRequest); } + catch { }; + } + + /// + /// On WebSocket Client Connected + /// -- close connection will stop thread -- + /// + /// + protected virtual void OnWebSocketClientConnected(ClientRequest clientRequest) + { + //clientRequest.Client.Close(); + } + + /// + /// On WebSocket Client Disconnected + /// + /// + protected virtual void OnWebSocketClientDisconnected(ClientRequest clientRequest) + { + + } + + /// + /// On WebSocket Client Data Received + /// -- close connection will stop thread -- + /// + /// + /// + protected virtual void OnWebSocketClientData(ClientRequest clientRequest, byte[] data) + { + //clientRequest.Client.Close(); + } + + // https://learn.javascript.ru/websockets + // http://tools.ietf.org/html/rfc6455 + public static string GetStringFromWebSocketFrame(byte[] buffer, int length) + { + if ((buffer == null) || (buffer.Length < 2)) return ""; // throw new Exception("The buffer length couldn't be less than 2 bytes"); + bool FIN = (buffer[0] & 0x80) == 0x80; // last packet? + int OPCODE = buffer[0] & 0x0F; // packet type: 0 continue, 1 - Text Frame, 2 - Binary frame, 8 - Close, 9 - Ping, 10 - Pong + bool MASKED = (buffer[1] & 0x80) == 0x80; // has mask? + int dataLength = buffer[1] & 0x7F; // data length + + if (OPCODE != 1) return ""; // Not Text Frame; + + int nextIndex = 0; + if (dataLength <= 125) // length here + { + nextIndex = 2; // [][] (M M M M) byte (no addit bytes length) 2/6 + } + else if (dataLength == 126) // length next 2 bytes + { + if (buffer.Length < 4) return ""; // throw new Exception("The buffer length couldn't be less than 4 bytes"); + dataLength = (int)BitConverter.ToUInt16(new byte[] { buffer[3], buffer[2] }, 0); + nextIndex = 4; // [][] X X (M M M M) byte (2 addit bytes length) 4/8 + } + else if (dataLength == 127) // length next 8 bytes + { + if (buffer.Length < 10) return ""; // throw new Exception("The buffer length couldn't be less than 10 bytes"); + dataLength = (int)BitConverter.ToUInt64(new byte[] { buffer[9], buffer[8], buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2] }, 0); + nextIndex = 10;// [][] X X X X X X X X (M M M M) byte (8 addit bytes length) 10/14 + }; + + int dataFrom = MASKED ? nextIndex + 4 : nextIndex; + if ((dataFrom + dataLength) > length) return ""; //throw new Exception("The buffer length is smaller than the data length"); + if (MASKED) + { + byte[] mask = new byte[] { buffer[nextIndex], buffer[nextIndex + 1], buffer[nextIndex + 2], buffer[nextIndex + 3] }; + int byteNum = 0; + int dataTill = dataFrom + dataLength; + for (int i = dataFrom; i < dataTill; i++) + buffer[i] = (byte)(buffer[i] ^ mask[byteNum++ % 4]); + }; + + try + { + string res = Encoding.UTF8.GetString(buffer, dataFrom, dataLength); + return res; + } + catch (Exception ex) + { + return ""; + }; + } + + // https://learn.javascript.ru/websockets + // http://tools.ietf.org/html/rfc6455 + public static byte[] GetBytesFromWebSocketFrame(byte[] buffer, int length) + { + if ((buffer == null) || (buffer.Length < 2)) return null; // throw new Exception("The buffer length couldn't be less than 2 bytes"); + bool FIN = (buffer[0] & 0x80) == 0x80; // last packet? + int OPCODE = buffer[0] & 0x0F; // packet type: 0 continue, 1 - Text Frame, 2 - Binary frame, 8 - Close, 9 - Ping, 10 - Pong + bool MASKED = (buffer[1] & 0x80) == 0x80; // has mask? + int dataLength = buffer[1] & 0x7F; // data length + + if (OPCODE != 1) return null; // Not Text Frame; + + int nextIndex = 0; + if (dataLength <= 125) // length here + { + nextIndex = 2; // [][] (M M M M) byte (no addit bytes length) 2/6 + } + else if (dataLength == 126) // length next 2 bytes + { + if (buffer.Length < 4) return null; // throw new Exception("The buffer length couldn't be less than 4 bytes"); + dataLength = (int)BitConverter.ToUInt16(new byte[] { buffer[3], buffer[2] }, 0); + nextIndex = 4; // [][] X X (M M M M) byte (2 addit bytes length) 4/8 + } + else if (dataLength == 127) // length next 8 bytes + { + if (buffer.Length < 10) return null; // throw new Exception("The buffer length couldn't be less than 10 bytes"); + dataLength = (int)BitConverter.ToUInt64(new byte[] { buffer[9], buffer[8], buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2] }, 0); + nextIndex = 10;// [][] X X X X X X X X (M M M M) byte (8 addit bytes length) 10/14 + }; + + int dataFrom = MASKED ? nextIndex + 4 : nextIndex; + if ((dataFrom + dataLength) > length) return null; //throw new Exception("The buffer length is smaller than the data length"); + if (MASKED) + { + byte[] mask = new byte[] { buffer[nextIndex], buffer[nextIndex + 1], buffer[nextIndex + 2], buffer[nextIndex + 3] }; + int byteNum = 0; + int dataTill = dataFrom + dataLength; + for (int i = dataFrom; i < dataTill; i++) + buffer[i] = (byte)(buffer[i] ^ mask[byteNum++ % 4]); + }; + + try + { + byte[] res = new byte[dataLength]; + Array.Copy(buffer,dataFrom,res,0,dataLength); + return res; + } + catch (Exception ex) + { + return null; + }; + } + + // https://learn.javascript.ru/websockets + // http://tools.ietf.org/html/rfc6455 + public static byte[] GetWebSocketFrameFromString(string Message) + { + if (String.IsNullOrEmpty(Message)) return null; + + Random rnd = new Random(); + byte[] BODY = Encoding.UTF8.GetBytes(Message); + byte[] MASK = new byte[0]; // no mask + // byte[] MASK = new byte[4] { (byte)rnd.Next(0, 255), (byte)rnd.Next(0, 255), (byte)rnd.Next(0, 255), (byte)rnd.Next(0, 255) }; // new byte[0] + int OPCODE = 1; // 1 - Text, 2 - Binary + byte[] FRAME = null; + + int nextIndex = 0; + if (BODY.Length < 126) + { + nextIndex = 2; + FRAME = new byte[2 + MASK.Length + BODY.Length]; + FRAME[1] = (byte)((MASK.Length == 4 ? 0x80 : 0) + BODY.Length); + } + else if (BODY.Length <= short.MaxValue) + { + nextIndex = 4; + FRAME = new byte[4 + MASK.Length + BODY.Length]; + FRAME[1] = (byte)((MASK.Length == 4 ? 0x80 : 0) + 126); + FRAME[2] = (byte)((BODY.Length >> 8) & 255); + FRAME[3] = (byte)(BODY.Length & 255); + } + else + { + nextIndex = 10; + FRAME = new byte[10 + MASK.Length + BODY.Length]; + FRAME[1] = (byte)((MASK.Length == 4 ? 0x80 : 0) + 127); + ulong blen = (ulong)BODY.Length; + FRAME[2] = (byte)((blen >> 56) & 255); + FRAME[3] = (byte)((blen >> 48) & 255); + FRAME[4] = (byte)((blen >> 40) & 255); + FRAME[5] = (byte)((blen >> 32) & 255); + FRAME[6] = (byte)((blen >> 24) & 255); + FRAME[7] = (byte)((blen >> 16) & 255); + FRAME[8] = (byte)((blen >> 08) & 255); + FRAME[9] = (byte)(blen & 255); + }; + FRAME[0] = (byte)(0x80 + OPCODE); // FIN + OPCODE + if (MASK.Length == 4) + { + for (int mi = 0; mi < MASK.Length; mi++) + FRAME[nextIndex + mi] = MASK[mi]; + nextIndex += MASK.Length; + }; + for (int bi = 0; bi < BODY.Length; bi++) + FRAME[nextIndex + bi] = MASK.Length == 4 ? (byte)(BODY[bi] ^ MASK[bi % 4]) : BODY[bi]; + + return FRAME; + } + + // https://learn.javascript.ru/websockets + // http://tools.ietf.org/html/rfc6455 + public static byte[] GetWebSocketFrameFromBytes(byte[] BODY) + { + if ((BODY == null) || (BODY.Length == 0)) return null; + + Random rnd = new Random(); + byte[] MASK = new byte[0]; // no mask + // byte[] MASK = new byte[4] { (byte)rnd.Next(0, 255), (byte)rnd.Next(0, 255), (byte)rnd.Next(0, 255), (byte)rnd.Next(0, 255) }; // new byte[0] + int OPCODE = 1; // 1 - Text, 2 - Binary + byte[] FRAME = null; + + int nextIndex = 0; + if (BODY.Length < 126) + { + nextIndex = 2; + FRAME = new byte[2 + MASK.Length + BODY.Length]; + FRAME[1] = (byte)((MASK.Length == 4 ? 0x80 : 0) + BODY.Length); + } + else if (BODY.Length <= short.MaxValue) + { + nextIndex = 4; + FRAME = new byte[4 + MASK.Length + BODY.Length]; + FRAME[1] = (byte)((MASK.Length == 4 ? 0x80 : 0) + 126); + FRAME[2] = (byte)((BODY.Length >> 8) & 255); + FRAME[3] = (byte)(BODY.Length & 255); + } + else + { + nextIndex = 10; + FRAME = new byte[10 + MASK.Length + BODY.Length]; + FRAME[1] = (byte)((MASK.Length == 4 ? 0x80 : 0) + 127); + ulong blen = (ulong)BODY.Length; + FRAME[2] = (byte)((blen >> 56) & 255); + FRAME[3] = (byte)((blen >> 48) & 255); + FRAME[4] = (byte)((blen >> 40) & 255); + FRAME[5] = (byte)((blen >> 32) & 255); + FRAME[6] = (byte)((blen >> 24) & 255); + FRAME[7] = (byte)((blen >> 16) & 255); + FRAME[8] = (byte)((blen >> 08) & 255); + FRAME[9] = (byte)(blen & 255); + }; + FRAME[0] = (byte)(0x80 + OPCODE); // FIN + OPCODE + if (MASK.Length == 4) + { + for (int mi = 0; mi < MASK.Length; mi++) + FRAME[nextIndex + mi] = MASK[mi]; + nextIndex += MASK.Length; + }; + for (int bi = 0; bi < BODY.Length; bi++) + FRAME[nextIndex + bi] = MASK.Length == 4 ? (byte)(BODY[bi] ^ MASK[bi % 4]) : BODY[bi]; + + return FRAME; + } + + public class ClientRequest + { + private ThreadedHttpServer server; + + /// + /// Client + /// + public TcpClient Client; + /// + /// CLient ID + /// + public ulong clientID; + /// + /// Original HTTP Request Client Request + /// + public string OriginRequest; + /// + /// Original Http Request Client Header + /// + public string OriginHeader; + /// + /// Http Client Request Body Data + /// + public byte[] BodyData; + /// + /// Http Client Request Full Query + /// + public string Query; + /// + /// Http Client Request Query Path/Page + /// + public string Page; + /// + /// Http Client Request Host + /// + public string Host; + /// + /// Http Client Request Query/Get Parameters + /// + public string QueryInline; + /// + /// Http Client Request Headers + /// + public IDictionary Headers; + /// + /// Http Client Request Query/Get Parameters + /// + public IDictionary QueryParams; + + internal ClientRequest() { } + internal static ClientRequest FromServer(ThreadedHttpServer server) + { + ClientRequest res = new ClientRequest(); + res.server = server; + return res; + } + + /// + /// Http Client Request Accept Header + /// + public string Accept { get { return DictGetKeyIgnoreCase(Headers, "Accept"); } } + /// + /// Http Client Request Accept-Language Header + /// + public string AcceptLanguage { get { return DictGetKeyIgnoreCase(Headers, "Accept-Language"); } } + /// + /// Http Client Request Accept-Language Header + /// + public string AcceptEncoding { get { return DictGetKeyIgnoreCase(Headers, "Accept-Encoding"); } } + /// + /// Http Client Request Authorization Header + /// + public string Authorization { get { return DictGetKeyIgnoreCase(Headers, "Authorization"); } } + /// + /// Http Client Request Body as Text + /// + public string BodyText { get { if((BodyData == null) || (BodyData.Length == 0)) return null; else return (server == null ? Encoding.ASCII.GetString(BodyData) : server._requestEnc.GetString(BodyData)); }} + /// + /// Http Client Request Cache-Control Header + /// + public string CacheControl { get { return DictGetKeyIgnoreCase(Headers, "Cache-Control"); } } + /// + /// Http Client Request Cookie Header + /// + public string Cookie { get { return DictGetKeyIgnoreCase(Headers, "Cookie"); } } + /// + /// Http Client Request Content-Encoding Header + /// + public string ContentEncoding { get { return DictGetKeyIgnoreCase(Headers, "Content-Encoding"); } } + /// + /// Http Client Request Content-Length Header + /// + public string ContentLength { get { return DictGetKeyIgnoreCase(Headers, "Content-Length"); } } + /// + /// Http Client Request Content-Type Header + /// + public string ContentType { get { return DictGetKeyIgnoreCase(Headers, "Content-Type"); } } + /// + /// Http Client Request Query/Get Parameters + /// + public IDictionary GetParams { get { return QueryParams; } } + /// + /// Http Client Request Origin Header + /// + public string Origin { get { return DictGetKeyIgnoreCase(Headers, "Origin"); } } + /// + /// Http Client Request Post Data + /// + public string PostData { get { if ((BodyData == null) || (BodyData.Length == 0)) return null; else return (server == null ? UrlUnescape(Encoding.ASCII.GetString(BodyData)) : UrlUnescape(server._requestEnc.GetString(BodyData))); } } + /// + /// Http Client Request Post Parameters + /// + public IDictionary PostParams { get { if ((BodyData == null) || (BodyData.Length == 0)) return null; else return GetClientParams(server._requestEnc.GetString(BodyData)); } } + /// + /// Http Client Request Referer Header + /// + public string Referer { get { return DictGetKeyIgnoreCase(Headers, "Referer"); } } + /// + /// Http Client Remote IP Address + /// + public string RemoteIP { get { return ((IPEndPoint)Client.Client.RemoteEndPoint).Address.ToString(); } } + + public string RemoteMac { get { return TTCPServer.GetMacAddressByIP(RemoteIP); } } + /// + /// Http Client Request User-Agent Header + /// + public string UserAgent { get { return DictGetKeyIgnoreCase(Headers, "User-Agent"); } } + /// + /// Http Client Authentificated User + /// + public string User { get { + string auth = DictGetKeyIgnoreCase(Headers, "Authorization"); + if (String.IsNullOrEmpty(auth)) return null; + if (auth.StartsWith("Basic")) + { + string sp = Base64Decode(auth.Substring(6)); + string[] up = sp.Split(new char[] { ':' }, 2); + return up[0]; + }; + return "Unknown"; + } } + /// + /// Http Client Request Query/Get or Post Parameters + /// + /// parameter name + /// + public string this[string value] + { + get + { + string res = null; + res = DictGetKeyIgnoreCase(QueryParams, value); + if (!String.IsNullOrEmpty(res)) return res; + res = DictGetKeyIgnoreCase(PostParams, value); + return res; + } + } + + /// + /// Get Header Parameter, Ignore Case + /// + /// parameter name + /// + public string GetHeaderParam(string value) { return DictGetKeyIgnoreCase(Headers, value); } + /// + /// Get Query/Get Parameter, Ignore Case + /// + /// parameter name + /// + public string GetQueryParam(string value) { return DictGetKeyIgnoreCase(QueryParams,value); } + /// + /// Get Post Parameter, Ignore Case + /// + /// parameter name + /// + public string GetPostParam(string value) { return DictGetKeyIgnoreCase(PostParams,value); } + } + } + + /// + /// Protobuf HTTP sever + /// curl http://127.0.0.1:8011/PROTOBUF+4/ --upload-file C:\xxx.bin + // curl http://127.0.0.1:8011/PROTOBUF+4/ --upload-file C:\xxx.bin --output c:\yyy.bin + /// + public class Threaded4BytesHttpServer : Threaded4BytesTCPServer + { + public Threaded4BytesHttpServer() : base(80) { } + public Threaded4BytesHttpServer(int Port) : base(Port) { } + public Threaded4BytesHttpServer(IPAddress IP, int Port) : base(IP, Port) { } + ~Threaded4BytesHttpServer() { this.Dispose(); } + + protected ushort _MaxHeaderSize = 4096; + public ushort MaxClientHeaderSize { get { return _MaxHeaderSize; } set { _MaxHeaderSize = value; } } + + private Mutex _h_mutex = new Mutex(); + private Dictionary _headers = new Dictionary(); + public Dictionary Headers + { + get + { + _h_mutex.WaitOne(); + Dictionary res = new Dictionary(); + foreach (KeyValuePair kvp in _headers) + res.Add(kvp.Key, kvp.Value); + _h_mutex.ReleaseMutex(); + return res; + } + set + { + _h_mutex.WaitOne(); + _headers.Clear(); + foreach (KeyValuePair kvp in value) + _headers.Add(kvp.Key, kvp.Value); + _h_mutex.ReleaseMutex(); + } + } + + private bool _authRequired = false; + public Dictionary AuthentificationCredintals = new Dictionary(); + public bool AuthentificationRequired { get { return _authRequired; } set { _authRequired = value; } } + + // + public virtual void HttpClientSendData(TcpClient Client, byte[] data, Dictionary dopHeaders) + { + // "200 OK" + // HttpStatusCode - HTTP/1.1 + int Code = 200; + string CodeStr = Code.ToString() + " " + ((HttpStatusCode)Code).ToString(); + // : , . - + string Str = "HTTP/1.1 " + CodeStr + "\r\n"; + this._h_mutex.WaitOne(); + foreach (KeyValuePair kvp in this._headers) + Str += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + this._h_mutex.ReleaseMutex(); + if (dopHeaders != null) + foreach (KeyValuePair kvp in dopHeaders) + Str += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + Str += "Content-type: application/" + this._prefix + "\r\nContent-Length: " + data.Length.ToString() + "\r\n\r\n"; + // + byte[] Buffer = Encoding.GetEncoding(1251).GetBytes(Str); + // + Client.GetStream().Write(Buffer, 0, Buffer.Length); + Client.GetStream().Write(data, 0, data.Length); + Client.GetStream().Flush(); + // + //Client.Client.Close(); + //Client.Close(); + } + public virtual void HttpClientSendData(TcpClient Client, byte[] data) + { + HttpClientSendData(Client, data, null); + } + + // + public virtual void HttpClientSendError(TcpClient Client, int Code, Dictionary dopHeaders) + { + // "200 OK" + // HttpStatusCode - HTTP/1.1 + string CodeStr = Code.ToString() + " " + ((HttpStatusCode)Code).ToString(); + // HTML- + string Html = "

" + CodeStr + "

"; + // : , . - + string Str = "HTTP/1.1 " + CodeStr + "\r\n"; + this._h_mutex.WaitOne(); + foreach (KeyValuePair kvp in this._headers) + Str += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + this._h_mutex.ReleaseMutex(); + if (dopHeaders != null) + foreach (KeyValuePair kvp in dopHeaders) + Str += String.Format("{0}: {1}\r\n", kvp.Key, kvp.Value); + Str += "Content-type: text/html\r\nContent-Length: " + Html.Length.ToString() + "\r\n\r\n" + Html; + // + byte[] Buffer = Encoding.GetEncoding(1251).GetBytes(Str); + // + Client.GetStream().Write(Buffer, 0, Buffer.Length); + Client.GetStream().Flush(); + // + //Client.Client.Close(); + //Client.Close(); + } + public virtual void HttpClientSendError(TcpClient Client, int Code) + { + HttpClientSendError(Client, Code, null); + } + + /// + /// Get Client, threaded + /// + /// + /// + protected override void GetClient(TcpClient Client, ulong id) + { + Regex CR = new Regex(@"Content-Length: (\d+)", RegexOptions.IgnoreCase); + + string s1 = "GET /" + this._prefix + "/"; + string s2 = "POST /" + this._prefix + "/"; + string s3 = "PUT /" + this._prefix + "/"; + + string Header = ""; + List Body = new List(); + + int bRead = -1; + int posCRLF = -1; + int receivedBytes = 0; + int contentLength = 0; + + try + { + // Get Header + //while ((Client.Available > 0) && ((bRead = Client.GetStream().ReadByte()) >= 0)) // doesn't work correct + while ((bRead = Client.GetStream().ReadByte()) >= 0) + { + receivedBytes++; + Header += (char)bRead; // standard symbol + if (bRead == 0x0A) posCRLF = Header.IndexOf("\r\n\r\n"); // get body start index + if (posCRLF >= 0 || Header.Length > _MaxHeaderSize) { break; }; // GET ONLY + }; + bool valid = (posCRLF > 0) && ((Header.IndexOf("GET") == 0) || (Header.IndexOf("POST") == 0) || (Header.IndexOf("PUT") == 0)); + if (!valid) + { + HttpClientSendError(Client, 400); // 400 Bad Request + return; + }; + + if (_authRequired && (AuthentificationCredintals.Count > 0)) + { + bool accept = false; + string sa = "Authorization:"; + if (Header.IndexOf(sa) > 0) + { + int iofcl = Header.IndexOf(sa); + sa = Header.Substring(iofcl + sa.Length, Header.IndexOf("\r", iofcl + sa.Length) - iofcl - sa.Length).Trim(); + if (sa.StartsWith("Basic")) + { + sa = Base64Decode(sa.Substring(6)); + string[] up = sa.Split(new char[] { ':' }, 2); + if (AuthentificationCredintals.ContainsKey(up[0]) && AuthentificationCredintals[up[0]] == up[1]) + accept = true; + }; + }; + if (!accept) + { + Dictionary dh = new Dictionary(); + dh.Add("WWW-Authenticate", "Basic realm=\"Authentification required\""); + HttpClientSendError(Client, 401, dh); // 401 Unauthorized + return; + }; + }; + + Match mx = CR.Match(Header); + if (mx.Success) contentLength = int.Parse(mx.Groups[1].Value); + if (contentLength == 0) + { + HttpClientSendError(Client, 411); // 411 Length Required + return; + } + if (contentLength < (this._prefix.Length + 4)) + { + HttpClientSendError(Client, 406); // 406 Not Acceptable + return; + }; + + byte[] buff = new byte[this._prefix.Length + 4]; + Client.GetStream().Read(buff, 0, buff.Length); + string prefix = System.Text.Encoding.GetEncoding(1251).GetString(buff, 0, this._prefix.Length); + if (prefix != this._prefix) + { + HttpClientSendError(Client, 415); // 415 Unsupported Media Type + return; + }; + + int length = System.BitConverter.ToInt32(buff, this._prefix.Length); + if (length > contentLength) + { + HttpClientSendError(Client, 416); // 416 Range Not Satisfiable + return; + }; + buff = new byte[length]; + Client.GetStream().Read(buff, 0, buff.Length); + GetClientRequestData(Client, id, Header, buff); + } + catch (Exception ex) + { + LastError = ex; + LastErrTime = DateTime.Now; + ErrorsCounter++; + onError(Client, id, ex); + }; + } + + /// + /// Get Client Request with 1st block of data + /// + /// + /// + /// + public virtual void GetClientRequestData(TcpClient Client, ulong id, string Header, byte[] data) + { + // Write No Data To Client + List result = new List(); + result.AddRange(System.Text.Encoding.GetEncoding(1251).GetBytes(this._prefix)); + byte[] buff = System.Text.Encoding.GetEncoding(1251).GetBytes("Hello " + DateTime.Now.ToString()); + result.AddRange(BitConverter.GetBytes(buff.Length)); + result.AddRange(buff); + HttpClientSendData(Client, result.ToArray()); + + // connection will be close after return + } + + protected override void onError(TcpClient Client, ulong id, Exception error) + { + + } + + + private static void sample() + { + // curl http://127.0.0.1:8011 --upload-file C:\xxx.bin + // curl http://127.0.0.1:8011 --upload-file C:\xxx.bin --output c:\yyy.bin + // curl --user sa:q http://127.0.0.1:8011 --upload-file C:\xxx.bin + + SimpleServersPBAuth.Threaded4BytesHttpServer svr = new SimpleServersPBAuth.Threaded4BytesHttpServer(8011); + svr.Headers.Add("Server", "Threaded4BytesHttpServer/0.1"); + svr.Headers.Add("Server-Name", "TEST SAMPLE"); + svr.Headers.Add("Server-Owner", "I am"); + svr.AuthentificationCredintals.Add("sa", "q"); + svr.AuthentificationRequired = false; + svr.ListenIPMode = SimpleServersPBAuth.ThreadedTCPServer.Mode.DenyBlackList; + svr.ListenIPDeny = new string[] { "127.0.0.2" }; + svr.Start(); + // + System.Threading.Thread.Sleep(10000); + // + svr.Stop(); + svr.Dispose(); + } + } + + /// + /// Module for call CGI-BIN executable file + /// + public class CGIBINCaller + { + public class Response + { + /// + /// Respone Header + /// + public string Header; + + /// + /// Response Body + /// + public string Body + { + get + { + if (Content == null) return null; + if (Content.Length == 0) return ""; + int chs = Header.IndexOf("charset="); + if (chs < 0) + return System.Text.Encoding.UTF8.GetString(Content); + else + { + int lind = Header.IndexOf("\n", chs + 8); + if (lind < 0) return System.Text.Encoding.UTF8.GetString(Content); + string charset = Header.Substring(chs + 8, lind - (chs + 8)).Trim('\n').Trim('\r').Trim(); + return System.Text.Encoding.GetEncoding(charset).GetString(Content); + }; + } + } + + /// + /// Response Content + /// + public byte[] Content; + + public Response(string header, byte[] content) + { + this.Header = header; + this.Content = content; + } + } + + private static void SetDefaultParams(string path, System.Diagnostics.ProcessStartInfo startInfo) + { + startInfo.EnvironmentVariables["CONTENT_TYPE"] = "application/x-www-form-urlencoded"; + startInfo.EnvironmentVariables["CONTENT_LENGTH"] = "0"; + startInfo.EnvironmentVariables["CONTENT_DATA"] = ""; + startInfo.EnvironmentVariables["GATEWAY_INTERFACE"] = "CGI/1.1"; + startInfo.EnvironmentVariables["SERVER_NAME"] = ""; + startInfo.EnvironmentVariables["SERVER_SOFTWARE"] = "SimpleServersPBAuth.ThreadedHttpServer"; + startInfo.EnvironmentVariables["SERVER_PROTOCOL"] = "HTTP/1.1"; + startInfo.EnvironmentVariables["SERVER_PORT"] = "80"; + startInfo.EnvironmentVariables["PATH_INFO"] = ""; + startInfo.EnvironmentVariables["PATH_TRANSLATED"] = path; + startInfo.EnvironmentVariables["SCRIPT_NAME"] = System.IO.Path.GetFileName(path); + startInfo.EnvironmentVariables["DOCUMENT_ROOT"] = System.IO.Path.GetFileName(path); + startInfo.EnvironmentVariables["REQUEST_METHOD"] = "GET"; + startInfo.EnvironmentVariables["REQUEST_URI"] = ""; + startInfo.EnvironmentVariables["QUERY_STRING"] = ""; + startInfo.EnvironmentVariables["REMOTE_HOST"] = "127.0.0.1"; + startInfo.EnvironmentVariables["REMOTE_ADDR"] = "127.0.0.1"; + startInfo.EnvironmentVariables["AUTH_TYPE"] = ""; + startInfo.EnvironmentVariables["REMOTE_USER"] = ""; + startInfo.EnvironmentVariables["HTTP_ACCEPT"] = "text/html,application/xhtml,application/xml"; + startInfo.EnvironmentVariables["HTTP_USER_AGENT"] = "CGIBINCaller"; + startInfo.EnvironmentVariables["HTTP_REFERER"] = ""; + startInfo.EnvironmentVariables["HTTP_COOKIE"] = ""; + startInfo.EnvironmentVariables["HTTPS"] = ""; + } + + private static void SetParams(IDictionary pars, System.Diagnostics.ProcessStartInfo startInfo) + { + foreach (KeyValuePair kv in pars) + startInfo.EnvironmentVariables[kv.Key] = kv.Value; + } + + private static void SetParams(IDictionary pars, System.Diagnostics.ProcessStartInfo startInfo) + { + foreach (KeyValuePair kv in pars) + startInfo.EnvironmentVariables[kv.Key] = kv.Value.ToString(); + } + + private static void SetParams(System.Collections.Specialized.NameValueCollection pars, System.Diagnostics.ProcessStartInfo startInfo) + { + foreach (string key in pars.AllKeys) + startInfo.EnvironmentVariables[key] = pars[key]; + } + + private static Response CallBin(string path, byte[] postBody, IDictionary p1, IDictionary p2, System.Collections.Specialized.NameValueCollection p3, string cmdLineArgs) + { + System.Diagnostics.ProcessStartInfo startInfo = new System.Diagnostics.ProcessStartInfo(); + startInfo.UseShellExecute = false; + startInfo.CreateNoWindow = true; + startInfo.FileName = path; + startInfo.Arguments = cmdLineArgs; + startInfo.RedirectStandardInput = true; + startInfo.RedirectStandardOutput = true; + startInfo.StandardOutputEncoding = System.Text.Encoding.UTF7; + + SetDefaultParams(path, startInfo); + if (p1 != null) SetParams(p1, startInfo); + if (p2 != null) SetParams(p2, startInfo); + if (p3 != null) SetParams(p3, startInfo); + + // IF POST BODY + if ((postBody != null) && (postBody.Length > 0)) + { + startInfo.EnvironmentVariables["CONTENT_LENGTH"] = postBody.Length.ToString(); + startInfo.EnvironmentVariables["REQUEST_METHOD"] = "POST"; + }; + + System.Diagnostics.Process proc = System.Diagnostics.Process.Start(startInfo); + + // IF POST BODY + if ((postBody != null) && (postBody.Length > 0)) + { + proc.StandardInput.BaseStream.Write(postBody, 0, postBody.Length); + proc.StandardInput.BaseStream.Flush(); + }; + proc.WaitForExit(); + + string header = ""; + // RECEIVE DATA + byte[] content = new byte[0]; + { + List resvd = new List(); + int hi = 0; + while (!proc.StandardOutput.EndOfStream) + { + int b = proc.StandardOutput.Read(); + if ((resvd.Count == 0) && (b == 10)) { hi = 1; }; // no header + if ((resvd.Count == 0) && (b == 60)) { resvd.Add(10); hi = 1; }; // no header + if (b >= 0) + { + if (hi == 0) + { + header += (char)b; + if (header.Length > 0) + { + int hend = header.IndexOf("\n\n"); + if (hend > 0) { hi = hend + 2; }; + hend = header.IndexOf("\r\n\r\n"); + if (hend > 0) { hi = hend + 4; }; + }; + }; + resvd.Add((byte)b); + } + else + break; + }; + + if (resvd.Count > header.Length) + { + content = new byte[resvd.Count - hi]; + Array.Copy(resvd.ToArray(), hi, content, 0, content.Length); + }; + }; + + return new Response(header, content); + } + + public static Response Call(string path, byte[] postBody, string cmdLineArgs) + { + return CallBin(path, postBody, null, null, null, cmdLineArgs); + } + + public static Response Call(string path, byte[] postBody, IDictionary parameters, string cmdLineArgs) + { + return CallBin(path, postBody, parameters, null, null, cmdLineArgs); + } + + public static Response Call(string path, byte[] postBody, IDictionary parameters, string cmdLineArgs) + { + return CallBin(path, postBody, null, parameters, null, cmdLineArgs); + } + + public static Response Call(string path, byte[] postBody, System.Collections.Specialized.NameValueCollection parameters, string cmdLineArgs) + { + return CallBin(path, postBody, null, null, parameters, cmdLineArgs); + } + + public static void Test() + { + Dictionary parameters = new Dictionary(); + + parameters.Add("QUERY_STRING", + "path=none" + + "&test=empty" + + "&var=a" + + "&dd=" + ); + + byte[] post = System.Text.Encoding.UTF8.GetBytes( + "test=post" + + "&codepage=utf-8" + + "&lang=ru" + + "&name=" + System.Uri.EscapeDataString("") + ); + + Response resp = CGIBINCaller.Call(System.Reflection.Assembly.GetExecutingAssembly().Location, post, parameters, null); + + Console.WriteLine("========================================================================"); + Console.Write(resp.Header); + Console.OutputEncoding = System.Text.Encoding.GetEncoding(866); + Console.Write(resp.Body); + Console.WriteLine(); + Console.WriteLine("========================================================================"); + + System.IO.FileStream fs = new System.IO.FileStream(System.Reflection.Assembly.GetExecutingAssembly().Location + "_header.txt", System.IO.FileMode.Create, System.IO.FileAccess.Write); + if (resp.Header.Length > 0) + fs.Write(System.Text.Encoding.ASCII.GetBytes(resp.Header), 0, resp.Header.Length); + fs.Close(); + + fs = new System.IO.FileStream(System.Reflection.Assembly.GetExecutingAssembly().Location + "_body.txt", System.IO.FileMode.Create, System.IO.FileAccess.Write); + if ((resp.Content != null) && (resp.Content.Length > 0)) + fs.Write(resp.Content, 0, resp.Content.Length); + fs.Close(); + } + + } + + /// + /// Module for CGI-BIN implementation of executable file + /// + public class CGIBINModule + { + public Dictionary Variables = new Dictionary(); + + public System.Collections.Specialized.NameValueCollection QUERY_PARAMS = new System.Collections.Specialized.NameValueCollection(); + public System.Collections.Specialized.NameValueCollection GET_PARAMS { get { return QUERY_PARAMS; } } + + public System.Collections.Specialized.NameValueCollection CONTENT_PARAMS = new System.Collections.Specialized.NameValueCollection(); + public System.Collections.Specialized.NameValueCollection POST_PARAMS { get { return CONTENT_PARAMS; } } + public string POST_DATA { get { if ((CONTENT_DATA != null) && (CONTENT_DATA.Length > 0)) return System.Text.Encoding.ASCII.GetString(CONTENT_DATA); else return ""; } } + public string POST_DATA_W1251 { get { if ((CONTENT_DATA != null) && (CONTENT_DATA.Length > 0)) return System.Text.Encoding.GetEncoding(1251).GetString(CONTENT_DATA); else return ""; } } + public string POST_DATA_UTF8 { get { if ((CONTENT_DATA != null) && (CONTENT_DATA.Length > 0)) return System.Text.Encoding.UTF8.GetString(CONTENT_DATA); else return ""; } } + public byte[] POST_DATA_BYTES { get { return CONTENT_DATA; } } + public int POST_DATA_LENGTH { get { return CONTENT_LENGTH; } } + + public string GATEWAY_INTERFACE; + + public string SERVER_NAME; + public string SERVER_SOFTWARE; + public string SERVER_PROTOCOL; + public int SERVER_PORT = 80; + + public string PATH_INFO; + public string PATH_TRANSLATED; + public string SCRIPT_NAME; + public string DOCUMENT_ROOT; + + public string REQUEST_METHOD; + public string REQUEST_URI; + public string QUERY_STRING; + public string GET_STRING { get { return QUERY_STRING; } } + + public string REMOTE_HOST; + public string REMOTE_ADDR; + + public string AUTH_TYPE; + public string REMOTE_USER; + + public string CONTENT_TYPE; + public byte[] CONTENT_DATA = null; + public int CONTENT_LENGTH = 0; + + public string HTTP_ACCEPT; + public string HTTP_USER_AGENT; + public string HTTP_REFERER; + public string HTTP_COOKIE; + public string HTTPS; + + private bool _canwriteheader = true; + + public CGIBINModule() + { + ReadVars(); + } + + private void ReadVars() + { + // Get Post Data + Variables.Add("CONTENT_TYPE", CONTENT_TYPE = System.Environment.GetEnvironmentVariable("CONTENT_TYPE")); + int.TryParse(System.Environment.GetEnvironmentVariable("CONTENT_LENGTH"), out CONTENT_LENGTH); + Variables.Add("CONTENT_LENGTH", CONTENT_LENGTH); + if (CONTENT_LENGTH > 0) + { + CONTENT_DATA = new byte[CONTENT_LENGTH]; + for (int i = 0; i < CONTENT_LENGTH; i++) + CONTENT_DATA[i] = (byte)Console.Read(); + }; + Variables.Add("CONTENT_DATA", CONTENT_DATA); + Variables.Add("GATEWAY_INTERFACE", GATEWAY_INTERFACE = System.Environment.GetEnvironmentVariable("GATEWAY_INTERFACE")); + Variables.Add("SERVER_NAME", SERVER_NAME = System.Environment.GetEnvironmentVariable("SERVER_NAME")); + Variables.Add("SERVER_SOFTWARE", SERVER_SOFTWARE = System.Environment.GetEnvironmentVariable("SERVER_SOFTWARE")); + Variables.Add("SERVER_PROTOCOL", SERVER_PROTOCOL = System.Environment.GetEnvironmentVariable("SERVER_PROTOCOL")); + int.TryParse(System.Environment.GetEnvironmentVariable("SERVER_PORT"), out SERVER_PORT); + Variables.Add("SERVER_PORT", SERVER_PORT); + Variables.Add("PATH_INFO", PATH_INFO = System.Environment.GetEnvironmentVariable("PATH_INFO")); + Variables.Add("PATH_TRANSLATED", PATH_TRANSLATED = System.Environment.GetEnvironmentVariable("PATH_TRANSLATED")); + Variables.Add("SCRIPT_NAME", SCRIPT_NAME = System.Environment.GetEnvironmentVariable("SCRIPT_NAME")); + Variables.Add("DOCUMENT_ROOT", DOCUMENT_ROOT = System.Environment.GetEnvironmentVariable("DOCUMENT_ROOT")); + Variables.Add("REQUEST_METHOD", REQUEST_METHOD = System.Environment.GetEnvironmentVariable("REQUEST_METHOD")); + Variables.Add("REQUEST_URI", REQUEST_URI = System.Environment.GetEnvironmentVariable("REQUEST_URI")); + Variables.Add("QUERY_STRING", QUERY_STRING = System.Environment.GetEnvironmentVariable("QUERY_STRING")); + Variables.Add("REMOTE_HOST", REMOTE_HOST = System.Environment.GetEnvironmentVariable("REMOTE_HOST")); + Variables.Add("REMOTE_ADDR", REMOTE_ADDR = System.Environment.GetEnvironmentVariable("REMOTE_ADDR")); + Variables.Add("AUTH_TYPE", AUTH_TYPE = System.Environment.GetEnvironmentVariable("AUTH_TYPE")); + Variables.Add("REMOTE_USER", REMOTE_USER = System.Environment.GetEnvironmentVariable("REMOTE_USER")); + Variables.Add("HTTP_ACCEPT", HTTP_ACCEPT = System.Environment.GetEnvironmentVariable("HTTP_ACCEPT")); + Variables.Add("HTTP_USER_AGENT", HTTP_USER_AGENT = System.Environment.GetEnvironmentVariable("HTTP_USER_AGENT")); + Variables.Add("HTTP_REFERER", HTTP_REFERER = System.Environment.GetEnvironmentVariable("HTTP_REFERER")); + Variables.Add("HTTP_COOKIE", HTTP_COOKIE = System.Environment.GetEnvironmentVariable("HTTP_COOKIE")); + Variables.Add("HTTPS", HTTPS = System.Environment.GetEnvironmentVariable("HTTPS")); + + if (QUERY_STRING != null) + QUERY_PARAMS = HttpUtility.ParseQueryString(QUERY_STRING); + + if ((CONTENT_DATA != null) && (CONTENT_DATA.Length > 0)) + { + string cd = System.Text.Encoding.UTF8.GetString(CONTENT_DATA); + try { CONTENT_PARAMS = HttpUtility.ParseQueryString(cd); } + catch { }; + }; + } + + public string VariableToString(object value) + { + if (value == null) return ""; + Type valueType = value.GetType(); + if (valueType.IsArray && (value.ToString() == "System.Byte[]")) + return System.Text.Encoding.UTF8.GetString((byte[])value); + return value.ToString(); + } + + public void WriteReponseHeader(string header) + { + if (_canwriteheader) + Console.Out.Write(header + "\n"); + else + throw new System.IO.EndOfStreamException("Write Headers before GetResponseStream"); + } + + public void WriteReponseHeader(System.Collections.Specialized.NameValueCollection headers) + { + if (_canwriteheader) + { + if (headers.Count > 0) + foreach (string key in headers.AllKeys) + Console.Out.Write(key + ": " + headers[key] + "\n"); + } + else + throw new System.IO.EndOfStreamException("Write Headers before GetResponseStream"); + } + + public void WriteReponseHeader(IDictionary headers) + { + if (_canwriteheader) + { + if (headers.Count > 0) + foreach (KeyValuePair nv in headers) + Console.Out.Write(nv.Key + ": " + nv.Value + "\n"); + } + else + throw new System.IO.EndOfStreamException("Write Headers before GetResponseStream"); + } + + public void WriteReponseHeader(KeyValuePair header) + { + if (_canwriteheader) + Console.Out.Write(header.Key + ": " + header.Value + "\n"); + else + throw new System.IO.EndOfStreamException("Write Headers before GetResponseStream"); + } + + public void WriteReponseHeader(string name, string value) + { + if (_canwriteheader) + Console.Out.Write(name + ": " + value + "\n"); + else + throw new System.IO.EndOfStreamException("Write Headers before GetResponseStream"); + } + + public System.IO.Stream GetResponseStream() + { + if (_canwriteheader) { Console.Out.Write("\n"); Console.Out.Flush(); }; + _canwriteheader = false; + return Console.OpenStandardOutput(); + } + + public System.IO.Stream GetResponseStream(System.Text.Encoding encoding) + { + if (_canwriteheader) { Console.Out.Write("\n"); Console.Out.Flush(); }; + _canwriteheader = false; + Console.OutputEncoding = encoding; + return Console.OpenStandardOutput(); + } + + public System.IO.StreamWriter GetResponseStreamWriter() + { + if (_canwriteheader) { Console.Out.Write("\n"); Console.Out.Flush(); }; + _canwriteheader = false; + return new System.IO.StreamWriter(Console.OpenStandardOutput()); + } + + public System.IO.StreamWriter GetResponseStreamWriter(System.Text.Encoding encoding) + { + if (_canwriteheader) { Console.Out.Write("\n"); Console.Out.Flush(); }; + _canwriteheader = false; + Console.OutputEncoding = encoding; + return new System.IO.StreamWriter(Console.OpenStandardOutput(), encoding); + } + + public void WriteResponse(string response) + { + { Console.Out.Write("\n"); Console.Out.Flush(); }; + _canwriteheader = false; + Console.Write(response); + } + + public void WriteResponse(byte[] data) + { + { Console.Out.Write("\n"); Console.Out.Flush(); }; + _canwriteheader = false; + Console.OpenStandardOutput().Write(data, 0, data.Length); + } + + public void CloseResponse() + { + if (_canwriteheader) + Console.Out.Write("\n"); + Console.Out.Close(); + _canwriteheader = false; + } + + /// + /// Use this method to simple test cgi-bin executebale appilcation -- + /// static void Main(string[] args) { SimpleServersPBAuth.CGIBINModule.Test(); }; + /// + public static void Test() + { + CGIBINModule cgi = new CGIBINModule(); + + // + // WRITE HEADERS FIRST + // + cgi.WriteReponseHeader("Status: 201 Created"); + cgi.WriteReponseHeader("CGI-Script: SimpleServersPBAuth.CGIBINModule C# CGI-bin Test"); + cgi.WriteReponseHeader("Content-Type: text/html; charset=utf-8"); + + // WRITE BODY NEXT + + System.IO.StreamWriter response = cgi.GetResponseStreamWriter(System.Text.Encoding.UTF8); + + response.Write("CGI in C#CGI Environment Variables
"); + response.Write(""); + // LIST Environment Variables + { + int del = 1; + foreach (KeyValuePair kv in cgi.Variables) + response.Write(""); + }; + // LIST GET QUERY + { + if (cgi.GET_PARAMS.Count > 0) + foreach (string q in cgi.GET_PARAMS.AllKeys) + response.Write(""); + }; + // LIST POST QUERY + { + if (cgi.POST_PARAMS.Count > 0) + foreach (string q in cgi.POST_PARAMS.AllKeys) + response.Write(""); + }; + response.Write(""); + response.Write("
" + (del++).ToString("00") + "" + kv.Key + "" + cgi.VariableToString(kv.Value) + "
GET" + q + "" + cgi.GET_PARAMS[q] + "
POST" + q + "" + cgi.POST_PARAMS[q] + "
"); + + response.Close(); + cgi.CloseResponse(); + + // Exit Environment + Environment.Exit(0); + } + } + + /// + /// HTTP- + /// Multithread HTTP Server with ready-on methods + /// -- -- + /// -- TEMPLATE -- + /// + public class HttpServer : ThreadedHttpServer + { + public HttpServer() : base(80) { } + public HttpServer(int Port) : base(Port) { } + public HttpServer(IPAddress IP, int Port) : base(IP, Port) { } + ~HttpServer() { this.Dispose(); } + + /// + /// Get HTTP Client Request, threaded (thread per client) + /// -- connection to client will be closed after return -- + /// -- do not call base method if response any data -- + /// + /// + protected override void GetClientRequest(ClientRequest Request) + { + // Stop Server Immideatly + if (Request.Query == "/exit") + { + Dictionary rH = new Dictionary(); + rH.Add("Refresh", "5; url=/"); + HttpClientSendData(Request.Client, _responseEnc.GetBytes("STOPPING SERVER..."), rH, 201, "text/html"); + this.Stop(); + Environment.Exit(0); + return; + }; + + //Web Socket + if (Request.Query == "/socket/") + { + HttpClientWebSocketInit(Request, true); + return; + }; + + // Test CGI-BIN executable + if (Request.Query.StartsWith("/exe")) + { + PassCGIBinResultToClientByRequest(Request, GetCurrentDir() + @"\dkxceHTTPServer.exe", "/cgi"); + return; + }; + + // Test Browsing Files + if ((Request.QueryParams == null) || (Request.QueryParams.Count == 0)) + { + if (Request.Query.StartsWith("/disk_C/")) { PassFileToClientByRequest(Request, @"C:\", "/disk_C/"); return; }; + if (Request.Query.StartsWith("/disk_D/")) { PassFileToClientByRequest(Request, @"D:\", "/disk_D/"); return; }; + if (Request.Query.StartsWith("/disk_E/")) { PassFileToClientByRequest(Request, @"E:\", "/disk_E/"); return; }; + if (Request.Query.StartsWith("/disk_F/")) { PassFileToClientByRequest(Request, @"F:\", "/disk_F/"); return; }; + if (Request.Query.StartsWith("/disk_G/")) { PassFileToClientByRequest(Request, @"G:\", "/disk_G/"); return; }; + if (Request.Query.StartsWith("/disk_H/")) { PassFileToClientByRequest(Request, @"H:\", "/disk_H/"); return; }; + if (Request.Query.StartsWith("/disk_M/")) { PassFileToClientByRequest(Request, @"M:\", "/disk_M/"); return; }; + PassFileToClientByRequest(Request);//, @"M:\Video"); + return; + }; + + // call base method if no response date to client + base.GetClientRequest(Request); // returns status 501 + } + + /// + /// On WebSocket Client Connected + /// -- close connection will stop thread -- + /// + /// + protected override void OnWebSocketClientConnected(ClientRequest clientRequest) + { + byte[] ba = GetWebSocketFrameFromString("Welcome"); + clientRequest.Client.GetStream().Write(ba, 0, ba.Length); + clientRequest.Client.GetStream().Flush(); + } + + /// + /// On WebSocket Client Data Received + /// -- close connection will stop thread -- + /// + /// + /// + protected override void OnWebSocketClientData(ClientRequest clientRequest, byte[] data) + { + string fws = GetStringFromWebSocketFrame(data, data.Length); + if (String.IsNullOrEmpty(fws)) return; + Console.WriteLine("From WebSocket: " + fws); + + string tws = fws + " ok"; + byte[] toSend = GetWebSocketFrameFromString(tws); + clientRequest.Client.GetStream().Write(toSend, 0, toSend.Length); + clientRequest.Client.GetStream().Flush(); + + if(fws == "kill") + clientRequest.Client.Close(); + } + } +} diff --git a/SimpleAPRSserver/obj/Debug/ResolveAssemblyReference.cache b/SimpleAPRSserver/obj/Debug/ResolveAssemblyReference.cache new file mode 100644 index 0000000..b0e2131 Binary files /dev/null and b/SimpleAPRSserver/obj/Debug/ResolveAssemblyReference.cache differ diff --git a/SimpleAPRSserver/obj/Debug/SimpleAPRSserver.exe b/SimpleAPRSserver/obj/Debug/SimpleAPRSserver.exe new file mode 100644 index 0000000..8cf9438 Binary files /dev/null and b/SimpleAPRSserver/obj/Debug/SimpleAPRSserver.exe differ diff --git a/SimpleAPRSserver/obj/Debug/SimpleAPRSserver.pdb b/SimpleAPRSserver/obj/Debug/SimpleAPRSserver.pdb new file mode 100644 index 0000000..598afd8 Binary files /dev/null and b/SimpleAPRSserver/obj/Debug/SimpleAPRSserver.pdb differ diff --git a/SimpleAPRSserver/obj/SimpleAPRSserver.csproj.FileListAbsolute.txt b/SimpleAPRSserver/obj/SimpleAPRSserver.csproj.FileListAbsolute.txt new file mode 100644 index 0000000..aaae742 --- /dev/null +++ b/SimpleAPRSserver/obj/SimpleAPRSserver.csproj.FileListAbsolute.txt @@ -0,0 +1,10 @@ +C:\Downloads\CD-REC\TEST\SimpleAPRSserver\SimpleAPRSserver\obj\Debug\ResolveAssemblyReference.cache +C:\Downloads\CD-REC\TEST\SimpleAPRSserver\BIN\SimpleAPRSserver.exe +C:\Downloads\CD-REC\TEST\SimpleAPRSserver\BIN\SimpleAPRSserver.pdb +C:\Downloads\CD-REC\TEST\SimpleAPRSserver\SimpleAPRSserver\obj\Debug\SimpleAPRSserver.exe +C:\Downloads\CD-REC\TEST\SimpleAPRSserver\SimpleAPRSserver\obj\Debug\SimpleAPRSserver.pdb +D:\PROJECTS\BaoFeng 2-Way Radio HAM (Рации)\HAM\APRS\MyAPRSSoft\SimpleAPRSserver\SimpleAPRSserver\obj\Debug\SimpleAPRSserver.exe +D:\PROJECTS\BaoFeng 2-Way Radio HAM (Рации)\HAM\APRS\MyAPRSSoft\SimpleAPRSserver\SimpleAPRSserver\obj\Debug\SimpleAPRSserver.pdb +D:\PROJECTS\BaoFeng 2-Way Radio HAM (Рации)\HAM\APRS\MyAPRSSoft\SimpleAPRSserver\BIN\SimpleAPRSserver.exe +D:\PROJECTS\BaoFeng 2-Way Radio HAM (Рации)\HAM\APRS\MyAPRSSoft\SimpleAPRSserver\BIN\SimpleAPRSserver.pdb +D:\PROJECTS\BaoFeng 2-Way Radio HAM (Рации)\HAM\APRS\MyAPRSSoft\SimpleAPRSserver\SimpleAPRSserver\obj\Debug\ResolveAssemblyReference.cache diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..e032bff --- /dev/null +++ b/readme.md @@ -0,0 +1,73 @@ +Это простой APRS сервер, который +позволяет обмениваться данными между +всеми подключенными к нему клиентами. +Позволяет кэшировать и хранить последние +полученные координаты и выдавать их при +входящем подключении. + +Сервер имеет HTTP интерфейс с картой и +статистикой. + +Поддерживается AIS протокол. + +Поддерживает стандартные фильтры клиентов: + r/lat/lon/dist -- Range filter + p/aa/bb/cc -- Prefix filter + b/call1/call2 -- Budlist filter + o/call1/call2 -- Object filter + os/call1/call2 -- Strict Object filter + t/poimqstuw/call/km -- Type filter + s/pri/alt/over -- Symbol filter + d/digi1/digi2 -- Digipeater filter + a/latN/lonW/latS/lonE -- Area filter + e/call1/call2 -- Entry station filter + g/call1/call2 -- Group Message filter + u/call1/call2 -- Unproto filter + m/dist -- My Range filter + f/user/dist -- Friend Range filter + +Клиентский софт: + YAAC + + Windows: + UI-View32 + + Android: + OruxMaps + APRSDroid + +=============================================== + +This is the Simple APRS Server +wich send received data to all +connected users. + +Has HTTP interface with live Map. + +Support AIS protocol. + +Supporting client filters: + r/lat/lon/dist -- Range filter + p/aa/bb/cc -- Prefix filter + b/call1/call2 -- Budlist filter + o/call1/call2 -- Object filter + os/call1/call2 -- Strict Object filter + t/poimqstuw/call/km -- Type filter + s/pri/alt/over -- Symbol filter + d/digi1/digi2 -- Digipeater filter + a/latN/lonW/latS/lonE -- Area filter + e/call1/call2 -- Entry station filter + g/call1/call2 -- Group Message filter + u/call1/call2 -- Unproto filter + m/dist -- My Range filter + f/user/dist -- Friend Range filter + +Client Software: + YAAC + + Windows: + UI-View32 + + Android: + OruxMaps + APRSDroid