geomap-0.5.2.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494
  1. /*
  2. * GeoMap v0.5.2
  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.2"
  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.prototype = {
  272. clear: function(){
  273. this.offset = null;
  274. this.scale = null;
  275. this.shapes.remove();
  276. },
  277. load: function(json){
  278. this.paths = json2path(json, this);
  279. },
  280. render: function(){
  281. var self = this,
  282. shapes = self.shapes,
  283. paths = self.paths,
  284. canvas = self.canvas,
  285. config = self.config,
  286. style = config.mapStyle,
  287. aPath = null, i, len, currentPath;
  288. for(i = 0, len = paths.length; i < len; i++){
  289. currentPath = paths[i];
  290. if(currentPath.type == 'point' || currentPath.type == 'MultiPoint'){
  291. //TODO
  292. }else{
  293. aPath = canvas.path(currentPath.path).data({'properties': currentPath.properties, 'id': currentPath.id}).attr(style);
  294. }
  295. shapes.push(aPath);
  296. }
  297. },
  298. /*!
  299. 将平面上的一个点的坐标,转换成实际经纬度坐标
  300. */
  301. getGeoPosition: function(p){
  302. var self = this,
  303. x = p[0],
  304. y = p[1];
  305. x = x / self.scale.x + self.offset.x - 170;
  306. x = x > 180 ? x - 360 : x;
  307. y = 90 - (y / self.scale.y + self.offset.y);
  308. return [x, y];
  309. },
  310. geo2pos: function(p){
  311. var self = this;
  312. convertor.offset = self.offset;
  313. convertor.scale = self.scale;
  314. if(typeof p.x != 'number') p.x *= 1;
  315. if(typeof p.y != 'number') p.y *= 1;
  316. p = convertor.makePoint([p.x, p.y]);
  317. return p;
  318. },
  319. setPoint: function(p){
  320. // 点的默认样式
  321. var self = this,
  322. a = {
  323. "x": 0,
  324. "y": 0,
  325. "r": 1,
  326. "opacity": 0.5,
  327. "fill": "#238CC3",
  328. "stroke": "#238CC3",
  329. "stroke-width": 0,
  330. "stroke-linejoin": "round"
  331. };
  332. p = self.geo2pos(p);
  333. p = {x:p[0],y:p[1]};
  334. $.extend(true, a, p);
  335. return self.canvas.circle(p.x, p.y, a.r).attr(a);
  336. },
  337. drawLineByMapPoints: function(pointsArray){
  338. var str = '';
  339. var op = ''
  340. $.each(pointsArray, function(k, v){
  341. op = k == 0 ? 'M' : 'L';
  342. str += op + v.attrs.x + ',' + v.attrs.y;
  343. });
  344. return this.canvas.path(str);
  345. }
  346. };
  347. //TODO: heat map
  348. GeoMap.isPointInsidePath = function(pts, pt) {
  349. var i,
  350. j,
  351. k,
  352. n = pts.length,
  353. wn = 0;
  354. for(i = n-1, j = 0; j < n; i = j, j++) {
  355. k = (pt[0] - pts[i][1]) * (pts[j][2] - pts[i][2]) - (pts[j][1] - pts[i][1]) * (pt[1] - pts[i][2]);
  356. if((pt[1] >= pts[i][2] && pt[1] <= pts[j][2])||(pt[1] <= pts[i][2] && pt[1] >= pts[j][2])) {
  357. if( k < 0){
  358. wn++;
  359. }else if(k > 0){
  360. wn--;
  361. }else{
  362. if( (pt[1] <= pts[i][2] && pt[1] >= pts[j][2] && pt[0] <= pts[i][1] && pt[0] >= pts[j][1]) ||
  363. (pt[1] <= pts[i][2] && pt[1] >= pts[j][2] && pt[0] >= pts[i][1] && pt[0] <= pts[j][1]) ||
  364. (pt[1] >= pts[i][2] && pt[1] <= pts[j][2] && pt[0] <= pts[i][1] && pt[0] >= pts[j][1]) ||
  365. (pt[1] >= pts[i][2] && pt[1] <= pts[j][2] && pt[0] >= pts[i][1] && pt[0] <= pts[j][1]) ){
  366. return 0; //点在多边形边界上
  367. }
  368. }
  369. }
  370. }
  371. if(wn == 0){
  372. return 1; //点在多边形外部
  373. }else{
  374. return -1; //点在多边形内部
  375. }
  376. };
  377. //计算耗时较长
  378. GeoMap.prototype.mosaic = function(proc) { // proc => 是否使用worker线程
  379. var self = this,
  380. shapes = self.shapes,
  381. paths = self.paths,
  382. canvas = self.canvas,
  383. config = self.config,
  384. style = config.mapStyle,
  385. offset = self.offset,
  386. scale = self.scale,
  387. background = config.background,
  388. width = self.width,
  389. height = self.height,
  390. mapleft = convertor.xmin,
  391. maptop = convertor.ymin,
  392. mapwidth = convertor.xmax - convertor.xmin,
  393. mapheight = convertor.ymax - convertor.ymin,
  394. aPath = null,
  395. sideSize = config.sideSize,
  396. halfSide = sideSize / 2,
  397. back, i, len, currentPath;
  398. for (i = 0, len = paths.length; i < len; i++) {
  399. currentPath = paths[i];
  400. if (currentPath.type == 'point' || currentPath.type == 'MultiPoint') {
  401. //TODO
  402. } else {
  403. aPath = canvas.path(currentPath.path).data({
  404. 'ps': currentPath.path,
  405. 'properties': currentPath.properties,
  406. 'id': currentPath.id
  407. }).attr({
  408. 'fill': background,
  409. 'stroke-width': 0
  410. });
  411. }
  412. shapes.push(aPath);
  413. }
  414. var arrPos = [],
  415. bbox,
  416. startX,
  417. startY,
  418. i,
  419. j,
  420. temX,
  421. temY;
  422. $.each(shapes, function(k, v){
  423. bbox = v.getBBox();
  424. startX = ~~( (bbox.x - halfSide) / sideSize ) * sideSize;
  425. startY = ~~( (bbox.y- halfSide) / sideSize ) * sideSize;
  426. for(i = 0; i * sideSize + startX <= bbox.x2; i++){
  427. temX = i * sideSize + startX;
  428. for(j = 0; j * sideSize + startY <= bbox.y2; j++){
  429. temY = j * sideSize + startY;
  430. if(GeoMap.isPointInsidePath(v.attrs.path, [temX, temY]) != 1){
  431. arrPos.push([temX, temY]);
  432. }
  433. }
  434. }
  435. });
  436. arrPos.forEach(function(v){
  437. canvas.rect(v[0] - 1, v[1] - 1, sideSize * 0.8, sideSize * 0.8).attr(style);
  438. //.scale(scale.x, scale.y, offset.x, offset.y);
  439. });
  440. };
  441. GeoMap.version = version;
  442. this.GeoMap = GeoMap;
  443. })(jQuery);