百度地圖、Google Maps 右下角的小地圖視窗即為「鷹眼」功能。拖動鷹眼中的矩形框,主地圖即移動至對應位置。本文深度解析基於 VC6 與 ArcEngine 9.2 的鷹眼實作技術。
鷹眼與主地圖雙向同步體現兩大核心機制:
ESRI MapControl:ArcEngine 顯示地圖的核心控制元件OnAfterDraw 事件:繪製完成後觸發,用於同步矩形框位置與大小OnMouseDown、OnMouseMove、OnMouseUp 實現矩形框拖曳class CEagleEyeCtrl : public CStatic
{
public:
CEagleEyeCtrl();
CMapControlDefault m_MapCtrl; // 鷹眼地圖控制元件
IFillShapeElementPtr m_ipFillShapeElement; // 矩形框圖元
IEnvelopePtr m_ipLastEnvelope; // 最後有效包絡
IPointPtr m_ipPoint; // 滑鼠點位置
int m_nAction;
BOOL m_bDrag; // 矩形框拖曳標記
BOOL m_bDragWnd; // 整個鷹眼視窗拖曳標記
BOOL m_bShow;
CPoint m_point; // 滑鼠上次左鍵按下螢幕座標
// 事件處理函式
afx_msg void OnMouseDownMapcontrol(long button, long shift, long X, long Y, double mapX, double mapY);
afx_msg void OnMouseMoveMapcontrol(long button, long shift, long X, long Y, double mapX, double mapY);
afx_msg void OnMouseUpMapcontrol(long button, long shift, long X, long Y, double mapX, double mapY);
afx_msg void OnAfterDrawMapcontrol(const VARIANT FAR& Display, long viewDrawPhase);
DECLARE_EVENTSINK_MAP()
DECLARE_MESSAGE_MAP()
};
void CEagleEyeCtrl::OnMouseDownMapcontrol(long button, long shift, long X, long Y, double mapX, double mapY)
{
IActiveViewPtr ipActiveView = m_MapCtrl.GetActiveView();
if (ipActiveView == NULL) return;
IPointPtr ipPoint(CLSID_Point);
IRgbColorPtr ipRgbColor(CLSID_RgbColor);
ipPoint->PutCoords(mapX, mapY);
ipRgbColor->put_RGB((OLE_COLOR)RGB(255, 0, 0));
IGraphicsContainerPtr ipGraphicsContainer;
ipActiveView->get_GraphicsContainer(&ipGraphicsContainer);
// 建立初始矩形框
IEnvelopePtr ipEnvelope(CLSID_Envelope);
ipEnvelope->put_XMax(mapX + 0.0005);
ipEnvelope->put_XMin(mapX - 0.0005);
ipEnvelope->put_YMax(mapY + 0.0003);
ipEnvelope->put_YMin(mapY - 0.0003);
IRectangleElementPtr ipRectangleElement(CLSID_RectangleElement);
ISimpleFillSymbolPtr ipSimpleFillSymbol(CLSID_SimpleFillSymbol);
ISimpleLineSymbolPtr ipSimpleLineSymbol(CLSID_SimpleLineSymbol);
ipSimpleFillSymbol->put_Style(esriSFSHollow);
ipSimpleLineSymbol->put_Color(ipRgbColor);
ipSimpleLineSymbol->put_Style(esriSLSSolid);
ipSimpleLineSymbol->put_Width(1.8);
ipSimpleFillSymbol->put_Outline(ipSimpleLineSymbol);
IElementPtr ipElement = ipRectangleElement;
ipElement->put_Geometry(ipEnvelope);
// 判斷點擊是否在現有矩形框內
if (m_ipFillShapeElement != NULL)
{
IElementPtr ipTempElement = m_ipFillShapeElement;
IGeometryPtr ipGeometry = NULL;
ipTempElement->get_Geometry(&ipGeometry);
esriGeometryType esriType = esriGeometryNull;
ipGeometry->get_GeometryType(&esriType);
if (esriType == esriGeometryPolygon)
{
IEnvelopePtr ipEnvelope2 = NULL;
IPolygonPtr ipPolygon = ipGeometry;
ipPolygon->get_Envelope(&ipEnvelope2);
double xMin, xMax, yMin, yMax;
ipEnvelope2->QueryCoords(&xMin, &yMin, &xMax, &yMax);
if (mapX >= xMin && mapX <= xMax && mapY >= yMin && mapY <= yMax)
m_bDrag = TRUE; // 在框內:進入拖曳模式
else
m_bDrag = FALSE; // 在框外:移動框架至點擊位置
}
}
}
void CEagleEyeCtrl::OnMouseMoveMapcontrol(long button, long shift, long X, long Y, double mapX, double mapY)
{
if (!m_bDrag || m_ipFillShapeElement == NULL) return;
IActiveViewPtr ipActiveView = m_MapCtrl.GetActiveView();
if (ipActiveView == NULL) return;
IGraphicsContainerPtr ipGraphicsContainer;
ipActiveView->get_GraphicsContainer(&ipGraphicsContainer);
IElementPtr ipElement = m_ipFillShapeElement;
IGeometryPtr ipGeometry = NULL;
ipElement->get_Geometry(&ipGeometry);
IPolygonPtr ipPolygon = ipGeometry;
IEnvelopePtr ipEnvelope(CLSID_Envelope);
ipPolygon->get_Envelope(&ipEnvelope);
IPointPtr ipPoint(CLSID_Point);
ipPoint->PutCoords(mapX, mapY);
ipEnvelope->CenterAt(ipPoint); // 矩形框中心移至滑鼠位置
m_ipLastEnvelope = ipEnvelope;
IEnvelopePtr ipEnvelope2 = m_MapCtrl.GetExtent();
ipActiveView->PartialRefresh(esriViewForeground, NULL, ipEnvelope2); // 部分刷新
}
void CEagleEyeCtrl::OnMouseUpMapcontrol(long button, long shift, long X, long Y, double mapX, double mapY)
{
m_bDrag = FALSE;
IActiveViewPtr ipActiveView = m_MapCtrl.GetActiveView();
if (ipActiveView == NULL || m_ipFillShapeElement == NULL) return;
IGraphicsContainerPtr ipGraphicsContainer;
ipActiveView->get_GraphicsContainer(&ipGraphicsContainer);
IElementPtr ipElement = m_ipFillShapeElement;
IGeometryPtr ipGeometry = NULL;
ipElement->get_Geometry(&ipGeometry);
IPolygonPtr ipPolygon = ipGeometry;
IEnvelopePtr ipEnvelope;
ipPolygon->get_Envelope(&ipEnvelope);
IPointPtr ipPoint(CLSID_Point);
ipPoint->PutCoords(mapX, mapY);
ipEnvelope->CenterAt(ipPoint);
ipElement->put_Geometry(ipEnvelope);
m_ipFillShapeElement = ipElement;
ipGraphicsContainer->UpdateElement((IElementPtr)m_ipFillShapeElement);
ipActiveView->Refresh();
// 通知父視窗切換視野
m_ipPoint = ipPoint;
GetParent()->GetParent()->SendMessage(WMU_GIS_EAGLE_NOTIFY, 0, 0);
}
void CEagleEyeCtrl::OnAfterDrawMapcontrol(const VARIANT FAR& Display, long viewDrawPhase)
{
if (m_ipLastEnvelope != NULL && m_bDrag && m_ipFillShapeElement != NULL)
{
IActiveViewPtr ipActiveView = m_MapCtrl.GetActiveView();
if (ipActiveView == NULL) return;
IElementPtr ipElement = m_ipFillShapeElement;
IGeometryPtr ipGeometry = NULL;
ipElement->get_Geometry(&ipGeometry);
IPolygonPtr ipPolygon = ipGeometry;
IEnvelopePtr ipEnvelope(CLSID_Envelope);
ipPolygon->get_Envelope(&ipEnvelope);
// 取得目前滑鼠位置並更新矩形框
CPoint pt;
GetCursorPos(&pt);
m_MapCtrl.ScreenToClient(&pt);
IPointPtr ipPoint = m_MapCtrl.ToMapPoint(pt.x, pt.y);
ipEnvelope->CenterAt(ipPoint);
// 直接在螢幕 DC 上繪製紅色矩形框
ISimpleFillSymbolPtr ipSimpleFillSymbol(CLSID_SimpleFillSymbol);
ISimpleLineSymbolPtr ipSimpleLineSymbol(CLSID_SimpleLineSymbol);
IRgbColorPtr ipRgbColor(CLSID_RgbColor);
ipRgbColor->put_RGB((OLE_COLOR)RGB(255, 0, 0));
ipSimpleFillSymbol->put_Style(esriSFSHollow);
ipSimpleLineSymbol->put_Color(ipRgbColor);
ipSimpleLineSymbol->put_Style(esriSLSSolid);
ipSimpleLineSymbol->put_Width(1.8);
ipSimpleFillSymbol->put_Outline(ipSimpleLineSymbol);
IScreenDisplayPtr ipScreenDisplay;
OLE_HANDLE hDC;
ipActiveView->get_ScreenDisplay(&ipScreenDisplay);
ipScreenDisplay->get_hDC(&hDC);
ipScreenDisplay->StartDrawing(hDC, -1);
ipScreenDisplay->SetSymbol((ISymbolPtr)ipSimpleFillSymbol);
ipScreenDisplay->DrawRectangle(ipEnvelope);
ipScreenDisplay->FinishDrawing();
}
}
主地圖的 OnAfterDraw 事件中,根據目前地圖範圍計算鷹眼矩形框大小與位置;接收 WMU_GIS_EAGLE_NOTIFY 訊息時,依鷹眼矩形框中心點設定主地圖 Extent 實現視野跳轉。
| 技術點 | 實作關鍵 |
|---|---|
| 雙向同步 | 主圖 OnAfterDraw 更新鷹眼框;鷹眼滑鼠事件驅動主圖 Extent 變更 |
| 拖曳流暢度 | OnMouseMove 中 PartialRefresh 區域重繪;OnAfterDraw 中直接繪製避免閃爍 |
| 座標轉換 | 螢幕座標 ↔ 地圖座標:ScreenToClient + ToMapPoint |
| 圖元管理 | GraphicsContainer 管理圖層元素;FillShapeElement 代表可拖曳矩形框 |
| 通訊機制 | 自訂訊息 WMU_GIS_EAGLE_NOTIFY 跨視窗通知 |