基于VC++的SHP地图读取与显示实现
一、项目概述
1.1 技术架构
┌─────────────────────────────────────────┐
│ 应用程序层 (MFC对话框/文档视图) │
├─────────────────────────────────────────┤
│ 地图显示控件 (C++自定义控件) │
├─────────────────────────────────────────┤
│ SHP文件解析器 (OGR/GDAL) │
├─────────────────────────────────────────┤
│ 图形渲染层 (GDI/GDI+) │
└─────────────────────────────────────────┘
1.2 功能特性
- 读取SHP格式矢量地图
- 支持点、线、面几何类型
- 读取DBF属性数据
- 地图缩放、平移
- 要素选择、属性查询
- 坐标转换
- 多图层叠加
- 样式化渲染
二、环境配置
2.1 所需库文件
- GDAL/OGR - 地理数据抽象库
- Shapelib - 轻量级SHP读写库(备选)
2.2 Visual Studio 配置
包含目录:
C:\gdal\include
C:\gdal\include\ogr
C:\gdal\include\gdal
库目录:
C:\gdal\lib
链接库:
gdal_i.lib
gdi32.lib
gdiplus.lib
预处理器定义:
_CRT_SECURE_NO_WARNINGS
WIN32
_DEBUG
_AFXDLL
三、核心代码实现
3.1 基础数据结构
// ShapeTypes.h
#pragma once
#include <vector>
#include <string>
// 几何类型枚举
enum ShapeType {
SHAPE_NULL = 0,
SHAPE_POINT = 1,
SHAPE_POLYLINE = 3,
SHAPE_POLYGON = 5,
SHAPE_MULTIPOINT = 8,
SHAPE_POINTZ = 11,
SHAPE_POLYLINEZ = 13,
SHAPE_POLYGONZ = 15,
SHAPE_MULTIPOINTZ = 18
};
// 点结构
struct PointD {
double x;
double y;
PointD() : x(0), y(0) {}
PointD(double _x, double _y) : x(_x), y(_y) {}
};
// 边界框
struct BoundingBox {
double xMin;
double yMin;
double xMax;
double yMax;
BoundingBox() : xMin(0), yMin(0), xMax(0), yMax(0) {}
BoundingBox(double xmin, double ymin, double xmax, double ymax)
: xMin(xmin), yMin(ymin), xMax(xmax), yMax(ymax) {}
double Width() const { return xMax - xMin; }
double Height() const { return yMax - yMin; }
double CenterX() const { return (xMin + xMax) / 2.0; }
double CenterY() const { return (yMin + yMax) / 2.0; }
bool Contains(double x, double y) const {
return x >= xMin && x <= xMax && y >= yMin && y <= yMax;
}
bool Intersects(const BoundingBox& other) const {
return !(xMin > other.xMax || xMax < other.xMin ||
yMin > other.yMax || yMax < other.yMin);
}
};
// 要素属性
struct FeatureAttribute {
std::string name;
std::string value;
int type; // 0:字符串, 1:整数, 2:浮点数, 3:日期
};
3.2 SHP文件读取器(使用OGR)
// ShpReader.h
#pragma once
#include "ShapeTypes.h"
#include <vector>
#include <memory>
#include <string>
// 前向声明
class OGRDataSource;
class OGRLayer;
class OGRFeature;
class OGRGeometry;
class CShpReader {
public:
CShpReader();
virtual ~CShpReader();
// 打开SHP文件
bool Open(const std::string& shpFilePath);
// 关闭文件
void Close();
// 获取图层数量
int GetLayerCount() const;
// 获取要素数量
int GetFeatureCount() const;
// 获取图层范围
BoundingBox GetExtent() const;
// 获取几何类型
ShapeType GetGeometryType() const;
// 获取字段列表
std::vector<std::string> GetFieldNames() const;
// 获取字段值
std::string GetFieldValue(int featureIndex, int fieldIndex) const;
std::string GetFieldValue(int featureIndex, const std::string& fieldName) const;
// 获取几何数据
std::vector<PointD> GetGeometryPoints(int featureIndex) const;
// 获取所有要素
std::vector<std::vector<PointD>> GetAllGeometries() const;
// 空间查询
std::vector<int> SpatialQuery(const BoundingBox& bbox) const;
// 属性查询
std::vector<int> AttributeQuery(const std::string& whereClause) const;
// 获取错误信息
std::string GetLastError() const { return m_strError; }
// 是否已打开
bool IsOpen() const { return m_pDataSource != NULL; }
// 获取文件路径
std::string GetFilePath() const { return m_strFilePath; }
// 获取投影信息
std::string GetProjection() const;
private:
OGRDataSource* m_pDataSource;
OGRLayer* m_pLayer;
std::string m_strFilePath;
std::string m_strError;
BoundingBox m_extent;
};
// ShpReader.cpp
#include "stdafx.h"
#include "ShpReader.h"
#include <gdal.h>
#include <ogr_api.h>
#include <ogrsf_frmts.h>
#include <algorithm>
CShpReader::CShpReader()
: m_pDataSource(NULL)
, m_pLayer(NULL) {
// 注册所有OGR驱动
GDALAllRegister();
OGRRegisterAll();
}
CShpReader::~CShpReader() {
Close();
}
bool CShpReader::Open(const std::string& shpFilePath) {
Close();
m_strFilePath = shpFilePath;
// 使用GDAL/OGR打开文件
m_pDataSource = (OGRDataSource*)GDALOpenEx(
shpFilePath.c_str(),
GDAL_OF_VECTOR | GDAL_OF_READONLY,
NULL, NULL, NULL);
if (m_pDataSource == NULL) {
m_strError = "无法打开SHP文件: " + shpFilePath;
return false;
}
// 获取第一个图层
m_pLayer = m_pDataSource->GetLayer(0);
if (m_pLayer == NULL) {
m_strError = "SHP文件不包含有效图层";
GDALClose(m_pDataSource);
m_pDataSource = NULL;
return false;
}
// 获取图层范围
OGREnvelope envelope;
if (m_pLayer->GetExtent(&envelope, TRUE) == OGRERR_NONE) {
m_extent.xMin = envelope.MinX;
m_extent.yMin = envelope.MinY;
m_extent.xMax = envelope.MaxX;
m_extent.yMax = envelope.MaxY;
}
return true;
}
void CShpReader::Close() {
if (m_pDataSource) {
GDALClose(m_pDataSource);
m_pDataSource = NULL;
m_pLayer = NULL;
}
m_strFilePath.clear();
m_strError.clear();
}
int CShpReader::GetLayerCount() const {
if (m_pDataSource) {
return m_pDataSource->GetLayerCount();
}
return 0;
}
int CShpReader::GetFeatureCount() const {
if (m_pLayer) {
return (int)m_pLayer->GetFeatureCount();
}
return 0;
}
BoundingBox CShpReader::GetExtent() const {
return m_extent;
}
ShapeType CShpReader::GetGeometryType() const {
if (!m_pLayer) return SHAPE_NULL;
OGRFeatureDefn* poFDefn = m_pLayer->GetLayerDefn();
if (!poFDefn) return SHAPE_NULL;
OGRwkbGeometryType eType = poFDefn->GetGeomType();
switch (eType) {
case wkbPoint:
case wkbMultiPoint:
return SHAPE_POINT;
case wkbLineString:
case wkbMultiLineString:
return SHAPE_POLYLINE;
case wkbPolygon:
case wkbMultiPolygon:
return SHAPE_POLYGON;
default:
return SHAPE_NULL;
}
}
std::vector<std::string> CShpReader::GetFieldNames() const {
std::vector<std::string> fields;
if (!m_pLayer) return fields;
OGRFeatureDefn* poFDefn = m_pLayer->GetLayerDefn();
if (!poFDefn) return fields;
int fieldCount = poFDefn->GetFieldCount();
for (int i = 0; i < fieldCount; i++) {
OGRFieldDefn* poFieldDef = poFDefn->GetFieldDefn(i);
if (poFieldDef) {
fields.push_back(poFieldDef->GetNameRef());
}
}
return fields;
}
std::string CShpReader::GetFieldValue(int featureIndex, int fieldIndex) const {
if (!m_pLayer) return "";
OGRFeature* poFeature = m_pLayer->GetFeature(featureIndex);
if (!poFeature) return "";
std::string value = poFeature->GetFieldAsString(fieldIndex);
OGRFeature::DestroyFeature(poFeature);
return value;
}
std::string CShpReader::GetFieldValue(int featureIndex, const std::string& fieldName) const {
if (!m_pLayer) return "";
OGRFeature* poFeature = m_pLayer->GetFeature(featureIndex);
if (!poFeature) return "";
std::string value = poFeature->GetFieldAsString(fieldName.c_str());
OGRFeature::DestroyFeature(poFeature);
return value;
}
std::vector<PointD> CShpReader::GetGeometryPoints(int featureIndex) const {
std::vector<PointD> points;
if (!m_pLayer) return points;
OGRFeature* poFeature = m_pLayer->GetFeature(featureIndex);
if (!poFeature) return points;
OGRGeometry* poGeometry = poFeature->GetGeometryRef();
if (!poGeometry) {
OGRFeature::DestroyFeature(poFeature);
return points;
}
OGRwkbGeometryType eType = poGeometry->getGeometryType();
if (eType == wkbPoint || eType == wkbPoint25D) {
OGRPoint* poPoint = (OGRPoint*)poGeometry;
points.push_back(PointD(poPoint->getX(), poPoint->getY()));
}
else if (eType == wkbLineString || eType == wkbLineString25D) {
OGRLineString* poLine = (OGRLineString*)poGeometry;
int pointCount = poLine->getNumPoints();
for (int i = 0; i < pointCount; i++) {
points.push_back(PointD(poLine->getX(i), poLine->getY(i)));
}
}
else if (eType == wkbPolygon || eType == wkbPolygon25D) {
OGRPolygon* poPolygon = (OGRPolygon*)poGeometry;
OGRLinearRing* poRing = poPolygon->getExteriorRing();
if (poRing) {
int pointCount = poRing->getNumPoints();
for (int i = 0; i < pointCount; i++) {
points.push_back(PointD(poRing->getX(i), poRing->getY(i)));
}
}
}
OGRFeature::DestroyFeature(poFeature);
return points;
}
std::vector<std::vector<PointD>> CShpReader::GetAllGeometries() const {
std::vector<std::vector<PointD>> geometries;
if (!m_pLayer) return geometries;
m_pLayer->ResetReading();
OGRFeature* poFeature = NULL;
while ((poFeature = m_pLayer->GetNextFeature()) != NULL) {
OGRGeometry* poGeometry = poFeature->GetGeometryRef();
if (poGeometry) {
std::vector<PointD> points = GetGeometryPoints(poFeature->GetFID());
if (!points.empty()) {
geometries.push_back(points);
}
}
OGRFeature::DestroyFeature(poFeature);
}
return geometries;
}
std::vector<int> CShpReader::SpatialQuery(const BoundingBox& bbox) const {
std::vector<int> results;
if (!m_pLayer) return results;
// 创建空间过滤器
OGRPolygon filter;
OGRLinearRing ring;
ring.addPoint(bbox.xMin, bbox.yMin);
ring.addPoint(bbox.xMax, bbox.yMin);
ring.addPoint(bbox.xMax, bbox.yMax);
ring.addPoint(bbox.xMin, bbox.yMax);
ring.addPoint(bbox.xMin, bbox.yMin);
filter.addRing(&ring);
m_pLayer->SetSpatialFilter(&filter);
m_pLayer->ResetReading();
OGRFeature* poFeature = NULL;
while ((poFeature = m_pLayer->GetNextFeature()) != NULL) {
results.push_back(poFeature->GetFID());
OGRFeature::DestroyFeature(poFeature);
}
m_pLayer->SetSpatialFilter(NULL);
return results;
}
std::vector<int> CShpReader::AttributeQuery(const std::string& whereClause) const {
std::vector<int> results;
if (!m_pLayer) return results;
m_pLayer->SetAttributeFilter(whereClause.c_str());
m_pLayer->ResetReading();
OGRFeature* poFeature = NULL;
while ((poFeature = m_pLayer->GetNextFeature()) != NULL) {
results.push_back(poFeature->GetFID());
OGRFeature::DestroyFeature(poFeature);
}
m_pLayer->SetAttributeFilter(NULL);
return results;
}
std::string CShpReader::GetProjection() const {
if (!m_pLayer) return "";
OGRSpatialReference* poSRS = m_pLayer->GetSpatialRef();
if (!poSRS) return "";
char* pszWKT = NULL;
poSRS->exportToWkt(&pszWKT);
std::string projection(pszWKT);
CPLFree(pszWKT);
return projection;
}
3.3 地图显示控件
// MapControl.h
#pragma once
#include "ShpReader.h"
#include <vector>
#include <map>
class CMapControl : public CWnd {
DECLARE_DYNAMIC(CMapControl)
public:
CMapControl();
virtual ~CMapControl();
// 添加SHP图层
bool AddLayer(const std::string& shpFilePath, const std::string& layerName = "");
// 移除图层
bool RemoveLayer(int index);
// 获取图层数量
int GetLayerCount() const { return (int)m_layers.size(); }
// 获取图层范围
BoundingBox GetFullExtent() const;
// 缩放到全图
void ZoomToFullExtent();
// 缩放到图层
void ZoomToLayer(int layerIndex);
// 平移
void Pan(int dx, int dy);
// 缩放
void Zoom(double factor, int centerX, int centerY);
// 获取当前范围
BoundingBox GetCurrentExtent() const { return m_currentExtent; }
// 坐标转换
CPoint MapToScreen(double mapX, double mapY) const;
PointD ScreenToMap(int screenX, int screenY) const;
// 要素查询
std::vector<int> QueryFeatures(int screenX, int screenY, int tolerance = 5);
// 设置背景色
void SetBackgroundColor(COLORREF color) { m_backgroundColor = color; Invalidate(); }
// 设置线颜色
void SetLineColor(int layerIndex, COLORREF color);
// 设置填充颜色
void SetFillColor(int layerIndex, COLORREF color);
// 设置线宽
void SetLineWidth(int layerIndex, int width);
protected:
DECLARE_MESSAGE_MAP()
afx_msg void OnPaint();
afx_msg void OnSize(UINT nType, int cx, int cy);
afx_msg void OnLButtonDown(UINT nFlags, CPoint point);
afx_msg void OnLButtonUp(UINT nFlags, CPoint point);
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
afx_msg BOOL OnMouseWheel(UINT nFlags, short zDelta, CPoint pt);
private:
// 图层结构
struct MapLayer {
std::shared_ptr<CShpReader> reader;
std::string name;
bool visible;
COLORREF lineColor;
COLORREF fillColor;
int lineWidth;
HPEN hPen;
HBRUSH hBrush;
MapLayer() : visible(true), lineColor(RGB(0, 0, 0)),
fillColor(RGB(200, 200, 200)), lineWidth(1),
hPen(NULL), hBrush(NULL) {}
~MapLayer() {
if (hPen) DeleteObject(hPen);
if (hBrush) DeleteObject(hBrush);
}
void CreatePen() {
if (hPen) DeleteObject(hPen);
hPen = CreatePen(PS_SOLID, lineWidth, lineColor);
}
void CreateBrush() {
if (hBrush) DeleteObject(hBrush);
hBrush = CreateSolidBrush(fillColor);
}
};
// 绘制函数
void DrawLayer(CDC* pDC, MapLayer* layer);
void DrawPoint(CDC* pDC, const PointD& point, MapLayer* layer);
void DrawPolyline(CDC* pDC, const std::vector<PointD>& points, MapLayer* layer);
void DrawPolygon(CDC* pDC, const std::vector<PointD>& points, MapLayer* layer);
// 计算全图范围
BoundingBox CalculateFullExtent() const;
private:
std::vector<std::shared_ptr<MapLayer>> m_layers;
BoundingBox m_fullExtent;
BoundingBox m_currentExtent;
COLORREF m_backgroundColor;
CRect m_clientRect;
bool m_isPanning;
CPoint m_lastMousePos;
};
// MapControl.cpp
#include "stdafx.h"
#include "MapControl.h"
IMPLEMENT_DYNAMIC(CMapControl, CWnd)
BEGIN_MESSAGE_MAP(CMapControl, CWnd)
ON_WM_PAINT()
ON_WM_SIZE()
ON_WM_LBUTTONDOWN()
ON_WM_LBUTTONUP()
ON_WM_MOUSEMOVE()
ON_WM_MOUSEWHEEL()
END_MESSAGE_MAP()
CMapControl::CMapControl()
: m_backgroundColor(RGB(240, 240, 240))
, m_isPanning(false) {
// 注册窗口类
CString className = AfxRegisterWndClass(
CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW),
(HBRUSH)::GetStockObject(WHITE_BRUSH),
::LoadIcon(NULL, IDI_APPLICATION));
Create(className, _T("Map Control"), WS_CHILD | WS_VISIBLE,
CRect(0, 0, 100, 100), GetDesktopWindow(), 0);
}
CMapControl::~CMapControl() {
m_layers.clear();
}
bool CMapControl::AddLayer(const std::string& shpFilePath, const std::string& layerName) {
auto layer = std::make_shared<MapLayer>();
layer->reader = std::make_shared<CShpReader>();
if (!layer->reader->Open(shpFilePath)) {
return false;
}
if (!layerName.empty()) {
layer->name = layerName;
} else {
// 从文件路径提取名称
size_t pos = shpFilePath.find_last_of("\\/");
if (pos != std::string::npos) {
layer->name = shpFilePath.substr(pos + 1);
} else {
layer->name = shpFilePath;
}
pos = layer->name.find_last_of(".");
if (pos != std::string::npos) {
layer->name = layer->name.substr(0, pos);
}
}
// 根据几何类型设置默认颜色
ShapeType type = layer->reader->GetGeometryType();
switch (type) {
case SHAPE_POINT:
layer->lineColor = RGB(255, 0, 0);
layer->fillColor = RGB(255, 0, 0);
break;
case SHAPE_POLYLINE:
layer->lineColor = RGB(0, 0, 255);
layer->fillColor = RGB(0, 0, 255);
break;
case SHAPE_POLYGON:
layer->lineColor = RGB(0, 0, 0);
layer->fillColor = RGB(200, 200, 200);
break;
}
layer->CreatePen();
layer->CreateBrush();
m_layers.push_back(layer);
// 更新范围
m_fullExtent = CalculateFullExtent();
m_currentExtent = m_fullExtent;
Invalidate();
return true;
}
bool CMapControl::RemoveLayer(int index) {
if (index < 0 || index >= (int)m_layers.size()) {
return false;
}
m_layers.erase(m_layers.begin() + index);
// 更新范围
m_fullExtent = CalculateFullExtent();
Invalidate();
return true;
}
BoundingBox CMapControl::GetFullExtent() const {
return m_fullExtent;
}
void CMapControl::ZoomToFullExtent() {
m_currentExtent = m_fullExtent;
Invalidate();
}
void CMapControl::ZoomToLayer(int layerIndex) {
if (layerIndex < 0 || layerIndex >= (int)m_layers.size()) {
return;
}
auto& layer = m_layers[layerIndex];
if (layer && layer->reader) {
m_currentExtent = layer->reader->GetExtent();
Invalidate();
}
}
void CMapControl::Pan(int dx, int dy) {
double mapDx = dx * (m_currentExtent.Width() / m_clientRect.Width());
double mapDy = -dy * (m_currentExtent.Height() / m_clientRect.Height()); // Y轴反向
m_currentExtent.xMin -= mapDx;
m_currentExtent.xMax -= mapDx;
m_currentExtent.yMin -= mapDy;
m_currentExtent.yMax -= mapDy;
Invalidate();
}
void CMapControl::Zoom(double factor, int centerX, int centerY) {
if (factor <= 0) return;
// 计算中心点地图坐标
PointD center = ScreenToMap(centerX, centerY);
// 计算新范围
double newWidth = m_currentExtent.Width() / factor;
double newHeight = m_currentExtent.Height() / factor;
m_currentExtent.xMin = center.x - newWidth / 2;
m_currentExtent.xMax = center.x + newWidth / 2;
m_currentExtent.yMin = center.y - newHeight / 2;
m_currentExtent.yMax = center.y + newHeight / 2;
Invalidate();
}
CPoint CMapControl::MapToScreen(double mapX, double mapY) const {
if (m_clientRect.IsRectEmpty()) {
return CPoint(0, 0);
}
double screenX = m_clientRect.left + (mapX - m_currentExtent.xMin) *
m_clientRect.Width() / m_currentExtent.Width();
double screenY = m_clientRect.bottom - (mapY - m_currentExtent.yMin) *
m_clientRect.Height() / m_currentExtent.Height();
return CPoint((int)screenX, (int)screenY);
}
PointD CMapControl::ScreenToMap(int screenX, int screenY) const {
if (m_clientRect.IsRectEmpty()) {
return PointD();
}
double mapX = m_currentExtent.xMin + (screenX - m_clientRect.left) *
m_currentExtent.Width() / m_clientRect.Width();
double mapY = m_currentExtent.yMin + (m_clientRect.bottom - screenY) *
m_currentExtent.Height() / m_clientRect.Height();
return PointD(mapX, mapY);
}
std::vector<int> CMapControl::QueryFeatures(int screenX, int screenY, int tolerance) {
std::vector<int> results;
PointD mapPoint = ScreenToMap(screenX, screenY);
// 计算容差范围
PointD mapPoint1 = ScreenToMap(screenX - tolerance, screenY - tolerance);
PointD mapPoint2 = ScreenToMap(screenX + tolerance, screenY + tolerance);
BoundingBox bbox;
bbox.xMin = min(mapPoint1.x, mapPoint2.x);
bbox.xMax = max(mapPoint1.x, mapPoint2.x);
bbox.yMin = min(mapPoint1.y, mapPoint2.y);
bbox.yMax = max(mapPoint1.y, mapPoint2.y);
// 遍历所有图层
for (const auto& layer : m_layers) {
if (!layer->visible || !layer->reader) continue;
std::vector<int> layerResults = layer->reader->SpatialQuery(bbox);
results.insert(results.end(), layerResults.begin(), layerResults.end());
}
return results;
}
void CMapControl::SetLineColor(int layerIndex, COLORREF color) {
if (layerIndex >= 0 && layerIndex < (int)m_layers.size()) {
m_layers[layerIndex]->lineColor = color;
m_layers[layerIndex]->CreatePen();
Invalidate();
}
}
void CMapControl::SetFillColor(int layerIndex, COLORREF color) {
if (layerIndex >= 0 && layerIndex < (int)m_layers.size()) {
m_layers[layerIndex]->fillColor = color;
m_layers[layerIndex]->CreateBrush();
Invalidate();
}
}
void CMapControl::SetLineWidth(int layerIndex, int width) {
if (layerIndex >= 0 && layerIndex < (int)m_layers.size() && width > 0) {
m_layers[layerIndex]->lineWidth = width;
m_layers[layerIndex]->CreatePen();
Invalidate();
}
}
void CMapControl::OnPaint() {
CPaintDC dc(this);
// 双缓冲绘图
CDC memDC;
memDC.CreateCompatibleDC(&dc);
CRect rect;
GetClientRect(&rect);
CBitmap memBitmap;
memBitmap.CreateCompatibleBitmap(&dc, rect.Width(), rect.Height());
CBitmap* pOldBitmap = memDC.SelectObject(&memBitmap);
// 绘制背景
CBrush bgBrush(m_backgroundColor);
memDC.FillRect(&rect, &bgBrush);
// 保存客户区矩形
m_clientRect = rect;
// 绘制所有可见图层
for (const auto& layer : m_layers) {
if (layer->visible) {
DrawLayer(&memDC, layer.get());
}
}
// 绘制到屏幕
dc.BitBlt(0, 0, rect.Width(), rect.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOldBitmap);
memBitmap.DeleteObject();
memDC.DeleteDC();
}
void CMapControl::DrawLayer(CDC* pDC, MapLayer* layer) {
if (!layer || !layer->reader) return;
std::vector<std::vector<PointD>> geometries = layer->reader->GetAllGeometries();
ShapeType type = layer->reader->GetGeometryType();
HPEN oldPen = (HPEN)pDC->SelectObject(layer->hPen);
HBRUSH oldBrush = (HBRUSH)pDC->SelectObject(layer->hBrush);
for (const auto& points : geometries) {
if (points.empty()) continue;
switch (type) {
case SHAPE_POINT:
DrawPoint(pDC, points[0], layer);
break;
case SHAPE_POLYLINE:
DrawPolyline(pDC, points, layer);
break;
case SHAPE_POLYGON:
DrawPolygon(pDC, points, layer);
break;
}
}
pDC->SelectObject(oldPen);
pDC->SelectObject(oldBrush);
}
void CMapControl::DrawPoint(CDC* pDC, const PointD& point, MapLayer* layer) {
CPoint screenPoint = MapToScreen(point.x, point.y);
int radius = 3;
CPen pointPen(PS_SOLID, 1, layer->lineColor);
CBrush pointBrush(layer->fillColor);
CPen* oldPen = pDC->SelectObject(&pointPen);
CBrush* oldBrush = pDC->SelectObject(&pointBrush);
pDC->Ellipse(screenPoint.x - radius, screenPoint.y - radius,
screenPoint.x + radius, screenPoint.y + radius);
pDC->SelectObject(oldPen);
pDC->SelectObject(oldBrush);
}
void CMapControl::DrawPolyline(CDC* pDC, const std::vector<PointD>& points, MapLayer* layer) {
if (points.size() < 2) return;
std::vector<POINT> screenPoints(points.size());
for (size_t i = 0; i < points.size(); i++) {
screenPoints[i] = MapToScreen(points[i].x, points[i].y);
}
pDC->Polyline(&screenPoints[0], (int)screenPoints.size());
}
void CMapControl::DrawPolygon(CDC* pDC, const std::vector<PointD>& points, MapLayer* layer) {
if (points.size() < 3) return;
std::vector<POINT> screenPoints(points.size());
for (size_t i = 0; i < points.size(); i++) {
screenPoints[i] = MapToScreen(points[i].x, points[i].y);
}
pDC->Polygon(&screenPoints[0], (int)screenPoints.size());
}
void CMapControl::OnSize(UINT nType, int cx, int cy) {
CWnd::OnSize(nType, cx, cy);
Invalidate();
}
void CMapControl::OnLButtonDown(UINT nFlags, CPoint point) {
m_isPanning = true;
m_lastMousePos = point;
SetCapture();
SetCursor(LoadCursor(NULL, IDC_SIZEALL));
CWnd::OnLButtonDown(nFlags, point);
}
void CMapControl::OnLButtonUp(UINT nFlags, CPoint point) {
m_isPanning = false;
ReleaseCapture();
SetCursor(LoadCursor(NULL, IDC_ARROW));
CWnd::OnLButtonUp(nFlags, point);
}
void CMapControl::OnMouseMove(UINT nFlags, CPoint point) {
if (m_isPanning) {
int dx = point.x - m_lastMousePos.x;
int dy = point.y - m_lastMousePos.y;
Pan(dx, dy);
m_lastMousePos = point;
}
CWnd::OnMouseMove(nFlags, point);
}
BOOL CMapControl::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt) {
// 转换为客户区坐标
ScreenToClient(&pt);
// 计算缩放因子
double zoomFactor = (zDelta > 0) ? 1.2 : 0.8;
// 执行缩放
Zoom(zoomFactor, pt.x, pt.y);
return CWnd::OnMouseWheel(nFlags, zDelta, pt);
}
BoundingBox CMapControl::CalculateFullExtent() const {
BoundingBox fullExtent;
bool first = true;
for (const auto& layer : m_layers) {
if (!layer->visible || !layer->reader) continue;
BoundingBox layerExtent = layer->reader->GetExtent();
if (first) {
fullExtent = layerExtent;
first = false;
} else {
fullExtent.xMin = min(fullExtent.xMin, layerExtent.xMin);
fullExtent.yMin = min(fullExtent.yMin, layerExtent.yMin);
fullExtent.xMax = max(fullExtent.xMax, layerExtent.xMax);
fullExtent.yMax = max(fullExtent.yMax, layerExtent.yMax);
}
}
if (first) {
// 如果没有图层,返回默认范围
fullExtent.xMin = 0;
fullExtent.yMin = 0;
fullExtent.xMax = 100;
fullExtent.yMax = 100;
}
return fullExtent;
}
3.4 主对话框程序
// ShpViewerDlg.h
#pragma once
#include "MapControl.h"
class CShpViewerDlg : public CDialogEx {
DECLARE_DYNAMIC(CShpViewerDlg)
public:
CShpViewerDlg(CWnd* pParent = nullptr);
virtual ~CShpViewerDlg();
enum { IDD = IDD_SHPVIEWER_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnFileOpen();
afx_msg void OnFileExit();
afx_msg void OnViewZoomIn();
afx_msg void OnViewZoomOut();
afx_msg void OnViewZoomToFull();
afx_msg void OnViewPan();
afx_msg void OnToolsQuery();
afx_msg void OnHelpAbout();
private:
void InitToolBar();
void InitStatusBar();
void UpdateStatusBar();
private:
CMapControl* m_mapControl;
CToolBar m_toolBar;
CStatusBar m_statusBar;
CString m_currentShpFile;
};
// ShpViewerDlg.cpp
#include "stdafx.h"
#include "ShpViewer.h"
#include "ShpViewerDlg.h"
#include "afxdialogex.h"
#include <commdlg.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 状态栏指示器
static UINT indicators[] = {
ID_SEPARATOR,
ID_INDICATOR_CAPS,
ID_INDICATOR_NUM,
ID_INDICATOR_SCRL,
};
BEGIN_MESSAGE_MAP(CShpViewerDlg, CDialogEx)
ON_COMMAND(ID_FILE_OPEN, &CShpViewerDlg::OnFileOpen)
ON_COMMAND(ID_FILE_EXIT, &CShpViewerDlg::OnFileExit)
ON_COMMAND(ID_VIEW_ZOOMIN, &CShpViewerDlg::OnViewZoomIn)
ON_COMMAND(ID_VIEW_ZOOMOUT, &CShpViewerDlg::OnViewZoomOut)
ON_COMMAND(ID_VIEW_ZOOMTOFULL, &CShpViewerDlg::OnViewZoomToFull)
ON_COMMAND(ID_VIEW_PAN, &CShpViewerDlg::OnViewPan)
ON_COMMAND(ID_TOOLS_QUERY, &CShpViewerDlg::OnToolsQuery)
ON_COMMAND(ID_HELP_ABOUT, &CShpViewerDlg::OnHelpAbout)
END_MESSAGE_MAP()
CShpViewerDlg::CShpViewerDlg(CWnd* pParent /*=nullptr*/)
: CDialogEx(IDD_SHPVIEWER_DIALOG, pParent) {
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
m_mapControl = new CMapControl();
}
CShpViewerDlg::~CShpViewerDlg() {
if (m_mapControl) {
delete m_mapControl;
}
}
void CShpViewerDlg::DoDataExchange(CDataExchange* pDX) {
CDialogEx::DoDataExchange(pDX);
}
BOOL CShpViewerDlg::OnInitDialog() {
CDialogEx::OnInitDialog();
SetIcon(m_hIcon, TRUE);
SetIcon(m_hIcon, FALSE);
SetWindowText(_T("SHP地图查看器"));
InitToolBar();
InitStatusBar();
// 调整地图控件大小
CRect clientRect;
GetClientRect(&clientRect);
int toolbarHeight = m_toolBar.GetToolBarCtrl().GetBarHeight();
int statusbarHeight = 20;
CRect mapRect(0, toolbarHeight, clientRect.Width(),
clientRect.Height() - statusbarHeight);
m_mapControl->MoveWindow(&mapRect);
m_mapControl->ShowWindow(SW_SHOW);
UpdateStatusBar();
return TRUE;
}
void CShpViewerDlg::InitToolBar() {
if (!m_toolBar.CreateEx(this, TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP
| CBRS_GRIPPER | CBRS_TOOLTIPS | CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||
!m_toolBar.LoadToolBar(IDR_MAINFRAME)) {
TRACE0("Failed to create toolbar\n");
return;
}
m_toolBar.EnableDocking(CBRS_ALIGN_ANY);
EnableDocking(CBRS_ALIGN_ANY);
DockControlBar(&m_toolBar);
}
void CShpViewerDlg::InitStatusBar() {
if (!m_statusBar.Create(this) ||
!m_statusBar.SetIndicators(indicators,
sizeof(indicators)/sizeof(UINT))) {
TRACE0("Failed to create status bar\n");
return;
}
m_statusBar.SetPaneInfo(0, ID_SEPARATOR, SBPS_STRETCH, 0);
RepositionBars(AFX_IDW_CONTROLBAR_FIRST, AFX_IDW_CONTROLBAR_LAST, 0);
}
void CShpViewerDlg::UpdateStatusBar() {
if (!m_mapControl) return;
BoundingBox extent = m_mapControl->GetCurrentExtent();
CString statusText;
statusText.Format(_T("范围: X[%.2f, %.2f] Y[%.2f, %.2f] 图层: %d"),
extent.xMin, extent.xMax, extent.yMin, extent.yMax,
m_mapControl->GetLayerCount());
m_statusBar.SetPaneText(0, statusText);
}
void CShpViewerDlg::OnFileOpen() {
CFileDialog dlg(TRUE, _T("shp"), NULL,
OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT,
_T("Shape Files (*.shp)|*.shp|All Files (*.*)|*.*||"),
this);
if (dlg.DoModal() == IDOK) {
CString shpFilePath = dlg.GetPathName();
std::string filePath = CT2A(shpFilePath);
if (m_mapControl->AddLayer(filePath)) {
m_currentShpFile = shpFilePath;
CString title;
title.Format(_T("SHP地图查看器 - %s"), shpFilePath);
SetWindowText(title);
UpdateStatusBar();
} else {
AfxMessageBox(_T("无法打开SHP文件!"), MB_ICONERROR);
}
}
}
void CShpViewerDlg::OnFileExit() {
OnOK();
}
void CShpViewerDlg::OnViewZoomIn() {
if (!m_mapControl) return;
CRect clientRect;
m_mapControl->GetClientRect(&clientRect);
CPoint center(clientRect.Width() / 2, clientRect.Height() / 2);
m_mapControl->Zoom(1.2, center.x, center.y);
UpdateStatusBar();
}
void CShpViewerDlg::OnViewZoomOut() {
if (!m_mapControl) return;
CRect clientRect;
m_mapControl->GetClientRect(&clientRect);
CPoint center(clientRect.Width() / 2, clientRect.Height() / 2);
m_mapControl->Zoom(0.8, center.x, center.y);
UpdateStatusBar();
}
void CShpViewerDlg::OnViewZoomToFull() {
if (!m_mapControl) return;
m_mapControl->ZoomToFullExtent();
UpdateStatusBar();
}
void CShpViewerDlg::OnViewPan() {
AfxMessageBox(_T("点击并拖动地图进行平移"));
}
void CShpViewerDlg::OnToolsQuery() {
if (!m_mapControl || m_currentShpFile.IsEmpty()) {
AfxMessageBox(_T("请先打开SHP文件!"), MB_ICONINFORMATION);
return;
}
AfxMessageBox(_T("点击地图要素进行查询"));
}
void CShpViewerDlg::OnHelpAbout() {
AfxMessageBox(_T("SHP地图查看器 v1.0\n基于VC++和GDAL/OGR"), MB_ICONINFORMATION);
}
3.5 资源文件
<!-- 工具栏定义 -->
<TOOLBAR>
<ITEM ID="ID_FILE_OPEN" TEXT="打开" IMAGE_INDEX="0" />
<ITEM ID="ID_SEPARATOR" />
<ITEM ID="ID_VIEW_ZOOMIN" TEXT="放大" IMAGE_INDEX="1" />
<ITEM ID="ID_VIEW_ZOOMOUT" TEXT="缩小" IMAGE_INDEX="2" />
<ITEM ID="ID_VIEW_ZOOMTOFULL" TEXT="全图" IMAGE_INDEX="3" />
<ITEM ID="ID_VIEW_PAN" TEXT="平移" IMAGE_INDEX="4" />
<ITEM ID="ID_SEPARATOR" />
<ITEM ID="ID_TOOLS_QUERY" TEXT="查询" IMAGE_INDEX="5" />
<ITEM ID="ID_SEPARATOR" />
<ITEM ID="ID_HELP_ABOUT" TEXT="关于" IMAGE_INDEX="6" />
</TOOLBAR>
3.6 主程序入口
// ShpViewer.cpp
#include "stdafx.h"
#include "ShpViewer.h"
#include "ShpViewerDlg.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
BEGIN_MESSAGE_MAP(CShpViewerApp, CWinApp)
ON_COMMAND(ID_HELP, &CWinApp::OnHelp)
END_MESSAGE_MAP()
CShpViewerApp::CShpViewerApp() {
m_dwRestartManagerSupportFlags = AFX_RESTART_MANAGER_SUPPORT_RESTART;
}
CShpViewerApp theApp;
BOOL CShpViewerApp::InitInstance() {
// 初始化GDAL
GDALAllRegister();
CWinApp::InitInstance();
AfxEnableControlContainer();
CShellManager* pShellManager = new CShellManager;
CMFCVisualManager::SetDefaultManager(RUNTIME_CLASS(CMFCVisualManagerWindows));
SetRegistryKey(_T("SHP Viewer"));
CShpViewerDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (pShellManager != nullptr) {
delete pShellManager;
}
return FALSE;
}
四、使用示例
4.1 快速测试
// 测试SHP文件读取
void TestShpFile() {
CShpReader reader;
if (reader.Open("C:\\data\\countries.shp")) {
printf("要素数量: %d\n", reader.GetFeatureCount());
printf("范围: %.6f, %.6f, %.6f, %.6f\n",
reader.GetExtent().xMin, reader.GetExtent().yMin,
reader.GetExtent().xMax, reader.GetExtent().yMax);
// 获取字段列表
std::vector<std::string> fields = reader.GetFieldNames();
printf("字段列表:\n");
for (const auto& field : fields) {
printf(" %s\n", field.c_str());
}
// 获取前5个要素
for (int i = 0; i < min(5, reader.GetFeatureCount()); i++) {
printf("\n要素 %d:\n", i);
for (size_t j = 0; j < fields.size(); j++) {
printf(" %s: %s\n", fields[j].c_str(),
reader.GetFieldValue(i, (int)j).c_str());
}
}
reader.Close();
}
}
4.2 属性查询对话框
// AttributeDlg.h
class CAttributeDlg : public CDialogEx {
DECLARE_DYNAMIC(CAttributeDlg)
public:
CAttributeDlg(CWnd* pParent = nullptr, CShpReader* pReader = nullptr);
virtual ~CAttributeDlg();
enum { IDD = IDD_ATTRIBUTE_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX);
virtual BOOL OnInitDialog();
DECLARE_MESSAGE_MAP()
private:
void FillListCtrl();
private:
CListCtrl m_listCtrl;
CShpReader* m_pReader;
};
参考代码 基于vc++实现对shp地图的读取和显示 www.youwenfan.com/contentcnv/103275.html
五、编译配置
5.1 项目属性设置
VC++目录:
- 包含目录:
$(GDAL_HOME)\include - 库目录:
$(GDAL_HOME)\lib
链接器 -> 输入:
- 附加依赖项:
gdal_i.lib
C/C++ -> 预处理器:
- 预处理器定义:
_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;_WINDOWS;_AFXDLL
5.2 运行时依赖
将以下DLL复制到可执行文件目录:
gdal304.dll
proj_9_1.dll
sqlite3.dll
六、功能扩展
6.1 添加图层控制
// LayerManagerDlg.h
class CLayerManagerDlg : public CDialogEx {
// 图层管理对话框
// 支持图层可见性控制
// 支持样式设置
// 支持图层顺序调整
};
6.2 添加标注功能
// LabelRenderer.h
class CLabelRenderer {
// 文本标注渲染
// 支持字段标注
// 支持避让算法
};
6.3 添加坐标显示
// 在状态栏显示鼠标坐标
void CShpViewerDlg::OnMouseMoveInMap(UINT nFlags, CPoint point) {
PointD mapPoint = m_mapControl->ScreenToMap(point.x, point.y);
CString coordText;
coordText.Format(_T("坐标: %.6f, %.6f"), mapPoint.x, mapPoint.y);
m_statusBar.SetPaneText(1, coordText);
}

浙公网安备 33010602011771号