geomap-0.5.4.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590
  1. /*
  2. * GeoMap v0.5.4
  3. * https://github.com/x6doooo/GeoMap
  4. *
  5. * Copyright 2013 Dx. Yang
  6. * Released under the MIT license
  7. */
  8. (function($, undefined){
  9. var version = "0.5.4"
  10. var convertor_parse = {
  11. "formatPoint": function(p){
  12. return [
  13. (p[0] < -168.5 ? p[0] + 360 : p[0]) + 170,
  14. 90 - p[1]
  15. ];
  16. },
  17. "makePoint": function(p){
  18. var self = this,
  19. point = self.formatPoint(p),
  20. x = point[0],
  21. y = point[1];
  22. if(self.xmin > x) self.xmin = x;
  23. if(self.xmax < x) self.xmax = x;
  24. if(self.ymin > y) self.ymin = y;
  25. if(self.ymax < y) self.ymax = y;
  26. },
  27. "Point": function(coordinates){
  28. this.makePoint(coordinates);
  29. },
  30. "LineString": function(coordinates){
  31. var self = this,
  32. i = 0,
  33. len = coordinates.length;
  34. for( ; i < len; i++){
  35. self.makePoint(coordinates[i]);
  36. }
  37. },
  38. "Polygon": function(coordinates){
  39. var i = 0,
  40. len = coordinates.length;
  41. for(; i < len; i++){
  42. this.LineString(coordinates[i]);
  43. }
  44. },
  45. "MultiPoint": function(coordinates){
  46. var i = 0,
  47. len = coordinates.length;
  48. for(; i < len; i++){
  49. this.Point(coordinates[i]);
  50. }
  51. },
  52. "MultiLineString": function(coordinates){
  53. var i = 0,
  54. len = coordinates.length;
  55. for(; i < len; i++){
  56. this.LineString(coordinates[i]);
  57. }
  58. },
  59. "MultiPolygon": function(coordinates){
  60. var i = 0,
  61. len = coordinates.length;
  62. for(; i < len; i++){
  63. this.Polygon(coordinates[i]);
  64. }
  65. }
  66. };
  67. function parseSrcSize(json){
  68. var
  69. shapes = json.features,
  70. shapeType,
  71. shapeCoordinates,
  72. geometries,
  73. i, j,
  74. len, len2,
  75. val,
  76. shape;
  77. convertor_parse.xmin = 360;
  78. convertor_parse.xmax = 0;
  79. convertor_parse.ymin = 180;
  80. convertor_parse.ymax = 0;
  81. for(i = 0, len = shapes.length; i < len; i++){
  82. shape = shapes[i];
  83. if(shape.type == 'Feature'){
  84. pushApath(shape.geometry, shape);
  85. }else if(shape.type = 'GeometryCollection'){
  86. geometries = shape.geometries;
  87. for(j = 0, len2 = geometries.length; j < len2; j++){
  88. val = geometries[j];
  89. pushApath(val, val);
  90. }
  91. }
  92. }
  93. function pushApath(gm){
  94. shapeType = gm.type;
  95. shapeCoordinates = gm.coordinates;
  96. convertor_parse[shapeType](shapeCoordinates);
  97. }
  98. json.srcSize = {
  99. left: convertor_parse.xmin.toFixed(4) * 1,
  100. top: convertor_parse.ymin.toFixed(4) * 1,
  101. width: (convertor_parse.xmax - convertor_parse.xmin).toFixed(4) * 1,
  102. height: (convertor_parse.ymax - convertor_parse.ymin).toFixed(4) * 1
  103. };
  104. return json;
  105. }
  106. var convertor = {
  107. /*!Private
  108. 让阿拉斯加地区在地图右侧显示
  109. */
  110. "formatPoint": function(p){
  111. return [
  112. (p[0] < -168.5 ? p[0] + 360 : p[0]) + 170,
  113. 90 - p[1]
  114. ];
  115. },
  116. "makePoint": function(p){
  117. var self = this,
  118. point = self.formatPoint(p),
  119. x = (point[0] - convertor.offset.x) * convertor.scale.x,
  120. y = (point[1] - convertor.offset.y) * convertor.scale.y;
  121. return [x, y];
  122. },
  123. "Point": function(coordinates){
  124. coordinates = this.makePoint(coordinates);
  125. return coordinates.join(',');
  126. },
  127. "LineString": function(coordinates){
  128. var str = '',
  129. self = this,
  130. i = 0,
  131. len = coordinates.length,
  132. point;
  133. for( ; i < len; i++){
  134. point = self.makePoint(coordinates[i]);
  135. if(i == 0){
  136. str = 'M' + point.join(',');
  137. }else{
  138. str = str + 'L' + point.join(',');
  139. }
  140. }
  141. return str;
  142. },
  143. "Polygon": function(coordinates){
  144. var str = '',
  145. i = 0,
  146. len = coordinates.length;
  147. for(; i < len; i++){
  148. str = str + convertor.LineString(coordinates[i]) + 'z';
  149. }
  150. return str;
  151. },
  152. "MultiPoint": function(coordinates){
  153. var arr = [],
  154. i = 0,
  155. len = coordinates.length;
  156. for(; i < len; i++){
  157. arr.push(convertor.Point(coordinates[i]));
  158. }
  159. return arr;
  160. },
  161. "MultiLineString": function(coordinates){
  162. var str = '',
  163. i = 0,
  164. len = coordinates.length;
  165. for(; i < len; i++){
  166. str += convertor.LineString(coordinates[i]);
  167. }
  168. return str;
  169. },
  170. "MultiPolygon": function(coordinates){
  171. var str = '',
  172. i = 0,
  173. len = coordinates.length;
  174. for(; i < len; i++){
  175. str += convertor.Polygon(coordinates[i]);
  176. }
  177. return str;
  178. }
  179. };
  180. function json2path(json, obj){
  181. var
  182. shapes = json.features,
  183. shapeType,
  184. shapeCoordinates,
  185. str,
  186. geometries,
  187. pathArray = [],
  188. i, j,
  189. len, len2,
  190. val,
  191. shape;
  192. convertor.scale = null;
  193. convertor.offset = null;
  194. if((!obj.scale || !obj.offset) && !json.srcSize){
  195. parseSrcSize(json);
  196. }
  197. if(!obj.offset){
  198. obj.offset = {
  199. x: json.srcSize.left,
  200. y: json.srcSize.top
  201. };
  202. }else if(json.srcSize){
  203. obj.offset.x = json.srcSize.left + obj.config.offset.x;
  204. obj.offset.y = json.srcSize.top + obj.config.offset.y;
  205. }
  206. if(!obj.scale){
  207. var temx = obj.width / json.srcSize.width,
  208. temy = obj.height / json.srcSize.height;
  209. temx > temy ? temx = temy : temy = temx;
  210. temx = temy * 0.73;
  211. obj.scale = {
  212. x: temx,
  213. y: temy
  214. };
  215. }
  216. convertor.scale = obj.scale;
  217. convertor.offset = obj.offset;
  218. for(i = 0, len = shapes.length; i < len; i++){
  219. shape = shapes[i];
  220. if(shape.type == 'Feature'){
  221. pushApath(shape.geometry, shape);
  222. }else if(shape.type = 'GeometryCollection'){
  223. geometries = shape.geometries;
  224. for(j = 0, len2 = geometries.length; j < len2; j++){
  225. val = geometries[j];
  226. pushApath(val, val);
  227. }
  228. }
  229. }
  230. function pushApath(gm, shape){
  231. shapeType = gm.type;
  232. shapeCoordinates = gm.coordinates;
  233. str = convertor[shapeType](shapeCoordinates);
  234. pathArray.push({
  235. type: shapeType,
  236. path: str,
  237. properties: shape.properties,
  238. id: shape.id
  239. });
  240. }
  241. return pathArray;
  242. }
  243. var GeoMap = function(cfg){
  244. var self = this,
  245. defaultCfg = {
  246. container: 'body',
  247. offset: null,
  248. scale: null,
  249. mapStyle: {
  250. 'fill': '#fff',
  251. 'stroke': '#999',
  252. 'stroke-width': 0.7
  253. },
  254. background:'#fff',
  255. sideSize: 4
  256. };
  257. $.extend(true, defaultCfg, cfg);
  258. self.container = $(defaultCfg.container);
  259. self.offset = defaultCfg.offset;
  260. self.scale = defaultCfg.scale;
  261. if(self.container.length == 0){
  262. throw new Error('map container is not defined!');
  263. }
  264. self.width = defaultCfg.width || self.container.width();
  265. self.height = defaultCfg.height || self.container.height();
  266. self.canvas = new Raphael(self.container.get(0), self.width, self.height);
  267. self.shapes = self.canvas.set();
  268. self.config = defaultCfg;
  269. self.paths = null;
  270. };
  271. GeoMap.arrow = function(ox, oy, tx, ty, aw){
  272. if(Object.prototype.toString.call(aw) == '[object Object]'){
  273. var L = aw.size || 10;
  274. var cc = aw.rad || 12;
  275. }else{
  276. var L = 10;
  277. var cc = 12;
  278. }
  279. var pi = Math.PI;
  280. var c = pi / cc;
  281. var tan_c = Math.tan(c);
  282. var dx = tx - ox;
  283. var dy = ty - oy;
  284. var L0 = Math.sqrt(dx * dx + dy * dy);
  285. var p1x = (L * dy * tan_c * (-1) + L0 * dx + L0 * ox - L * dx) / L0;
  286. var p1y = (L * dx * tan_c + L0 * dy + L0 * oy - L * dy) / L0;
  287. var p2x = (L * dy * tan_c + L0 * dx + L0 * ox -L * dx) / L0;
  288. var p2y = (L * dx * tan_c * (-1) + L0 * dy + L0 * oy - L * dy) / L0;
  289. return 'M' + tx + ',' + ty + 'L' + p1x + ',' + p1y + 'L' + p2x + ',' + p2y + 'z';
  290. };
  291. GeoMap.prototype = {
  292. constructor: GeoMap,
  293. clear: function(){
  294. this.offset = null;
  295. this.scale = null;
  296. this.shapes.remove();
  297. },
  298. load: function(json){
  299. this.paths = json2path(json, this);
  300. },
  301. render: function(){
  302. var self = this,
  303. shapes = self.shapes,
  304. paths = self.paths,
  305. canvas = self.canvas,
  306. config = self.config,
  307. style = config.mapStyle,
  308. aPath = null, i, len, currentPath;
  309. for(i = 0, len = paths.length; i < len; i++){
  310. currentPath = paths[i];
  311. if(currentPath.type == 'point' || currentPath.type == 'MultiPoint'){
  312. //TODO
  313. }else{
  314. aPath = canvas.path(currentPath.path).data({'properties': currentPath.properties, 'id': currentPath.id}).attr(style);
  315. }
  316. shapes.push(aPath);
  317. }
  318. },
  319. /*!
  320. 将平面上的一个点的坐标,转换成实际经纬度坐标
  321. */
  322. getGeoPosition: function(p){
  323. var self = this,
  324. x = p[0],
  325. y = p[1];
  326. x = x / self.scale.x + self.offset.x - 170;
  327. x = x > 180 ? x - 360 : x;
  328. y = 90 - (y / self.scale.y + self.offset.y);
  329. return [x, y];
  330. },
  331. geo2pos: function(p){
  332. var self = this;
  333. convertor.offset = self.offset;
  334. convertor.scale = self.scale;
  335. if(typeof p.x != 'number') p.x *= 1;
  336. if(typeof p.y != 'number') p.y *= 1;
  337. p = convertor.makePoint([p.x, p.y]);
  338. return p;
  339. },
  340. setPoint: function(p){
  341. // 点的默认样式
  342. var self = this,
  343. a = {
  344. "x": 0,
  345. "y": 0,
  346. "r": 1,
  347. "opacity": 0.5,
  348. "fill": "#238CC3",
  349. "stroke": "#238CC3",
  350. "stroke-width": 0,
  351. "stroke-linejoin": "round"
  352. };
  353. p = self.geo2pos(p);
  354. p = {x:p[0],y:p[1]};
  355. $.extend(true, a, p);
  356. return self.canvas.circle(p.x, p.y, a.r).attr(a);
  357. },
  358. //TODO:
  359. line: function(type, points){
  360. // type: 'geo' or 'plane' or 'points', default 'geo'
  361. // points: [{x:1,y:1}, {x:2, y:2}, ...]
  362. if(typeof type !== 'string'){
  363. points = type;
  364. type = 'geo';
  365. }
  366. var makePathString = type == 'geo' ?
  367. function(p){
  368. return p.x + ',' + p.y;
  369. } : function(p){
  370. p = geo2pos(p);
  371. return p[0] + ',' + p[1];
  372. };
  373. var str = '';
  374. var op = '';
  375. $.each(points, function(k, v){
  376. op = k == 0 ? 'M' : 'L';
  377. str += op + makePathString(v);
  378. });
  379. return this.canvas.path(str);
  380. },
  381. drawLineByMapPoints: function(pointsArray, aw){
  382. var str = '';
  383. var op = '';
  384. var self = this;
  385. var set = self.canvas.set();
  386. $.each(pointsArray, function(k, v){
  387. op = k == 0 ? 'M' : 'L';
  388. str += op + v.attrs.x + ',' + v.attrs.y;
  389. //箭头
  390. if(op == 'L' && aw){
  391. var ot = pointsArray[k - 1].attrs;
  392. var ox = ot.x;
  393. var oy = ot.y
  394. var tt = v.attrs;
  395. var tx = tt.x;
  396. var ty = tt.y;
  397. var as = GeoMap.arrow(ox, oy, tx, ty, aw);
  398. set.push(self.canvas.path(as));
  399. }
  400. });
  401. var color = '#08c';
  402. if(Object.prototype.toString.call(aw) == '[object Object]'){
  403. color = aw.color;
  404. }
  405. return {
  406. arrows: set.attr({
  407. stroke: color,
  408. fill: color
  409. }),
  410. lines: self.canvas.path(str).attr({
  411. stroke: color
  412. })
  413. };
  414. },
  415. showRegionName: function(){
  416. var self = this;
  417. var shapes = self.shapes;
  418. shapes.forEach(function(v){
  419. var properties = v.data('properties');
  420. var name = properties.name;
  421. var cp = properties.cp || [0, 0];
  422. var point = self.geo2pos({
  423. x: cp[0],
  424. y: cp[1]
  425. });
  426. if(name == '广东'){
  427. point[1] -= 5;
  428. }
  429. if(name == '澳门'){
  430. point[0] -=5;
  431. }
  432. self.canvas.text(point[0], point[1], name).attr({
  433. 'font-size':'12px',
  434. 'fill':'#000'
  435. });
  436. });
  437. }
  438. };
  439. //TODO: heat map
  440. GeoMap.isPointInsidePath = function(pts, pt) {
  441. var i,
  442. j,
  443. k,
  444. n = pts.length,
  445. wn = 0;
  446. for(i = n-1, j = 0; j < n; i = j, j++) {
  447. k = (pt[0] - pts[i][1]) * (pts[j][2] - pts[i][2]) - (pts[j][1] - pts[i][1]) * (pt[1] - pts[i][2]);
  448. if((pt[1] >= pts[i][2] && pt[1] <= pts[j][2])||(pt[1] <= pts[i][2] && pt[1] >= pts[j][2])) {
  449. if( k < 0){
  450. wn++;
  451. }else if(k > 0){
  452. wn--;
  453. }else{
  454. if( (pt[1] <= pts[i][2] && pt[1] >= pts[j][2] && pt[0] <= pts[i][1] && pt[0] >= pts[j][1]) ||
  455. (pt[1] <= pts[i][2] && pt[1] >= pts[j][2] && pt[0] >= pts[i][1] && pt[0] <= pts[j][1]) ||
  456. (pt[1] >= pts[i][2] && pt[1] <= pts[j][2] && pt[0] <= pts[i][1] && pt[0] >= pts[j][1]) ||
  457. (pt[1] >= pts[i][2] && pt[1] <= pts[j][2] && pt[0] >= pts[i][1] && pt[0] <= pts[j][1]) ){
  458. return 0; //点在多边形边界上
  459. }
  460. }
  461. }
  462. }
  463. if(wn == 0){
  464. return 1; //点在多边形外部
  465. }else{
  466. return -1; //点在多边形内部
  467. }
  468. };
  469. //计算耗时较长
  470. GeoMap.prototype.mosaic = function(proc) { // proc => 是否使用worker线程
  471. var self = this,
  472. shapes = self.shapes,
  473. paths = self.paths,
  474. canvas = self.canvas,
  475. config = self.config,
  476. style = config.mapStyle,
  477. offset = self.offset,
  478. scale = self.scale,
  479. background = config.background,
  480. width = self.width,
  481. height = self.height,
  482. mapleft = convertor.xmin,
  483. maptop = convertor.ymin,
  484. mapwidth = convertor.xmax - convertor.xmin,
  485. mapheight = convertor.ymax - convertor.ymin,
  486. aPath = null,
  487. sideSize = config.sideSize,
  488. halfSide = sideSize / 2,
  489. back, i, len, currentPath;
  490. for (i = 0, len = paths.length; i < len; i++) {
  491. currentPath = paths[i];
  492. if (currentPath.type == 'point' || currentPath.type == 'MultiPoint') {
  493. //TODO
  494. } else {
  495. aPath = canvas.path(currentPath.path).data({
  496. 'ps': currentPath.path,
  497. 'properties': currentPath.properties,
  498. 'id': currentPath.id
  499. }).attr({
  500. 'fill': background,
  501. 'stroke-width': 0
  502. });
  503. }
  504. shapes.push(aPath);
  505. }
  506. var arrPos = [],
  507. bbox,
  508. startX,
  509. startY,
  510. i,
  511. j,
  512. temX,
  513. temY;
  514. $.each(shapes, function(k, v){
  515. bbox = v.getBBox();
  516. startX = ~~( (bbox.x - halfSide) / sideSize ) * sideSize;
  517. startY = ~~( (bbox.y- halfSide) / sideSize ) * sideSize;
  518. for(i = 0; i * sideSize + startX <= bbox.x2; i++){
  519. temX = i * sideSize + startX;
  520. for(j = 0; j * sideSize + startY <= bbox.y2; j++){
  521. temY = j * sideSize + startY;
  522. if(GeoMap.isPointInsidePath(v.attrs.path, [temX, temY]) != 1){
  523. arrPos.push([temX, temY]);
  524. }
  525. }
  526. }
  527. });
  528. arrPos.forEach(function(v){
  529. canvas.rect(v[0] - 1, v[1] - 1, sideSize * 0.8, sideSize * 0.8).attr(style);
  530. //.scale(scale.x, scale.y, offset.x, offset.y);
  531. });
  532. };
  533. GeoMap.version = version;
  534. this.GeoMap = GeoMap;
  535. })(jQuery);