geomap-old.js 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422
  1. /*
  2. *
  3. * Author: Yang Dongxu
  4. * Date: 12-12-29
  5. * Time: 上午9:59
  6. *
  7. * GeoMap.js 矢量地图展示控件
  8. *
  9. */
  10. // TODO: 格式化坐标的方法 | 画geoJSON的点和线的方法
  11. (function(){
  12. var GeoMap = function (container, w, h){
  13. var self = this;
  14. self.container = $(container);
  15. self.width = w || self.container.width();
  16. self.height = h || self.container.height();
  17. // 记录可视区域的状态
  18. self.viewBox = {
  19. a : 0, // 可视区域x轴偏移
  20. b : 0, // 可视区域y轴偏移
  21. w : self.width, // 可视区域宽
  22. h : self.height // 可视区域高
  23. };
  24. self.canvas = new Raphael(self.container.get(0), self.width, self.height);
  25. // 记录地图上所有path的对象
  26. self.mapPaths = {};
  27. // 地图的偏移量和缩放比例
  28. self.offset = {},
  29. self.scale = {};
  30. self.defaultConfig = {
  31. 'srcPath' : '',
  32. 'scale' : {
  33. 'x' : 1,
  34. 'y' : 1
  35. },
  36. // 设置平移量,以便让地图放在合适的位置
  37. 'translate' : {
  38. 'x' : 0,
  39. 'y' : 0
  40. },
  41. 'style' : {
  42. 'fill' : '#CFEBF7',
  43. 'stroke' : '#fff',
  44. 'stroke-width' : 1,
  45. 'stroke-linejoin' : 'round'
  46. },
  47. 'loadingTxt' : {
  48. 'loading' : '载入数据',
  49. 'fail' : '数据载入失败',
  50. 'fill' : '#333'
  51. }
  52. };
  53. };
  54. GeoMap.prototype = {
  55. // 数据载入提示,画布正中显示
  56. showLoadingTip : function(t){
  57. var self = this,
  58. canvas = self.canvas,
  59. txt, box, w, h;
  60. self.removeLoadingTip();
  61. txt = canvas.text(self.width/2, self.height/2, t).attr({
  62. 'fill':'#fff',
  63. 'font-size':'12px'
  64. });
  65. txt.id = 'loadingTip';
  66. w = txt.node.clientWidth;
  67. h = txt.node.clientHeight;
  68. box = canvas.rect((self.width - w)/2, (self.height - h)/2, w, h).attr({
  69. 'fill':'#000'
  70. });
  71. box.insertBefore(txt);
  72. box.id = 'loadingTipBox';
  73. },
  74. removeLoadingTip : function(){
  75. var self = this,
  76. tip = self.canvas.getById('loadingTip'),
  77. box = self.canvas.getById('loadingTipBox');
  78. if(tip){
  79. tip.remove();
  80. box.remove();
  81. }
  82. },
  83. // 绘制地图
  84. render : function(){ /* 支持两个参数 config, callback */
  85. var self = this,
  86. canvas = self.canvas,
  87. xhr,
  88. sx, sy, argtype,
  89. callback = function(){},
  90. config = {};
  91. if(arguments.length == 1){
  92. argtype = {}.toString.call(arguments[0]);
  93. if(argtype == '[object Object]'){
  94. config = arguments[0];
  95. }else if(argtype == '[object Function]'){
  96. callback = arguments[0];
  97. }
  98. }else if(arguments.length == 2){
  99. config = arguments[0];
  100. callback = arguments[1];
  101. }
  102. // 清空画布
  103. canvas.clear();
  104. // 清空mapPaths
  105. self.mapPaths = {};
  106. // 扩展设置
  107. $.extend(true, self.defaultConfig, config);
  108. config = self.defaultConfig;
  109. self.showLoadingTip('载入数据...');
  110. xhr = $.ajax({
  111. url: config.srcPath,
  112. dataType: 'json'
  113. }).done(function(geoJSON){
  114. canvas.clear();
  115. // 为了保证地图在容器的0,0坐标开始绘制
  116. // 需要确定每张地图的偏移量,即geoJSON对象的offset属性
  117. // 但是,geoJSON的标准格式不存在offset, 所以
  118. // 对于没有经过处理的数据源 需要动态判断offset
  119. if(!geoJSON.offset){
  120. var a = geoJSON.features, //地区条目数组
  121. x = 180,
  122. y = 0,
  123. s, o, r, p; //临时变量不重要
  124. for(var i = 0, len = a.length; i < len; i++){
  125. s = a[i].properties.name;
  126. o = a[i].geometry;
  127. r = o.coordinates;
  128. for(var j=0,l2=r.length;j<l2;j++){
  129. if(o.type == 'Polygon'){
  130. p = r[j];
  131. }else if(o.type == 'MultiPolygon'){
  132. p = r[j][0];
  133. }
  134. for(var u=0,l3=p.length;u<l3;u++){
  135. if(p[u][0]<x){
  136. x = p[u][0];
  137. }
  138. if(p[u][1]>y){
  139. y = p[u][1];
  140. }
  141. }
  142. }
  143. geoJSON.offset = {
  144. // x轴的偏移量 需要取实际坐标度数的负值
  145. x : -x,
  146. y : y
  147. };
  148. }
  149. }
  150. geoJSON.offset.x += config.translate.x;
  151. geoJSON.offset.y += config.translate.y;
  152. // 记录当前地图的偏移量和缩放倍数
  153. self.offset = geoJSON.offset;
  154. self.scale = config.scale;
  155. // 格式化json数据
  156. geoJSON = self.formatGeoJSON(geoJSON);
  157. sx = self.scale.x;
  158. sy = self.scale.y;
  159. // 绘制path
  160. $.each(geoJSON,function(k,v){
  161. var p = canvas.path(v).attr(config.style);
  162. // 使用scale方法,在ie8下出现bug,so:
  163. // 初始化的缩放,改在格式化geoJSON数据时,直接将点坐标的值乘以缩放倍数
  164. // p.scale(sx, sy, 0, 0);
  165. //给每个path元素增加属性,标注地名
  166. $(p.node).attr({
  167. 'class': 'map-path',
  168. 'path-name': k
  169. });
  170. //通过数组缓存raphael的图形对象
  171. self.mapPaths[k] = p;
  172. });
  173. // 在放大情况下,允许拖动地图
  174. self.dragMove();
  175. // 执行render的回调函数
  176. callback();
  177. }).fail(function(){
  178. canvas.clear();
  179. self.showLoadingTip('载入失败!');
  180. });
  181. },
  182. // 绑定拖动
  183. dragMove : function(){
  184. var self = this,
  185. v = self.viewBox,
  186. c = $(self.canvas.canvas),
  187. bool = false,
  188. oX = 0,
  189. oY = 0;
  190. c.on('mousedown', down).on('mouseup', up).on('mousemove', move);
  191. // 通过mouse的down、up、move方法来实现拖拽事件
  192. // 通过Raphael的setViewBox方法,挪动可视区域,实现拖拽地图的效果
  193. function down(e){
  194. bool = true;
  195. oX = e.clientX;
  196. oY = e.clientY;
  197. $(this).css('cursor','move');
  198. }
  199. function up(){
  200. bool = false;
  201. $(this).css('cursor','default');
  202. }
  203. function move(e){
  204. if(!bool){
  205. return false;
  206. }
  207. v.a = checkRange(v.a, e.clientX, oX, self.width, v.w);
  208. v.b = checkRange(v.b, e.clientY, oY, self.height, v.h);
  209. oX = e.clientX;
  210. oY = e.clientY;
  211. self.canvas.setViewBox(v.a, v.b, v.w, v.h);
  212. // 拖拽不能超出地图原有范围
  213. function checkRange(v, c, o, s, t){
  214. var x = v - (c - o)/2;
  215. if(x <= 0 || x >= s - t){
  216. return v;
  217. }
  218. return x;
  219. }
  220. }
  221. },
  222. // 缩放
  223. viewScale : function(type){
  224. var self = this,
  225. v = self.viewBox,
  226. w = v.w,
  227. h = v.h,
  228. a = v.a,
  229. b = v.b,
  230. s = 1;
  231. switch(type){
  232. case 'up':
  233. s += 0.2;
  234. break;
  235. case 'down':
  236. s -= 0.2;
  237. break;
  238. default:
  239. return false;
  240. break;
  241. }
  242. v.w = v.w / s;
  243. v.h = v.h / s;
  244. v.a = v.a + (w - v.w)/2;
  245. v.b = v.b + (h - v.h)/2;
  246. // 边界条件 放大倍数不能大于2 原大时不能缩小
  247. if(s > 1 && v.w < self.width / 2){
  248. v.w = w;
  249. v.h = h;
  250. v.a = a;
  251. v.b = b;
  252. }else if(s < 1 && v.w > self.width){
  253. v.w = self.width;
  254. v.h = self.height;
  255. v.a = 0;
  256. v.b = 0;
  257. }
  258. // 如果放大或缩小时,某个边界超出原有画布 则强制收敛回来
  259. if(v.a < 0) v.a = 0;
  260. if(v.b < 0) v.b = 0;
  261. if(v.a > (self.width - v.w)) v.a = self.width - v.w;
  262. if(v.b > (self.width - v.h)) v.b = self.height - v.h;
  263. // 使用setViewBox方法,实现缩放
  264. self.canvas.setViewBox(v.a, v.b, v.w, v.h);
  265. },
  266. // 画点
  267. setPoint : function(p){
  268. var self = this,
  269. // 点的默认样式
  270. a = {
  271. "x":0,
  272. "y":0,
  273. "r":1,
  274. "opacity":0.5,
  275. "fill": "#238CC3",
  276. "stroke": "#238CC3",
  277. "stroke-width": 0,
  278. "stroke-linejoin": "round"
  279. },
  280. x, y, c;
  281. $.extend(true, a, p);
  282. x = (a.x + self.offset.x) * self.scale.x;
  283. y = (self.offset.y - a.y) * self.scale.y;
  284. c = self.canvas.circle(x, y, a.r).attr(a);
  285. return c;
  286. },
  287. // 画线
  288. setLine : function(b){
  289. var self = this,
  290. a = {
  291. "ps":[],
  292. "stroke": "#238CC3",
  293. "stroke-width": 0.5,
  294. "stroke-linejoin": "round"
  295. },
  296. d = [],
  297. x, y, l;
  298. $.extend(true, a, b);
  299. // 将点数组连接起来,形成线的path描述
  300. for(var i=0, len=a.ps.length; i<len; i++){
  301. x = (a.ps[i].x *1 + self.offset.x) * self.scale.x;
  302. y = (self.offset.y - a.ps[i].y *1) * self.scale.y;
  303. d.push(x+','+y);
  304. }
  305. d = 'M' + d.join('L');
  306. l = self.canvas.path(d).attr(a);
  307. return l;
  308. },
  309. // 格式化地理数据,构成path描述
  310. formatGeoJSON : function(g){
  311. var self = this;
  312. var a = g.features, // 地区条目数组
  313. x = g.offset.x, // x轴偏移量
  314. y = g.offset.y, // y轴偏移量
  315. d = {}, // 对象返回值:地区->路径字符串
  316. s, o, r, p; // 临时变量不重要
  317. // XX:此方法暂弃
  318. // IE8绘制vml,中国地图(包括中国地区图)板块会向左上偏移,但画点或线则不偏移
  319. // 检查路径描述,和绘制svg时一致
  320. // 偏移仍在查找,暂时通过增加判断,强制消除偏移问题
  321. /*
  322. if(!Raphael.svg && g.offset.x != 170){
  323. x += 1.25;
  324. y += 1.55;
  325. }
  326. */
  327. for(var i = 0, len = a.length; i < len; i++){
  328. s = a[i].properties.name;
  329. o = a[i].geometry;
  330. r = o.coordinates;
  331. for(var j = 0, l2 = r.length; j < l2; j++){
  332. // 判断数据结构,取出点数组的值
  333. if(o.type == 'Polygon'){
  334. p = r[j];
  335. }else if(o.type == 'MultiPolygon'){
  336. p = r[j][0];
  337. }
  338. // 将点数组转换为描述path的字符串
  339. for(var u = 0, l3 = p.length; u < l3; u++){
  340. // 调整地图位置,最左侧为美洲大陆最西端
  341. // 美洲大陆最西端坐标约为西经168.5°左右
  342. if(p[u][0] < -168.5){
  343. p[u][0] = p[u][0] + 360;
  344. }
  345. // 初始化的缩放,在此处通过乘法直接执行,不再调用scale方法
  346. p[u] = (p[u][0] + x) * self.scale.x + ',' + (y - p[u][1])*self.scale.y;
  347. }
  348. r[j] = 'M' + p.join('L') + 'z';
  349. }
  350. d[s] = r.join('');
  351. }
  352. return d;
  353. }
  354. }
  355. window.GeoMap = GeoMap;
  356. })();