使用OpenLayers进行地图开发

之前使用openLayers开发了离线瓦片地图,做了下整理

使用OpenLayers进行地图开发

需要了解的前置知识

坐标系

常见坐标系

坐标系分为:地理坐标系投影坐标系。通过第一次抽象,将地表物体抽象成规则的椭球面,即是地理坐标系;在此基础上,再进行一次,将椭球体上的物体抽象到平面,即是投影坐标系;所以对于一个具体的地图服务提供商,它的地理坐标系和投影坐标系一般是配合的,如高德采用GCJ02地理坐标系和GCJ02Web墨卡托投影坐标系;

坐标系名 坐标系类型 EPSG代号 备注
WGS84 地理坐标系 EPSG:4326 世界大地坐标系(国际上标准的经纬度坐标系,如GPS定位、国外谷歌地图、Open Street Map 使用此坐标系)
CGCS2000 地理坐标系 EPSG4490 中国大地坐标系,天地图使用此坐标系
GCJ02 地理坐标系 无具体代号 国测局坐标系,又称火星坐标系,在WGS84坐标系上加密得来;(国内高德使用此坐标系)
BD09 地理坐标系 无具体代号 百度坐标系,在GCJ02坐标系基础上再次加密(国内百度使用此坐标系)
Web墨卡托投影坐标系 投影坐标系 EPSG:3857 很多其他投影坐标系是基于此,BingMap使用此坐标系

