├── 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 | 
11 | 
12 | 
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 | }
--------------------------------------------------------------------------------