what is openlayer
openlayers是一個(gè)高性能、功能全面的地圖庫辅辩。有以下特性:
- 支持各種格式的tiled layers數(shù)據(jù)
- 使用canvas的高性能vector layer
- 細(xì)粒度豐富的交互操作
- 支持commanjs風(fēng)格
- 開源免費(fèi)
why use it
一般來說google map api就滿足需求了演顾,但是當(dāng)需要在地圖上繪制大量節(jié)點(diǎn)時(shí)重抖,使用svg來顯示部件就會(huì)出現(xiàn)很嚴(yán)重的性能問題楚午,一般情況下google maps等的編輯操作也不夠流暢宴偿。而openlayers使用canvas作為矢量層湘捎,大量的點(diǎn)線面信息對(duì)于canvas的影響是較小的,而且canvas的交互也更加流暢窄刘。
why write this article
- 與高德地圖窥妇、google maps、百度地圖等國(guó)內(nèi)常用地圖沒有很方便的接口
- 大版本更新后官方文檔不全面而且不夠清晰娩践,沒有g(shù)uild
- 而且網(wǎng)上簡(jiǎn)明詳細(xì)的教程很少
針對(duì)以上幾點(diǎn)活翩,覺得自己的小經(jīng)驗(yàn)會(huì)對(duì)大家有所幫助烹骨,可以少走些彎路,也是對(duì)自己這幾天工作的一個(gè)總結(jié)材泄。
a simple example(step by step)
下面通過一個(gè)簡(jiǎn)單地小例子介紹openlayers的一些基本用法沮焕,以及我覺得重要的地方。
需求
需要使用google map或者高德地圖作為底圖脸爱,在地圖上繪制一個(gè)矩形遇汞,實(shí)現(xiàn)矩形的編輯并且能夠得到矩形被修改之后的回調(diào)函數(shù)。大概會(huì)涉及到這些技術(shù)點(diǎn):
- 如何與google map等地圖組合使用
- 知道坐標(biāo)數(shù)組或者某格式的geo數(shù)據(jù)簿废,如何在地圖上添加feature
- 完成feature的修改
- 得到feature修改后的回調(diào)方法
附上簡(jiǎn)單截圖
初始化地圖
我們希望openlayer能和google map等組合使用,但是由于google等地圖廠商不愿意向openlayer“妥協(xié)”络它,因此現(xiàn)在無法直接使用Tiled Layers
族檬,但我們可以將openlayer與地圖api組合起來使用。
只使用地圖的底圖化戳,在底圖上覆蓋一層 openlayer 的 canvas 層來顯示數(shù)據(jù)并攔截與底圖之間的交互单料,通過地圖的 api 來重設(shè)狀態(tài)。
此方案還有以下優(yōu)點(diǎn):
- 解決了頁面節(jié)點(diǎn)過多之后的性能問題
- 底圖可以被抽象出去点楼,能夠在不影響交互邏輯的基礎(chǔ)上更換底圖扫尖。
代碼如下:
<!DOCTYPE html>
<html>
<head>
<title>Snap interaction example</title>
<script src="https://code.jquery.com/jquery-1.11.2.min.js"></script>
<link rel="stylesheet" >
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js"></script>
<link rel="stylesheet" type="text/css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/ol3/3.6.0/ol.js"></script>
<script src="https://maps.googleapis.com/maps/api/js"></script>
<style type="text/css">
div.fill {
width: 100%;
height: 100%;
}
.map {
width: 800px;
height: 400px;
}
</style>
</head>
<body>
<div class="container-fluid">
<div class="row-fluid">
<div class="span12">
<div id="map" class="map">
<!-- gmap用于加載google maps, olmap用于加載openlayer canvas掠廓。
目標(biāo)是加載完畢之后olmap覆蓋與gmap之上并且攔截交互操作换怖。
開始時(shí)放在同一層的好處是都能根據(jù)父節(jié)點(diǎn)來設(shè)置長(zhǎng)寬。也可以在js中動(dòng)態(tài)生成div蟀瞧,渲染后插入 -->
<div id="gmap" class="fill"></div>
<div id="olmap" class="fill"></div>
</div>
</div>
</div>
</div>
<script type="application/javascript">
// 加載google map并禁用地圖的交互操作
var gmap = new google.maps.Map(document.getElementById('gmap'), {
disableDefaultUI: true,
keyboardShortcuts: false,
draggable: false,
disableDoubleClickZoom: true,
scrollwheel: false,
streetViewControl: false
});
// ol.View 是openlayers用于控制地圖的 坐標(biāo)系標(biāo)準(zhǔn) zoom center rotate等操作的對(duì)象沉颂,在實(shí)例化map時(shí)候需要使用
var view = new ol.View({
// make sure the view doesn't go beyond the 22 zoom levels of Google Maps
maxZoom: 21,
projection: 'EPSG:4326' // 設(shè)置為標(biāo)準(zhǔn)經(jīng)緯度的坐標(biāo)標(biāo)準(zhǔn),十分重要悦污! 默認(rèn)是'EPSG:3857'
});
// view 拖動(dòng)時(shí)觸發(fā)事件铸屉,根據(jù)當(dāng)前的坐標(biāo)轉(zhuǎn)化為經(jīng)緯度,調(diào)用谷歌地圖setCenter方法同步地圖位置
view.on('change:center', function () {
var center = view.getCenter();
gmap.setCenter(new google.maps.LatLng(center[1], center[0])); // 注意順序
});
// 同上切端,更改焦距時(shí)觸發(fā)的時(shí)間
view.on('change:resolution', function () {
gmap.setZoom(view.getZoom());
});
// ol.source.Vector 作為 ol.layer.Vector的數(shù)據(jù)集梦重,增刪改feature的方法由source提供
var vectorSource = new ol.source.Vector();
var vector = new ol.layer.Vector({
source: vectorSource
});
var olMapDiv = document.getElementById('olmap');
var map = new ol.Map({
layers: [vector], // 所使用的圖層
// 禁用掉默認(rèn)的拖動(dòng)、旋轉(zhuǎn)等交互
interactions: ol.interaction.defaults({
altShiftDragRotate: false,
dragPan: false,
rotate: false
}).extend([new ol.interaction.DragPan({kinetic: null})]),
target: olMapDiv,
view: view // 這里可以使用 new ol.View({options}) 但是在這里需要通過手動(dòng)設(shè)置來觸發(fā)google maps調(diào)節(jié)到正確地zoom與center
});
view.setCenter([10.689697265625, -25.0927734375]); // 如果未設(shè)置view的坐標(biāo)標(biāo)準(zhǔn)藕夫,這里千萬要注意不要直接寫經(jīng)緯度
view.setZoom(6); // 設(shè)置縮放等級(jí)
// 將openlayers容器放置到google地圖容器中
olMapDiv.parentNode.removeChild(olMapDiv);
gmap.controls[google.maps.ControlPosition.TOP_LEFT].push(olMapDiv);
</script>
</body>
</html>
有了這段代碼應(yīng)該就有了一個(gè)可以拖動(dòng)縮放的地圖了丸凭。
添加feature
當(dāng)然光有一個(gè)地圖是不行的,如果需要往地圖上面添加feature怎么辦呢椰于?大致有以下兩種場(chǎng)景:
- 提供整個(gè)地圖的信息描述數(shù)據(jù)怠益,比如geoJson,WKT或者自定義的數(shù)據(jù)結(jié)構(gòu)等瘾婿,需要解析整個(gè)數(shù)據(jù)并批量顯示在地圖上蜻牢。
- 拿到某個(gè)feature的坐標(biāo)數(shù)據(jù)烤咧,需要添加到地圖上,并實(shí)現(xiàn)特異化的控制抢呆。
批量添加數(shù)據(jù)
先上代碼
/**
* 將geoJson字符串解析后添加到地圖中
* @param vectorSource {ol.source.Vector} 需要添加feature的矢量層數(shù)據(jù)對(duì)象
* @param data {string} geoJson字符串
*/
function addFeatures(vectorSource, data){
vectorSource.addFeatures(ol.format.GeoJSON.readFeatures(data, {
// 數(shù)據(jù)的坐標(biāo)code
dataProjection: 'EPSG:3857',
// 地圖view使用的坐標(biāo)code
featureProjection: 'EPSG:4326'
}));
}
ol.format
下有很多種數(shù)據(jù)類型煮嫌,選擇匹配的數(shù)據(jù)格式。
比如WKT
使用ol.format.WKT.readFeature
readFeature
返回的是ol.Feature
的數(shù)組抱虐,可以通過遍歷其來獲得feature對(duì)象昌阿,從而按需求修改或者掛載監(jiān)聽事件。
添加單個(gè)feature
如果現(xiàn)有的數(shù)據(jù)是geoJson等標(biāo)準(zhǔn)格式的數(shù)據(jù)恳邀,可以通過ol.format
中的類進(jìn)行轉(zhuǎn)換懦冰。如果有需求則使用ol.proj.transform
進(jìn)行坐標(biāo)系轉(zhuǎn)換。
// 返回單個(gè)feature對(duì)象
var feature = ol.format.GeoJSON.readFeature(data)
// 添加到source
vectorSource.addFeature(feature);
如果拿到的是經(jīng)緯度信息谣沸,添加一個(gè)polygon
// data 是一個(gè)coordinates的二維數(shù)組 需要注意
var feature = new ol.feature(new ol.geom.Polygon(data));
修改feature
介紹如何修改feature以及掛載監(jiān)聽事件刷钢。
初始化修改添加、修改交互
// 聲明選擇交互
var select = new ol.interaction.Select({
// 根據(jù) feature editable 選項(xiàng)來判斷是否可以選中
filter: function(feature) {
if (_this._featureMap[feature.getId()].editable) {
return true;
}
}
});
// 得到被選中元件的對(duì)象
var selected = select.getFeatures();
// 聲明修改交互乳附,可以修改被選中的feature
var modify = new ol.interaction.Modify({
features: selected
});
// 當(dāng)新元件被選中時(shí)觸發(fā)
selected.on('add', event => {
var feature = event.element
})
// 當(dāng)元件被取消選中時(shí)觸發(fā)内地,一般把元件的修改回調(diào)放在這
selected.on('remove', evt => {
var feature = evt.element;
var fid = feature.getId();
// 判斷元件是否被修改還是需要feature的change事件
console.log(fid);
});
// 在interactions中添加
this._map = new ol.Map({
layers: [vector],
interactions: ol.interaction.defaults({
altShiftDragRotate: false,
dragPan: false,
rotate: false
}).extend([new ol.interaction.DragPan({kinetic: null}), select, modify]),
target: $olMapDiv,
view: this._view
});
一般來說如果需要后續(xù)對(duì)feature進(jìn)行操作,可以使用getId
方法拿到feature
的id
赋除,可以通過setId
來設(shè)置自己想要的id
阱缓,否則會(huì)自動(dòng)生成。 將id
存在常駐的對(duì)象中供以后使用举农。
假設(shè)拿到ol.Feature
對(duì)象 feature
feature.on('change:geometry', function(e){
var feature = e.element;
// do what you want 比如標(biāo)記元件已被修改
})
需要注意的是這個(gè)onChange
事件在修改的過程中會(huì)不斷地觸發(fā)荆针,如果需要的是修改完成之后的回調(diào),需要使用select
的remove
事件并蝗。
select.getFeatures().on('remove', function(e){})
修改feature對(duì)象
設(shè)置id
feature.setId(id)
得到geometry
對(duì)象
var geometry = feature.getGeometry();
// 通過調(diào)用geometry類的方法修改元件坐標(biāo)
feature to string
var format = new ol.format.GeoJSON();
format.writeFeature(feature);
需要注意的地方
- openlayers默認(rèn)的坐標(biāo)系是
'EPSG:3857'
祭犯,標(biāo)準(zhǔn)經(jīng)緯度坐標(biāo)系是'EPSG:4326'
- 看openlayer文檔最重要的技巧是注意類型
- geometry接受的coordinates其實(shí)是一個(gè)三維數(shù)組,一定要注意
常用操作
坐標(biāo)系轉(zhuǎn)換
根據(jù)當(dāng)前坐標(biāo)系與目標(biāo)坐標(biāo)系進(jìn)行轉(zhuǎn)換滚停。
ol.proj.transform(coordinate, source, destination)
coordinate
在文檔中得類型是 Coordinate
其實(shí)就是一個(gè)有橫縱坐標(biāo)組成的數(shù)組沃粗,因此一定要注意官方文檔中得數(shù)據(jù)類型。
source
當(dāng)前坐標(biāo)編碼 string類型
destination
目標(biāo)坐標(biāo)編碼 string類型
從經(jīng)緯度轉(zhuǎn)化到指定坐標(biāo)系
ol.proj.fromLonLat(coordinate, opt_projection)
opt_projection
目標(biāo)坐標(biāo)編碼 string類型
從某坐標(biāo)轉(zhuǎn)經(jīng)緯度
ol.proj.toLonLat(coordinate, opt_projection)
數(shù)據(jù)格式化
ol.format