what is openlayer
openlayers是一個高性能、功能全面的地圖庫。有以下特性:
- 支持各種格式的tiled layers數據
- 使用canvas的高性能vector layer
- 細粒度豐富的交互操作
- 支持commanjs風格
- 開源免費
why use it
一般來說google map api就滿足需求了,但是當需要在地圖上繪制大量節點時,使用svg來顯示部件就會出現很嚴重的性能問題,一般情況下google maps等的編輯操作也不夠流暢。而openlayers使用canvas作為矢量層,大量的點線面信息對于canvas的影響是較小的,而且canvas的交互也更加流暢。
why write this article
- 與高德地圖、google maps、百度地圖等國內常用地圖沒有很方便的接口
- 大版本更新后官方文檔不全面而且不夠清晰,沒有guild
- 而且網上簡明詳細的教程很少
針對以上幾點,覺得自己的小經驗會對大家有所幫助,可以少走些彎路,也是對自己這幾天工作的一個總結。
a simple example(step by step)
下面通過一個簡單地小例子介紹openlayers的一些基本用法,以及我覺得重要的地方。
需求
需要使用google map或者高德地圖作為底圖,在地圖上繪制一個矩形,實現矩形的編輯并且能夠得到矩形被修改之后的回調函數。大概會涉及到這些技術點:
- 如何與google map等地圖組合使用
- 知道坐標數組或者某格式的geo數據,如何在地圖上添加feature
- 完成feature的修改
- 得到feature修改后的回調方法
附上簡單截圖
初始化地圖
我們希望openlayer能和google map等組合使用,但是由于google等地圖廠商不愿意向openlayer“妥協”,因此現在無法直接使用Tiled Layers
,但我們可以將openlayer與地圖api組合起來使用。
只使用地圖的底圖,在底圖上覆蓋一層 openlayer 的 canvas 層來顯示數據并攔截與底圖之間的交互,通過地圖的 api 來重設狀態。
此方案還有以下優點:
- 解決了頁面節點過多之后的性能問題
- 底圖可以被抽象出去,能夠在不影響交互邏輯的基礎上更換底圖。
代碼如下:
<!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。
目標是加載完畢之后olmap覆蓋與gmap之上并且攔截交互操作。
開始時放在同一層的好處是都能根據父節點來設置長寬。也可以在js中動態生成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用于控制地圖的 坐標系標準 zoom center rotate等操作的對象,在實例化map時候需要使用
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' // 設置為標準經緯度的坐標標準,十分重要! 默認是'EPSG:3857'
});
// view 拖動時觸發事件,根據當前的坐標轉化為經緯度,調用谷歌地圖setCenter方法同步地圖位置
view.on('change:center', function () {
var center = view.getCenter();
gmap.setCenter(new google.maps.LatLng(center[1], center[0])); // 注意順序
});
// 同上,更改焦距時觸發的時間
view.on('change:resolution', function () {
gmap.setZoom(view.getZoom());
});
// ol.source.Vector 作為 ol.layer.Vector的數據集,增刪改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], // 所使用的圖層
// 禁用掉默認的拖動、旋轉等交互
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}) 但是在這里需要通過手動設置來觸發google maps調節到正確地zoom與center
});
view.setCenter([10.689697265625, -25.0927734375]); // 如果未設置view的坐標標準,這里千萬要注意不要直接寫經緯度
view.setZoom(6); // 設置縮放等級
// 將openlayers容器放置到google地圖容器中
olMapDiv.parentNode.removeChild(olMapDiv);
gmap.controls[google.maps.ControlPosition.TOP_LEFT].push(olMapDiv);
</script>
</body>
</html>
有了這段代碼應該就有了一個可以拖動縮放的地圖了。
添加feature
當然光有一個地圖是不行的,如果需要往地圖上面添加feature怎么辦呢?大致有以下兩種場景:
- 提供整個地圖的信息描述數據,比如geoJson,WKT或者自定義的數據結構等,需要解析整個數據并批量顯示在地圖上。
- 拿到某個feature的坐標數據,需要添加到地圖上,并實現特異化的控制。
批量添加數據
先上代碼
/**
* 將geoJson字符串解析后添加到地圖中
* @param vectorSource {ol.source.Vector} 需要添加feature的矢量層數據對象
* @param data {string} geoJson字符串
*/
function addFeatures(vectorSource, data){
vectorSource.addFeatures(ol.format.GeoJSON.readFeatures(data, {
// 數據的坐標code
dataProjection: 'EPSG:3857',
// 地圖view使用的坐標code
featureProjection: 'EPSG:4326'
}));
}
ol.format
下有很多種數據類型,選擇匹配的數據格式。
比如WKT
使用ol.format.WKT.readFeature
readFeature
返回的是ol.Feature
的數組,可以通過遍歷其來獲得feature對象,從而按需求修改或者掛載監聽事件。
添加單個feature
如果現有的數據是geoJson等標準格式的數據,可以通過ol.format
中的類進行轉換。如果有需求則使用ol.proj.transform
進行坐標系轉換。
// 返回單個feature對象
var feature = ol.format.GeoJSON.readFeature(data)
// 添加到source
vectorSource.addFeature(feature);
如果拿到的是經緯度信息,添加一個polygon
// data 是一個coordinates的二維數組 需要注意
var feature = new ol.feature(new ol.geom.Polygon(data));
修改feature
介紹如何修改feature以及掛載監聽事件。
初始化修改添加、修改交互
// 聲明選擇交互
var select = new ol.interaction.Select({
// 根據 feature editable 選項來判斷是否可以選中
filter: function(feature) {
if (_this._featureMap[feature.getId()].editable) {
return true;
}
}
});
// 得到被選中元件的對象
var selected = select.getFeatures();
// 聲明修改交互,可以修改被選中的feature
var modify = new ol.interaction.Modify({
features: selected
});
// 當新元件被選中時觸發
selected.on('add', event => {
var feature = event.element
})
// 當元件被取消選中時觸發,一般把元件的修改回調放在這
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
});
一般來說如果需要后續對feature進行操作,可以使用getId
方法拿到feature
的id
,可以通過setId
來設置自己想要的id
,否則會自動生成。 將id
存在常駐的對象中供以后使用。
假設拿到ol.Feature
對象 feature
feature.on('change:geometry', function(e){
var feature = e.element;
// do what you want 比如標記元件已被修改
})
需要注意的是這個onChange
事件在修改的過程中會不斷地觸發,如果需要的是修改完成之后的回調,需要使用select
的remove
事件。
select.getFeatures().on('remove', function(e){})
修改feature對象
設置id
feature.setId(id)
得到geometry
對象
var geometry = feature.getGeometry();
// 通過調用geometry類的方法修改元件坐標
feature to string
var format = new ol.format.GeoJSON();
format.writeFeature(feature);
需要注意的地方
- openlayers默認的坐標系是
'EPSG:3857'
,標準經緯度坐標系是'EPSG:4326'
- 看openlayer文檔最重要的技巧是注意類型
- geometry接受的coordinates其實是一個三維數組,一定要注意
常用操作
坐標系轉換
根據當前坐標系與目標坐標系進行轉換。
ol.proj.transform(coordinate, source, destination)
coordinate
在文檔中得類型是 Coordinate
其實就是一個有橫縱坐標組成的數組,因此一定要注意官方文檔中得數據類型。
source
當前坐標編碼 string類型
destination
目標坐標編碼 string類型
從經緯度轉化到指定坐標系
ol.proj.fromLonLat(coordinate, opt_projection)
opt_projection
目標坐標編碼 string類型
從某坐標轉經緯度
ol.proj.toLonLat(coordinate, opt_projection)
數據格式化
ol.format