- Planetary grid by William Becker and Beth Hagens
- Places mostly based on Simon E. Davies design
- Location data based on this map
- Controls: Left/Right and Esc
SVG version: Planetary Grid Browser I
SVG version: Planetary Grid Browser I
void 0==CanvasRenderingContext2D.prototype.ellipse&&(CanvasRenderingContext2D.prototype.ellipse=function(t,r,n,e,o,a,i,s){this.save(),this.translate(t,r),this.rotate(o),this.scale(n,e),this.arc(0,0,1,a,i,s),this.restore()}),"function"!=typeof Path2D&&!function(){function t(t){if(this.ops_=[],void 0!=t)if("string"==typeof t)try{this.ops_=parser.parse(t)}catch(r){}else{if(!t.hasOwnProperty("ops_"))throw"Error: "+typeof t+"is not a valid argument to Path";this.ops_=t.ops_.slice(0)}}function r(t){return function(){this.ops_.push({type:t,args:Array.prototype.slice.call(arguments,0)})}}parser=function(){function t(t,r){function n(){this.constructor=t}n.prototype=r.prototype,t.prototype=new n}function r(t,r,n,e,o,a){this.message=t,this.expected=r,this.found=n,this.offset=e,this.line=o,this.column=a,this.name="SyntaxError"}function n(t){function n(r){function n(r,n,e){var o,a;for(o=n;e>o;o++)a=t.charAt(o),"\n"===a?(r.seenCR||r.line++,r.column=1,r.seenCR=!1):"\r"===a||"\u2028"===a||"\u2029"===a?(r.line++,r.column=1,r.seenCR=!0):(r.column++,r.seenCR=!1)}return xn!==r&&(xn>r&&(xn=0,mn={line:1,column:1,seenCR:!1}),n(mn,xn,r),xn=r),mn}function e(t){Pn>_n||(_n>Pn&&(Pn=_n,Rn=[]),Rn.push(t))}function o(e,o,a){function i(t){var r=1;for(t.sort(function(t,r){return t.description<r.description?-1:t.description>r.description?1:0});r<t.length;)t[r-1]===t[r]?t.splice(r,1):r++}function s(t,r){function n(t){function r(t){return t.charCodeAt(0).toString(16).toUpperCase()}return t.replace(/\\/g,"\\\\").replace(/"/g,'\\"').replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E\x0F]/g,function(t){return"\\x0"+r(t)}).replace(/[\x10-\x1F\x80-\xFF]/g,function(t){return"\\x"+r(t)}).replace(/[\u0180-\u0FFF]/g,function(t){return"\\u0"+r(t)}).replace(/[\u1080-\uFFFF]/g,function(t){return"\\u"+r(t)})}var e,o,a,i=new Array(t.length);for(a=0;a<t.length;a++)i[a]=t[a].description;return e=t.length>1?i.slice(0,-1).join(", ")+" or "+i[t.length-1]:i[0],o=r?'"'+n(r)+'"':"end of input","Expected "+e+" but "+o+" found."}var u=n(a),c=a<t.length?t.charAt(a):null;return null!==o&&i(o),new r(null!==e?e:s(o,c),o,c,a,u.line,u.column)}function a(){var t,r,n,e,o;for(t=_n,r=[],n=B();n!==lr;)r.push(n),n=B();if(r!==lr)if(n=i(),n===lr&&(n=gr),n!==lr){for(e=[],o=B();o!==lr;)e.push(o),o=B();e!==lr?(An=t,r=yr(n),t=r):(_n=t,t=vr)}else _n=t,t=vr;else _n=t,t=vr;return t}function i(){var t,r,n,e,o;if(t=_n,r=s(),r!==lr){for(n=_n,e=[],o=B();o!==lr;)e.push(o),o=B();e!==lr?(o=i(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(r=[r,n],t=r):(_n=t,t=vr)}else _n=t,t=vr;return t}function s(){var t,r,n,e,o;if(t=_n,r=p(),r!==lr){for(n=_n,e=[],o=B();o!==lr;)e.push(o),o=B();e!==lr?(o=u(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(r=[r,n],t=r):(_n=t,t=vr)}else _n=t,t=vr;return t}function u(){var t,r,n,e,o;if(t=_n,r=c(),r!==lr){for(n=_n,e=[],o=B();o!==lr;)e.push(o),o=B();e!==lr?(o=u(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(r=[r,n],t=r):(_n=t,t=vr)}else _n=t,t=vr;return t}function c(){var t;return t=f(),t===lr&&(t=h(),t===lr&&(t=g(),t===lr&&(t=d(),t===lr&&(t=C(),t===lr&&(t=x(),t===lr&&(t=R(),t===lr&&(t=D(),t===lr&&(t=b())))))))),t}function p(){var r,n,o,a;if(r=_n,dr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(Cr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=l(),a!==lr?(An=r,n=_r(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function l(){var t,r,n,e,o;return t=_n,r=q(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=v(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function f(){var r,n;return r=_n,xr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(mr)),n!==lr&&(An=r,n=Pr()),r=n}function h(){var r,n,o,a;if(r=_n,Rr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(Mr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=v(),a!==lr?(An=r,n=wr(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function v(){var t,r,n,e,o;return t=_n,r=q(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=v(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function g(){var r,n,o,a;if(r=_n,Dr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(Tr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=y(),a!==lr?(An=r,n=br(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function y(){var t,r,n,e,o;return t=_n,r=E(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=y(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function d(){var r,n,o,a;if(r=_n,Fr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(kr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=y(),a!==lr?(An=r,n=qr(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function C(){var r,n,o,a;if(r=_n,Er.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(Sr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=_(),a!==lr?(An=r,n=Ir(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function _(){var t,r,n,e,o;return t=_n,r=A(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=_(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function A(){var t,r,n,e,o,a;return t=_n,r=q(),r!==lr?(n=z(),n===lr&&(n=gr),n!==lr?(e=q(),e!==lr?(o=z(),o===lr&&(o=gr),o!==lr?(a=q(),a!==lr?(An=t,r=Or(r,e,a),t=r):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr),t}function x(){var r,n,o,a;if(r=_n,zr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(jr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=m(),a!==lr?(An=r,n=Hr(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function m(){var t,r,n,e,o;return t=_n,r=P(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=m(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function P(){var t,r,n,e;return t=_n,r=q(),r!==lr?(n=z(),n===lr&&(n=gr),n!==lr?(e=q(),e!==lr?(An=t,r=Qr(r,e),t=r):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr),t}function R(){var r,n,o,a;if(r=_n,Vr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(Lr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=M(),a!==lr?(An=r,n=Zr(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function M(){var t,r,n,e,o;return t=_n,r=w(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=M(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function w(){var t,r,n,e;return t=_n,r=q(),r!==lr?(n=z(),n===lr&&(n=gr),n!==lr?(e=q(),e!==lr?(An=t,r=Qr(r,e),t=r):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr),t}function D(){var r,n,o,a;if(r=_n,Br.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(Ur)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=T(),a!==lr?(An=r,n=Gr(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function T(){var t,r,n,e,o;return t=_n,r=q(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=T(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function b(){var r,n,o,a;if(r=_n,Jr.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(Kr)),n!==lr){for(o=[],a=B();a!==lr;)o.push(a),a=B();o!==lr?(a=F(),a!==lr?(An=r,n=Nr(n,a),r=n):(_n=r,r=vr)):(_n=r,r=vr)}else _n=r,r=vr;return r}function F(){var t,r,n,e,o;return t=_n,r=k(),r!==lr?(n=_n,e=z(),e===lr&&(e=gr),e!==lr?(o=F(),o!==lr?(e=[e,o],n=e):(_n=n,n=vr)):(_n=n,n=vr),n===lr&&(n=gr),n!==lr?(An=t,r=Ar(r,n),t=r):(_n=t,t=vr)):(_n=t,t=vr),t}function k(){var t,r,n,e,o,a,i,s,u,c,p,l;return t=_n,r=S(),r!==lr?(n=z(),n===lr&&(n=gr),n!==lr?(e=S(),e!==lr?(o=z(),o===lr&&(o=gr),o!==lr?(a=I(),a!==lr?(i=z(),i!==lr?(s=O(),s!==lr?(u=z(),u===lr&&(u=gr),u!==lr?(c=O(),c!==lr?(p=z(),p===lr&&(p=gr),p!==lr?(l=q(),l!==lr?(An=t,r=Wr(r,e,a,s,c,l),t=r):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr),t}function q(){var t,r,n,e;return t=_n,r=E(),r!==lr?(n=z(),n===lr&&(n=gr),n!==lr?(e=E(),e!==lr?(An=t,r=Xr(r,e),t=r):(_n=t,t=vr)):(_n=t,t=vr)):(_n=t,t=vr),t}function E(){var t,r;return t=_n,r=I(),r!==lr&&(An=t,r=Yr(r)),t=r}function S(){var t;return t=H(),t===lr&&(t=Z()),t}function I(){var t,r,n;return t=_n,r=L(),r===lr&&(r=gr),r!==lr?(n=H(),n!==lr?(r=[r,n],t=r):(_n=t,t=vr)):(_n=t,t=vr),t===lr&&(t=_n,r=L(),r===lr&&(r=gr),r!==lr?(n=Z(),n!==lr?(r=[r,n],t=r):(_n=t,t=vr)):(_n=t,t=vr)),t}function O(){var r;return 48===t.charCodeAt(_n)?(r=$r,_n++):(r=lr,0===Mn&&e(tn)),r===lr&&(49===t.charCodeAt(_n)?(r=rn,_n++):(r=lr,0===Mn&&e(nn))),r}function z(){var t,r,n,e,o;if(t=_n,r=[],n=B(),n!==lr)for(;n!==lr;)r.push(n),n=B();else r=vr;if(r!==lr)if(n=j(),n===lr&&(n=gr),n!==lr){for(e=[],o=B();o!==lr;)e.push(o),o=B();e!==lr?(r=[r,n,e],t=r):(_n=t,t=vr)}else _n=t,t=vr;else _n=t,t=vr;if(t===lr)if(t=_n,r=j(),r!==lr){for(n=[],e=B();e!==lr;)n.push(e),e=B();n!==lr?(r=[r,n],t=r):(_n=t,t=vr)}else _n=t,t=vr;return t}function j(){var r;return 44===t.charCodeAt(_n)?(r=en,_n++):(r=lr,0===Mn&&e(on)),r}function H(){var t,r,n;return t=_n,r=Q(),r!==lr?(n=V(),n===lr&&(n=gr),n!==lr?(r=[r,n],t=r):(_n=t,t=vr)):(_n=t,t=vr),t===lr&&(t=_n,r=Z(),r!==lr?(n=V(),n!==lr?(r=[r,n],t=r):(_n=t,t=vr)):(_n=t,t=vr)),t}function Q(){var r,n,o,a;return r=_n,n=Z(),n===lr&&(n=gr),n!==lr?(46===t.charCodeAt(_n)?(o=an,_n++):(o=lr,0===Mn&&e(sn)),o!==lr?(a=Z(),a!==lr?(n=[n,o,a],r=n):(_n=r,r=vr)):(_n=r,r=vr)):(_n=r,r=vr),r===lr&&(r=_n,n=Z(),n!==lr?(46===t.charCodeAt(_n)?(o=an,_n++):(o=lr,0===Mn&&e(sn)),o!==lr?(n=[n,o],r=n):(_n=r,r=vr)):(_n=r,r=vr)),r}function V(){var r,n,o,a;return r=_n,un.test(t.charAt(_n))?(n=t.charAt(_n),_n++):(n=lr,0===Mn&&e(cn)),n!==lr?(o=L(),o===lr&&(o=gr),o!==lr?(a=Z(),a!==lr?(n=[n,o,a],r=n):(_n=r,r=vr)):(_n=r,r=vr)):(_n=r,r=vr),r}function L(){var r;return 43===t.charCodeAt(_n)?(r=pn,_n++):(r=lr,0===Mn&&e(ln)),r===lr&&(45===t.charCodeAt(_n)?(r=fn,_n++):(r=lr,0===Mn&&e(hn))),r}function Z(){var r,n,o;if(r=_n,n=[],vn.test(t.charAt(_n))?(o=t.charAt(_n),_n++):(o=lr,0===Mn&&e(gn)),o!==lr)for(;o!==lr;)n.push(o),vn.test(t.charAt(_n))?(o=t.charAt(_n),_n++):(o=lr,0===Mn&&e(gn));else n=vr;return n!==lr&&(An=r,n=yn(n)),r=n}function B(){var r;return dn.test(t.charAt(_n))?(r=t.charAt(_n),_n++):(r=lr,0===Mn&&e(Cn)),r}function U(t,r){return-1==="mlazhvcsqt".indexOf(t)?wn=r:(wn[0]+=r[0],wn[1]+=r[1]),Fn=t,wn.slice(0)}function G(t,r){for(var n=[],e=wn.slice(0),o=0;o<r.length;o+=2){wn=e.slice(0);var a=U(t,r.slice(o,o+2));n=n.concat(a),o==r.length-4&&(Dn=a.slice(0))}return n}function J(){-1=="CcSsQqTt".indexOf(Fn)&&(Dn=wn.slice(0));var t=[0,0];return t[0]=2*wn[0]-Dn[0],t[1]=2*wn[1]-Dn[1],t}function K(t,r){var n=[r,0];return"H"==t&&(n[1]=wn[1]),U(t,n)}function N(t,r){var n=[0,r];return"V"==t&&(n[0]=wn[0]),U(t,n)}function W(t,r){var n=[t];if(r&&r.length>1)for(var e=r[1],o=0;o<e.length;o++)n.push(e[o]);return n}function X(t){return Math.sqrt(Math.pow(t[0],2)+Math.pow(t[1],2))}function Y(t,r){return t[0]*r[0]+t[1]*r[1]}function $(t,r){return Y(t,r)/(X(t)*X(r))}function tr(t,r){var n=1;return t[0]*r[1]-t[1]*r[0]<0&&(n=-1),n*Math.acos($(t,r))}function rr(t,r){var n=Math.cos(r),e=Math.sin(r);return[n*t[0]+e*t[1],-1*e*t[0]+n*t[1]]}function nr(t,r){var n=Math.cos(r),e=Math.sin(r);return[n*t[0]-e*t[1],e*t[0]+n*t[1]]}function er(t,r){return[(t[0]-r[0])/2,(t[1]-r[1])/2]}function or(t,r){return[(t[0]+r[0])/2,(t[1]+r[1])/2]}function ar(t,r){return[t[0]*r[0],t[1]*r[1]]}function ir(t,r){return[t*r[0],t*r[1]]}function sr(t,r){return[t[0]+r[0],t[1]+r[1]]}function ur(t,r,n,e,o,a,i){if(0==r||0==n)return void Tn.push({type:"lineTo",args:i});var e=e*(Math.PI/180);r=Math.abs(r),n=Math.abs(n);var s=rr(er(t,i),e),u=ar(s,s),c=Math.pow(r,2),p=Math.pow(n,2),l=Math.sqrt(u[0]/c+u[1]/p);l>1&&(r*=l,n*=l,c=Math.pow(r,2),p=Math.pow(n,2));var f=Math.sqrt((c*p-c*u[1]-p*u[0])/(c*u[1]+p*u[0]));o==a&&(f*=-1);var h=ir(f,[r*s[1]/n,-n*s[0]/r]),v=sr(nr(h,e),or(t,i)),g=[(s[0]-h[0])/r,(s[1]-h[1])/n],y=[(-1*s[0]-h[0])/r,(-1*s[1]-h[1])/n],d=tr([1,0],g),C=tr(g,y),_=d,A=d+C;Tn.push({type:"save",args:[]},{type:"translate",args:[v[0],v[1]]},{type:"rotate",args:[e]},{type:"scale",args:[r,n]},{type:"arc",args:[0,0,1,_,A,1-a]},{type:"restore",args:[]})}var cr,pr=arguments.length>1?arguments[1]:{},lr={},fr={svg_path:a},hr=a,vr=lr,gr=null,yr=function(){return Tn},dr=/^[Mm]/,Cr={type:"class",value:"[Mm]",description:"[Mm]"},_r=function(t,r){var n=t;bn&&(n="M",bn=!1),Tn.push({type:"moveTo",args:U(n,r[0])});for(var e=1;e<r.length;e++)Tn.push({type:"lineTo",args:U(t,r[e])})},Ar=function(t,r){return W(t,r)},xr=/^[Zz]/,mr={type:"class",value:"[Zz]",description:"[Zz]"},Pr=function(){Tn.push({type:"closePath",args:[]})},Rr=/^[Ll]/,Mr={type:"class",value:"[Ll]",description:"[Ll]"},wr=function(t,r){for(var n=0;n<r.length;n++)Tn.push({type:"lineTo",args:U(t,r[n])})},Dr=/^[Hh]/,Tr={type:"class",value:"[Hh]",description:"[Hh]"},br=function(t,r){for(var n=0;n<r.length;n++)Tn.push({type:"lineTo",args:K(t,r[n])})},Fr=/^[Vv]/,kr={type:"class",value:"[Vv]",description:"[Vv]"},qr=function(t,r){for(var n=0;n<r.length;n++)Tn.push({type:"lineTo",args:N(t,r[n])})},Er=/^[Cc]/,Sr={type:"class",value:"[Cc]",description:"[Cc]"},Ir=function(t,r){for(var n=0;n<r.length;n++)Tn.push({type:"bezierCurveTo",args:G(t,r[n])})},Or=function(t,r,n){return t.concat(r,n)},zr=/^[Ss]/,jr={type:"class",value:"[Ss]",description:"[Ss]"},Hr=function(t,r){for(var n=0;n<r.length;n++)Tn.push({type:"bezierCurveTo",args:J().concat(G(t,r[n]))})},Qr=function(t,r){return t.concat(r)},Vr=/^[Qq]/,Lr={type:"class",value:"[Qq]",description:"[Qq]"},Zr=function(t,r){for(var n=0;n<r.length;n++)Tn.push({type:"quadraticCurveTo",args:G(t,r[n])})},Br=/^[Tt]/,Ur={type:"class",value:"[Tt]",description:"[Tt]"},Gr=function(t,r){for(var n=0;n<r.length;n++){var e=J();Tn.push({type:"quadraticCurveTo",args:e.concat(G(t,r[n]))}),Dn=e.slice(0)}},Jr=/^[Aa]/,Kr={type:"class",value:"[Aa]",description:"[Aa]"},Nr=function(t,r){for(var n=0;n<r.length;n++){var e=[wn.slice()],o=[U(t,r[n].slice(-2))];absArgs=e.concat(r[n].slice(0,-2),o),ur.apply(this,absArgs)}},Wr=function(t,r,n,e,o,a){return[parseFloat(t),parseFloat(r),parseFloat(n.join("")),parseInt(e),parseInt(o),a[0],a[1]]},Xr=function(t,r){return[t,r]},Yr=function(t){return parseFloat(t.join(""))},$r="0",tn={type:"literal",value:"0",description:'"0"'},rn="1",nn={type:"literal",value:"1",description:'"1"'},en=",",on={type:"literal",value:",",description:'","'},an=".",sn={type:"literal",value:".",description:'"."'},un=/^[eE]/,cn={type:"class",value:"[eE]",description:"[eE]"},pn="+",ln={type:"literal",value:"+",description:'"+"'},fn="-",hn={type:"literal",value:"-",description:'"-"'},vn=/^[0-9]/,gn={type:"class",value:"[0-9]",description:"[0-9]"},yn=function(t){return t.join("")},dn=/^[ \t\n\r]/,Cn={type:"class",value:"[ \\t\\n\\r]",description:"[ \\t\\n\\r]"},_n=0,An=0,xn=0,mn={line:1,column:1,seenCR:!1},Pn=0,Rn=[],Mn=0;if("startRule"in pr){if(!(pr.startRule in fr))throw new Error("Can't start parsing from rule \""+pr.startRule+'".');hr=fr[pr.startRule]}var wn=[0,0],Dn=[0,0],Tn=[],bn=!0,Fn="";if(cr=hr(),cr!==lr&&_n===t.length)return cr;throw cr!==lr&&_n<t.length&&e({type:"end",description:"end of input"}),o(null,Rn,Pn)}return t(r,Error),{SyntaxError:r,parse:n}}();for(var n=["closePath","moveTo","lineTo","quadraticCurveTo","bezierCurveTo","rect","arc","arcTo","ellipse","isPointInPath","isPointInStroke"],e=0;e<n.length;e++){var o=n[e];t.prototype[o]=r(o)}t.prototype.addPath=function(t,r){var n=!1;r&&r.hasOwnProperty("a")&&r.hasOwnProperty("b")&&r.hasOwnProperty("c")&&r.hasOwnProperty("d")&&r.hasOwnProperty("e")&&r.hasOwnProperty("f")&&(n=!0,this.ops_.push({type:"save",args:[]}),this.ops_.push({type:"transform",args:[r.a,r.b,r.c,r.d,r.e,r.f]})),this.ops_=this.ops_.concat(t.ops_),n&&this.ops_.push({type:"restore",args:[]})},original_fill=CanvasRenderingContext2D.prototype.fill,original_stroke=CanvasRenderingContext2D.prototype.stroke,original_clip=CanvasRenderingContext2D.prototype.clip,original_is_point_in_path=CanvasRenderingContext2D.prototype.isPointInPath,original_is_point_in_stroke=CanvasRenderingContext2D.prototype.isPointInStroke,CanvasRenderingContext2D.prototype.fill=function(r){if(r instanceof t){this.beginPath();for(var n=0,e=r.ops_.length;e>n;n++){var o=r.ops_[n];CanvasRenderingContext2D.prototype[o.type].apply(this,o.args)}original_fill.apply(this,Array.prototype.slice.call(arguments,1))}else original_fill.apply(this,arguments)},CanvasRenderingContext2D.prototype.stroke=function(r){if(r instanceof t){this.beginPath();for(var n=0,e=r.ops_.length;e>n;n++){var o=r.ops_[n];CanvasRenderingContext2D.prototype[o.type].apply(this,o.args)}original_stroke.call(this)}else original_stroke.call(this)},CanvasRenderingContext2D.prototype.clip=function(r){if(r instanceof t){this.beginPath();for(var n=0,e=r.ops_.length;e>n;n++){var o=r.ops_[n];CanvasRenderingContext2D.prototype[o.type].apply(this,o.args)}original_clip.apply(this,Array.prototype.slice.call(arguments,1))}else original_clip.apply(this,arguments)},CanvasRenderingContext2D.prototype.isPointInPath=function(r){if(r instanceof t){this.beginPath();for(var n=0,e=r.ops_.length;e>n;n++){var o=r.ops_[n];CanvasRenderingContext2D.prototype[o.type].apply(this,o.args)}return original_is_point_in_path.apply(this,Array.prototype.slice.call(arguments,1))}return original_is_point_in_path.apply(this,arguments)},CanvasRenderingContext2D.prototype.isPointInStroke=function(r){if(r instanceof t){this.beginPath();for(var n=0,e=r.ops_.length;e>n;n++){var o=r.ops_[n];CanvasRenderingContext2D.prototype[o.type].apply(this,o.args)}return original_is_point_in_stroke.apply(this,Array.prototype.slice.call(arguments,1))}return original_is_point_in_stroke.apply(this,arguments)},Path2D=t}(); |
function CanvasGlobe(ctx, width, height, cfg, overlay) { | |
'use strict'; | |
var maxScale = 3; | |
var self; | |
var projectionGlobe = d3.geo | |
.orthographic() | |
.clipAngle(90) | |
.precision(0) | |
.translate([height / 2, height / 2]) | |
.scale(height / 2); | |
var projectionRaw = d3.geo | |
.orthographic() | |
.precision(1) | |
.clipAngle(90) | |
.translate([height / 2, height / 2]) | |
.scale(height / 2); | |
var projectionGlobeCalc = d3.geo | |
.orthographic() | |
.clipAngle(90) | |
.translate([height / 2, height / 2]) | |
.scale(height / 2); | |
var projectionLinesGlobe = d3.geo | |
.orthographic() | |
.rotate([cfg.shift, 0, 0]) | |
.precision(1) | |
.clipAngle(90) | |
.translate([height / 2, height / 2]) | |
.scale(height / 2); | |
var pathRaw = d3.geo.path() | |
.context(ctx) | |
.projection(projectionRaw); | |
var pathLines = d3.geo.path() | |
.context(ctx) | |
.projection(projectionLinesGlobe); | |
var zoom = d3.behavior.zoom().scaleExtent([1, maxScale]) | |
.on('zoomstart', zoomed) | |
.on('zoom', zoomed) | |
.on('zoomend', zoomed); | |
var graticuleData = d3.geo.graticule()(); | |
var h = d3.geo.hexakisIcosahedron; | |
var coolLinesData = h.icosahedronEdges(); | |
var hotLinesData = h.hexakisCenterEdges(); | |
var balancedLinesData = h.hexakisSideEdges(); | |
overlay.call(zoom); | |
function draw(running) { | |
projectionRaw.precision(running ? 2 : 1); | |
projectionLinesGlobe.precision(running ? 2 : 1); | |
ctx.save(); | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
ctx.lineWidth = 1; | |
ctx.translate(ctx.canvas.width - width, ctx.canvas.height - height); | |
// Water | |
ctx.beginPath(); | |
ctx.arc(width / 2, height / 2, width / 2 - cfg.frameLineWidth, 0, Math.PI * 2); | |
ctx.closePath(); | |
ctx.fillStyle = !cfg.filter.map.off ? cfg.colors.water : cfg.colors.bg; | |
ctx.fill(); | |
// Frame | |
ctx.beginPath(); | |
ctx.arc(width / 2, height / 2, width / 2 - cfg.frameLineWidth / 2, 0, Math.PI * 2); | |
ctx.closePath(); | |
ctx.strokeStyle = cfg.colors.frame; | |
ctx.lineWidth = cfg.frameLineWidth; | |
ctx.stroke(); | |
// Clip | |
ctx.beginPath(); | |
ctx.arc(width / 2, height / 2, width / 2 - cfg.frameLineWidth, 0, Math.PI * 2); | |
ctx.clip(); | |
ctx.lineWidth = 0.5; | |
if (!cfg.filter.graticule.off) { | |
ctx.beginPath(); | |
pathRaw(graticuleData); | |
ctx.strokeStyle = cfg.colors.graticule; | |
ctx.setLineDash(cfg.dash); | |
ctx.stroke(); | |
ctx.setLineDash([]); | |
} | |
if (!cfg.filter.map.off && self.landDatum) { | |
ctx.beginPath(); | |
pathRaw(self.landDatum); | |
ctx.fillStyle = cfg.colors.land; | |
ctx.strokeStyle = cfg.colors.border; | |
ctx.fill(); | |
ctx.stroke(); | |
} | |
ctx.lineWidth = 1; | |
if (!cfg.filter['cool-line'].off) { | |
ctx.beginPath(); | |
pathLines(coolLinesData); | |
ctx.strokeStyle = cfg.colors.cool; | |
ctx.stroke(); | |
} | |
if (!cfg.filter['hot-line'].off) { | |
ctx.beginPath(); | |
pathLines(hotLinesData); | |
ctx.strokeStyle = cfg.colors.hot; | |
ctx.stroke(); | |
} | |
if (!cfg.filter['balanced-line'].off) { | |
ctx.beginPath(); | |
pathLines(balancedLinesData); | |
ctx.strokeStyle = cfg.colors.balanced; | |
ctx.stroke(); | |
} | |
if (self.selection) { | |
var p = pathRaw.centroid(self.selection.geometry); | |
if (!isNaN(p[0]) && !isNaN(p[1])) { | |
ctx.beginPath(); | |
ctx.arc(p[0], p[1], 7, 0, 2 * Math.PI); | |
ctx.strokeStyle = cfg.colors.focus; | |
ctx.lineWidth = 3; | |
ctx.fillStyle = cfg.colors.selection; | |
ctx.fill(); | |
ctx.stroke(); | |
} | |
} | |
ctx.restore(); | |
} | |
var a, pG, pGL; | |
function zoomed() { | |
var m = d3.mouse(this); | |
if (d3.event && d3.event.sourceEvent) { | |
d3.event.sourceEvent.stopPropagation(); | |
d3.event.sourceEvent.preventDefault(); | |
} | |
({ | |
zoomstart: function () { | |
pG = projectionGlobe.rotate(); | |
pGL = projectionLinesGlobe.rotate(); | |
projectionGlobeCalc.rotate(pG); | |
a = projectionGlobeCalc.invert(m); | |
}, | |
zoom: function () { | |
var b = projectionGlobeCalc.invert(m); | |
var pgR = [pG[0] + b[0] - a[0], pG[1] + b[1] - a[1]]; | |
var plgR = [pGL[0] + b[0] - a[0], pGL[1] + b[1] - a[1]]; | |
if (self.canZoom && !self.canZoom(pgR)) { | |
return; | |
} | |
if (!isNaN(b[0]) && !isNaN(b[1])) { | |
projectionRaw.rotate(pgR); | |
projectionGlobe.rotate(pgR); | |
projectionLinesGlobe.rotate(plgR); | |
if (self.onZoomed) { | |
var s = zoom.scale(); | |
if (cfg.mraf) { | |
requestAnimationFrame(function () { | |
self.onZoomed(null, s, pgR); | |
}); | |
} else { | |
self.onZoomed(null, s, pgR); | |
} | |
} | |
} | |
}, | |
zoomend: function () { | |
} | |
})[d3.event.type](); | |
} | |
function setZoom(t, s, i) { | |
zoom.scale(s); | |
projectionLinesGlobe.rotate([i[0] + cfg.shift, i[1]]); | |
projectionGlobe.rotate(i); | |
projectionRaw.rotate(i); | |
} | |
self = { | |
draw: draw, | |
pathRaw: pathRaw, | |
setZoom: setZoom | |
}; | |
return self; | |
} |
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<title>Planetary Grid II</title> | |
<link rel="stylesheet" href="/darosh/raw/14e2e4e14898f13e13c7/style.css"> | |
<body> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/topojson/1.6.19/topojson.min.js"></script> | |
<script src="canvas.min.js"></script> | |
<script src="/darosh/raw/2d12a584a14910032ab8/togeojson.js"></script> | |
<script src="/darosh/raw/2fe464efd794bde5ed68/hexakis-icosahedron.js"></script> | |
<script src="/darosh/raw/14e2e4e14898f13e13c7/config.js"></script> | |
<script src="/darosh/raw/14e2e4e14898f13e13c7/utils.js"></script> | |
<script src="/darosh/raw/14e2e4e14898f13e13c7/legend.js"></script> | |
<script src="/darosh/raw/14e2e4e14898f13e13c7/list.js"></script> | |
<script src="/darosh/raw/14e2e4e14898f13e13c7/info.js"></script> | |
<script src="globe.js"></script> | |
<script src="map.js"></script> | |
<script> | |
(function () { | |
'use strict'; | |
var widthList = 220; | |
var margin = 12; | |
var widthScrollBar = self.frameElement ? 0 : 20; | |
var widthScreen = Math.floor(Math.max(document.body.clientWidth || 0, 960)) - widthScrollBar; | |
widthScreen = Math.min(1480, widthScreen); | |
var heightScreen = Math.max(500, widthScreen / (960 / 480)); | |
margin = widthScreen > 960 ? margin * 2 : margin; | |
var widthGlobe = 200; | |
var heightGlobe = 200; | |
if (heightScreen > 660) { | |
widthGlobe = 280; | |
heightGlobe = 280; | |
} | |
var widthLegend = 140; | |
var widthMap = widthScreen - Math.max(widthLegend, widthGlobe / 2) - 2 * margin; | |
var heightMap = heightScreen - heightGlobe / 2 - 2 * margin; | |
var selected; | |
var list; | |
var cfg = new Config(); | |
cfg.globeRadius = widthGlobe / 2; | |
cfg.url = 'http://bl.ocks.org/darosh/7b816a50e66bb62208a7'; | |
var canvas = d3.select('body').append('canvas') | |
.attr('class', 'no-select') | |
.attr('width', widthMap + widthGlobe / 2) | |
.attr('height', heightMap + heightGlobe / 2) | |
.style('margin-left', margin + 'px') | |
.style('margin-top', margin + 'px'); | |
var ctx = canvas.node().getContext('2d', {alpha: false}); | |
var map = new CanvasMap(ctx, widthMap, heightMap, margin, cfg); | |
var globeOverlay = d3.select('body') | |
.append('div') | |
.attr('class', 'no-select') | |
.style('position', 'absolute') | |
.style('border-radius', (widthGlobe / 2) + 'px') | |
.style('left', (widthMap - widthGlobe / 2 + margin) + 'px') | |
.style('top', (heightMap - widthGlobe / 2 + margin) + 'px') | |
.style('width', (widthGlobe) + 'px') | |
.style('height', (heightGlobe) + 'px'); | |
var globe = new CanvasGlobe(ctx, widthGlobe, heightGlobe, cfg, globeOverlay); | |
map.drawGlobe = globe.draw; | |
var svgLegendWrap = d3.select('body').append('svg') | |
.attr('width', widthLegend) | |
.attr('height', heightMap - heightGlobe / 2) | |
.style('position', 'absolute') | |
.style('left', (widthMap + margin + cfg.lineMid) + 'px') | |
.style('top', margin + 'px'); | |
var legend = new SvgLegend(svgLegendWrap, cfg, clickedLegend); | |
cfg.filter = legend.lookFilter; | |
var svgListWrap = d3.select('body').append('svg') | |
.attr('width', widthMap - widthGlobe / 2) | |
.attr('height', 0) | |
.style('position', 'absolute') | |
.style('left', margin + 'px') | |
.style('top', (heightMap + margin + cfg.lineMid) + 'px'); | |
list = new SvgList(svgListWrap, | |
cfg, widthMap - widthGlobe / 2, widthList, | |
function (h) { | |
h = h + margin; | |
svgListWrap.attr('height', h); | |
d3.select(self.frameElement).style('height', (h + heightMap + 2 * margin) + 'px'); | |
}, | |
selectedPlace | |
); | |
var info = new HtmlInfo(d3.select('body').append('div') | |
.style('position', 'absolute') | |
.style('left', (2 * margin) + 'px') | |
.style('top', (2 * margin) + 'px') | |
.style('border', cfg.frameLineWidth + 'px solid ' + cfg.colors.frame) | |
.style('padding', (cfg.titleSize * 0.75) + 'px') | |
.style('min-width', (cfg.titleSize * 8) + 'px') | |
.style('background-color', cfg.colors.bg), | |
cfg | |
); | |
var controls = d3.select('body').append('svg') | |
.attr('width', 70) | |
.attr('height', 22) | |
.style('position', 'absolute') | |
.style('left', (margin * 1.5) + 'px') | |
.style('top', (heightMap + margin * .5 - 22) + 'px'); | |
Utils.svgControls(controls, setIndex, map.reset); | |
map.clickedPoint = function (p) { | |
selectedPlace(p); | |
}; | |
map.onZoomed = globe.setZoom; | |
globe.onZoomed = map.setZoom; | |
globe.canZoom = function (i) { | |
var p = map.projection(i); | |
return !isNaN(p[0]) && !isNaN(p[1]); | |
}; | |
if (self.frameElement) { | |
self.frameElement.focus(); | |
} | |
d3.select('body').on('keydown', function () { | |
var i = null; | |
if (d3.event.keyCode === 37) { | |
i = -1; | |
} else if (d3.event.keyCode === 39) { | |
i = +1; | |
} else if (d3.event.keyCode === 27 && selected) { | |
selectedPlace(selected); | |
map.update(1); | |
d3.event.stopPropagation(); | |
d3.event.preventDefault(); | |
} | |
if (i !== null) { | |
setIndex(i); | |
d3.event.stopPropagation(); | |
d3.event.preventDefault(); | |
} | |
}); | |
map.update(1); | |
d3.json('/darosh/raw/14e2e4e14898f13e13c7/mercator-countries.json', function (topo) { | |
topojson.presimplify(topo); | |
map.countriesDatum = topojson.mesh(topo, topo.objects.countries); | |
map.update(1); | |
}); | |
d3.json('/darosh/raw/14e2e4e14898f13e13c7/mercator-land.json', function (topo) { | |
topojson.presimplify(topo); | |
map.landDatum = topojson.feature(topo, topo.objects.land); | |
map.update(1); | |
}); | |
d3.json('/darosh/raw/14e2e4e14898f13e13c7/land.json', function (topo) { | |
globe.landDatum = topojson.feature(topo, topo.objects.land); | |
map.update(1); | |
}); | |
d3.xml('/darosh/raw/2d12a584a14910032ab8/places.kml', function (xml) { | |
var geo = toGeoJSON.kml(xml); | |
Utils.parsePlaces(geo); | |
map.geo = geo; | |
if (list) { | |
list.update(geo); | |
} | |
cfg.filtered = geo.features; | |
map.update(1); | |
}); | |
function clickedLegend() { | |
map.update(1); | |
list.filter(legend.lookShapes); | |
} | |
function selectedPlace(d) { | |
if (list) { | |
list.selection(selected, d); | |
} | |
if (d === selected) { | |
d = null; | |
} | |
globe.selection = d; | |
info.update(d); | |
selected = d; | |
map.zoomTo(d, info.size); | |
} | |
function setIndex(i) { | |
var f = cfg.filtered; | |
var c = f.indexOf(selected); | |
c = c === -1 ? 0 : (c + i); | |
c = (c + f.length) % f.length; | |
if (list) { | |
list.selection(selected, f[c]); | |
} | |
info.update(f[c]); | |
globe.selection = f[c]; | |
setTimeout(function () { | |
map.zoomTo(f[c], info.size); | |
}, 50); | |
selected = f[c]; | |
} | |
})(); | |
</script> | |
</body> |
function CanvasMap(ctx, width, height, margin, cfg) { | |
'use strict'; | |
var maxScale = 3; | |
var self; | |
var focused; | |
var focusedFeature; | |
var cache = {}; | |
var cachePath = false; | |
var cachePathLines = []; | |
var translate; | |
var scale; | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
ctx.fillStyle = cfg.colors.bg; | |
ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height); | |
var referenceSize = Math.sqrt(height * height + width * width) * cfg.durationScale; | |
var clickMapCanvas = document.createElement('canvas'); | |
clickMapCanvas.width = width; | |
clickMapCanvas.height = height; | |
var clickCtx = clickMapCanvas.getContext('2d', {alpha: false}); | |
var clip = d3.geo.clipExtent(); | |
var projection = d3.geo.mercator() | |
.translate([0, 0]) | |
.precision(0) | |
.scale(width / 2 / Math.PI); | |
var simplify = d3.geo.transform({ | |
point: function (x, y, z) { | |
if (z >= simplifiedProjection.area) { | |
this.stream.point(x * width | 0, y * width | 0); | |
} | |
} | |
}); | |
var simplifiedProjection = { | |
stream: function (s) { | |
return simplify.stream(clip.stream(s)); | |
}, | |
baseArea: 4e-3 / width | |
}; | |
simplifiedProjection.area = simplifiedProjection.baseArea; | |
var projectionLines = d3.geo.mercator() | |
.rotate([cfg.shift, 0, 0]) | |
.translate([0, 0]) | |
.precision(1) | |
.scale(width / 2 / Math.PI); | |
var path = d3.geo.path() | |
.context(!cachePath ? ctx : null) | |
.projection(simplifiedProjection); | |
var pathRaw = d3.geo.path() | |
.context(/*!cachePathLines ? */ctx/* : null*/) | |
.projection(projection); | |
var pathLines = d3.geo.path() | |
.context(!cachePathLines ? ctx : null) | |
.projection(projectionLines); | |
var zoom = d3.behavior.zoom() | |
.scaleExtent([1, maxScale]) | |
.on('zoom', zoomed); | |
var graticuleDatum = d3.geo.graticule()(); | |
var h = d3.geo.hexakisIcosahedron; | |
var coolLinesData = h.icosahedronEdges(); | |
var hotLinesData = h.hexakisCenterEdges(); | |
var balancedLinesData = h.hexakisSideEdges(); | |
var coolPointsData = Utils.pointsToFeatures(h.icosahedronPoints(), 'cool-point', 'Cool point', cfg.shift); | |
var hotPointsData = Utils.pointsToFeatures(h.hexakisCenterPoints(), 'hot-point', 'Hot point', cfg.shift); | |
var balancedPointsData = Utils.pointsToFeatures(h.hexakisCrossPoints(), 'balanced-point', 'Balanced point', cfg.shift); | |
d3.select(ctx.canvas).call(zoom).on('mouseup', click); | |
function draw() { | |
var t = zoom.translate(); | |
var s = zoom.scale(); | |
ctx.save(); | |
ctx.setTransform(1, 0, 0, 1, 0, 0); | |
// Clip | |
ctx.beginPath(); | |
ctx.moveTo(cfg.frameLineWidth, cfg.frameLineWidth); | |
ctx.lineTo(cfg.frameLineWidth, height - cfg.frameLineWidth); | |
ctx.lineTo(width - cfg.globeRadius, height - cfg.frameLineWidth); | |
ctx.arcTo(width - cfg.globeRadius, height - cfg.globeRadius, | |
width - cfg.frameLineWidth, height - cfg.globeRadius, | |
cfg.globeRadius); | |
ctx.lineTo(width - cfg.frameLineWidth, height - cfg.globeRadius); | |
ctx.lineTo(width - cfg.frameLineWidth, cfg.frameLineWidth); | |
ctx.lineTo(cfg.frameLineWidth, cfg.frameLineWidth); | |
ctx.clip(); | |
// Water | |
if (!cfg.filter.map.off) { | |
ctx.fillStyle = cfg.colors.water; | |
ctx.fillRect(cfg.frameLineWidth, cfg.frameLineWidth, width - cfg.frameLineWidth * 2, height - cfg.frameLineWidth * 2); | |
} else { | |
ctx.fillStyle = cfg.colors.bg; | |
ctx.fillRect(cfg.frameLineWidth, cfg.frameLineWidth, width - cfg.frameLineWidth * 2, height - cfg.frameLineWidth * 2); | |
} | |
// Transform | |
ctx.translate(Math.round(s * width / 2 + t[0]), Math.round(s * height / 2 + t[1])); | |
ctx.scale(s, s); | |
// Clip | |
var c = [-t[0] / s - (s - 1) * (width / s) / 2, -t[1] / s - (s - 1) * (height / s) / 2]; | |
var ce = [[-width / 2 / s + c[0], -height / 2 / s + c[1]], [width / 2 / s + c[0], height / 2 / s + c[1]]]; | |
clip.extent(ce); | |
if (!cachePathLines) { | |
projectionLines.clipExtent(ce); | |
} | |
projection.clipExtent(ce); | |
ctx.lineWidth = 0.5 / s; | |
// Graticule | |
if (!cfg.filter.graticule.off) { | |
ctx.beginPath(); | |
ctx.setLineDash([cfg.dash[0] / s, cfg.dash[1] / s]); | |
ctx.strokeStyle = cfg.colors.graticule; | |
if (false && cachePathLines) { | |
ctx.stroke(cachePathLines[3] = cachePathLines[3] || new Path2D(pathRaw(graticuleDatum))); | |
} else { | |
pathRaw(graticuleDatum); | |
ctx.stroke(); | |
} | |
ctx.setLineDash([]); | |
} | |
// Land | |
if (!cachePath && !cfg.filter.map.off && self.landDatum) { | |
ctx.beginPath(); | |
ctx.fillStyle = cfg.colors.land; | |
path(self.landDatum); | |
ctx.fill(); | |
} else if (cachePath && !cfg.filter.map.off && self.landDatum) { | |
ctx.beginPath(); | |
ctx.fillStyle = cfg.colors.land; | |
path(self.landDatum); | |
ctx.fill(cachePath[10 + Math.round(s * 10)] = cachePath[10 + Math.round(s * 2)] || new Path2D(path(self.landDatum))); | |
} | |
// Borders | |
if (!cachePath && !cfg.filter.map.off && self.countriesDatum) { | |
ctx.beginPath(); | |
path(self.countriesDatum); | |
ctx.strokeStyle = cfg.colors.border; | |
ctx.stroke(); | |
} | |
ctx.lineWidth = 1 / s; | |
// Lines | |
if (!cfg.filter['cool-line'].off) { | |
ctx.beginPath(); | |
ctx.strokeStyle = cfg.colors.cool; | |
if (cachePathLines) { | |
ctx.stroke(cachePathLines[0] = cachePathLines[0] || new Path2D(pathLines(coolLinesData))); | |
} else { | |
pathLines(coolLinesData); | |
ctx.stroke(); | |
} | |
} | |
if (!cfg.filter['hot-line'].off) { | |
ctx.beginPath(); | |
ctx.strokeStyle = cfg.colors.hot; | |
if (cachePathLines) { | |
ctx.stroke(cachePathLines[1] = cachePathLines[1] || new Path2D(pathLines(hotLinesData))); | |
} else { | |
pathLines(hotLinesData); | |
ctx.stroke(); | |
} | |
} | |
if (!cfg.filter['balanced-line'].off) { | |
ctx.beginPath(); | |
ctx.strokeStyle = cfg.colors.balanced; | |
if (cachePathLines) { | |
ctx.stroke(cachePathLines[2] = cachePathLines[2] || new Path2D(pathLines(balancedLinesData))); | |
} else { | |
pathLines(balancedLinesData); | |
ctx.stroke(); | |
} | |
} | |
// Points | |
if (!cfg.filter['cool-point'].off) { | |
coolPointsData.forEach(function (d) { | |
drawPoint(d, ctx, projectionLines, cfg.colors.cool, 10 / s); | |
}); | |
} | |
if (!cfg.filter['hot-point'].off) { | |
hotPointsData.forEach(function (d) { | |
drawPoint(d, ctx, projectionLines, cfg.colors.hot, 10 / s); | |
}); | |
} | |
if (!cfg.filter['balanced-point'].off) { | |
balancedPointsData.forEach(function (d) { | |
drawPoint(d, ctx, projectionLines, cfg.colors.balanced, 10 / s); | |
}); | |
} | |
// Symbols | |
if (self.geo) { | |
ctx.lineWidth = cfg.symbolLine / s; | |
ctx.strokeStyle = cfg.colors.shape; | |
self.geo.features.forEach(function (d) { | |
drawSymbol(d, s, ctx, cfg.shapes[d.properties.description]); | |
}); | |
} | |
if (focused) { | |
ctx.beginPath(); | |
ctx.arc(focused[0], focused[1], 25 / s, 0, 2 * Math.PI); | |
ctx.strokeStyle = cfg.colors.focus; | |
ctx.fillStyle = cfg.colors.selection; | |
ctx.lineWidth = 6 / s; | |
ctx.fill(); | |
ctx.stroke(); | |
if (focusedFeature && focusedFeature.type === 'Feature') { | |
ctx.lineWidth = cfg.symbolLine / s; | |
ctx.strokeStyle = cfg.colors.shape; | |
drawSymbol(focusedFeature, s, ctx, cfg.shapes[focusedFeature.properties.description]); | |
} | |
var n = translateToCenter(t, s); | |
var l = [[n[0] + (-width / 2 + margin + self.size.width / 2) / s, | |
n[1] + (-height / 2 + margin + self.size.height / 2) / s], | |
[focused[0], focused[1]]]; | |
var d = Math.sqrt(Math.pow(l[1][0] - l[0][0], 2) + Math.pow(l[1][1] - l[0][1], 2)); | |
var r = (d - 25 / s) / d; | |
l[1][0] = l[0][0] + r * (l[1][0] - l[0][0]); | |
l[1][1] = l[0][1] + r * (l[1][1] - l[0][1]); | |
ctx.beginPath(); | |
ctx.moveTo(l[0][0], l[0][1]); | |
ctx.lineTo(l[1][0], l[1][1]); | |
ctx.lineWidth = 3 / s; | |
ctx.strokeStyle = cfg.colors.frame; | |
ctx.stroke(); | |
} | |
ctx.restore(); | |
// Frame | |
ctx.beginPath(); | |
ctx.moveTo(width - cfg.frameLineWidth / 2, height - cfg.globeRadius); | |
ctx.lineTo(width - cfg.frameLineWidth / 2, cfg.frameLineWidth / 2); | |
ctx.lineTo(cfg.frameLineWidth / 2, cfg.frameLineWidth / 2); | |
ctx.lineTo(cfg.frameLineWidth / 2, height - cfg.frameLineWidth / 2); | |
ctx.lineTo(width - cfg.globeRadius, height - cfg.frameLineWidth / 2); | |
ctx.strokeStyle = cfg.colors.frame; | |
ctx.lineWidth = cfg.frameLineWidth; | |
ctx.stroke(); | |
} | |
function drawSymbol(d, s, ctx, color) { | |
if (cfg.filter[d.properties.description].off) { | |
return; | |
} | |
var t = pathRaw.centroid(d); | |
if (isNaN(t[0]) || isNaN(t[1])) { | |
return; | |
} | |
var symbolSize = cfg.sizes[d.properties.description] / s; | |
var cp; | |
if (cache[cfg.symbols[d.properties.description] + symbolSize]) { | |
cp = cache[cfg.symbols[d.properties.description] + symbolSize] | |
} else { | |
var p = d3.svg.symbol().size(symbolSize).type(cfg.symbols[d.properties.description])(); | |
cp = cache[cfg.symbols[d.properties.description] + symbolSize] = new Path2D(p); | |
} | |
ctx.save(); | |
ctx.translate(t[0], t[1]); | |
ctx.stroke(cp); | |
ctx.fillStyle = color; | |
ctx.fill(cp); | |
ctx.restore(); | |
} | |
function drawPoint(d, ctx, projection, color, s) { | |
var p = projection(d.coordinates); | |
if (Math.abs(p[0]) !== Infinity && Math.abs(p[1]) !== Infinity) { | |
ctx.beginPath(); | |
ctx.arc(p[0], p[1], s, 0, 2 * Math.PI); | |
ctx.strokeStyle = color; | |
ctx.stroke(); | |
} | |
} | |
function numberToColor(n) { | |
var b = 0xff & n; | |
n >>= 8; | |
var g = 0xff & n; | |
n >>= 8; | |
return 'rgb(' + n + ',' + g + ',' + b + ')'; | |
} | |
function pixelToNumber(p) { | |
return (p[0] << 16) + (p[1] << 8) + p[2]; | |
} | |
function click() { | |
var m = d3.mouse(this); | |
var t = zoom.translate(); | |
var s = zoom.scale(); | |
var n = 1; | |
var map = {}; | |
var c; | |
clickCtx.setTransform(1, 0, 0, 1, 0, 0); | |
clickCtx.clearRect(0, 0, width, height); | |
clickCtx.translate(s * width / 2 + t[0], s * height / 2 + t[1]); | |
clickCtx.scale(s, s); | |
if (!cfg.filter['cool-point'].off) { | |
coolPointsData.forEach(function (d) { | |
map[n] = d; | |
c = numberToColor(n++); | |
drawPoint(d, clickCtx, projectionLines, c, 10 / s); | |
clickCtx.fillStyle = c; | |
clickCtx.fill(); | |
}); | |
} | |
if (!cfg.filter['hot-point'].off) { | |
hotPointsData.forEach(function (d) { | |
map[n] = d; | |
c = numberToColor(n++); | |
drawPoint(d, clickCtx, projectionLines, c, 10 / s); | |
clickCtx.fillStyle = c; | |
clickCtx.fill(); | |
}); | |
} | |
if (!cfg.filter['balanced-point'].off) { | |
balancedPointsData.forEach(function (d) { | |
map[n] = d; | |
c = numberToColor(n++); | |
drawPoint(d, clickCtx, projectionLines, c, 10 / s); | |
clickCtx.fillStyle = c; | |
clickCtx.fill(); | |
}); | |
} | |
if (self.geo) { | |
clickCtx.lineWidth = cfg.symbolLine / s; | |
self.geo.features.forEach(function (d) { | |
map[n] = d; | |
c = numberToColor(n++); | |
clickCtx.strokeStyle = c; | |
drawSymbol(d, s, clickCtx, c); | |
}); | |
if (focusedFeature && focusedFeature.type === 'Feature') { | |
map[n] = focusedFeature; | |
c = numberToColor(n++); | |
clickCtx.strokeStyle = c; | |
drawSymbol(focusedFeature, s, clickCtx, c); | |
} | |
} | |
var p = clickCtx.getImageData(m[0], m[1], 1, 1).data; | |
var b = pixelToNumber(p); | |
if (map[b] && self.clickedPoint) { | |
self.clickedPoint(map[b]); | |
} | |
} | |
function getFixedZoom(t, s) { | |
var S = (width - height) / 2; | |
t[0] = Math.min(0, Math.max(-width * (s - 1), t[0])); | |
t[1] = Math.min(+S * s, Math.max(-S * s - height * (s - 1), t[1])); | |
} | |
function fixZoom() { | |
var t = zoom.translate(); | |
var s = zoom.scale(); | |
getFixedZoom(t, s); | |
zoom.translate(t); | |
} | |
function zoomed() { | |
if (d3.event && d3.event.sourceEvent) { | |
d3.event.sourceEvent.stopPropagation(); | |
d3.event.sourceEvent.preventDefault(); | |
} | |
fixZoom(); | |
var t = zoom.translate(); | |
var s = zoom.scale(); | |
if ((s === scale) && translate && (t[0] === translate[0]) && (t[1] === translate[1])) { | |
return; | |
} | |
translate = t; | |
scale = s; | |
var c = translateToCenter(t, s); | |
var i = projection.invert([-c[0], -c[1]]); | |
if (cfg.mraf) { | |
requestAnimationFrame(function () { | |
update(); | |
if (self.onZoomed) { | |
self.onZoomed(t, s, i); | |
} | |
}); | |
} else { | |
update(); | |
if (self.onZoomed) { | |
self.onZoomed(t, s, i); | |
} | |
} | |
} | |
function update(f, running) { | |
var s = (running === true) ? 1 : zoom.scale(); | |
projectionLines.precision(cachePathLines ? Math.sqrt(1 / 2) / maxScale / maxScale : Math.sqrt(1 / 2) / s / s); | |
simplifiedProjection.area = simplifiedProjection.baseArea / s / s; | |
draw(); | |
if (self.drawGlobe) { | |
self.drawGlobe(running); | |
} | |
} | |
function setZoom(t, s, i) { | |
var p = projection(i); | |
zoom.scale(s); | |
t = centerToTranslate([-p[0], -p[1]], s); | |
zoom.translate(t); | |
fixZoom(); | |
update(); | |
} | |
function translateToCenter(t, s) { | |
return [-t[0] / s - (s - 1) * (width / s) / 2, -t[1] / s - (s - 1) * (height / s) / 2]; | |
} | |
function centerToTranslate(c, s) { | |
return [-(c[0] + (s - 1) * (width / s) / 2) * s, -(c[1] + (s - 1) * (height / s) / 2) * s]; | |
} | |
function zoomTransition(p, toScale) { | |
var fromScale = zoom.scale(); | |
var fromTranslate = zoom.translate(); | |
var toTranslate = toScale === 1 ? p : [-p[0] * toScale - width, -p[1] * toScale - height]; | |
var from = translateToCenter(fromTranslate, fromScale); | |
from[2] = referenceSize / fromScale; | |
getFixedZoom(toTranslate, toScale); | |
var to = translateToCenter(toTranslate, toScale); | |
to[2] = referenceSize / toScale; | |
var zi = d3.interpolateZoom(from, to); | |
var dur = Math.min(cfg.durationMax, Math.max(cfg.durationMin, zi.duration * cfg.durationSpeed)); | |
d3.transition().duration(dur).tween('tween', tween); | |
function tween() { | |
return function (t) { | |
var z = zi(t); | |
var s = referenceSize / z[2]; | |
var translate = centerToTranslate(z, s); | |
getFixedZoom(translate, s); | |
zoom.translate(translate); | |
zoom.scale(s); | |
update(t === 1 ? 1 : 0, t < 1); | |
if (self.onZoomed) { | |
var c = translateToCenter(translate, s); | |
var i = projection.invert([-c[0], -c[1]]); | |
self.onZoomed(translate, s, i); | |
} | |
}; | |
} | |
} | |
function zoomTo(d, infoSize) { | |
self.size = infoSize; | |
if (!d) { | |
focusedFeature = null; | |
focused = null; | |
update(); | |
return; | |
} | |
var c = d.geometry ? [d.geometry.coordinates[0], d.geometry.coordinates[1]] : [d[0] + cfg.shift, d[1]]; | |
var p = projection(c); | |
focusedFeature = d; | |
focused = p; | |
zoomTransition(p, maxScale); | |
} | |
function reset() { | |
zoomTransition(projection([0, 0]), 1); | |
} | |
return self = { | |
ctx: ctx, | |
path: path, | |
pathRaw: pathRaw, | |
projection: projection, | |
update: update, | |
setZoom: setZoom, | |
zoomTo: zoomTo, | |
reset: reset | |
}; | |
} |