坐标系的转换

  • 由于我们使用的OpenLayers框架是国外产品, 默认不支持国内坐标系的,所以就涉及坐标系的转换问题;
  • 由于坐标系涉及地理知识,对于我们这些一般不会接触地理的web前端开发不是很友好,所以坐标系的转换都是查找网上已经写好的转换方案或者使用别人封装好的库(如gcoord

离线瓦片地图

什么是瓦片地图

​ 瓦片地图金字塔模型是一种多分辨率层次模型,从瓦片金字塔的底层到顶层,分辨率越来越低,但表示的地理范围不变。首先确定地图服务平台所要提供的缩放级别的数量N,把缩放级别最高、地图比例尺最大的地图图片作为金字塔的底层,即第0层,并对其进行分块,从地图图片的左上角开始,从左至右、从上到下进行切割,分割成相同大小(比如256x256像素)的正方形地图瓦片,形成第0层瓦片矩阵;在第0层地图图片的基础上,按每像素分割为2×2个像素的方法生成第1层地图图片,并对其进行分块,分割成与下一层相同大小的正方形地图瓦片,形成第1层瓦片矩阵;采用同样的方法生成第2层瓦片矩阵;…;如此下去,直到第N-1层,构成整个瓦片金字塔。简单概括如下:

  • 具有唯一的瓦片等级(Level)和瓦片坐标编号(tileX, tileY)
  • 瓦片图片分辨率为256*256(单位像素)
  • 最小的地图等级是0,此时世界地图只由一张瓦片组成。
  • 瓦片等级越高,组成世界地图的瓦片数越多,可以展示的地图越详细。
  • 某一瓦片等级地图的瓦片是由低一级的各瓦片切割成的4个瓦片组成,遂形成了瓦片金字塔。

瓦片地图分类

特性 矢量瓦片 栅格瓦片
使用方式 将矢量数据通过不同的描述文件来组织和定义,在客户端实时解析数据完成绘制 预先在服务端绘制好固定的PNG或其他格式的图片集合
瓦片体量
瓦片生成效率
分辨率
样式修改 支持 不支持
技术成熟度 差,数据不通用 好,图片可通用
交互性 很好 一般
对客户端要求 要求高,存在兼容性问题 要求低,性能稳定

瓦片地图分辨率

以百度地图为例:百度地图的瓦片定义的方式比较独特,原点的位置在经纬度都为0的地方,X向左为正,向右为负;Y向上为正,向下为负。切分的方式不是在每一级进行二等分,而是通过定义每一级的地图分辨率 ,确定每一级应该划分的行列数。地图分辨率的表达式为:Math.pow(2, 18 - z) z取值0~18 ,其含义是每个像素所对应的实际长度。由此,可得每一级应该划分的行列数为:2πR/(256*(2^18-z )),其中R为地球的半径,单位是米,z是地图缩放层级。也就是说,当瓦片地图放大到18级时,图片每单位像素表示1米;

瓦片坐标与瓦片像素坐标

通常情况下,我们是知道一个点的地理坐标,然后标注到地图上。但是瓦片图就只是图片,并没有携带地理位置信息,那么是如何定位到具体某一个瓦片的某个像素点来做标注的呢?仍然以百度地图为例。

首先,瓦片坐标和瓦片像素坐标都应该放在具体地图缩放级别来讨论;当瓦片尺寸为256*256pixel的时候:

瓦片坐标(tileX,tileY) 瓦片像素坐标(pixelX,pixelY) 平面投影坐标(x,y)
tileX=int(x2^(z-18)/256) pixelX=x * 2^(z-18)-int(x * 2^(z-18)/256) * 256 x=(tileX * 256+pixelX) * 2^(18-z)
tileY=int(y2^(z-18)/256) pixelY=y * 2^(z-18)-int(y * 2^(z-18)/256) * 256 y=(tileY * 256+pixelY) * 2^(18-z)

x和y是平面投影坐标,百度地图瓦片平面投影坐标x最大值为20037726m, y最大值为12474104m。z是缩放级别(0~18),tileX,tileY表示当前坐标(x,y)定位的具体的这一张瓦片图片在地图中的什么位置;pixelX,pixelY 表示当前坐标(x,y)在这张瓦片图的哪个像素点上;

OpenLayers介绍

什么是OpenLayers

OpenLayers是一个高性能、功能丰富的库,用于在网络上创建交互式地图。它可以在任何网页上显示从任何来源加载的地图图块、矢量数据和标记。

OpenLayers可以实现哪些功能

  • 从 OSM、Bing、MapBox、Stamen 以及您能找到的任何其他 XYZ 源中提取图块。还支持 OGC 地图服务和未耕图层。
  • 渲染来自 GeoJSON、TopoJSON、KML、GML、Mapbox 矢量切片和其他格式的矢量数据。
  • 利用 Canvas 2D、WebGL 以及 HTML5 的所有最新功能。开箱即用的移动支持。仅使用您需要的组件构建轻量级自定义配置文件。
  • 使用简单的 CSS 设计您的地图控件。连接不同级别的 API 或使用第三方库来自定义和扩展功能。

OpenLayers的简单使用

例如一个功能(生成地图,绘制区域边界,添加指定坐标标点),还是以百度地图为例:

生成地图

  • 获取瓦片图数据(网络搜索,例如:望远网

  • 瓦片图的投影与openlayers 默认的球面墨卡托投影 (EPSG:3857)不一致,需要重新定义BD:09的投影

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    const confineProj = () => {
    /* 定义百度投影,这是实现无偏移加载百度地图离线瓦片核心所在。
    网上很多相关资料在用OpenLayers加载百度地图离线瓦片时都认为投影就是EPSG:3857(也就是Web墨卡托投影)。
    事实上这是错误的,因此无法做到无偏移加载。
    百度地图有自己独特的投影体系,必须在OpenLayers中自定义百度投影,才能实现无偏移加载。
    百度投影实现的核心文件为bd09.js,在迈高图官网可以找到查看这个文件。 */
    const projBD09 = new Projection({
    code: 'BD:09',
    extent: [-20037726.37, -11708041.66, 20037726.37, 12474104.17],
    units: 'm',
    axisOrientation: 'neu',
    global: false,
    });

    addProjection(projBD09);
    addCoordinateTransforms('EPSG:4326', 'BD:09',
    (coordinate) => lngLatToMercator(coordinate),
    (coordinate) => mercatorToLngLat(coordinate),
    );
    };
  • 设置地图数据分辨率

    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 定义百度分辨率
    const resolutions = [];
    for (let i = 0; i <= 18; i++) {
    resolutions[i] = Math.pow(2, 18 - i);
    }
    const tilegrid = new TileGrid({
    origin: [0, 0],
    resolutions,
    });
  • 添加图层,在指定元素渲染地图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    const source = new TileImage({
    projection: 'BD:09',
    tileGrid: tilegrid,
    tilePixelRatio: 2,
    cacheSize: 10000,
    tileUrlFunction(tileCoord, pixelRatio, proj) {
    if (!tileCoord) {
    return "";
    }
    let z = tileCoord[0];
    let x = tileCoord[1];
    let y = -tileCoord[2] - 1;//ol6需要此处减一,否则缩放有偏移
    return `/baidu/tiles/fulin/${z}/${x}/${y}.png`;
    },
    });

    const mapLayer = new TileLayer({
    source,
    });

    myMap = new Map({
    layers: [
    mapLayer,
    vectorTL,
    ],
    view: new View({
    //将坐标从源投影转换为目标投影。这将返回一个新坐标(并且不会修改原始坐标)
    center: ProjTransform([
    107.37492826262412,
    29.689381110571663,
    ], 'EPSG:4326', 'BD:09'),
    // 投影。默认为球面墨卡托。指定百度的坐标系
    projection: 'BD:09',
    // 层级 从 0 开始,zoom:10 对应的文件夹11
    zoom: 10,
    minZoom: 10,
    maxZoom: 16,
    constrainOnlyCenter: true,
    extent: [
    ...ProjTransform([
    106.66490758777209,
    29.312665938287,
    ], 'EPSG:4326', 'BD:09'),
    ...ProjTransform([
    107.91822344761631,
    30.067675651437312,
    ], 'EPSG:4326', 'BD:09'),
    ],
    }),
    target: 'map',
    //对地图容器中的控件设置
    controls: defaultControls({
    zoom: false,
    rotate: false,
    attribution: false,
    }),
    });

绘制区域边界

  • 获取区域边界数据(从高德地图获取行政区域边界坐标后再通过bd09.js中的gcj02tobd09方法转换为百度坐标系的坐标)

  • 设置矢量图层数据格式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    const vectorTL = new LayerVector({
    source: new SourceVector({
    url: '/fulin_geo_baidu.json',
    format: new GeoJSON({
    extractStyles: false,
    }),
    }),
    style(feature) {
    style.getText().setText(feature.get('name'));
    return style;
    },
    });
  • 给边界矢量图层添加样式、文字等

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    const style = new Style({
    fill: new Fill({
    color: [124, 201, 245, 0.1],
    }),
    stroke: new Stroke({
    color: '#7cc9f5',
    width: 3,
    }),
    text: new Text({
    font: '12px Calibri,sans-serif',
    fill: new Fill({
    color: '#fff',
    }),
    }),
    });
  • 创建矢量图层,加载到地图中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    myMap = new Map({
    layers: [
    mapLayer,
    vectorTL,
    ],
    view: new View({
    //将坐标从源投影转换为目标投影。这将返回一个新坐标(并且不会修改原始坐标)。
    center: ProjTransform([
    107.37492826262412,
    29.689381110571663,
    ], 'EPSG:4326', 'BD:09'),
    // 投影。默认为球面墨卡托。指定百度的坐标系
    projection: 'BD:09',
    // 层级 从 0 开始,zoom:10 对应的文件夹11
    zoom: 10,
    minZoom: 10,
    maxZoom: 16,
    constrainOnlyCenter: true,
    extent: [
    ...ProjTransform([
    106.66490758777209,
    29.312665938287,
    ], 'EPSG:4326', 'BD:09'),
    ...ProjTransform([
    107.91822344761631,
    30.067675651437312,
    ], 'EPSG:4326', 'BD:09'),
    ],
    }),
    target: 'map',
    //对地图容器中的控件设置
    controls: defaultControls({
    zoom: false,
    rotate: false,
    attribution: false,
    }),
    });

添加指定坐标标点

  • 从后端获取坐标数据
  • 转换为百度BD09坐标系(上面已定义)的坐标点
  • 添加矢量图层

OpenLayers使用示例

安装依赖

1
npm i ol

在vue3中使用

以上面的功能为例,完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
<script setup>
import {ElMessage} from 'element-plus';
import {onMounted} from 'vue';
import {reactive} from 'vue';

// 地图初始中心点
const mapInitCenter = [107.37492826262412, 29.689381110571663,];


let myMap = reactive(null);
let tempMarker = reactive([]);
let districtCode = '500102007';


// 点击小区后,定位过去,视野聚焦到点击的小区
const renderHandleUpdate = (point, zoomLevel) => {
// 重新渲染层级、定位中心点
let view = myMap.getView();
view.setZoom(zoomLevel);
view.setCenter(ProjTransform(point, 'EPSG:4326', 'BD:09'));
};

const getCommunityGeoList = () => {
let params = {
districtCode: districtCode,
};
dataScreenApi.communityGeoList(params).then(res => {
tempMarker = res.data.content.map(el => {
return {
...el,
location: [Number(el.longitude), Number(el.latitude)],
};
});
let vectorSource = new SourceVector({
features: listDataPointFunc(tempMarker),
});
let vectorLayer = new LayerVector({
source: vectorSource,
});
// 动态添加矢量图层
myMap.addLayer(vectorLayer);
}).catch(err => {
console.log(err);
ElMessage.error(err?.response?.data?.message || '获取地图标点失败');
});
};

const listDataPointFunc = (listData) => {
const listDataPoint = [];
if (listData && listData.length > 0) {
listData.forEach(item => {
const stringToArray = item.location;
let iconDefaultFeature = new Feature({
geometry: new Point(ProjTransform(gcj02tobd09(stringToArray), 'EPSG:4326', 'BD:09')),
name: item.name,
});
iconDefaultFeature.setStyle(iconStyleTextFunc(item.name));
listDataPoint.push(iconDefaultFeature);
});
}
return listDataPoint;
};
const iconStyleTextFunc = (name) => {
return new Style({
image: new Icon(({
anchor: [0.5, 46],
anchorXUnits: 'fraction',
anchorYUnits: 'pixels',
src: require('./images/marker-icon.png'),
width: 45,
height: 60,
})),
text: new Text({
font: '16px Calibri,sans-serif',
text: name,
offsetY: -30,
fill: new Fill({
color: '#fff',
}),
}),
});
};
const confineProj = () => {
/* 定义百度投影,这是实现无偏移加载百度地图离线瓦片核心所在。
网上很多相关资料在用OpenLayers加载百度地图离线瓦片时都认为投影就是EPSG:3857(也就是Web墨卡托投影)。
事实上这是错误的,因此无法做到无偏移加载。
百度地图有自己独特的投影体系,必须在OpenLayers中自定义百度投影,才能实现无偏移加载。
百度投影实现的核心文件为bd09.js,在迈高图官网可以找到查看这个文件。 */
const projBD09 = new Projection({
code: 'BD:09',
extent: [-20037726.37, -11708041.66, 20037726.37, 12474104.17], //百度地图投影边界
units: 'm',
axisOrientation: 'neu',
global: false,
});

addProjection(projBD09);
addCoordinateTransforms('EPSG:4326', 'BD:09',
(coordinate) => lngLatToMercator(coordinate),
(coordinate) => mercatorToLngLat(coordinate),
);
};
const initMap = () => {
confineProj();
// 定义百度分辨率
const resolutions = [];
for (let i = 0; i <= 18; i++) {
resolutions[i] = Math.pow(2, 18 - i);
}
const tilegrid = new TileGrid({
origin: [0, 0],
resolutions,
});
const source = new TileImage({
projection: 'BD:09',
tileGrid: tilegrid,
tilePixelRatio: 2,
cacheSize: 10000,
tileUrlFunction(tileCoord, pixelRatio, proj) {
if (!tileCoord) {
return "";
}
let z = tileCoord[0];
let x = tileCoord[1];
let y = -tileCoord[2] - 1;//ol6需要此处减一,否则缩放有偏移
return `/baidu/tiles/fulin/${z}/${x}/${y}.png`;
},
});

// 绘制行政区边界----
const style = new Style({
fill: new Fill({
color: [124, 201, 245, 0.1],
}),
stroke: new Stroke({
color: '#7cc9f5',
width: 3,
}),
text: new Text({
font: '12px Calibri,sans-serif',
fill: new Fill({
color: '#fff',
}),
}),
});
const vectorTL = new LayerVector({
source: new SourceVector({
url: '/fulin_geo_baidu.json',
format: new GeoJSON({
extractStyles: false,
}),
}),
style(feature) {
style.getText().setText(feature.get('name'));
return style;
},
});
const mapLayer = new TileLayer({
source,
});
// 绘制行政区边界----
myMap = new Map({
layers: [
mapLayer,
vectorTL,
],
view: new View({
//将坐标从源投影转换为目标投影。这将返回一个新坐标(并且不会修改原始坐标)。坐标对应的是百度地理坐标系的坐标,需要转化为 百度投影坐标系对应的坐标;其他地方也是类似的操作
center: ProjTransform(mapInitCenter, 'EPSG:4326', 'BD:09'),
// 投影。默认为球面墨卡托。指定百度的坐标系
projection: 'BD:09',
// 层级 从 0 开始,zoom:10 对应的文件夹11
zoom: 10,
minZoom: 10,
maxZoom: 16,
constrainOnlyCenter: true,
extent: [
...ProjTransform([
106.66490758777209,
29.312665938287,
], 'EPSG:4326', 'BD:09'),
...ProjTransform([
107.91822344761631,
30.067675651437312,
], 'EPSG:4326', 'BD:09'),
],
}),
target: 'map',
//对地图容器中的控件设置
controls: defaultControls({
zoom: false,
rotate: false,
attribution: false,
}),
});

myMap.on('click', evt => {
const feature = myMap.forEachFeatureAtPixel(evt.pixel, feature => {
return feature;
});
if (feature) {
tempMarker.forEach(item => {
if (item.name === feature.values_.name) {
renderHandleUpdate(gcj02tobd09(item.location), 16);
}
});
}
});

};
const initData = () => {
getCommunityGeoList();
};

onMounted(() => {
initMap();
initData();
});


</script>
<script>

import 'ol/ol.css';
import {Map, View, Feature} from 'ol';
import TileLayer from 'ol/layer/Tile';
import TileGrid from 'ol/tilegrid/TileGrid.js';
import TileImage from 'ol/source/TileImage.js';
import {Style, Fill, Icon, Text, Stroke} from 'ol/style';
import LayerVector from 'ol/layer/Vector.js';
import SourceVector from 'ol/source/Vector.js';
import GeoJSON from 'ol/format/GeoJSON.js';
import {Point} from 'ol/geom';
import {
transform as ProjTransform, addCoordinateTransforms,
addProjection, Projection,
} from
'ol/proj.js';
import {defaults as defaultControls} from 'ol/control';

import {lngLatToMercator, mercatorToLngLat, gcj02tobd09} from '@/utils/bd09';

import * as dataScreenApi from '@/api/dataScreen';

export default {
name: 'homeIndex',
};
</script>
<template>
<section class="main-container">
<div id="map" class="my-map"></div>
</section>
</template>

<style scoped lang="less">
.main-container {
width: 100%;
height: 100%;
}

.my-map {
width: 100%;
height: 100%;
}

</style>



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
//bd09.js

var MCBAND = [
12890594.86, 8362377.87, 5591021, 3481989.83,
1678043.12, 0,
];
var LLBAND = [75, 60, 45, 30, 15, 0];
var 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,
],
];

