polymaps.js 64 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376237723782379238023812382238323842385238623872388238923902391239223932394239523962397239823992400240124022403240424052406
  1. if (!org) var org = {};
  2. if (!org.polymaps) org.polymaps = {};
  3. (function(po){
  4. po.version = "2.5.1"; // semver.org
  5. var zero = {x: 0, y: 0};
  6. po.ns = {
  7. svg: "http://www.w3.org/2000/svg",
  8. xlink: "http://www.w3.org/1999/xlink"
  9. };
  10. function ns(name) {
  11. var i = name.indexOf(":");
  12. return i < 0 ? name : {
  13. space: po.ns[name.substring(0, i)],
  14. local: name.substring(i + 1)
  15. };
  16. }
  17. po.id = (function() {
  18. var id = 0;
  19. return function() {
  20. return ++id;
  21. };
  22. })();
  23. po.svg = function(type) {
  24. return document.createElementNS(po.ns.svg, type);
  25. };
  26. po.transform = function(a, b, c, d, e, f) {
  27. var transform = {},
  28. zoomDelta,
  29. zoomFraction,
  30. k;
  31. if (!arguments.length) {
  32. a = 1; c = 0; e = 0;
  33. b = 0; d = 1; f = 0;
  34. }
  35. transform.zoomFraction = function(x) {
  36. if (!arguments.length) return zoomFraction;
  37. zoomFraction = x;
  38. zoomDelta = Math.floor(zoomFraction + Math.log(Math.sqrt(a * a + b * b + c * c + d * d)) / Math.LN2);
  39. k = Math.pow(2, -zoomDelta);
  40. return transform;
  41. };
  42. transform.apply = function(x) {
  43. var k0 = Math.pow(2, -x.zoom),
  44. k1 = Math.pow(2, x.zoom - zoomDelta);
  45. return {
  46. column: (a * x.column * k0 + c * x.row * k0 + e) * k1,
  47. row: (b * x.column * k0 + d * x.row * k0 + f) * k1,
  48. zoom: x.zoom - zoomDelta
  49. };
  50. };
  51. transform.unapply = function(x) {
  52. var k0 = Math.pow(2, -x.zoom),
  53. k1 = Math.pow(2, x.zoom + zoomDelta);
  54. return {
  55. column: (x.column * k0 * d - x.row * k0 * c - e * d + f * c) / (a * d - b * c) * k1,
  56. row: (x.column * k0 * b - x.row * k0 * a - e * b + f * a) / (c * b - d * a) * k1,
  57. zoom: x.zoom + zoomDelta
  58. };
  59. };
  60. transform.toString = function() {
  61. return "matrix(" + [a * k, b * k, c * k, d * k].join(" ") + " 0 0)";
  62. };
  63. return transform.zoomFraction(0);
  64. };
  65. po.cache = function(load, unload) {
  66. var cache = {},
  67. locks = {},
  68. map = {},
  69. head = null,
  70. tail = null,
  71. size = 64,
  72. n = 0;
  73. function remove(tile) {
  74. n--;
  75. if (unload) unload(tile);
  76. delete map[tile.key];
  77. if (tile.next) tile.next.prev = tile.prev;
  78. else if (tail = tile.prev) tail.next = null;
  79. if (tile.prev) tile.prev.next = tile.next;
  80. else if (head = tile.next) head.prev = null;
  81. }
  82. function flush() {
  83. for (var tile = tail; n > size; tile = tile.prev) {
  84. if (!tile) break;
  85. if (tile.lock) continue;
  86. remove(tile);
  87. }
  88. }
  89. cache.peek = function(c) {
  90. return map[[c.zoom, c.column, c.row].join("/")];
  91. };
  92. cache.load = function(c, projection) {
  93. var key = [c.zoom, c.column, c.row].join("/"),
  94. tile = map[key];
  95. if (tile) {
  96. if (tile.prev) {
  97. tile.prev.next = tile.next;
  98. if (tile.next) tile.next.prev = tile.prev;
  99. else tail = tile.prev;
  100. tile.prev = null;
  101. tile.next = head;
  102. head.prev = tile;
  103. head = tile;
  104. }
  105. tile.lock = 1;
  106. locks[key] = tile;
  107. return tile;
  108. }
  109. tile = {
  110. key: key,
  111. column: c.column,
  112. row: c.row,
  113. zoom: c.zoom,
  114. next: head,
  115. prev: null,
  116. lock: 1
  117. };
  118. load.call(null, tile, projection);
  119. locks[key] = map[key] = tile;
  120. if (head) head.prev = tile;
  121. else tail = tile;
  122. head = tile;
  123. n++;
  124. return tile;
  125. };
  126. cache.unload = function(key) {
  127. if (!(key in locks)) return false;
  128. var tile = locks[key];
  129. tile.lock = 0;
  130. delete locks[key];
  131. if (tile.request && tile.request.abort(false)) remove(tile);
  132. return tile;
  133. };
  134. cache.locks = function() {
  135. return locks;
  136. };
  137. cache.size = function(x) {
  138. if (!arguments.length) return size;
  139. size = x;
  140. flush();
  141. return cache;
  142. };
  143. cache.flush = function() {
  144. flush();
  145. return cache;
  146. };
  147. cache.clear = function() {
  148. for (var key in map) {
  149. var tile = map[key];
  150. if (tile.request) tile.request.abort(false);
  151. if (unload) unload(map[key]);
  152. if (tile.lock) {
  153. tile.lock = 0;
  154. tile.element.parentNode.removeChild(tile.element);
  155. }
  156. }
  157. locks = {};
  158. map = {};
  159. head = tail = null;
  160. n = 0;
  161. return cache;
  162. };
  163. return cache;
  164. };
  165. po.url = function(template) {
  166. var hosts = [],
  167. repeat = true;
  168. function format(c) {
  169. var max = c.zoom < 0 ? 1 : 1 << c.zoom,
  170. column = c.column;
  171. if (repeat) {
  172. column = c.column % max;
  173. if (column < 0) column += max;
  174. } else if ((column < 0) || (column >= max)) {
  175. return null;
  176. }
  177. return template.replace(/{(.)}/g, function(s, v) {
  178. switch (v) {
  179. case "S": return hosts[(Math.abs(c.zoom) + c.row + column) % hosts.length];
  180. case "Z": return c.zoom;
  181. case "X": return column;
  182. case "Y": return c.row;
  183. case "B": {
  184. var nw = po.map.coordinateLocation({row: c.row, column: column, zoom: c.zoom}),
  185. se = po.map.coordinateLocation({row: c.row + 1, column: column + 1, zoom: c.zoom}),
  186. pn = Math.ceil(Math.log(c.zoom) / Math.LN2);
  187. return se.lat.toFixed(pn)
  188. + "," + nw.lon.toFixed(pn)
  189. + "," + nw.lat.toFixed(pn)
  190. + "," + se.lon.toFixed(pn);
  191. }
  192. }
  193. return v;
  194. });
  195. }
  196. format.template = function(x) {
  197. if (!arguments.length) return template;
  198. template = x;
  199. return format;
  200. };
  201. format.hosts = function(x) {
  202. if (!arguments.length) return hosts;
  203. hosts = x;
  204. return format;
  205. };
  206. format.repeat = function(x) {
  207. if (!arguments.length) return repeat;
  208. repeat = x;
  209. return format;
  210. };
  211. return format;
  212. };
  213. po.dispatch = function(that) {
  214. var types = {};
  215. that.on = function(type, handler) {
  216. var listeners = types[type] || (types[type] = []);
  217. for (var i = 0; i < listeners.length; i++) {
  218. if (listeners[i].handler == handler) return that; // already registered
  219. }
  220. listeners.push({handler: handler, on: true});
  221. return that;
  222. };
  223. that.off = function(type, handler) {
  224. var listeners = types[type];
  225. if (listeners) for (var i = 0; i < listeners.length; i++) {
  226. var l = listeners[i];
  227. if (l.handler == handler) {
  228. l.on = false;
  229. listeners.splice(i, 1);
  230. break;
  231. }
  232. }
  233. return that;
  234. };
  235. return function(event) {
  236. var listeners = types[event.type];
  237. if (!listeners) return;
  238. listeners = listeners.slice(); // defensive copy
  239. for (var i = 0; i < listeners.length; i++) {
  240. var l = listeners[i];
  241. if (l.on) l.handler.call(that, event);
  242. }
  243. };
  244. };
  245. po.queue = (function() {
  246. var queued = [], active = 0, size = 6;
  247. function process() {
  248. if ((active >= size) || !queued.length) return;
  249. active++;
  250. queued.pop()();
  251. }
  252. function dequeue(send) {
  253. for (var i = 0; i < queued.length; i++) {
  254. if (queued[i] == send) {
  255. queued.splice(i, 1);
  256. return true;
  257. }
  258. }
  259. return false;
  260. }
  261. function request(url, callback, mimeType) {
  262. var req;
  263. function send() {
  264. req = new XMLHttpRequest();
  265. if (mimeType && req.overrideMimeType) {
  266. req.overrideMimeType(mimeType);
  267. }
  268. req.open("GET", url, true);
  269. req.onreadystatechange = function(e) {
  270. if (req.readyState == 4) {
  271. active--;
  272. if (req.status < 300) callback(req);
  273. process();
  274. }
  275. };
  276. req.send(null);
  277. }
  278. function abort(hard) {
  279. if (dequeue(send)) return true;
  280. if (hard && req) { req.abort(); return true; }
  281. return false;
  282. }
  283. queued.push(send);
  284. process();
  285. return {abort: abort};
  286. }
  287. function text(url, callback, mimeType) {
  288. return request(url, function(req) {
  289. if (req.responseText) callback(req.responseText);
  290. }, mimeType);
  291. }
  292. /*
  293. * We the override MIME type here so that you can load local files; some
  294. * browsers don't assign a proper MIME type for local files.
  295. */
  296. function json(url, callback) {
  297. return request(url, function(req) {
  298. if (req.responseText) callback(JSON.parse(req.responseText));
  299. }, "application/json");
  300. }
  301. function xml(url, callback) {
  302. return request(url, function(req) {
  303. if (req.responseXML) callback(req.responseXML);
  304. }, "application/xml");
  305. }
  306. function image(image, src, callback) {
  307. var img;
  308. function send() {
  309. img = document.createElement("img");
  310. img.onerror = function() {
  311. active--;
  312. process();
  313. };
  314. img.onload = function() {
  315. active--;
  316. callback(img);
  317. process();
  318. };
  319. img.src = src;
  320. image.setAttributeNS(po.ns.xlink, "href", src);
  321. }
  322. function abort(hard) {
  323. if (dequeue(send)) return true;
  324. if (hard && img) { img.src = "about:"; return true; } // cancels request
  325. return false;
  326. }
  327. queued.push(send);
  328. process();
  329. return {abort: abort};
  330. }
  331. return {text: text, xml: xml, json: json, image: image};
  332. })();
  333. po.map = function() {
  334. var map = {},
  335. container,
  336. size,
  337. sizeActual = zero,
  338. sizeRadius = zero, // sizeActual / 2
  339. tileSize = {x: 256, y: 256},
  340. center = {lat: 37.76487, lon: -122.41948},
  341. zoom = 12,
  342. zoomFraction = 0,
  343. zoomFactor = 1, // Math.pow(2, zoomFraction)
  344. zoomRange = [1, 18],
  345. angle = 0,
  346. angleCos = 1, // Math.cos(angle)
  347. angleSin = 0, // Math.sin(angle)
  348. angleCosi = 1, // Math.cos(-angle)
  349. angleSini = 0, // Math.sin(-angle)
  350. ymin = -180, // lat2y(centerRange[0].lat)
  351. ymax = 180; // lat2y(centerRange[1].lat)
  352. var centerRange = [
  353. {lat: y2lat(ymin), lon: -Infinity},
  354. {lat: y2lat(ymax), lon: Infinity}
  355. ];
  356. map.locationCoordinate = function(l) {
  357. var c = po.map.locationCoordinate(l),
  358. k = Math.pow(2, zoom);
  359. c.column *= k;
  360. c.row *= k;
  361. c.zoom += zoom;
  362. return c;
  363. };
  364. map.coordinateLocation = po.map.coordinateLocation;
  365. map.coordinatePoint = function(tileCenter, c) {
  366. var kc = Math.pow(2, zoom - c.zoom),
  367. kt = Math.pow(2, zoom - tileCenter.zoom),
  368. dx = (c.column * kc - tileCenter.column * kt) * tileSize.x * zoomFactor,
  369. dy = (c.row * kc - tileCenter.row * kt) * tileSize.y * zoomFactor;
  370. return {
  371. x: sizeRadius.x + angleCos * dx - angleSin * dy,
  372. y: sizeRadius.y + angleSin * dx + angleCos * dy
  373. };
  374. };
  375. map.pointCoordinate = function(tileCenter, p) {
  376. var kt = Math.pow(2, zoom - tileCenter.zoom),
  377. dx = (p.x - sizeRadius.x) / zoomFactor,
  378. dy = (p.y - sizeRadius.y) / zoomFactor;
  379. return {
  380. column: tileCenter.column * kt + (angleCosi * dx - angleSini * dy) / tileSize.x,
  381. row: tileCenter.row * kt + (angleSini * dx + angleCosi * dy) / tileSize.y,
  382. zoom: zoom
  383. };
  384. };
  385. map.locationPoint = function(l) {
  386. var k = Math.pow(2, zoom + zoomFraction - 3) / 45,
  387. dx = (l.lon - center.lon) * k * tileSize.x,
  388. dy = (lat2y(center.lat) - lat2y(l.lat)) * k * tileSize.y;
  389. return {
  390. x: sizeRadius.x + angleCos * dx - angleSin * dy,
  391. y: sizeRadius.y + angleSin * dx + angleCos * dy
  392. };
  393. };
  394. map.pointLocation = function(p) {
  395. var k = 45 / Math.pow(2, zoom + zoomFraction - 3),
  396. dx = (p.x - sizeRadius.x) * k,
  397. dy = (p.y - sizeRadius.y) * k;
  398. return {
  399. lon: center.lon + (angleCosi * dx - angleSini * dy) / tileSize.x,
  400. lat: y2lat(lat2y(center.lat) - (angleSini * dx + angleCosi * dy) / tileSize.y)
  401. };
  402. };
  403. function rezoom() {
  404. if (zoomRange) {
  405. if (zoom < zoomRange[0]) zoom = zoomRange[0];
  406. else if (zoom > zoomRange[1]) zoom = zoomRange[1];
  407. }
  408. zoomFraction = zoom - (zoom = Math.round(zoom));
  409. zoomFactor = Math.pow(2, zoomFraction);
  410. }
  411. function recenter() {
  412. if (!centerRange) return;
  413. var k = 45 / Math.pow(2, zoom + zoomFraction - 3);
  414. // constrain latitude
  415. var y = Math.max(Math.abs(angleSin * sizeRadius.x + angleCos * sizeRadius.y),
  416. Math.abs(angleSini * sizeRadius.x + angleCosi * sizeRadius.y)),
  417. lat0 = y2lat(ymin - y * k / tileSize.y),
  418. lat1 = y2lat(ymax + y * k / tileSize.y);
  419. center.lat = Math.max(lat0, Math.min(lat1, center.lat));
  420. // constrain longitude
  421. var x = Math.max(Math.abs(angleSin * sizeRadius.y + angleCos * sizeRadius.x),
  422. Math.abs(angleSini * sizeRadius.y + angleCosi * sizeRadius.x)),
  423. lon0 = centerRange[0].lon - x * k / tileSize.x,
  424. lon1 = centerRange[1].lon + x * k / tileSize.x;
  425. center.lon = Math.max(lon0, Math.min(lon1, center.lon));
  426. }
  427. // a place to capture mouse events if no tiles exist
  428. var rect = po.svg("rect");
  429. rect.setAttribute("visibility", "hidden");
  430. rect.setAttribute("pointer-events", "all");
  431. map.container = function(x) {
  432. if (!arguments.length) return container;
  433. container = x;
  434. container.setAttribute("class", "map");
  435. container.appendChild(rect);
  436. return map.resize(); // infer size
  437. };
  438. map.focusableParent = function() {
  439. for (var p = container; p; p = p.parentNode) {
  440. if (p.tabIndex >= 0) return p;
  441. }
  442. return window;
  443. };
  444. map.mouse = function(e) {
  445. var point = (container.ownerSVGElement || container).createSVGPoint();
  446. if ((bug44083 < 0) && (window.scrollX || window.scrollY)) {
  447. var svg = document.body.appendChild(po.svg("svg"));
  448. svg.style.position = "absolute";
  449. svg.style.top = svg.style.left = "0px";
  450. var ctm = svg.getScreenCTM();
  451. bug44083 = !(ctm.f || ctm.e);
  452. document.body.removeChild(svg);
  453. }
  454. if (bug44083) {
  455. point.x = e.pageX;
  456. point.y = e.pageY;
  457. } else {
  458. point.x = e.clientX;
  459. point.y = e.clientY;
  460. }
  461. return point.matrixTransform(container.getScreenCTM().inverse());
  462. };
  463. map.size = function(x) {
  464. if (!arguments.length) return sizeActual;
  465. size = x;
  466. return map.resize(); // size tiles
  467. };
  468. map.resize = function() {
  469. if (!size) {
  470. rect.setAttribute("width", "100%");
  471. rect.setAttribute("height", "100%");
  472. b = rect.getBBox();
  473. sizeActual = {x: b.width, y: b.height};
  474. resizer.add(map);
  475. } else {
  476. sizeActual = size;
  477. resizer.remove(map);
  478. }
  479. rect.setAttribute("width", sizeActual.x);
  480. rect.setAttribute("height", sizeActual.y);
  481. sizeRadius = {x: sizeActual.x / 2, y: sizeActual.y / 2};
  482. recenter();
  483. map.dispatch({type: "resize"});
  484. return map;
  485. };
  486. map.tileSize = function(x) {
  487. if (!arguments.length) return tileSize;
  488. tileSize = x;
  489. map.dispatch({type: "move"});
  490. return map;
  491. };
  492. map.center = function(x) {
  493. if (!arguments.length) return center;
  494. center = x;
  495. recenter();
  496. map.dispatch({type: "move"});
  497. return map;
  498. };
  499. map.panBy = function(x) {
  500. var k = 45 / Math.pow(2, zoom + zoomFraction - 3),
  501. dx = x.x * k,
  502. dy = x.y * k;
  503. return map.center({
  504. lon: center.lon + (angleSini * dy - angleCosi * dx) / tileSize.x,
  505. lat: y2lat(lat2y(center.lat) + (angleSini * dx + angleCosi * dy) / tileSize.y)
  506. });
  507. };
  508. map.centerRange = function(x) {
  509. if (!arguments.length) return centerRange;
  510. centerRange = x;
  511. if (centerRange) {
  512. ymin = centerRange[0].lat > -90 ? lat2y(centerRange[0].lat) : -Infinity;
  513. ymax = centerRange[0].lat < 90 ? lat2y(centerRange[1].lat) : Infinity;
  514. } else {
  515. ymin = -Infinity;
  516. ymax = Infinity;
  517. }
  518. recenter();
  519. map.dispatch({type: "move"});
  520. return map;
  521. };
  522. map.zoom = function(x) {
  523. if (!arguments.length) return zoom + zoomFraction;
  524. zoom = x;
  525. rezoom();
  526. return map.center(center);
  527. };
  528. map.zoomBy = function(z, x0, l) {
  529. if (arguments.length < 2) return map.zoom(zoom + zoomFraction + z);
  530. // compute the location of x0
  531. if (arguments.length < 3) l = map.pointLocation(x0);
  532. // update the zoom level
  533. zoom = zoom + zoomFraction + z;
  534. rezoom();
  535. // compute the new point of the location
  536. var x1 = map.locationPoint(l);
  537. return map.panBy({x: x0.x - x1.x, y: x0.y - x1.y});
  538. };
  539. map.zoomRange = function(x) {
  540. if (!arguments.length) return zoomRange;
  541. zoomRange = x;
  542. return map.zoom(zoom + zoomFraction);
  543. };
  544. map.extent = function(x) {
  545. if (!arguments.length) return [
  546. map.pointLocation({x: 0, y: sizeActual.y}),
  547. map.pointLocation({x: sizeActual.x, y: 0})
  548. ];
  549. // compute the extent in points, scale factor, and center
  550. var bl = map.locationPoint(x[0]),
  551. tr = map.locationPoint(x[1]),
  552. k = Math.max((tr.x - bl.x) / sizeActual.x, (bl.y - tr.y) / sizeActual.y),
  553. l = map.pointLocation({x: (bl.x + tr.x) / 2, y: (bl.y + tr.y) / 2});
  554. // update the zoom level
  555. zoom = zoom + zoomFraction - Math.log(k) / Math.LN2;
  556. rezoom();
  557. // set the new center
  558. return map.center(l);
  559. };
  560. map.angle = function(x) {
  561. if (!arguments.length) return angle;
  562. angle = x;
  563. angleCos = Math.cos(angle);
  564. angleSin = Math.sin(angle);
  565. angleCosi = Math.cos(-angle);
  566. angleSini = Math.sin(-angle);
  567. recenter();
  568. map.dispatch({type: "move"});
  569. return map;
  570. };
  571. map.add = function(x) {
  572. x.map(map);
  573. return map;
  574. };
  575. map.remove = function(x) {
  576. x.map(null);
  577. return map;
  578. };
  579. map.dispatch = po.dispatch(map);
  580. return map;
  581. };
  582. function resizer(e) {
  583. for (var i = 0; i < resizer.maps.length; i++) {
  584. resizer.maps[i].resize();
  585. }
  586. }
  587. resizer.maps = [];
  588. resizer.add = function(map) {
  589. for (var i = 0; i < resizer.maps.length; i++) {
  590. if (resizer.maps[i] == map) return;
  591. }
  592. resizer.maps.push(map);
  593. };
  594. resizer.remove = function(map) {
  595. for (var i = 0; i < resizer.maps.length; i++) {
  596. if (resizer.maps[i] == map) {
  597. resizer.maps.splice(i, 1);
  598. return;
  599. }
  600. }
  601. };
  602. // Note: assumes single window (no frames, iframes, etc.)!
  603. if (window.addEventListener) {
  604. window.addEventListener("resize", resizer, false);
  605. window.addEventListener("load", resizer, false);
  606. }
  607. // See http://wiki.openstreetmap.org/wiki/Mercator
  608. function y2lat(y) {
  609. return 360 / Math.PI * Math.atan(Math.exp(y * Math.PI / 180)) - 90;
  610. }
  611. function lat2y(lat) {
  612. return 180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360));
  613. }
  614. po.map.locationCoordinate = function(l) {
  615. var k = 1 / 360;
  616. return {
  617. column: (l.lon + 180) * k,
  618. row: (180 - lat2y(l.lat)) * k,
  619. zoom: 0
  620. };
  621. };
  622. po.map.coordinateLocation = function(c) {
  623. var k = 45 / Math.pow(2, c.zoom - 3);
  624. return {
  625. lon: k * c.column - 180,
  626. lat: y2lat(180 - k * c.row)
  627. };
  628. };
  629. // https://bugs.webkit.org/show_bug.cgi?id=44083
  630. var bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0;
  631. po.layer = function(load, unload) {
  632. var layer = {},
  633. cache = layer.cache = po.cache(load, unload).size(512),
  634. tile = true,
  635. visible = true,
  636. zoom,
  637. id,
  638. map,
  639. container = po.svg("g"),
  640. transform,
  641. levelZoom,
  642. levels = {};
  643. container.setAttribute("class", "layer");
  644. for (var i = -4; i <= -1; i++) levels[i] = container.appendChild(po.svg("g"));
  645. for (var i = 2; i >= 1; i--) levels[i] = container.appendChild(po.svg("g"));
  646. levels[0] = container.appendChild(po.svg("g"));
  647. function zoomIn(z) {
  648. var end = levels[0].nextSibling;
  649. for (; levelZoom < z; levelZoom++) {
  650. // -4, -3, -2, -1, +2, +1, =0 // current order
  651. // -3, -2, -1, +2, +1, =0, -4 // insertBefore(-4, end)
  652. // -3, -2, -1, +1, =0, -4, +2 // insertBefore(+2, end)
  653. // -3, -2, -1, =0, -4, +2, +1 // insertBefore(+1, end)
  654. // -4, -3, -2, -1, +2, +1, =0 // relabel
  655. container.insertBefore(levels[-4], end);
  656. container.insertBefore(levels[2], end);
  657. container.insertBefore(levels[1], end);
  658. var t = levels[-4];
  659. for (var dz = -4; dz < 2;) levels[dz] = levels[++dz];
  660. levels[dz] = t;
  661. }
  662. }
  663. function zoomOut(z) {
  664. var end = levels[0].nextSibling;
  665. for (; levelZoom > z; levelZoom--) {
  666. // -4, -3, -2, -1, +2, +1, =0 // current order
  667. // -4, -3, -2, +2, +1, =0, -1 // insertBefore(-1, end)
  668. // +2, -4, -3, -2, +1, =0, -1 // insertBefore(+2, -4)
  669. // -4, -3, -2, -1, +2, +1, =0 // relabel
  670. container.insertBefore(levels[-1], end);
  671. container.insertBefore(levels[2], levels[-4]);
  672. var t = levels[2];
  673. for (var dz = 2; dz > -4;) levels[dz] = levels[--dz];
  674. levels[dz] = t;
  675. }
  676. }
  677. function move() {
  678. var map = layer.map(), // in case the layer is removed
  679. mapZoom = map.zoom(),
  680. mapZoomFraction = mapZoom - (mapZoom = Math.round(mapZoom)),
  681. mapSize = map.size(),
  682. mapAngle = map.angle(),
  683. tileSize = map.tileSize(),
  684. tileCenter = map.locationCoordinate(map.center());
  685. // set the layer zoom levels
  686. if (levelZoom != mapZoom) {
  687. if (levelZoom < mapZoom) zoomIn(mapZoom);
  688. else if (levelZoom > mapZoom) zoomOut(mapZoom);
  689. else levelZoom = mapZoom;
  690. for (var z = -4; z <= 2; z++) {
  691. var l = levels[z];
  692. l.setAttribute("class", "zoom" + (z < 0 ? "" : "+") + z + " zoom" + (mapZoom + z));
  693. l.setAttribute("transform", "scale(" + Math.pow(2, -z) + ")");
  694. }
  695. }
  696. // set the layer transform
  697. container.setAttribute("transform",
  698. "translate(" + (mapSize.x / 2) + "," + (mapSize.y / 2) + ")"
  699. + (mapAngle ? "rotate(" + mapAngle / Math.PI * 180 + ")" : "")
  700. + (mapZoomFraction ? "scale(" + Math.pow(2, mapZoomFraction) + ")" : "")
  701. + (transform ? transform.zoomFraction(mapZoomFraction) : ""));
  702. // get the coordinates of the four corners
  703. var c0 = map.pointCoordinate(tileCenter, zero),
  704. c1 = map.pointCoordinate(tileCenter, {x: mapSize.x, y: 0}),
  705. c2 = map.pointCoordinate(tileCenter, mapSize),
  706. c3 = map.pointCoordinate(tileCenter, {x: 0, y: mapSize.y});
  707. // round to pixel boundary to avoid anti-aliasing artifacts
  708. if (!mapZoomFraction && !mapAngle && !transform) {
  709. tileCenter.column = (Math.round(tileSize.x * tileCenter.column) + (mapSize.x & 1) / 2) / tileSize.x;
  710. tileCenter.row = (Math.round(tileSize.y * tileCenter.row) + (mapSize.y & 1) / 2) / tileSize.y;
  711. }
  712. // layer-specific coordinate transform
  713. if (transform) {
  714. c0 = transform.unapply(c0);
  715. c1 = transform.unapply(c1);
  716. c2 = transform.unapply(c2);
  717. c3 = transform.unapply(c3);
  718. tileCenter = transform.unapply(tileCenter);
  719. }
  720. // layer-specific zoom transform
  721. var tileLevel = zoom ? zoom(c0.zoom) - c0.zoom : 0;
  722. if (tileLevel) {
  723. var k = Math.pow(2, tileLevel);
  724. c0.column *= k; c0.row *= k;
  725. c1.column *= k; c1.row *= k;
  726. c2.column *= k; c2.row *= k;
  727. c3.column *= k; c3.row *= k;
  728. c0.zoom = c1.zoom = c2.zoom = c3.zoom += tileLevel;
  729. }
  730. // tile-specific projection
  731. function projection(c) {
  732. var zoom = c.zoom,
  733. max = zoom < 0 ? 1 : 1 << zoom,
  734. column = c.column % max,
  735. row = c.row;
  736. if (column < 0) column += max;
  737. return {
  738. locationPoint: function(l) {
  739. var c = po.map.locationCoordinate(l),
  740. k = Math.pow(2, zoom - c.zoom);
  741. return {
  742. x: tileSize.x * (k * c.column - column),
  743. y: tileSize.y * (k * c.row - row)
  744. };
  745. }
  746. };
  747. }
  748. // record which tiles are visible
  749. var oldLocks = cache.locks(), newLocks = {};
  750. // reset the proxy counts
  751. for (var key in oldLocks) {
  752. oldLocks[key].proxyCount = 0;
  753. }
  754. // load the tiles!
  755. if (visible && tileLevel > -5 && tileLevel < 3) {
  756. var ymax = c0.zoom < 0 ? 1 : 1 << c0.zoom;
  757. if (tile) {
  758. scanTriangle(c0, c1, c2, 0, ymax, scanLine);
  759. scanTriangle(c2, c3, c0, 0, ymax, scanLine);
  760. } else {
  761. var x = Math.floor((c0.column + c2.column) / 2),
  762. y = Math.max(0, Math.min(ymax - 1, Math.floor((c1.row + c3.row) / 2))),
  763. z = Math.min(4, c0.zoom);
  764. x = x >> z << z;
  765. y = y >> z << z;
  766. scanLine(x, x + 1, y);
  767. }
  768. }
  769. // scan-line conversion
  770. function scanLine(x0, x1, y) {
  771. var z = c0.zoom,
  772. z0 = 2 - tileLevel,
  773. z1 = 4 + tileLevel;
  774. for (var x = x0; x < x1; x++) {
  775. var t = cache.load({column: x, row: y, zoom: z}, projection);
  776. if (!t.ready && !(t.key in newLocks)) {
  777. t.proxyRefs = {};
  778. var c, full, proxy;
  779. // downsample high-resolution tiles
  780. for (var dz = 1; dz <= z0; dz++) {
  781. full = true;
  782. for (var dy = 0, k = 1 << dz; dy <= k; dy++) {
  783. for (var dx = 0; dx <= k; dx++) {
  784. proxy = cache.peek(c = {
  785. column: (x << dz) + dx,
  786. row: (y << dz) + dy,
  787. zoom: z + dz
  788. });
  789. if (proxy && proxy.ready) {
  790. newLocks[proxy.key] = cache.load(c);
  791. proxy.proxyCount++;
  792. t.proxyRefs[proxy.key] = proxy;
  793. } else {
  794. full = false;
  795. }
  796. }
  797. }
  798. if (full) break;
  799. }
  800. // upsample low-resolution tiles
  801. if (!full) {
  802. for (var dz = 1; dz <= z1; dz++) {
  803. proxy = cache.peek(c = {
  804. column: x >> dz,
  805. row: y >> dz,
  806. zoom: z - dz
  807. });
  808. if (proxy && proxy.ready) {
  809. newLocks[proxy.key] = cache.load(c);
  810. proxy.proxyCount++;
  811. t.proxyRefs[proxy.key] = proxy;
  812. break;
  813. }
  814. }
  815. }
  816. }
  817. newLocks[t.key] = t;
  818. }
  819. }
  820. // position tiles
  821. for (var key in newLocks) {
  822. var t = newLocks[key],
  823. k = Math.pow(2, t.level = t.zoom - tileCenter.zoom);
  824. t.element.setAttribute("transform", "translate("
  825. + (t.x = tileSize.x * (t.column - tileCenter.column * k)) + ","
  826. + (t.y = tileSize.y * (t.row - tileCenter.row * k)) + ")");
  827. }
  828. // remove tiles that are no longer visible
  829. for (var key in oldLocks) {
  830. if (!(key in newLocks)) {
  831. var t = cache.unload(key);
  832. t.element.parentNode.removeChild(t.element);
  833. delete t.proxyRefs;
  834. }
  835. }
  836. // append tiles that are now visible
  837. for (var key in newLocks) {
  838. var t = newLocks[key];
  839. if (t.element.parentNode != levels[t.level]) {
  840. levels[t.level].appendChild(t.element);
  841. if (layer.show) layer.show(t);
  842. }
  843. }
  844. // flush the cache, clearing no-longer-needed tiles
  845. cache.flush();
  846. // dispatch the move event
  847. layer.dispatch({type: "move"});
  848. }
  849. // remove proxy tiles when tiles load
  850. function cleanup(e) {
  851. if (e.tile.proxyRefs) {
  852. for (var proxyKey in e.tile.proxyRefs) {
  853. var proxyTile = e.tile.proxyRefs[proxyKey];
  854. if ((--proxyTile.proxyCount <= 0) && cache.unload(proxyKey)) {
  855. proxyTile.element.parentNode.removeChild(proxyTile.element);
  856. }
  857. }
  858. delete e.tile.proxyRefs;
  859. }
  860. }
  861. layer.map = function(x) {
  862. if (!arguments.length) return map;
  863. if (map) {
  864. if (map == x) {
  865. container.parentNode.appendChild(container); // move to end
  866. return layer;
  867. }
  868. map.off("move", move).off("resize", move);
  869. container.parentNode.removeChild(container);
  870. }
  871. map = x;
  872. if (map) {
  873. map.container().appendChild(container);
  874. if (layer.init) layer.init(container);
  875. map.on("move", move).on("resize", move);
  876. move();
  877. }
  878. return layer;
  879. };
  880. layer.container = function() {
  881. return container;
  882. };
  883. layer.levels = function() {
  884. return levels;
  885. };
  886. layer.id = function(x) {
  887. if (!arguments.length) return id;
  888. id = x;
  889. container.setAttribute("id", x);
  890. return layer;
  891. };
  892. layer.visible = function(x) {
  893. if (!arguments.length) return visible;
  894. if (visible = x) container.removeAttribute("visibility")
  895. else container.setAttribute("visibility", "hidden");
  896. if (map) move();
  897. return layer;
  898. };
  899. layer.transform = function(x) {
  900. if (!arguments.length) return transform;
  901. transform = x;
  902. if (map) move();
  903. return layer;
  904. };
  905. layer.zoom = function(x) {
  906. if (!arguments.length) return zoom;
  907. zoom = typeof x == "function" || x == null ? x : function() { return x; };
  908. if (map) move();
  909. return layer;
  910. };
  911. layer.tile = function(x) {
  912. if (!arguments.length) return tile;
  913. tile = x;
  914. if (map) move();
  915. return layer;
  916. };
  917. layer.reload = function() {
  918. cache.clear();
  919. if (map) move();
  920. return layer;
  921. };
  922. layer.dispatch = po.dispatch(layer);
  923. layer.on("load", cleanup);
  924. return layer;
  925. };
  926. // scan-line conversion
  927. function edge(a, b) {
  928. if (a.row > b.row) { var t = a; a = b; b = t; }
  929. return {
  930. x0: a.column,
  931. y0: a.row,
  932. x1: b.column,
  933. y1: b.row,
  934. dx: b.column - a.column,
  935. dy: b.row - a.row
  936. };
  937. }
  938. // scan-line conversion
  939. function scanSpans(e0, e1, ymin, ymax, scanLine) {
  940. var y0 = Math.max(ymin, Math.floor(e1.y0)),
  941. y1 = Math.min(ymax, Math.ceil(e1.y1));
  942. // sort edges by x-coordinate
  943. if ((e0.x0 == e1.x0 && e0.y0 == e1.y0)
  944. ? (e0.x0 + e1.dy / e0.dy * e0.dx < e1.x1)
  945. : (e0.x1 - e1.dy / e0.dy * e0.dx < e1.x0)) {
  946. var t = e0; e0 = e1; e1 = t;
  947. }
  948. // scan lines!
  949. var m0 = e0.dx / e0.dy,
  950. m1 = e1.dx / e1.dy,
  951. d0 = e0.dx > 0, // use y + 1 to compute x0
  952. d1 = e1.dx < 0; // use y + 1 to compute x1
  953. for (var y = y0; y < y1; y++) {
  954. var x0 = m0 * Math.max(0, Math.min(e0.dy, y + d0 - e0.y0)) + e0.x0,
  955. x1 = m1 * Math.max(0, Math.min(e1.dy, y + d1 - e1.y0)) + e1.x0;
  956. scanLine(Math.floor(x1), Math.ceil(x0), y);
  957. }
  958. }
  959. // scan-line conversion
  960. function scanTriangle(a, b, c, ymin, ymax, scanLine) {
  961. var ab = edge(a, b),
  962. bc = edge(b, c),
  963. ca = edge(c, a);
  964. // sort edges by y-length
  965. if (ab.dy > bc.dy) { var t = ab; ab = bc; bc = t; }
  966. if (ab.dy > ca.dy) { var t = ab; ab = ca; ca = t; }
  967. if (bc.dy > ca.dy) { var t = bc; bc = ca; ca = t; }
  968. // scan span! scan span!
  969. if (ab.dy) scanSpans(ca, ab, ymin, ymax, scanLine);
  970. if (bc.dy) scanSpans(ca, bc, ymin, ymax, scanLine);
  971. }
  972. po.image = function() {
  973. var image = po.layer(load, unload),
  974. url;
  975. function load(tile) {
  976. var element = tile.element = po.svg("image"), size = image.map().tileSize();
  977. element.setAttribute("preserveAspectRatio", "none");
  978. element.setAttribute("width", size.x);
  979. element.setAttribute("height", size.y);
  980. if (typeof url == "function") {
  981. element.setAttribute("opacity", 0);
  982. var tileUrl = url(tile);
  983. if (tileUrl != null) {
  984. tile.request = po.queue.image(element, tileUrl, function(img) {
  985. delete tile.request;
  986. tile.ready = true;
  987. tile.img = img;
  988. element.removeAttribute("opacity");
  989. image.dispatch({type: "load", tile: tile});
  990. });
  991. } else {
  992. tile.ready = true;
  993. image.dispatch({type: "load", tile: tile});
  994. }
  995. } else {
  996. tile.ready = true;
  997. if (url != null) element.setAttributeNS(po.ns.xlink, "href", url);
  998. image.dispatch({type: "load", tile: tile});
  999. }
  1000. }
  1001. function unload(tile) {
  1002. if (tile.request) tile.request.abort(true);
  1003. }
  1004. image.url = function(x) {
  1005. if (!arguments.length) return url;
  1006. url = typeof x == "string" && /{.}/.test(x) ? po.url(x) : x;
  1007. return image.reload();
  1008. };
  1009. return image;
  1010. };
  1011. po.geoJson = function(fetch) {
  1012. var geoJson = po.layer(load, unload),
  1013. container = geoJson.container(),
  1014. url,
  1015. clip = true,
  1016. clipId = "org.polymaps." + po.id(),
  1017. clipHref = "url(#" + clipId + ")",
  1018. clipPath = container.insertBefore(po.svg("clipPath"), container.firstChild),
  1019. clipRect = clipPath.appendChild(po.svg("rect")),
  1020. scale = "auto",
  1021. zoom = null,
  1022. features;
  1023. container.setAttribute("fill-rule", "evenodd");
  1024. clipPath.setAttribute("id", clipId);
  1025. if (!arguments.length) fetch = po.queue.json;
  1026. function projection(proj) {
  1027. var l = {lat: 0, lon: 0};
  1028. return function(coordinates) {
  1029. l.lat = coordinates[1];
  1030. l.lon = coordinates[0];
  1031. var p = proj(l);
  1032. coordinates.x = p.x;
  1033. coordinates.y = p.y;
  1034. return p;
  1035. };
  1036. }
  1037. function geometry(o, proj) {
  1038. return o && o.type in types && types[o.type](o, proj);
  1039. }
  1040. var types = {
  1041. Point: function(o, proj) {
  1042. var p = proj(o.coordinates),
  1043. c = po.svg("circle");
  1044. c.setAttribute("r", 4.5);
  1045. c.setAttribute("transform", "translate(" + p.x + "," + p.y + ")");
  1046. return c;
  1047. },
  1048. MultiPoint: function(o, proj) {
  1049. var g = po.svg("g"),
  1050. c = o.coordinates,
  1051. p, // proj(c[i])
  1052. x, // svg:circle
  1053. i = -1,
  1054. n = c.length;
  1055. while (++i < n) {
  1056. x = g.appendChild(po.svg("circle"));
  1057. x.setAttribute("r", 4.5);
  1058. x.setAttribute("transform", "translate(" + (p = proj(c[i])).x + "," + p.y + ")");
  1059. }
  1060. return g;
  1061. },
  1062. LineString: function(o, proj) {
  1063. var x = po.svg("path"),
  1064. d = ["M"],
  1065. c = o.coordinates,
  1066. p, // proj(c[i])
  1067. i = -1,
  1068. n = c.length;
  1069. while (++i < n) d.push((p = proj(c[i])).x, ",", p.y, "L");
  1070. d.pop();
  1071. if (!d.length) return;
  1072. x.setAttribute("d", d.join(""));
  1073. return x;
  1074. },
  1075. MultiLineString: function(o, proj) {
  1076. var x = po.svg("path"),
  1077. d = [],
  1078. ci = o.coordinates,
  1079. cj, // ci[i]
  1080. i = -1,
  1081. j,
  1082. n = ci.length,
  1083. m;
  1084. while (++i < n) {
  1085. cj = ci[i];
  1086. j = -1;
  1087. m = cj.length;
  1088. d.push("M");
  1089. while (++j < m) d.push((p = proj(cj[j])).x, ",", p.y, "L");
  1090. d.pop();
  1091. }
  1092. if (!d.length) return;
  1093. x.setAttribute("d", d.join(""));
  1094. return x;
  1095. },
  1096. Polygon: function(o, proj) {
  1097. var x = po.svg("path"),
  1098. d = [],
  1099. ci = o.coordinates,
  1100. cj, // ci[i]
  1101. i = -1,
  1102. j,
  1103. n = ci.length,
  1104. m;
  1105. while (++i < n) {
  1106. cj = ci[i];
  1107. j = -1;
  1108. m = cj.length - 1;
  1109. d.push("M");
  1110. while (++j < m) d.push((p = proj(cj[j])).x, ",", p.y, "L");
  1111. d[d.length - 1] = "Z";
  1112. }
  1113. if (!d.length) return;
  1114. x.setAttribute("d", d.join(""));
  1115. return x;
  1116. },
  1117. MultiPolygon: function(o, proj) {
  1118. var x = po.svg("path"),
  1119. d = [],
  1120. ci = o.coordinates,
  1121. cj, // ci[i]
  1122. ck, // cj[j]
  1123. i = -1,
  1124. j,
  1125. k,
  1126. n = ci.length,
  1127. m,
  1128. l;
  1129. while (++i < n) {
  1130. cj = ci[i];
  1131. j = -1;
  1132. m = cj.length;
  1133. while (++j < m) {
  1134. ck = cj[j];
  1135. k = -1;
  1136. l = ck.length - 1;
  1137. d.push("M");
  1138. while (++k < l) d.push((p = proj(ck[k])).x, ",", p.y, "L");
  1139. d[d.length - 1] = "Z";
  1140. }
  1141. }
  1142. if (!d.length) return;
  1143. x.setAttribute("d", d.join(""));
  1144. return x;
  1145. },
  1146. GeometryCollection: function(o, proj) {
  1147. var g = po.svg("g"),
  1148. i = -1,
  1149. c = o.geometries,
  1150. n = c.length,
  1151. x;
  1152. while (++i < n) {
  1153. x = geometry(c[i], proj);
  1154. if (x) g.appendChild(x);
  1155. }
  1156. return g;
  1157. }
  1158. };
  1159. function rescale(o, e, k) {
  1160. return o.type in rescales && rescales[o.type](o, e, k);
  1161. }
  1162. var rescales = {
  1163. Point: function (o, e, k) {
  1164. var p = o.coordinates;
  1165. e.setAttribute("transform", "translate(" + p.x + "," + p.y + ")" + k);
  1166. },
  1167. MultiPoint: function (o, e, k) {
  1168. var c = o.coordinates,
  1169. i = -1,
  1170. n = p.length,
  1171. x = e.firstChild,
  1172. p;
  1173. while (++i < n) {
  1174. p = c[i];
  1175. x.setAttribute("transform", "translate(" + p.x + "," + p.y + ")" + k);
  1176. x = x.nextSibling;
  1177. }
  1178. }
  1179. };
  1180. function load(tile, proj) {
  1181. var g = tile.element = po.svg("g");
  1182. tile.features = [];
  1183. proj = projection(proj(tile).locationPoint);
  1184. function update(data) {
  1185. var updated = [];
  1186. /* Fetch the next batch of features, if so directed. */
  1187. if (data.next) tile.request = fetch(data.next.href, update);
  1188. /* Convert the GeoJSON to SVG. */
  1189. switch (data.type) {
  1190. case "FeatureCollection": {
  1191. for (var i = 0; i < data.features.length; i++) {
  1192. var feature = data.features[i],
  1193. element = geometry(feature.geometry, proj);
  1194. if (element) updated.push({element: g.appendChild(element), data: feature});
  1195. }
  1196. break;
  1197. }
  1198. case "Feature": {
  1199. var element = geometry(data.geometry, proj);
  1200. if (element) updated.push({element: g.appendChild(element), data: data});
  1201. break;
  1202. }
  1203. default: {
  1204. var element = geometry(data, proj);
  1205. if (element) updated.push({element: g.appendChild(element), data: {type: "Feature", geometry: data}});
  1206. break;
  1207. }
  1208. }
  1209. tile.ready = true;
  1210. updated.push.apply(tile.features, updated);
  1211. geoJson.dispatch({type: "load", tile: tile, features: updated});
  1212. }
  1213. if (url != null) {
  1214. tile.request = fetch(typeof url == "function" ? url(tile) : url, update);
  1215. } else {
  1216. update({type: "FeatureCollection", features: features || []});
  1217. }
  1218. }
  1219. function unload(tile) {
  1220. if (tile.request) tile.request.abort(true);
  1221. }
  1222. function move() {
  1223. var zoom = geoJson.map().zoom(),
  1224. tiles = geoJson.cache.locks(), // visible tiles
  1225. key, // key in locks
  1226. tile, // locks[key]
  1227. features, // tile.features
  1228. i, // current feature index
  1229. n, // current feature count, features.length
  1230. feature, // features[i]
  1231. k; // scale transform
  1232. if (scale == "fixed") {
  1233. for (key in tiles) {
  1234. if ((tile = tiles[key]).scale != zoom) {
  1235. k = "scale(" + Math.pow(2, tile.zoom - zoom) + ")";
  1236. i = -1;
  1237. n = (features = tile.features).length;
  1238. while (++i < n) rescale((feature = features[i]).data.geometry, feature.element, k);
  1239. tile.scale = zoom;
  1240. }
  1241. }
  1242. } else {
  1243. for (key in tiles) {
  1244. i = -1;
  1245. n = (features = (tile = tiles[key]).features).length;
  1246. while (++i < n) rescale((feature = features[i]).data.geometry, feature.element, "");
  1247. delete tile.scale;
  1248. }
  1249. }
  1250. }
  1251. geoJson.url = function(x) {
  1252. if (!arguments.length) return url;
  1253. url = typeof x == "string" && /{.}/.test(x) ? po.url(x) : x;
  1254. if (url != null) features = null;
  1255. if (typeof url == "string") geoJson.tile(false);
  1256. return geoJson.reload();
  1257. };
  1258. geoJson.features = function(x) {
  1259. if (!arguments.length) return features;
  1260. if (features = x) {
  1261. url = null;
  1262. geoJson.tile(false);
  1263. }
  1264. return geoJson.reload();
  1265. };
  1266. geoJson.clip = function(x) {
  1267. if (!arguments.length) return clip;
  1268. if (clip) container.removeChild(clipPath);
  1269. if (clip = x) container.insertBefore(clipPath, container.firstChild);
  1270. var locks = geoJson.cache.locks();
  1271. for (var key in locks) {
  1272. if (clip) locks[key].element.setAttribute("clip-path", clipHref);
  1273. else locks[key].element.removeAttribute("clip-path");
  1274. }
  1275. return geoJson;
  1276. };
  1277. var __tile__ = geoJson.tile;
  1278. geoJson.tile = function(x) {
  1279. if (arguments.length && !x) geoJson.clip(x);
  1280. return __tile__.apply(geoJson, arguments);
  1281. };
  1282. var __map__ = geoJson.map;
  1283. geoJson.map = function(x) {
  1284. if (x && clipRect) {
  1285. var size = x.tileSize();
  1286. clipRect.setAttribute("width", size.x);
  1287. clipRect.setAttribute("height", size.y);
  1288. }
  1289. return __map__.apply(geoJson, arguments);
  1290. };
  1291. geoJson.scale = function(x) {
  1292. if (!arguments.length) return scale;
  1293. if (scale = x) geoJson.on("move", move);
  1294. else geoJson.off("move", move);
  1295. if (geoJson.map()) move();
  1296. return geoJson;
  1297. };
  1298. geoJson.show = function(tile) {
  1299. if (clip) tile.element.setAttribute("clip-path", clipHref);
  1300. else tile.element.removeAttribute("clip-path");
  1301. geoJson.dispatch({type: "show", tile: tile, features: tile.features});
  1302. return geoJson;
  1303. };
  1304. geoJson.reshow = function() {
  1305. var locks = geoJson.cache.locks();
  1306. for (var key in locks) geoJson.show(locks[key]);
  1307. return geoJson;
  1308. };
  1309. return geoJson;
  1310. };
  1311. po.dblclick = function() {
  1312. var dblclick = {},
  1313. zoom = "mouse",
  1314. map,
  1315. container;
  1316. function handle(e) {
  1317. var z = map.zoom();
  1318. if (e.shiftKey) z = Math.ceil(z) - z - 1;
  1319. else z = 1 - z + Math.floor(z);
  1320. zoom === "mouse" ? map.zoomBy(z, map.mouse(e)) : map.zoomBy(z);
  1321. }
  1322. dblclick.zoom = function(x) {
  1323. if (!arguments.length) return zoom;
  1324. zoom = x;
  1325. return dblclick;
  1326. };
  1327. dblclick.map = function(x) {
  1328. if (!arguments.length) return map;
  1329. if (map) {
  1330. container.removeEventListener("dblclick", handle, false);
  1331. container = null;
  1332. }
  1333. if (map = x) {
  1334. container = map.container();
  1335. container.addEventListener("dblclick", handle, false);
  1336. }
  1337. return dblclick;
  1338. };
  1339. return dblclick;
  1340. };
  1341. po.drag = function() {
  1342. var drag = {},
  1343. map,
  1344. container,
  1345. dragging;
  1346. function mousedown(e) {
  1347. if (e.shiftKey) return;
  1348. dragging = {
  1349. x: e.clientX,
  1350. y: e.clientY
  1351. };
  1352. map.focusableParent().focus();
  1353. e.preventDefault();
  1354. document.body.style.setProperty("cursor", "move", null);
  1355. }
  1356. function mousemove(e) {
  1357. if (!dragging) return;
  1358. map.panBy({x: e.clientX - dragging.x, y: e.clientY - dragging.y});
  1359. dragging.x = e.clientX;
  1360. dragging.y = e.clientY;
  1361. }
  1362. function mouseup(e) {
  1363. if (!dragging) return;
  1364. mousemove(e);
  1365. dragging = null;
  1366. document.body.style.removeProperty("cursor");
  1367. }
  1368. drag.map = function(x) {
  1369. if (!arguments.length) return map;
  1370. if (map) {
  1371. container.removeEventListener("mousedown", mousedown, false);
  1372. container = null;
  1373. }
  1374. if (map = x) {
  1375. container = map.container();
  1376. container.addEventListener("mousedown", mousedown, false);
  1377. }
  1378. return drag;
  1379. };
  1380. window.addEventListener("mousemove", mousemove, false);
  1381. window.addEventListener("mouseup", mouseup, false);
  1382. return drag;
  1383. };
  1384. po.wheel = function() {
  1385. var wheel = {},
  1386. timePrev = 0,
  1387. last = 0,
  1388. smooth = true,
  1389. zoom = "mouse",
  1390. location,
  1391. map,
  1392. container;
  1393. function move(e) {
  1394. location = null;
  1395. }
  1396. // mousewheel events are totally broken!
  1397. // https://bugs.webkit.org/show_bug.cgi?id=40441
  1398. // not only that, but Chrome and Safari differ in re. to acceleration!
  1399. var inner = document.createElement("div"),
  1400. outer = document.createElement("div");
  1401. outer.style.visibility = "hidden";
  1402. outer.style.top = "0px";
  1403. outer.style.height = "0px";
  1404. outer.style.width = "0px";
  1405. outer.style.overflowY = "scroll";
  1406. inner.style.height = "2000px";
  1407. outer.appendChild(inner);
  1408. document.body.appendChild(outer);
  1409. function mousewheel(e) {
  1410. var delta = e.wheelDelta || -e.detail,
  1411. point;
  1412. /* Detect the pixels that would be scrolled by this wheel event. */
  1413. if (delta) {
  1414. if (smooth) {
  1415. try {
  1416. outer.scrollTop = 1000;
  1417. outer.dispatchEvent(e);
  1418. delta = 1000 - outer.scrollTop;
  1419. } catch (error) {
  1420. // Derp! Hope for the best?
  1421. }
  1422. delta *= .005;
  1423. }
  1424. /* If smooth zooming is disabled, batch events into unit steps. */
  1425. else {
  1426. var timeNow = Date.now();
  1427. if (timeNow - timePrev > 200) {
  1428. delta = delta > 0 ? +1 : -1;
  1429. timePrev = timeNow;
  1430. } else {
  1431. delta = 0;
  1432. }
  1433. }
  1434. }
  1435. if (delta) {
  1436. switch (zoom) {
  1437. case "mouse": {
  1438. point = map.mouse(e);
  1439. if (!location) location = map.pointLocation(point);
  1440. map.off("move", move).zoomBy(delta, point, location).on("move", move);
  1441. break;
  1442. }
  1443. case "location": {
  1444. map.zoomBy(delta, map.locationPoint(location), location);
  1445. break;
  1446. }
  1447. default: { // center
  1448. map.zoomBy(delta);
  1449. break;
  1450. }
  1451. }
  1452. }
  1453. e.preventDefault();
  1454. return false; // for Firefox
  1455. }
  1456. wheel.smooth = function(x) {
  1457. if (!arguments.length) return smooth;
  1458. smooth = x;
  1459. return wheel;
  1460. };
  1461. wheel.zoom = function(x, l) {
  1462. if (!arguments.length) return zoom;
  1463. zoom = x;
  1464. location = l;
  1465. if (map) {
  1466. if (zoom == "mouse") map.on("move", move);
  1467. else map.off("move", move);
  1468. }
  1469. return wheel;
  1470. };
  1471. wheel.map = function(x) {
  1472. if (!arguments.length) return map;
  1473. if (map) {
  1474. container.removeEventListener("mousemove", move, false);
  1475. container.removeEventListener("mousewheel", mousewheel, false);
  1476. container.removeEventListener("MozMousePixelScroll", mousewheel, false);
  1477. container = null;
  1478. map.off("move", move);
  1479. }
  1480. if (map = x) {
  1481. if (zoom == "mouse") map.on("move", move);
  1482. container = map.container();
  1483. container.addEventListener("mousemove", move, false);
  1484. container.addEventListener("mousewheel", mousewheel, false);
  1485. container.addEventListener("MozMousePixelScroll", mousewheel, false);
  1486. }
  1487. return wheel;
  1488. };
  1489. return wheel;
  1490. };
  1491. po.arrow = function() {
  1492. var arrow = {},
  1493. key = {left: 0, right: 0, up: 0, down: 0},
  1494. last = 0,
  1495. repeatTimer,
  1496. repeatDelay = 250,
  1497. repeatInterval = 50,
  1498. speed = 16,
  1499. map,
  1500. parent;
  1501. function keydown(e) {
  1502. if (e.ctrlKey || e.altKey || e.metaKey) return;
  1503. var now = Date.now(), dx = 0, dy = 0;
  1504. switch (e.keyCode) {
  1505. case 37: {
  1506. if (!key.left) {
  1507. last = now;
  1508. key.left = 1;
  1509. if (!key.right) dx = speed;
  1510. }
  1511. break;
  1512. }
  1513. case 39: {
  1514. if (!key.right) {
  1515. last = now;
  1516. key.right = 1;
  1517. if (!key.left) dx = -speed;
  1518. }
  1519. break;
  1520. }
  1521. case 38: {
  1522. if (!key.up) {
  1523. last = now;
  1524. key.up = 1;
  1525. if (!key.down) dy = speed;
  1526. }
  1527. break;
  1528. }
  1529. case 40: {
  1530. if (!key.down) {
  1531. last = now;
  1532. key.down = 1;
  1533. if (!key.up) dy = -speed;
  1534. }
  1535. break;
  1536. }
  1537. default: return;
  1538. }
  1539. if (dx || dy) map.panBy({x: dx, y: dy});
  1540. if (!repeatTimer && (key.left | key.right | key.up | key.down)) {
  1541. repeatTimer = setInterval(repeat, repeatInterval);
  1542. }
  1543. e.preventDefault();
  1544. }
  1545. function keyup(e) {
  1546. last = Date.now();
  1547. switch (e.keyCode) {
  1548. case 37: key.left = 0; break;
  1549. case 39: key.right = 0; break;
  1550. case 38: key.up = 0; break;
  1551. case 40: key.down = 0; break;
  1552. default: return;
  1553. }
  1554. if (repeatTimer && !(key.left | key.right | key.up | key.down)) {
  1555. repeatTimer = clearInterval(repeatTimer);
  1556. }
  1557. e.preventDefault();
  1558. }
  1559. function keypress(e) {
  1560. switch (e.charCode) {
  1561. case 45: case 95: map.zoom(Math.ceil(map.zoom()) - 1); break; // - _
  1562. case 43: case 61: map.zoom(Math.floor(map.zoom()) + 1); break; // = +
  1563. default: return;
  1564. }
  1565. e.preventDefault();
  1566. }
  1567. function repeat() {
  1568. if (!map) return;
  1569. if (Date.now() < last + repeatDelay) return;
  1570. var dx = (key.left - key.right) * speed,
  1571. dy = (key.up - key.down) * speed;
  1572. if (dx || dy) map.panBy({x: dx, y: dy});
  1573. }
  1574. arrow.map = function(x) {
  1575. if (!arguments.length) return map;
  1576. if (map) {
  1577. parent.removeEventListener("keypress", keypress, false);
  1578. parent.removeEventListener("keydown", keydown, false);
  1579. parent.removeEventListener("keyup", keyup, false);
  1580. parent = null;
  1581. }
  1582. if (map = x) {
  1583. parent = map.focusableParent();
  1584. parent.addEventListener("keypress", keypress, false);
  1585. parent.addEventListener("keydown", keydown, false);
  1586. parent.addEventListener("keyup", keyup, false);
  1587. }
  1588. return arrow;
  1589. };
  1590. arrow.speed = function(x) {
  1591. if (!arguments.length) return speed;
  1592. speed = x;
  1593. return arrow;
  1594. };
  1595. return arrow;
  1596. };
  1597. po.hash = function() {
  1598. var hash = {},
  1599. s0, // cached location.hash
  1600. lat = 90 - 1e-8, // allowable latitude range
  1601. map;
  1602. var parser = function(map, s) {
  1603. var args = s.split("/").map(Number);
  1604. if (args.length < 3 || args.some(isNaN)) return true; // replace bogus hash
  1605. else {
  1606. var size = map.size();
  1607. map.zoomBy(args[0] - map.zoom(),
  1608. {x: size.x / 2, y: size.y / 2},
  1609. {lat: Math.min(lat, Math.max(-lat, args[1])), lon: args[2]});
  1610. }
  1611. };
  1612. var formatter = function(map) {
  1613. var center = map.center(),
  1614. zoom = map.zoom(),
  1615. precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2));
  1616. return "#" + zoom.toFixed(2)
  1617. + "/" + center.lat.toFixed(precision)
  1618. + "/" + center.lon.toFixed(precision);
  1619. };
  1620. function move() {
  1621. var s1 = formatter(map);
  1622. if (s0 !== s1) location.replace(s0 = s1); // don't recenter the map!
  1623. }
  1624. function hashchange() {
  1625. if (location.hash === s0) return; // ignore spurious hashchange events
  1626. if (parser(map, (s0 = location.hash).substring(1)))
  1627. move(); // replace bogus hash
  1628. }
  1629. hash.map = function(x) {
  1630. if (!arguments.length) return map;
  1631. if (map) {
  1632. map.off("move", move);
  1633. window.removeEventListener("hashchange", hashchange, false);
  1634. }
  1635. if (map = x) {
  1636. map.on("move", move);
  1637. window.addEventListener("hashchange", hashchange, false);
  1638. location.hash ? hashchange() : move();
  1639. }
  1640. return hash;
  1641. };
  1642. hash.parser = function(x) {
  1643. if (!arguments.length) return parser;
  1644. parser = x;
  1645. return hash;
  1646. };
  1647. hash.formatter = function(x) {
  1648. if (!arguments.length) return formatter;
  1649. formatter = x;
  1650. return hash;
  1651. };
  1652. return hash;
  1653. };
  1654. po.touch = function() {
  1655. var touch = {},
  1656. map,
  1657. container,
  1658. rotate = false,
  1659. last = 0,
  1660. zoom,
  1661. angle,
  1662. locations = {}; // touch identifier -> location
  1663. window.addEventListener("touchmove", touchmove, false);
  1664. function touchstart(e) {
  1665. var i = -1,
  1666. n = e.touches.length,
  1667. t = Date.now();
  1668. // doubletap detection
  1669. if ((n == 1) && (t - last < 300)) {
  1670. var z = map.zoom();
  1671. map.zoomBy(1 - z + Math.floor(z), map.mouse(e.touches[0]));
  1672. e.preventDefault();
  1673. }
  1674. last = t;
  1675. // store original zoom & touch locations
  1676. zoom = map.zoom();
  1677. angle = map.angle();
  1678. while (++i < n) {
  1679. t = e.touches[i];
  1680. locations[t.identifier] = map.pointLocation(map.mouse(t));
  1681. }
  1682. }
  1683. function touchmove(e) {
  1684. switch (e.touches.length) {
  1685. case 1: { // single-touch pan
  1686. var t0 = e.touches[0];
  1687. map.zoomBy(0, map.mouse(t0), locations[t0.identifier]);
  1688. e.preventDefault();
  1689. break;
  1690. }
  1691. case 2: { // double-touch pan + zoom + rotate
  1692. var t0 = e.touches[0],
  1693. t1 = e.touches[1],
  1694. p0 = map.mouse(t0),
  1695. p1 = map.mouse(t1),
  1696. p2 = {x: (p0.x + p1.x) / 2, y: (p0.y + p1.y) / 2}, // center point
  1697. c0 = po.map.locationCoordinate(locations[t0.identifier]),
  1698. c1 = po.map.locationCoordinate(locations[t1.identifier]),
  1699. c2 = {row: (c0.row + c1.row) / 2, column: (c0.column + c1.column) / 2, zoom: 0},
  1700. l2 = po.map.coordinateLocation(c2); // center location
  1701. map.zoomBy(Math.log(e.scale) / Math.LN2 + zoom - map.zoom(), p2, l2);
  1702. if (rotate) map.angle(e.rotation / 180 * Math.PI + angle);
  1703. e.preventDefault();
  1704. break;
  1705. }
  1706. }
  1707. }
  1708. touch.rotate = function(x) {
  1709. if (!arguments.length) return rotate;
  1710. rotate = x;
  1711. return touch;
  1712. };
  1713. touch.map = function(x) {
  1714. if (!arguments.length) return map;
  1715. if (map) {
  1716. container.removeEventListener("touchstart", touchstart, false);
  1717. container = null;
  1718. }
  1719. if (map = x) {
  1720. container = map.container();
  1721. container.addEventListener("touchstart", touchstart, false);
  1722. }
  1723. return touch;
  1724. };
  1725. return touch;
  1726. };
  1727. // Default map controls.
  1728. po.interact = function() {
  1729. var interact = {},
  1730. drag = po.drag(),
  1731. wheel = po.wheel(),
  1732. dblclick = po.dblclick(),
  1733. touch = po.touch(),
  1734. arrow = po.arrow();
  1735. interact.map = function(x) {
  1736. drag.map(x);
  1737. wheel.map(x);
  1738. dblclick.map(x);
  1739. touch.map(x);
  1740. arrow.map(x);
  1741. return interact;
  1742. };
  1743. return interact;
  1744. };
  1745. po.compass = function() {
  1746. var compass = {},
  1747. g = po.svg("g"),
  1748. ticks = {},
  1749. r = 30,
  1750. speed = 16,
  1751. last = 0,
  1752. repeatDelay = 250,
  1753. repeatInterval = 50,
  1754. position = "top-left", // top-left, top-right, bottom-left, bottom-right
  1755. zoomStyle = "small", // none, small, big
  1756. zoomContainer,
  1757. panStyle = "small", // none, small
  1758. panTimer,
  1759. panDirection,
  1760. panContainer,
  1761. drag,
  1762. dragRect = po.svg("rect"),
  1763. map,
  1764. container,
  1765. window;
  1766. g.setAttribute("class", "compass");
  1767. dragRect.setAttribute("class", "back fore");
  1768. dragRect.setAttribute("pointer-events", "none");
  1769. dragRect.setAttribute("display", "none");
  1770. function panStart(e) {
  1771. g.setAttribute("class", "compass active");
  1772. if (!panTimer) panTimer = setInterval(panRepeat, repeatInterval);
  1773. if (panDirection) map.panBy(panDirection);
  1774. last = Date.now();
  1775. return cancel(e);
  1776. }
  1777. function panRepeat() {
  1778. if (panDirection && (Date.now() > last + repeatDelay)) {
  1779. map.panBy(panDirection);
  1780. }
  1781. }
  1782. function mousedown(e) {
  1783. if (e.shiftKey) {
  1784. drag = {x0: map.mouse(e)};
  1785. map.focusableParent().focus();
  1786. return cancel(e);
  1787. }
  1788. }
  1789. function mousemove(e) {
  1790. if (!drag) return;
  1791. drag.x1 = map.mouse(e);
  1792. dragRect.setAttribute("x", Math.min(drag.x0.x, drag.x1.x));
  1793. dragRect.setAttribute("y", Math.min(drag.x0.y, drag.x1.y));
  1794. dragRect.setAttribute("width", Math.abs(drag.x0.x - drag.x1.x));
  1795. dragRect.setAttribute("height", Math.abs(drag.x0.y - drag.x1.y));
  1796. dragRect.removeAttribute("display");
  1797. }
  1798. function mouseup(e) {
  1799. g.setAttribute("class", "compass");
  1800. if (drag) {
  1801. if (drag.x1) {
  1802. map.extent([
  1803. map.pointLocation({
  1804. x: Math.min(drag.x0.x, drag.x1.x),
  1805. y: Math.max(drag.x0.y, drag.x1.y)
  1806. }),
  1807. map.pointLocation({
  1808. x: Math.max(drag.x0.x, drag.x1.x),
  1809. y: Math.min(drag.x0.y, drag.x1.y)
  1810. })
  1811. ]);
  1812. dragRect.setAttribute("display", "none");
  1813. }
  1814. drag = null;
  1815. }
  1816. if (panTimer) {
  1817. clearInterval(panTimer);
  1818. panTimer = 0;
  1819. }
  1820. }
  1821. function panBy(x) {
  1822. return function() {
  1823. x ? this.setAttribute("class", "active") : this.removeAttribute("class");
  1824. panDirection = x;
  1825. };
  1826. }
  1827. function zoomBy(x) {
  1828. return function(e) {
  1829. g.setAttribute("class", "compass active");
  1830. var z = map.zoom();
  1831. map.zoom(x < 0 ? Math.ceil(z) - 1 : Math.floor(z) + 1);
  1832. return cancel(e);
  1833. };
  1834. }
  1835. function zoomTo(x) {
  1836. return function(e) {
  1837. map.zoom(x);
  1838. return cancel(e);
  1839. };
  1840. }
  1841. function zoomOver() {
  1842. this.setAttribute("class", "active");
  1843. }
  1844. function zoomOut() {
  1845. this.removeAttribute("class");
  1846. }
  1847. function cancel(e) {
  1848. e.stopPropagation();
  1849. e.preventDefault();
  1850. return false;
  1851. }
  1852. function pan(by) {
  1853. var x = Math.SQRT1_2 * r,
  1854. y = r * .7,
  1855. z = r * .2,
  1856. g = po.svg("g"),
  1857. dir = g.appendChild(po.svg("path")),
  1858. chv = g.appendChild(po.svg("path"));
  1859. dir.setAttribute("class", "direction");
  1860. dir.setAttribute("pointer-events", "all");
  1861. dir.setAttribute("d", "M0,0L" + x + "," + x + "A" + r + "," + r + " 0 0,1 " + -x + "," + x + "Z");
  1862. chv.setAttribute("class", "chevron");
  1863. chv.setAttribute("d", "M" + z + "," + (y - z) + "L0," + y + " " + -z + "," + (y - z));
  1864. chv.setAttribute("pointer-events", "none");
  1865. g.addEventListener("mousedown", panStart, false);
  1866. g.addEventListener("mouseover", panBy(by), false);
  1867. g.addEventListener("mouseout", panBy(null), false);
  1868. g.addEventListener("dblclick", cancel, false);
  1869. return g;
  1870. }
  1871. function zoom(by) {
  1872. var x = r * .4,
  1873. y = x / 2,
  1874. g = po.svg("g"),
  1875. back = g.appendChild(po.svg("path")),
  1876. dire = g.appendChild(po.svg("path")),
  1877. chev = g.appendChild(po.svg("path")),
  1878. fore = g.appendChild(po.svg("path"));
  1879. back.setAttribute("class", "back");
  1880. back.setAttribute("d", "M" + -x + ",0V" + -x + "A" + x + "," + x + " 0 1,1 " + x + "," + -x + "V0Z");
  1881. dire.setAttribute("class", "direction");
  1882. dire.setAttribute("d", back.getAttribute("d"));
  1883. chev.setAttribute("class", "chevron");
  1884. chev.setAttribute("d", "M" + -y + "," + -x + "H" + y + (by > 0 ? "M0," + (-x - y) + "V" + -y : ""));
  1885. fore.setAttribute("class", "fore");
  1886. fore.setAttribute("fill", "none");
  1887. fore.setAttribute("d", back.getAttribute("d"));
  1888. g.addEventListener("mousedown", zoomBy(by), false);
  1889. g.addEventListener("mouseover", zoomOver, false);
  1890. g.addEventListener("mouseout", zoomOut, false);
  1891. g.addEventListener("dblclick", cancel, false);
  1892. return g;
  1893. }
  1894. function tick(i) {
  1895. var x = r * .2,
  1896. y = r * .4,
  1897. g = po.svg("g"),
  1898. back = g.appendChild(po.svg("rect")),
  1899. chev = g.appendChild(po.svg("path"));
  1900. back.setAttribute("pointer-events", "all");
  1901. back.setAttribute("fill", "none");
  1902. back.setAttribute("x", -y);
  1903. back.setAttribute("y", -.75 * y);
  1904. back.setAttribute("width", 2 * y);
  1905. back.setAttribute("height", 1.5 * y);
  1906. chev.setAttribute("class", "chevron");
  1907. chev.setAttribute("d", "M" + -x + ",0H" + x);
  1908. g.addEventListener("mousedown", zoomTo(i), false);
  1909. g.addEventListener("dblclick", cancel, false);
  1910. return g;
  1911. }
  1912. function move() {
  1913. var x = r + 6, y = x, size = map.size();
  1914. switch (position) {
  1915. case "top-left": break;
  1916. case "top-right": x = size.x - x; break;
  1917. case "bottom-left": y = size.y - y; break;
  1918. case "bottom-right": x = size.x - x; y = size.y - y; break;
  1919. }
  1920. g.setAttribute("transform", "translate(" + x + "," + y + ")");
  1921. dragRect.setAttribute("transform", "translate(" + -x + "," + -y + ")");
  1922. for (var i in ticks) {
  1923. i == map.zoom()
  1924. ? ticks[i].setAttribute("class", "active")
  1925. : ticks[i].removeAttribute("class");
  1926. }
  1927. }
  1928. function draw() {
  1929. while (g.lastChild) g.removeChild(g.lastChild);
  1930. g.appendChild(dragRect);
  1931. if (panStyle != "none") {
  1932. panContainer = g.appendChild(po.svg("g"));
  1933. panContainer.setAttribute("class", "pan");
  1934. var back = panContainer.appendChild(po.svg("circle"));
  1935. back.setAttribute("class", "back");
  1936. back.setAttribute("r", r);
  1937. var s = panContainer.appendChild(pan({x: 0, y: -speed}));
  1938. s.setAttribute("transform", "rotate(0)");
  1939. var w = panContainer.appendChild(pan({x: speed, y: 0}));
  1940. w.setAttribute("transform", "rotate(90)");
  1941. var n = panContainer.appendChild(pan({x: 0, y: speed}));
  1942. n.setAttribute("transform", "rotate(180)");
  1943. var e = panContainer.appendChild(pan({x: -speed, y: 0}));
  1944. e.setAttribute("transform", "rotate(270)");
  1945. var fore = panContainer.appendChild(po.svg("circle"));
  1946. fore.setAttribute("fill", "none");
  1947. fore.setAttribute("class", "fore");
  1948. fore.setAttribute("r", r);
  1949. } else {
  1950. panContainer = null;
  1951. }
  1952. if (zoomStyle != "none") {
  1953. zoomContainer = g.appendChild(po.svg("g"));
  1954. zoomContainer.setAttribute("class", "zoom");
  1955. var j = -.5;
  1956. if (zoomStyle == "big") {
  1957. ticks = {};
  1958. for (var i = map.zoomRange()[0], j = 0; i <= map.zoomRange()[1]; i++, j++) {
  1959. (ticks[i] = zoomContainer.appendChild(tick(i)))
  1960. .setAttribute("transform", "translate(0," + (-(j + .75) * r * .4) + ")");
  1961. }
  1962. }
  1963. var p = panStyle == "none" ? .4 : 2;
  1964. zoomContainer.setAttribute("transform", "translate(0," + r * (/^top-/.test(position) ? (p + (j + .5) * .4) : -p) + ")");
  1965. zoomContainer.appendChild(zoom(+1)).setAttribute("transform", "translate(0," + (-(j + .5) * r * .4) + ")");
  1966. zoomContainer.appendChild(zoom(-1)).setAttribute("transform", "scale(-1)");
  1967. } else {
  1968. zoomContainer = null;
  1969. }
  1970. move();
  1971. }
  1972. compass.radius = function(x) {
  1973. if (!arguments.length) return r;
  1974. r = x;
  1975. if (map) draw();
  1976. return compass;
  1977. };
  1978. compass.speed = function(x) {
  1979. if (!arguments.length) return r;
  1980. speed = x;
  1981. return compass;
  1982. };
  1983. compass.position = function(x) {
  1984. if (!arguments.length) return position;
  1985. position = x;
  1986. if (map) draw();
  1987. return compass;
  1988. };
  1989. compass.pan = function(x) {
  1990. if (!arguments.length) return panStyle;
  1991. panStyle = x;
  1992. if (map) draw();
  1993. return compass;
  1994. };
  1995. compass.zoom = function(x) {
  1996. if (!arguments.length) return zoomStyle;
  1997. zoomStyle = x;
  1998. if (map) draw();
  1999. return compass;
  2000. };
  2001. compass.map = function(x) {
  2002. if (!arguments.length) return map;
  2003. if (map) {
  2004. container.removeEventListener("mousedown", mousedown, false);
  2005. container.removeChild(g);
  2006. container = null;
  2007. window.removeEventListener("mousemove", mousemove, false);
  2008. window.removeEventListener("mouseup", mouseup, false);
  2009. window = null;
  2010. map.off("move", move).off("resize", move);
  2011. }
  2012. if (map = x) {
  2013. container = map.container();
  2014. container.appendChild(g);
  2015. container.addEventListener("mousedown", mousedown, false);
  2016. window = container.ownerDocument.defaultView;
  2017. window.addEventListener("mousemove", mousemove, false);
  2018. window.addEventListener("mouseup", mouseup, false);
  2019. map.on("move", move).on("resize", move);
  2020. draw();
  2021. }
  2022. return compass;
  2023. };
  2024. return compass;
  2025. };
  2026. po.grid = function() {
  2027. var grid = {},
  2028. map,
  2029. g = po.svg("g");
  2030. g.setAttribute("class", "grid");
  2031. function move(e) {
  2032. var p,
  2033. line = g.firstChild,
  2034. size = map.size(),
  2035. nw = map.pointLocation(zero),
  2036. se = map.pointLocation(size),
  2037. step = Math.pow(2, 4 - Math.round(map.zoom()));
  2038. // Round to step.
  2039. nw.lat = Math.floor(nw.lat / step) * step;
  2040. nw.lon = Math.ceil(nw.lon / step) * step;
  2041. // Longitude ticks.
  2042. for (var x; (x = map.locationPoint(nw).x) <= size.x; nw.lon += step) {
  2043. if (!line) line = g.appendChild(po.svg("line"));
  2044. line.setAttribute("x1", x);
  2045. line.setAttribute("x2", x);
  2046. line.setAttribute("y1", 0);
  2047. line.setAttribute("y2", size.y);
  2048. line = line.nextSibling;
  2049. }
  2050. // Latitude ticks.
  2051. for (var y; (y = map.locationPoint(nw).y) <= size.y; nw.lat -= step) {
  2052. if (!line) line = g.appendChild(po.svg("line"));
  2053. line.setAttribute("y1", y);
  2054. line.setAttribute("y2", y);
  2055. line.setAttribute("x1", 0);
  2056. line.setAttribute("x2", size.x);
  2057. line = line.nextSibling;
  2058. }
  2059. // Remove extra ticks.
  2060. while (line) {
  2061. var next = line.nextSibling;
  2062. g.removeChild(line);
  2063. line = next;
  2064. }
  2065. }
  2066. grid.map = function(x) {
  2067. if (!arguments.length) return map;
  2068. if (map) {
  2069. g.parentNode.removeChild(g);
  2070. map.off("move", move).off("resize", move);
  2071. }
  2072. if (map = x) {
  2073. map.on("move", move).on("resize", move);
  2074. map.container().appendChild(g);
  2075. map.dispatch({type: "move"});
  2076. }
  2077. return grid;
  2078. };
  2079. return grid;
  2080. };
  2081. po.stylist = function() {
  2082. var attrs = [],
  2083. styles = [],
  2084. title;
  2085. function stylist(e) {
  2086. var ne = e.features.length,
  2087. na = attrs.length,
  2088. ns = styles.length,
  2089. f, // feature
  2090. d, // data
  2091. o, // element
  2092. x, // attr or style or title descriptor
  2093. v, // attr or style or title value
  2094. i,
  2095. j;
  2096. for (i = 0; i < ne; ++i) {
  2097. if (!(o = (f = e.features[i]).element)) continue;
  2098. d = f.data;
  2099. for (j = 0; j < na; ++j) {
  2100. v = (x = attrs[j]).value;
  2101. if (typeof v === "function") v = v.call(null, d);
  2102. v == null ? (x.name.local
  2103. ? o.removeAttributeNS(x.name.space, x.name.local)
  2104. : o.removeAttribute(x.name)) : (x.name.local
  2105. ? o.setAttributeNS(x.name.space, x.name.local, v)
  2106. : o.setAttribute(x.name, v));
  2107. }
  2108. for (j = 0; j < ns; ++j) {
  2109. v = (x = styles[j]).value;
  2110. if (typeof v === "function") v = v.call(null, d);
  2111. v == null
  2112. ? o.style.removeProperty(x.name)
  2113. : o.style.setProperty(x.name, v, x.priority);
  2114. }
  2115. if (v = title) {
  2116. if (typeof v === "function") v = v.call(null, d);
  2117. while (o.lastChild) o.removeChild(o.lastChild);
  2118. if (v != null) o.appendChild(po.svg("title")).appendChild(document.createTextNode(v));
  2119. }
  2120. }
  2121. }
  2122. stylist.attr = function(n, v) {
  2123. attrs.push({name: ns(n), value: v});
  2124. return stylist;
  2125. };
  2126. stylist.style = function(n, v, p) {
  2127. styles.push({name: n, value: v, priority: arguments.length < 3 ? null : p});
  2128. return stylist;
  2129. };
  2130. stylist.title = function(v) {
  2131. title = v;
  2132. return stylist;
  2133. };
  2134. return stylist;
  2135. };
  2136. })(org.polymaps);