基于VC++的SHP地图读取与显示实现

一、项目概述

1.1 技术架构

┌─────────────────────────────────────────┐
│       应用程序层 (MFC对话框/文档视图)    │
├─────────────────────────────────────────┤
│       地图显示控件 (C++自定义控件)       │
├─────────────────────────────────────────┤
│       SHP文件解析器 (OGR/GDAL)          │
├─────────────────────────────────────────┤
│          图形渲染层 (GDI/GDI+)          │
└─────────────────────────────────────────┘

1.2 功能特性

  • 读取SHP格式矢量地图
  • 支持点、线、面几何类型
  • 读取DBF属性数据
  • 地图缩放、平移
  • 要素选择、属性查询
  • 坐标转换
  • 多图层叠加
  • 样式化渲染

二、环境配置

2.1 所需库文件

  1. GDAL/OGR - 地理数据抽象库
  2. 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);
}
posted @ 2026-06-04 10:23  康帅服  阅读(7)  评论(0)    收藏  举报