var 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,
],
];

/**
* BD09LL 转为 BD09MC 百度地理坐标系到百度投影坐标系的转化
* @param lng
* @param lat
* @returns {*[]}
*/
function lngLatToMercator(T) {
var c = new cd(T[0], T[1]);
var r = convertLL2MC(c);
return [r.lng, r.lat];
}

/**
* BD09MC 转为 BD09LL 百度投影坐标系到百度地理坐标系的转化
* @param lng
* @param lat
* @returns {*[]}
*/
function mercatorToLngLat(T) {
var c = new cd(T[0], T[1]);
var r = convertMC2LL(c);
return [r.lng, r.lat];
}

function convertLL2MC(T) {
var cL, cN;
T.lng = getLoop(T.lng, -180, 180);
T.lat = getRange(T.lat, -74, 74);
cL = new cd(T.lng, T.lat);
for (let cM = 0; cM < LLBAND.length; cM++) {
if (cL.lat >= LLBAND[cM]) {
cN = LL2MC[cM];
break;
}
}
if (!cN) {
for (let cM = LLBAND.length - 1; cM >= 0; cM--) {
if (cL.lat <= -LLBAND[cM]) {
cN = LL2MC[cM];
break;
}
}
}
var cO = convertor(T, cN);
return new cd(cO.lng.toFixed(2), cO.lat.toFixed(2));
}

