├── README.md ├── Demo ├── layerswitch │ ├── ol3-layerswitcher.css │ └── ol3-layerswitcher.js ├── Index.html └── CoordinateTransform.js └── bin └── CoordinateTransform.js /README.md: -------------------------------------------------------------------------------- 1 | # CoordinateTransform 2 | 用于将少量点叠加各个在线网络底图纠偏库 3 | 4 | 在做WebGIS开发时,常常使用在线网络底图,由于我国的特殊国情,在线底图都是实行加密措施,导致我们想把自己采集的经纬度数据在地图展示时,令人苦恼的是坐标“飞了”。显而易见的思路是,根据网络底图的偏移,对自己的采集数据加减偏移量,从而达到比较好的叠加。但更令人痛苦的是,不同的底图,如百度和谷歌,和天地图等等不同地图,偏移量却又各不相同,如之奈何?本文作者对常用地图偏移总结如下: 5 | - ** 火星坐标系组 高德地图,谷歌地图,ESRI的部分服务底图等都是使用火星坐标系(谷歌地图中国数据由高德提供,两者数据本质其实是一模一样的) 6 | - ** 几乎无偏移组 天地图,OpenStreetMap是几乎没有偏移的,也就是我们的坐标不用加减偏移,直接转墨卡托就可以叠加上 7 | - ** 万恶的百度 百度地图是最最恶心的,一般,先将自己无偏移的坐标转火星坐标系,再由火星转百度经纬度,而你叠加的一定是百度的墨卡托投影的地图,百度墨卡托不等于我们常用的墨卡托,所以仍然是特例,将百度经纬转百度墨卡托。幸运的是,我们这个库都已经能支持了。 8 | 本文于南京市中心采集某一个坐标,根据不同底图的切换,验证我们的纠偏js库的正确性。(注意:本库只用于叠加底图显示,不等于严格坐标解密,基本满足业务需求) 9 | 10 | ![Alt 天地图底图](http://freegis.github.io/images/demo/tianditu.png "天地图") 11 | ![Alt 百度底图](http://freegis.github.io/images/demo/baidu.png "百度地图") 12 | ![Alt 谷歌底图](http://freegis.github.io/images/demo/google.png "谷歌地图") 13 | 14 | 例子请参考[ol3坐标纠正][1]. 15 | 本js代码只适用于少量点在前台叠加,如果是点线面等复杂图层且存放在PostGIS中,应进行全部图层批量转换。PostGIS转换库地址:[postgis_LayerTransform][2]. 16 | 17 | [1]: http://freegis.github.io/examples/CoordinateTransform.html 18 | [2]: https://github.com/FreeGIS/postgis_LayerTransform 19 | 20 | -------------------------------------------------------------------------------- /Demo/layerswitch/ol3-layerswitcher.css: -------------------------------------------------------------------------------- 1 | .layer-switcher.shown.ol-control { 2 | background-color: transparent; 3 | } 4 | 5 | .layer-switcher.shown.ol-control:hover { 6 | background-color: transparent; 7 | } 8 | 9 | .layer-switcher { 10 | position: absolute; 11 | top: 3.5em; 12 | right: 0.5em; 13 | text-align: left; 14 | } 15 | 16 | .layer-switcher.shown { 17 | bottom: 3em; 18 | } 19 | 20 | .layer-switcher .panel { 21 | padding: 0 1em 0 0; 22 | margin: 0; 23 | border: 4px solid #eee; 24 | border-radius: 4px; 25 | background-color: white; 26 | display: none; 27 | max-height: 100%; 28 | overflow-y: auto; 29 | } 30 | 31 | .layer-switcher.shown .panel { 32 | display: block; 33 | } 34 | 35 | .layer-switcher button { 36 | float: right; 37 | width: 38px; 38 | height: 38px; 39 | background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAACE1BMVEX///8A//8AgICA//8AVVVAQID///8rVVVJtttgv98nTmJ2xNgkW1ttyNsmWWZmzNZYxM4gWGgeU2JmzNNr0N1Rwc0eU2VXxdEhV2JqytQeVmMhVmNoydUfVGUgVGQfVGQfVmVqy9hqy9dWw9AfVWRpydVry9YhVmMgVGNUw9BrytchVWRexdGw294gVWQgVmUhVWPd4N6HoaZsy9cfVmQgVGRrytZsy9cgVWQgVWMgVWRsy9YfVWNsy9YgVWVty9YgVWVry9UgVWRsy9Zsy9UfVWRsy9YgVWVty9YgVWRty9Vsy9aM09sgVWRTws/AzM0gVWRtzNYgVWRuy9Zsy9cgVWRGcHxty9bb5ORbxdEgVWRty9bn6OZTws9mydRfxtLX3Nva5eRix9NFcXxOd4JPeINQeIMiVmVUws9Vws9Vw9BXw9BYxNBaxNBbxNBcxdJexdElWWgmWmhjyNRlx9IqXGtoipNpytVqytVryNNrytZsjZUuX210k5t1y9R2zNR3y9V4lp57zth9zdaAnKOGoaeK0NiNpquV09mesrag1tuitbmj1tuj19uktrqr2d2svcCu2d2xwMO63N+7x8nA3uDC3uDFz9DK4eHL4eLN4eIyYnDX5OM5Z3Tb397e4uDf4uHf5uXi5ePi5+Xj5+Xk5+Xm5+Xm6OY6aHXQ19fT4+NfhI1Ww89gx9Nhx9Nsy9ZWw9Dpj2abAAAAWnRSTlMAAQICAwQEBgcIDQ0ODhQZGiAiIyYpKywvNTs+QklPUlNUWWJjaGt0dnd+hIWFh4mNjZCSm6CpsbW2t7nDzNDT1dje5efr7PHy9PT29/j4+Pn5+vr8/f39/f6DPtKwAAABTklEQVR4Xr3QVWPbMBSAUTVFZmZmhhSXMjNvkhwqMzMzMzPDeD+xASvObKePPa+ffHVl8PlsnE0+qPpBuQjVJjno6pZpSKXYl7/bZyFaQxhf98hHDKEppwdWIW1frFnrxSOWHFfWesSEWC6R/P4zOFrix3TzDFLlXRTR8c0fEEJ1/itpo7SVO9Jdr1DVxZ0USyjZsEY5vZfiiAC0UoTGOrm9PZLuRl8X+Dq1HQtoFbJZbv61i+Poblh/97TC7n0neCcK0ETNUrz1/xPHf+DNAW9Ac6t8O8WH3Vp98f5lCaYKAOFZMLyHL4Y0fe319idMNgMMp+zWVSybUed/+/h7I4wRAG1W6XDy4XmjR9HnzvDRZXUAYDFOhC1S/Hh+fIXxen+eO+AKqbs+wAo30zDTDvDxKoJN88sjUzDFAvBzEUGFsnADoIvAJzoh2BZ8sner+Ke/vwECuQAAAABJRU5ErkJggg==') /*logo.png*/; 40 | background-repeat: no-repeat; 41 | background-position: 2px; 42 | background-color: white; 43 | border: none; 44 | } 45 | 46 | .layer-switcher.shown button { 47 | display: none; 48 | } 49 | 50 | .layer-switcher button:focus, .layer-switcher button:hover { 51 | background-color: white; 52 | } 53 | 54 | .layer-switcher ul { 55 | padding-left: 1em; 56 | list-style: none; 57 | } 58 | 59 | .layer-switcher li.group { 60 | padding-top: 5px; 61 | } 62 | 63 | .layer-switcher li.group > label { 64 | font-weight: bold; 65 | } 66 | 67 | .layer-switcher li.layer { 68 | display: table; 69 | } 70 | 71 | .layer-switcher li.layer label, .layer-switcher li.layer input { 72 | display: table-cell; 73 | vertical-align: sub; 74 | } 75 | 76 | .layer-switcher input { 77 | margin: 4px; 78 | } 79 | 80 | .layer-switcher.touch ::-webkit-scrollbar { 81 | width: 4px; 82 | } 83 | 84 | .layer-switcher.touch ::-webkit-scrollbar-track { 85 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3); 86 | border-radius: 10px; 87 | } 88 | 89 | .layer-switcher.touch ::-webkit-scrollbar-thumb { 90 | border-radius: 10px; 91 | -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.5); 92 | } 93 | -------------------------------------------------------------------------------- /Demo/Index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 坐标转换 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 155 | 156 | -------------------------------------------------------------------------------- /Demo/layerswitch/ol3-layerswitcher.js: -------------------------------------------------------------------------------- 1 | /** 2 | * OpenLayers 3 Layer Switcher Control. 3 | * See [the examples](./examples) for usage. 4 | * @constructor 5 | * @extends {ol.control.Control} 6 | * @param {Object} opt_options Control options, extends olx.control.ControlOptions adding: 7 | * **`tipLabel`** `String` - the button tooltip. 8 | */ 9 | ol.control.LayerSwitcher = function(opt_options) { 10 | 11 | var options = opt_options || {}; 12 | 13 | var tipLabel = options.tipLabel ? 14 | options.tipLabel : 'Legend'; 15 | 16 | this.mapListeners = []; 17 | 18 | this.hiddenClassName = 'ol-unselectable ol-control layer-switcher'; 19 | if (ol.control.LayerSwitcher.isTouchDevice_()) { 20 | this.hiddenClassName += ' touch'; 21 | } 22 | this.shownClassName = this.hiddenClassName + ' shown'; 23 | 24 | var element = document.createElement('div'); 25 | element.className = this.hiddenClassName; 26 | 27 | var button = document.createElement('button'); 28 | button.setAttribute('title', tipLabel); 29 | element.appendChild(button); 30 | 31 | this.panel = document.createElement('div'); 32 | this.panel.className = 'panel'; 33 | element.appendChild(this.panel); 34 | ol.control.LayerSwitcher.enableTouchScroll_(this.panel); 35 | 36 | var this_ = this; 37 | 38 | button.onmouseover = function(e) { 39 | this_.showPanel(); 40 | }; 41 | 42 | button.onclick = function(e) { 43 | e = e || window.event; 44 | this_.showPanel(); 45 | e.preventDefault(); 46 | }; 47 | 48 | this_.panel.onmouseout = function(e) { 49 | e = e || window.event; 50 | if (!this_.panel.contains(e.toElement || e.relatedTarget)) { 51 | this_.hidePanel(); 52 | } 53 | }; 54 | 55 | ol.control.Control.call(this, { 56 | element: element, 57 | target: options.target 58 | }); 59 | 60 | }; 61 | 62 | ol.inherits(ol.control.LayerSwitcher, ol.control.Control); 63 | 64 | /** 65 | * Show the layer panel. 66 | */ 67 | ol.control.LayerSwitcher.prototype.showPanel = function() { 68 | if (this.element.className != this.shownClassName) { 69 | this.element.className = this.shownClassName; 70 | this.renderPanel(); 71 | } 72 | }; 73 | 74 | /** 75 | * Hide the layer panel. 76 | */ 77 | ol.control.LayerSwitcher.prototype.hidePanel = function() { 78 | if (this.element.className != this.hiddenClassName) { 79 | this.element.className = this.hiddenClassName; 80 | } 81 | }; 82 | 83 | /** 84 | * Re-draw the layer panel to represent the current state of the layers. 85 | */ 86 | ol.control.LayerSwitcher.prototype.renderPanel = function() { 87 | 88 | this.ensureTopVisibleBaseLayerShown_(); 89 | 90 | while(this.panel.firstChild) { 91 | this.panel.removeChild(this.panel.firstChild); 92 | } 93 | 94 | var ul = document.createElement('ul'); 95 | this.panel.appendChild(ul); 96 | this.renderLayers_(this.getMap(), ul); 97 | 98 | }; 99 | 100 | /** 101 | * Set the map instance the control is associated with. 102 | * @param {ol.Map} map The map instance. 103 | */ 104 | ol.control.LayerSwitcher.prototype.setMap = function(map) { 105 | // Clean up listeners associated with the previous map 106 | for (var i = 0, key; i < this.mapListeners.length; i++) { 107 | this.getMap().unByKey(this.mapListeners[i]); 108 | } 109 | this.mapListeners.length = 0; 110 | // Wire up listeners etc. and store reference to new map 111 | ol.control.Control.prototype.setMap.call(this, map); 112 | if (map) { 113 | var this_ = this; 114 | this.mapListeners.push(map.on('pointerdown', function() { 115 | this_.hidePanel(); 116 | })); 117 | this.renderPanel(); 118 | } 119 | }; 120 | 121 | /** 122 | * Ensure only the top-most base layer is visible if more than one is visible. 123 | * @private 124 | */ 125 | ol.control.LayerSwitcher.prototype.ensureTopVisibleBaseLayerShown_ = function() { 126 | var lastVisibleBaseLyr; 127 | ol.control.LayerSwitcher.forEachRecursive(this.getMap(), function(l, idx, a) { 128 | if (l.get('type') === 'base' && l.getVisible()) { 129 | lastVisibleBaseLyr = l; 130 | } 131 | }); 132 | if (lastVisibleBaseLyr) this.setVisible_(lastVisibleBaseLyr, true); 133 | }; 134 | 135 | /** 136 | * Toggle the visible state of a layer. 137 | * Takes care of hiding other layers in the same exclusive group if the layer 138 | * is toggle to visible. 139 | * @private 140 | * @param {ol.layer.Base} The layer whos visibility will be toggled. 141 | */ 142 | ol.control.LayerSwitcher.prototype.setVisible_ = function(lyr, visible) { 143 | var map = this.getMap(); 144 | lyr.setVisible(visible); 145 | if (visible && lyr.get('type') === 'base') { 146 | // Hide all other base layers regardless of grouping 147 | ol.control.LayerSwitcher.forEachRecursive(map, function(l, idx, a) { 148 | if (l != lyr && l.get('type') === 'base') { 149 | l.setVisible(false); 150 | } 151 | }); 152 | } 153 | }; 154 | 155 | /** 156 | * Render all layers that are children of a group. 157 | * @private 158 | * @param {ol.layer.Base} lyr Layer to be rendered (should have a title property). 159 | * @param {Number} idx Position in parent group list. 160 | */ 161 | ol.control.LayerSwitcher.prototype.renderLayer_ = function(lyr, idx) { 162 | 163 | var this_ = this; 164 | 165 | var li = document.createElement('li'); 166 | 167 | var lyrTitle = lyr.get('title'); 168 | var lyrId = ol.control.LayerSwitcher.uuid(); 169 | 170 | var label = document.createElement('label'); 171 | 172 | if (lyr.getLayers && !lyr.get('combine')) { 173 | 174 | li.className = 'group'; 175 | label.innerHTML = lyrTitle; 176 | li.appendChild(label); 177 | var ul = document.createElement('ul'); 178 | li.appendChild(ul); 179 | 180 | this.renderLayers_(lyr, ul); 181 | 182 | } else { 183 | 184 | li.className = 'layer'; 185 | var input = document.createElement('input'); 186 | if (lyr.get('type') === 'base') { 187 | input.type = 'radio'; 188 | input.name = 'base'; 189 | } else { 190 | input.type = 'checkbox'; 191 | } 192 | input.id = lyrId; 193 | input.checked = lyr.get('visible'); 194 | input.onchange = function(e) { 195 | this_.setVisible_(lyr, e.target.checked); 196 | }; 197 | li.appendChild(input); 198 | 199 | label.htmlFor = lyrId; 200 | label.innerHTML = lyrTitle; 201 | li.appendChild(label); 202 | 203 | } 204 | 205 | return li; 206 | 207 | }; 208 | 209 | /** 210 | * Render all layers that are children of a group. 211 | * @private 212 | * @param {ol.layer.Group} lyr Group layer whos children will be rendered. 213 | * @param {Element} elm DOM element that children will be appended to. 214 | */ 215 | ol.control.LayerSwitcher.prototype.renderLayers_ = function(lyr, elm) { 216 | var lyrs = lyr.getLayers().getArray().slice().reverse(); 217 | for (var i = 0, l; i < lyrs.length; i++) { 218 | l = lyrs[i]; 219 | if (l.get('title')) { 220 | elm.appendChild(this.renderLayer_(l, i)); 221 | } 222 | } 223 | }; 224 | 225 | /** 226 | * **Static** Call the supplied function for each layer in the passed layer group 227 | * recursing nested groups. 228 | * @param {ol.layer.Group} lyr The layer group to start iterating from. 229 | * @param {Function} fn Callback which will be called for each `ol.layer.Base` 230 | * found under `lyr`. The signature for `fn` is the same as `ol.Collection#forEach` 231 | */ 232 | ol.control.LayerSwitcher.forEachRecursive = function(lyr, fn) { 233 | lyr.getLayers().forEach(function(lyr, idx, a) { 234 | fn(lyr, idx, a); 235 | if (lyr.getLayers) { 236 | ol.control.LayerSwitcher.forEachRecursive(lyr, fn); 237 | } 238 | }); 239 | }; 240 | 241 | /** 242 | * Generate a UUID 243 | * @returns {String} UUID 244 | * 245 | * Adapted from http://stackoverflow.com/a/2117523/526860 246 | */ 247 | ol.control.LayerSwitcher.uuid = function() { 248 | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { 249 | var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8); 250 | return v.toString(16); 251 | }); 252 | } 253 | 254 | /** 255 | * @private 256 | * @desc Apply workaround to enable scrolling of overflowing content within an 257 | * element. Adapted from https://gist.github.com/chrismbarr/4107472 258 | */ 259 | ol.control.LayerSwitcher.enableTouchScroll_ = function(elm) { 260 | if(ol.control.LayerSwitcher.isTouchDevice_()){ 261 | var scrollStartPos = 0; 262 | elm.addEventListener("touchstart", function(event) { 263 | scrollStartPos = this.scrollTop + event.touches[0].pageY; 264 | }, false); 265 | elm.addEventListener("touchmove", function(event) { 266 | this.scrollTop = scrollStartPos - event.touches[0].pageY; 267 | }, false); 268 | } 269 | }; 270 | 271 | /** 272 | * @private 273 | * @desc Determine if the current browser supports touch events. Adapted from 274 | * https://gist.github.com/chrismbarr/4107472 275 | */ 276 | ol.control.LayerSwitcher.isTouchDevice_ = function() { 277 | try { 278 | document.createEvent("TouchEvent"); 279 | return true; 280 | } catch(e) { 281 | return false; 282 | } 283 | }; 284 | -------------------------------------------------------------------------------- /Demo/CoordinateTransform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by FreeGIS on 2016/7/18. 3 | * 提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换 4 | * 提供了百度经纬度与百度墨卡托互相转换方法\ 5 | * 本js模块适合用于将适量数量点坐标加减偏移用于叠加不同网络底图 6 | * 本js模块不适合成千上万等大量点做偏移量修正 7 | * 本js模块暂不支持线面转换 8 | */ 9 | 10 | function CoordinateTransform() 11 | { 12 | //定义一些常量 13 | this.PI = 3.1415926535897932384626; 14 | this.x_PI = 3.14159265358979324 * 3000.0 / 180.0; 15 | this.a = 6378245.0; 16 | this.ee = 0.00669342162296594323; 17 | this.EARTHRADIUS = 6370996.81; 18 | this.MCBAND = [12890594.86,8362377.87,5591021,3481989.83,1678043.12,0]; 19 | this.LLBAND = [75,60,45,30,15,0]; 20 | this.MC2LL = [[1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331, 200.9824383106796, -187.2403703815547, 91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2], [-7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289, 96.32687599759846, -1.85204757529826, -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86], [-3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616, 59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37], [-1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591, 40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06], [3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062, 23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4], [2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8, 7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5]]; 21 | this.LL2MC = [[-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340, 26112667856603880, -35149669176653700, 26595700718403920, -10725012454188240, 1800819912950474, 82.5], [0.0008277824516172526, 111320.7020463578, 647795574.6671607, -4082003173.641316, 10774905663.51142, -15171875531.51559, 12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5], [0.00337398766765, 111320.7020202162, 4481351.045890365, -23393751.19931662, 79682215.47186455, -115964993.2797253, 97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5], [0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245, 992013.7397791013, -1221952.21711287, 1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5], [-0.0003441963504368392, 111320.7020576856, 278.2353980772752, 2485758.690035394, 6070.750963243378, 54821.18345352118, 9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5], [-0.0003218135878613132, 111320.7020701615, 0.00369383431289, 823725.6402795718, 0.46104986909093, 2351.343141331292, 1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45]]; 22 | 23 | 24 | 25 | 26 | this._transformlat=function(lng, lat) { 27 | var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); 28 | ret += (20.0 * Math.sin(6.0 * lng * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0; 29 | ret += (20.0 * Math.sin(lat * this.PI) + 40.0 * Math.sin(lat / 3.0 * this.PI)) * 2.0 / 3.0; 30 | ret += (160.0 * Math.sin(lat / 12.0 * this.PI) + 320 * Math.sin(lat * this.PI / 30.0)) * 2.0 / 3.0; 31 | return ret 32 | }; 33 | 34 | this._transformlng=function(lng, lat) { 35 | var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); 36 | ret += (20.0 * Math.sin(6.0 * lng * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0; 37 | ret += (20.0 * Math.sin(lng * this.PI) + 40.0 * Math.sin(lng / 3.0 * this.PI)) * 2.0 / 3.0; 38 | ret += (150.0 * Math.sin(lng / 12.0 * this.PI) + 300.0 * Math.sin(lng / 30.0 * this.PI)) * 2.0 / 3.0; 39 | return ret 40 | }; 41 | /** 42 | * 判断是否在国内,不在国内则不做偏移 43 | * @param lng 44 | * @param lat 45 | * @returns {boolean} 46 | */ 47 | this._out_of_china=function(lng, lat) { 48 | return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false); 49 | }; 50 | 51 | this._getLoop=function(lng,min,max) { 52 | while (lng > max) { 53 | lng -= max - min; 54 | } 55 | while (lng < min) { 56 | lng += max - min; 57 | } 58 | return lng; 59 | }; 60 | 61 | this._getRange=function(lat,min,max) { 62 | if (min != null) { 63 | lat = Math.max(lat, min); 64 | } 65 | if (max != null) { 66 | lat = Math.min(lat, max); 67 | } 68 | return lat; 69 | }; 70 | } 71 | /** 72 | * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 73 | * 即 百度 转 谷歌、高德 74 | * @param bd_lon 75 | * @param bd_lat 76 | * @returns {*[]} 77 | */ 78 | CoordinateTransform.prototype.BD2GCJ=function(bd_lon,bd_lat) { 79 | var x = bd_lon - 0.0065; 80 | var y = bd_lat - 0.006; 81 | var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.x_PI); 82 | var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.x_PI); 83 | var gg_lng = z * Math.cos(theta); 84 | var gg_lat = z * Math.sin(theta); 85 | return [gg_lng, gg_lat]; 86 | } 87 | /** 88 | * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 89 | * 即谷歌、高德 转 百度 90 | * @param lon 91 | * @param lat 92 | * @returns {*[]} 93 | */ 94 | CoordinateTransform.prototype.GCJ2BD=function(lon,lat) { 95 | var z = Math.sqrt(lon * lon + lat * lat) + 0.00002 * Math.sin(lat * this.x_PI); 96 | var theta = Math.atan2(lat, lon) + 0.000003 * Math.cos(lon * this.x_PI); 97 | var bd_lon = z * Math.cos(theta) + 0.0065; 98 | var bd_lat = z * Math.sin(theta) + 0.006; 99 | return [bd_lon, bd_lat]; 100 | } 101 | /** 102 | * WGS84转GCj02 103 | * @param lon 104 | * @param lat 105 | * @returns {*[]} 106 | */ 107 | CoordinateTransform.prototype.WGS2GCJ=function(lon,lat) { 108 | if (this._out_of_china(lon, lat)) { 109 | return [lon, lat] 110 | } else { 111 | var dlat = this._transformlat(lon - 105.0, lat - 35.0); 112 | var dlon = this._transformlng(lon - 105.0, lat - 35.0); 113 | var radlat = lat / 180.0 * this.PI; 114 | var magic = Math.sin(radlat); 115 | magic = 1 - this.ee * magic * magic; 116 | var sqrtmagic = Math.sqrt(magic); 117 | dlat = (dlat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtmagic) * this.PI); 118 | dlon = (dlon * 180.0) / (this.a / sqrtmagic * Math.cos(radlat) * this.PI); 119 | var mglat = lat + dlat; 120 | var mglon = lon + dlon; 121 | return [mglon, mglat]; 122 | } 123 | } 124 | /** 125 | * GCJ02 转换为 WGS84 126 | * @param lon 127 | * @param lat 128 | * @returns {*[]} 129 | */ 130 | CoordinateTransform.prototype.GCJ2WGS=function(lon,lat) { 131 | if (this._out_of_china(lon, lat)) { 132 | return [lon, lat] 133 | } else { 134 | var dlat = this._transformlat(lon - 105.0, lat - 35.0); 135 | var dlon = this._transformlng(lon - 105.0, lat - 35.0); 136 | var radlat = lat / 180.0 * this.PI; 137 | var magic = Math.sin(radlat); 138 | magic = 1 - this.ee * magic * magic; 139 | var sqrtmagic = Math.sqrt(magic); 140 | dlat = (dlat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtmagic) * this.PI); 141 | dlon = (dlon * 180.0) / (this.a / sqrtmagic * Math.cos(radlat) * this.PI); 142 | var mglat = lat + dlat; 143 | var mglon = lon + dlon; 144 | return [lon * 2 - mglon, lat * 2 - mglat]; 145 | } 146 | } 147 | /** 148 | * WGS转百度经纬 149 | * @param lon 150 | * @param lat 151 | * @returns {*[]} 152 | */ 153 | CoordinateTransform.prototype.WGS2BD=function(lon,lat) { 154 | //先由经纬转火星 155 | var coor=this.WGS2GCJ(lon,lat); 156 | //再将火星转百度 157 | coor=this.GCJ2BD(coor[0],coor[1]); 158 | return coor; 159 | } 160 | /** 161 | * 百度经纬转WGS84 162 | * @param lon 163 | * @param lat 164 | * @returns {*[]} 165 | */ 166 | CoordinateTransform.prototype.BD2WGS=function(lon,lat) { 167 | //先由百度转火星 168 | var coor=this.BD2GCJ(lon,lat); 169 | //再将火星转百度 170 | coor=this.GCJ2WGS(coor[0],coor[1]); 171 | return coor; 172 | } 173 | 174 | 175 | /** 176 | * 百度墨卡托转百度经纬度 177 | * @param lng 178 | * @param lat 179 | * @returns {*[]} 180 | */ 181 | CoordinateTransform.prototype.BD_MKT2WGS=function(lng, lat) { 182 | var cF = null; 183 | lng = Math.abs(lng); 184 | lat = Math.abs(lat); 185 | for(var cE = 0; cE < this.MCBAND.length; cE++) { 186 | if (lat >= this.MCBAND[cE]) { 187 | cF = this.MC2LL[cE]; 188 | break; 189 | } 190 | } 191 | lng = cF[0] + cF[1] * Math.abs(lng); 192 | var cC = Math.abs(lat) / cF[9]; 193 | lat = cF[2] + cF[3] * cC + cF[4] * cC * cC + cF[5] * cC * cC * cC + cF[6] * cC * cC * cC * cC + cF[7] * cC * cC * cC * cC * cC + cF[8] * cC * cC * cC * cC * cC * cC; 194 | lng *= (lng < 0 ? -1 : 1); 195 | lat *= (lat < 0 ? -1 : 1); 196 | return [lng,lat]; 197 | } 198 | /** 199 | * 百度经纬度转百度墨卡托 200 | * @param lng 201 | * @param lat 202 | * @returns {*[]} 203 | */ 204 | CoordinateTransform.prototype.BD_WGS2MKT=function(lng, lat) { 205 | var cF = null; 206 | lng = this._getLoop(lng, -180, 180); 207 | lat = this._getRange(lat, -74, 74); 208 | for (var i = 0; i < this.LLBAND.length; i++) { 209 | if (lat >= this.LLBAND[i]) { 210 | cF = this.LL2MC[i]; 211 | break; 212 | } 213 | } 214 | if (cF!=null) { 215 | for (var i = this.LLBAND.length - 1; i >= 0; i--) { 216 | if (lat <= -this.LLBAND[i]) { 217 | cF = this.LL2MC[i]; 218 | break; 219 | } 220 | } 221 | } 222 | lng = cF[0] + cF[1] * Math.abs(lng); 223 | var cC = Math.abs(lat) / cF[9]; 224 | lat = cF[2] + cF[3] * cC + cF[4] * cC * cC + cF[5] * cC * cC * cC + cF[6] * cC * cC * cC * cC + cF[7] * cC * cC * cC * cC * cC + cF[8] * cC * cC * cC * cC * cC * cC; 225 | lng *= (lng < 0 ? -1 : 1); 226 | lat *= (lat < 0 ? -1 : 1); 227 | return [lng,lat]; 228 | } -------------------------------------------------------------------------------- /bin/CoordinateTransform.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by FreeGIS on 2016/7/18. 3 | * 提供了百度坐标(BD09)、国测局坐标(火星坐标,GCJ02)、和WGS84坐标系之间的转换 4 | * 提供了百度经纬度与百度墨卡托互相转换方法\ 5 | * 本js模块适合用于将适量数量点坐标加减偏移用于叠加不同网络底图 6 | * 本js模块不适合成千上万等大量点做偏移量修正 7 | * 本js模块暂不支持线面转换 8 | */ 9 | 10 | function CoordinateTransform() 11 | { 12 | //定义一些常量 13 | this.PI = 3.1415926535897932384626; 14 | this.x_PI = 3.14159265358979324 * 3000.0 / 180.0; 15 | this.a = 6378245.0; 16 | this.ee = 0.00669342162296594323; 17 | this.EARTHRADIUS = 6370996.81; 18 | this.MCBAND = [12890594.86,8362377.87,5591021,3481989.83,1678043.12,0]; 19 | this.LLBAND = [75,60,45,30,15,0]; 20 | this.MC2LL = [[1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331, 200.9824383106796, -187.2403703815547, 91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2], [-7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289, 96.32687599759846, -1.85204757529826, -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86], [-3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616, 59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37], [-1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591, 40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06], [3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062, 23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4], [2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8, 7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5]]; 21 | this.LL2MC = [[-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340, 26112667856603880, -35149669176653700, 26595700718403920, -10725012454188240, 1800819912950474, 82.5], [0.0008277824516172526, 111320.7020463578, 647795574.6671607, -4082003173.641316, 10774905663.51142, -15171875531.51559, 12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5], [0.00337398766765, 111320.7020202162, 4481351.045890365, -23393751.19931662, 79682215.47186455, -115964993.2797253, 97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5], [0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245, 992013.7397791013, -1221952.21711287, 1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5], [-0.0003441963504368392, 111320.7020576856, 278.2353980772752, 2485758.690035394, 6070.750963243378, 54821.18345352118, 9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5], [-0.0003218135878613132, 111320.7020701615, 0.00369383431289, 823725.6402795718, 0.46104986909093, 2351.343141331292, 1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45]]; 22 | 23 | 24 | 25 | 26 | this._transformlat=function(lng, lat) { 27 | var ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + 0.1 * lng * lat + 0.2 * Math.sqrt(Math.abs(lng)); 28 | ret += (20.0 * Math.sin(6.0 * lng * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0; 29 | ret += (20.0 * Math.sin(lat * this.PI) + 40.0 * Math.sin(lat / 3.0 * this.PI)) * 2.0 / 3.0; 30 | ret += (160.0 * Math.sin(lat / 12.0 * this.PI) + 320 * Math.sin(lat * this.PI / 30.0)) * 2.0 / 3.0; 31 | return ret 32 | }; 33 | 34 | this._transformlng=function(lng, lat) { 35 | var ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + 0.1 * lng * lat + 0.1 * Math.sqrt(Math.abs(lng)); 36 | ret += (20.0 * Math.sin(6.0 * lng * this.PI) + 20.0 * Math.sin(2.0 * lng * this.PI)) * 2.0 / 3.0; 37 | ret += (20.0 * Math.sin(lng * this.PI) + 40.0 * Math.sin(lng / 3.0 * this.PI)) * 2.0 / 3.0; 38 | ret += (150.0 * Math.sin(lng / 12.0 * this.PI) + 300.0 * Math.sin(lng / 30.0 * this.PI)) * 2.0 / 3.0; 39 | return ret 40 | }; 41 | /** 42 | * 判断是否在国内,不在国内则不做偏移 43 | * @param lng 44 | * @param lat 45 | * @returns {boolean} 46 | */ 47 | this._out_of_china=function(lng, lat) { 48 | return (lng < 72.004 || lng > 137.8347) || ((lat < 0.8293 || lat > 55.8271) || false); 49 | }; 50 | 51 | this._getLoop=function(lng,min,max) { 52 | while (lng > max) { 53 | lng -= max - min; 54 | } 55 | while (lng < min) { 56 | lng += max - min; 57 | } 58 | return lng; 59 | }; 60 | 61 | this._getRange=function(lat,min,max) { 62 | if (min != null) { 63 | lat = Math.max(lat, min); 64 | } 65 | if (max != null) { 66 | lat = Math.min(lat, max); 67 | } 68 | return lat; 69 | }; 70 | } 71 | /** 72 | * 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换 73 | * 即 百度 转 谷歌、高德 74 | * @param bd_lon 75 | * @param bd_lat 76 | * @returns {*[]} 77 | */ 78 | CoordinateTransform.prototype.BD2GCJ=function(bd_lon,bd_lat) { 79 | var x = bd_lon - 0.0065; 80 | var y = bd_lat - 0.006; 81 | var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * this.x_PI); 82 | var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * this.x_PI); 83 | var gg_lng = z * Math.cos(theta); 84 | var gg_lat = z * Math.sin(theta); 85 | return [gg_lng, gg_lat]; 86 | } 87 | /** 88 | * 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换 89 | * 即谷歌、高德 转 百度 90 | * @param lon 91 | * @param lat 92 | * @returns {*[]} 93 | */ 94 | CoordinateTransform.prototype.GCJ2BD=function(lon,lat) { 95 | var z = Math.sqrt(lon * lon + lat * lat) + 0.00002 * Math.sin(lat * this.x_PI); 96 | var theta = Math.atan2(lat, lon) + 0.000003 * Math.cos(lon * this.x_PI); 97 | var bd_lon = z * Math.cos(theta) + 0.0065; 98 | var bd_lat = z * Math.sin(theta) + 0.006; 99 | return [bd_lon, bd_lat]; 100 | } 101 | /** 102 | * WGS84转GCj02 103 | * @param lon 104 | * @param lat 105 | * @returns {*[]} 106 | */ 107 | CoordinateTransform.prototype.WGS2GCJ=function(lon,lat) { 108 | if (this._out_of_china(lon, lat)) { 109 | return [lon, lat] 110 | } else { 111 | var dlat = this._transformlat(lon - 105.0, lat - 35.0); 112 | var dlon = this._transformlng(lon - 105.0, lat - 35.0); 113 | var radlat = lat / 180.0 * this.PI; 114 | var magic = Math.sin(radlat); 115 | magic = 1 - this.ee * magic * magic; 116 | var sqrtmagic = Math.sqrt(magic); 117 | dlat = (dlat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtmagic) * this.PI); 118 | dlon = (dlon * 180.0) / (this.a / sqrtmagic * Math.cos(radlat) * this.PI); 119 | var mglat = lat + dlat; 120 | var mglon = lon + dlon; 121 | return [mglon, mglat]; 122 | } 123 | } 124 | /** 125 | * GCJ02 转换为 WGS84 126 | * @param lon 127 | * @param lat 128 | * @returns {*[]} 129 | */ 130 | CoordinateTransform.prototype.GCJ2WGS=function(lon,lat) { 131 | if (this._out_of_china(lon, lat)) { 132 | return [lon, lat] 133 | } else { 134 | var dlat = this._transformlat(lon - 105.0, lat - 35.0); 135 | var dlon = this._transformlng(lon - 105.0, lat - 35.0); 136 | var radlat = lat / 180.0 * this.PI; 137 | var magic = Math.sin(radlat); 138 | magic = 1 - this.ee * magic * magic; 139 | var sqrtmagic = Math.sqrt(magic); 140 | dlat = (dlat * 180.0) / ((this.a * (1 - this.ee)) / (magic * sqrtmagic) * this.PI); 141 | dlon = (dlon * 180.0) / (this.a / sqrtmagic * Math.cos(radlat) * this.PI); 142 | var mglat = lat + dlat; 143 | var mglon = lon + dlon; 144 | return [lon * 2 - mglon, lat * 2 - mglat]; 145 | } 146 | } 147 | /** 148 | * WGS转百度经纬 149 | * @param lon 150 | * @param lat 151 | * @returns {*[]} 152 | */ 153 | CoordinateTransform.prototype.WGS2BD=function(lon,lat) { 154 | //先由经纬转火星 155 | var coor=this.WGS2GCJ(lon,lat); 156 | //再将火星转百度 157 | coor=this.GCJ2BD(coor[0],coor[1]); 158 | return coor; 159 | } 160 | /** 161 | * 百度经纬转WGS84 162 | * @param lon 163 | * @param lat 164 | * @returns {*[]} 165 | */ 166 | CoordinateTransform.prototype.BD2WGS=function(lon,lat) { 167 | //先由百度转火星 168 | var coor=this.BD2GCJ(lon,lat); 169 | //再将火星转百度 170 | coor=this.GCJ2WGS(coor[0],coor[1]); 171 | return coor; 172 | } 173 | 174 | 175 | /** 176 | * 百度墨卡托转百度经纬度 177 | * @param lng 178 | * @param lat 179 | * @returns {*[]} 180 | */ 181 | CoordinateTransform.prototype.BD_MKT2WGS=function(lng, lat) { 182 | var cF = null; 183 | lng = Math.abs(lng); 184 | lat = Math.abs(lat); 185 | for(var cE = 0; cE < this.MCBAND.length; cE++) { 186 | if (lat >= this.MCBAND[cE]) { 187 | cF = this.MC2LL[cE]; 188 | break; 189 | } 190 | } 191 | lng = cF[0] + cF[1] * Math.abs(lng); 192 | var cC = Math.abs(lat) / cF[9]; 193 | lat = cF[2] + cF[3] * cC + cF[4] * cC * cC + cF[5] * cC * cC * cC + cF[6] * cC * cC * cC * cC + cF[7] * cC * cC * cC * cC * cC + cF[8] * cC * cC * cC * cC * cC * cC; 194 | lng *= (lng < 0 ? -1 : 1); 195 | lat *= (lat < 0 ? -1 : 1); 196 | return [lng,lat]; 197 | } 198 | /** 199 | * 百度经纬度转百度墨卡托 200 | * @param lng 201 | * @param lat 202 | * @returns {*[]} 203 | */ 204 | CoordinateTransform.prototype.BD_WGS2MKT=function(lng, lat) { 205 | var cF = null; 206 | lng = this._getLoop(lng, -180, 180); 207 | lat = this._getRange(lat, -74, 74); 208 | for (var i = 0; i < this.LLBAND.length; i++) { 209 | if (lat >= this.LLBAND[i]) { 210 | cF = this.LL2MC[i]; 211 | break; 212 | } 213 | } 214 | if (cF!=null) { 215 | for (var i = this.LLBAND.length - 1; i >= 0; i--) { 216 | if (lat <= -this.LLBAND[i]) { 217 | cF = this.LL2MC[i]; 218 | break; 219 | } 220 | } 221 | } 222 | lng = cF[0] + cF[1] * Math.abs(lng); 223 | var cC = Math.abs(lat) / cF[9]; 224 | lat = cF[2] + cF[3] * cC + cF[4] * cC * cC + cF[5] * cC * cC * cC + cF[6] * cC * cC * cC * cC + cF[7] * cC * cC * cC * cC * cC + cF[8] * cC * cC * cC * cC * cC * cC; 225 | lng *= (lng < 0 ? -1 : 1); 226 | lat *= (lat < 0 ? -1 : 1); 227 | return [lng,lat]; 228 | } --------------------------------------------------------------------------------