地圖應用分三種級別:示意地圖(Map Chart),地圖(Map),地理信息系統(GIS),第一種通常使用相對坐標系,后兩種則為真實的地理坐標,其中第二種以谷歌地圖為代表,日常生活中普遍使用,后一種則為專業的GIS,專業領域做拓撲分析、流域分析時用到,示意地圖我們已經有很多例子,比如美國大選示例、中國地圖示例等,今天介紹第二種地圖的應用,結合OpenLayers和谷歌地圖實現地圖的拓撲圖應用:demo.qunee.com/map/map.html 
創建地圖
OpenLayers是開源地理基金會作(OSGeo.org)支持的項目之一,是一種通用的地理客戶端平台,支持谷歌地圖,Bing地圖,WMS,GML等多種地圖在線服務,這里用到的是谷歌地圖,需要引入OpenLayers和google map的js類庫和css文件 引入相關類庫
<link rel="stylesheet" href="OpenLayers/theme/default/style.css" type="text/css"> <script src="http://maps.google.com/maps/api/js?v=3&sensor=false"></script> <script src="OpenLayers/OpenLayers.js"></script>
初始化地圖
參照OpenLayers官方示例,完成地圖初始化工作
function initMap(canvas, lon, lat){
map = new OpenLayers.Map(canvas, {
projection: 'EPSG:3857',
layers: [
new OpenLayers.Layer.Google(
"Google Streets", // the default
{numZoomLevels: 20}
),
new OpenLayers.Layer.Google(
"Google Physical",
{type: google.maps.MapTypeId.TERRAIN}
),
new OpenLayers.Layer.Google(
"Google Hybrid",
{type: google.maps.MapTypeId.HYBRID, numZoomLevels: 20}
),
new OpenLayers.Layer.Google(
"Google Satellite",
{type: google.maps.MapTypeId.SATELLITE, numZoomLevels: 22}
)
],
center: new OpenLayers.LonLat(lon, lat).transform('EPSG:4326', 'EPSG:3857'),
zoom: 5
});
map.addControl(new OpenLayers.Control.LayerSwitcher());
return map;
}
運行效果
左邊是縮放按鈕,右邊可以選擇地圖圖層 
地圖與拓撲圖的結合
OpenLayers與Qunee是兩套不同的組件庫,有着各自的交互系統和坐標系,需要實現組件疊加,以及坐標和交互的同步
組件疊加
OpenLayers結構復雜,具有多個HTML圖層,而Qunee相對簡單,所以最終決定將Qunee插入到OpenLayers的viewportDiv中
var canvas = document.createElement('div');
canvas.style.width = '100%';
canvas.style.height = '100%';
canvas.style.position = 'absolute';
canvas.style.top = '0px';
canvas.style.left = '0px';
canvas.style.zIndex = 999;
map.viewPortDiv.insertBefore(canvas, map.viewPortDiv.firstChild);
Q.doSuperConstructor(this, MapGraph, [canvas]);
坐標轉換
Qunee使用的是屏幕坐標,與地圖坐標系完全不同,需要做轉換
經緯度轉換成屏幕坐標
需要兩步,首先將經緯度轉換成當前地圖的投影坐標,使用的是OpenLayers提供的OpenLayers.LonLat#transform(原始投影, 目標投影)方法
toLonLat: function(lon, lat){
var l = new OpenLayers.LonLat(lon, lat);
l.transform('EPSG:4326', graph.map.getProjectionObject());
return l;
}
然后將轉換后的坐標轉換成屏幕坐標
getPixelFromLonLat: function(lonLat){
return this.map.getPixelFromLonLat(lonLat);
}
根據經緯度創建節點
createNodeByLonLat: function(name, lon, lat){
var l = this.toLonLat(lon, lat);
var p = this.getPixelFromLonLat(l);
var node = graph.createNode(name, p.x, p.y);
node.lonLat = l;
return node;
}
屏幕坐標轉換成地理坐標
同理,在節點移動后,需要將屏幕坐標轉換成地理坐標 也需要兩步,首先將qunee的邏輯坐標轉換成屏幕坐標,然后再用OpenLayers的getLonLatFromPixel方法,轉換成地理坐標
var pixel = this.toCanvas(data.location.x, data.location.y); data.lonLat = this.map.getLonLatFromPixel(new OpenLayers.Pixel(pixel.x, pixel.y));
在節點移動后都需要做這些轉換,監聽節點拖拽完成事件,進行坐標的同步
this.interactionDispatcher.addListener(function(evt){
if(evt.kind == Q.InteractionEvent.ELEMENT_MOVE_END){
var datas = evt.datas;
Q.forEach(datas, function(data){
var pixel = this.toCanvas(data.location.x, data.location.y);
data.lonLat = this.map.getLonLatFromPixel(new OpenLayers.Pixel(pixel.x, pixel.y));
}, this);
}
}, this)
交互同步
OpenLayers和Qunee的交互是沖突的,比如拖拽操作,qunee響應了,OpenLayers就沒法響應,這里我們在Qunee交互的基礎之上實現地圖的漫游縮放操作
平移操作
通過重寫Q.Graph的translate方法,實現兩者的同步,是不是挺簡單
translate: function (tx, ty) {
Q.doSuper(this, MapGraph, "translate", arguments);
this.map.moveByPx(-tx, -ty);
}
縮放操作
OpenLayers默認的通過雙擊、鼠標滾輪實現縮放,這些事件默認會被Qunee所攔截,所以需要自己添加和派發
this.html.ondblclick = createEventFunction(this, function(evt){
if(this.getElementByMouseEvent(evt)){
Q.stopEvent(evt);
}
});
this.onmousewheel = function(evt){
if (this._scaling) {
return;
}
this._scaling = true;
Q.callLater(function() {
delete this._scaling;
}, this, 200);
this.map.zoomTo(this.map.zoom + (evt.delta > 0 ? 1 : -1), this.globalToLocal(evt));
}
縮放后的坐標同步 Qunee也有默認的縮放機制,但在地圖應用中不太適用,所以需要屏蔽掉
this.enableWheelZoom = false; this.enableDoubleClickToOverview = false;
然后監聽地圖的縮放事件
this.map.events.register('zoomend', this, function(){this.updateNodes(true)});
實現對節點的坐標同步
updateNodes: function(updateLocation){
if(updateLocation === true){
this.forEach(function(d){
if(d instanceof Q.Node){
var l = d.lonLat;
var p = this.getPixelFromLonLat(l);
d.location = p;
}
}, this);
this.translateTo(0, 0);
return;
}
this.translateTo(this.map.layerContainerOriginPx.x, this.map.layerContainerOriginPx.y);
}
最終的運行效果
在線演示:demo.qunee.com/map/map.html