function convertMC2LL(cL) {
var cM, cO;
cM = new cd(Math.abs(cL.lng), Math.abs(cL.lat));
for (var cN = 0; cN < MCBAND.length; cN++) {
if (cM.lat >= MCBAND[cN]) {
cO = MC2LL[cN];
break;
}
}
var T = convertor(cL, cO);
return new cd(T.lng.toFixed(6), T.lat.toFixed(6));
}

function getRange(cM, cL, T) {
if (cL != null) {
cM = Math.max(cM, cL);
}
if (T != null) {
cM = Math.min(cM, T);
}
return cM;
}

function getLoop(cM, cL, T) {
while (cM > T) {
cM -= T - cL;
}
while (cM < cL) {
cM += T - cL;
}
return cM;
}

function convertor(cM, cN) {
if (!cM || !cN) {
return;
}
var T = cN[0] + cN[1] * Math.abs(cM.lng);
var cL = Math.abs(cM.lat) / cN[9];
var cO = cN[2] + cN[3] * cL + cN[4] * cL * cL + cN[5] * cL *
cL * cL + cN[6] * cL * cL * cL * cL + cN[7] * cL *
cL * cL * cL * cL + cN[8] * cL * cL * cL * cL *
cL * cL;
T *= (cM.lng < 0 ? -1 : 1);
cO *= (cM.lat < 0 ? -1 : 1);
return new cd(T, cO);
}

function cd(T, cL) {
if (isNaN(T)) {
T = bV(T);
T = isNaN(T) ? 0 : T;
}
if (b3(T)) {
T = parseFloat(T);
}
if (isNaN(cL)) {
cL = bV(cL);
cL = isNaN(cL) ? 0 : cL;
}
if (b3(cL)) {
cL = parseFloat(cL);
}
this.lng = T;
this.lat = cL;
}

cd.isInRange = function (T) {
return T && T.lng <= 180 && T.lng >= -180 && T.lat <= 74 &&
T.lat >= -74;
};
cd.prototype.equals = function (T) {
return T && lat == T.lat && lng == T.lng;
};

function bV(cN) {
var cL = "";
var cU, cS, cQ = "";
var cT, cR, cP, cO = "";
var cM = 0;
var T = /[^A-Za-z0-9\+\/\=]/g;
if (!cN || T.exec(cN)) {
return cN;
}
cN = cN.replace(/[^A-Za-z0-9\+\/\=]/g, "");
do {
cT = cf.indexOf(cN.charAt(cM++));
cR = cf.indexOf(cN.charAt(cM++));
cP = cf.indexOf(cN.charAt(cM++));
cO = cf.indexOf(cN.charAt(cM++));
cU = (cT << 2) | (cR >> 4);
cS = ((cR & 15) << 4) | (cP >> 2);
cQ = ((cP & 3) << 6) | cO;
cL = cL + String.fromCharCode(cU);
if (cP != 64) {
cL = cL + String.fromCharCode(cS);
}
if (cO != 64) {
cL = cL + String.fromCharCode(cQ);
}
cU = cS = cQ = "";
cT = cR = cP = cO = "";
} while (cM < cN.length);
return cL;
}

function b3(T) {
return typeof T == "string";
}


/**
* 百度坐标系 (BD-09) 与 火星坐标系 (GCJ-02)的转换
* 即 百度 转 谷歌、高德
* @param bd_lon
* @param bd_lat
* @returns {*[]}
*/
function bd09togcj02([bd_lon, bd_lat]) {
var x_PI = 3.14159265358979324 * 3000.0 / 180.0;
var x = bd_lon - 0.0065;
var y = bd_lat - 0.006;
var z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_PI);
var theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_PI);
var gg_lng = z * Math.cos(theta);
var gg_lat = z * Math.sin(theta);
return [gg_lng, gg_lat]
}

/**
* 火星坐标系 (GCJ-02) 与百度坐标系 (BD-09) 的转换
* 即谷歌、高德 转 百度
* @param lng
* @param lat
* @returns {*[]}
*/
function gcj02tobd09([lng, lat]) {
var x_PI = 3.14159265358979324 * 3000.0 / 180.0;
var z = Math.sqrt(lng * lng + lat * lat) + 0.00002 * Math.sin(lat * x_PI);
var theta = Math.atan2(lat, lng) + 0.000003 * Math.cos(lng * x_PI);
var bd_lng = z * Math.cos(theta) + 0.0065;
var bd_lat = z * Math.sin(theta) + 0.006;
return [bd_lng, bd_lat]
}

module.exports = {
lngLatToMercator,
mercatorToLngLat,
bd09togcj02,
gcj02tobd09
}



参考文档:

坐标系详解

瓦片地图

地图瓦片介绍

地图瓦片:矢量瓦片和栅格瓦片详解

WebGIS中的坐标系和瓦片地图

百度地图瓦片切片规则

地图瓦片坐标系定义及计算原理

使用OpenLayers进行地图开发

http://dwchou.cn/blog/1662950789/

作者

dwchou

发布于

2024-09-26

更新于

2024-09-26

许可协议

评论

:D 一言句子获取中...

加载中,最新评论有1分钟缓存...