可能是专业比较冷门,网上没找到比较系统的墨卡托海图的投影画法,故此自己按照《电子海图的数学和算法基础》一书的公式写了一套简单的算例,供参考,转载请标明出处。
实现效果
最后实现的效果如下,只是做了练手的,界面比较粗糙。
代码
直接上代码!!
1.没什么特别的头文件
#include "pch.h"
#include "framework.h"
#include "MFCMercator.h"
#include "MFCMercatorDlg.h"
#include "afxdialogex.h"
2.绘制底图
按照建立的窗口大小,一定自适应地绘制墨卡托投影的基本线,线间距可以自行设定。
void CMFCMercatorDlg::LocalPaint(CDC* pDC)
{
// TODO: 在此处为本机数据添加绘制代码
CRect rect; //定义一个矩形对象
GetClientRect(rect);//初始化(rect不是一个数值,很多属性)
//使用映射模式函数
pDC->SetMapMode(8);
int lat = rect.Width();//窗口宽度
int lon = rect.Height();//窗口高度
pDC->SetWindowExt(rect.Width(), rect.Height());//设置窗口宽度和高度,使用rect的方法
//pDC->SetWindowExt(2000, 1000);//设置窗口宽度和高度,使用rect的方法
pDC->SetViewportExt(rect.Width(), -rect.Height());//设置视区大小
//得到一个x轴水平向右,y轴垂直向上,原点位于视区左下顶点的坐标轴
pDC->SetViewportOrg(rect.Width() / 2 - 0, rect.Height() / 2);//设置原点位置
rect.OffsetRect(-rect.Width() / 2, -rect.Height() / 2);//将矩形移回框内
//绘制墨卡托经线
float lonprecision = 180 / 20;
float londelta = x / lonprecision;
for (int j = 0; j <= 2 * lonprecision; j++)
{
CPoint P5(-x + j * londelta, -y1);
pDC->MoveTo(P5);
CPoint P6(-x + j * londelta, y1);
pDC->LineTo(P6);
}
//绘制墨卡托纬线
for (int i = 0; i <= 80; i = i + 20)
{
//北纬
CPoint P1(-x, latlocation / latlen * 400);
pDC->MoveTo(P1);
CPoint P2(x, latlocation / latlen * 400);
pDC->LineTo(P2);
//南纬
CPoint P3(-x, -latlocation / latlen * 400);
pDC->MoveTo(P3);
CPoint P4(x, -latlocation / latlen * 400);
pDC->LineTo(P4);
latlocation = latlocation + DistanceCal(i, i + 20);
}
GetDlgItem(IDC_BUTTON_Draw)->EnableWindow(false);
GetDlgItem(IDC_STATIC_now)->ShowWindow(true);
GetDlgItem(IDC_EDIT_Location)->ShowWindow(true);
SetDlgItemText(IDC_BUTTON_Draw, _T("已绘制墨卡托投影"));
}
更新墨卡托投影中纬线的位置。
float CMFCMercatorDlg::DistanceCal(float lat_0, float lat_1)
{
//e2 = (pow(a, 2) - pow(b, 2)) / pow(a, 2);
//e = sqrt(e2);//第一偏心率
lat_0 = (M_PI / 180) * lat_0;//纬度弧度
lat_1 = (M_PI / 180) * lat_1;
//计算纬度圈半径
//float r0;
//r0 = a * cos(lat_0) / sqrt(1 - e * e * pow(sin(lat_0), 2));
float x;
x = r0 * (Qcal(lat_1, e) - Qcal(lat_0, e));
return x;
}
3.显示所在经纬度
获取鼠标的位置,在label中显示。
void CMFCMercatorDlg::OnMouseMove(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRect rect; //定义一个矩形对象
GetClientRect(rect);//初始化(rect不是一个数值,很多属性)
CString str, str1, str2;
float lon, lat;
lon = (point.x - rect.Width() / 2 + 0);
lat = -(point.y - rect.Height() / 2);
if (lon <= x1 && lon >= -x1 && lat >= -438 && lat <= 438)
{
lon = 180 * lon / x1;//经度显示
//lat = lat * latlen / 400;//纬度显示
lat = AntiCal(lat * latlen / 400);//纬度显示
str1 = CString((to_string(lon).c_str()));
str2 = CString((to_string(lat).c_str()));
str.Format(_T("[%s,%s]"), str1, str2);
//str.Format(_T("[%d,%d]"), 180 * lon / x1, 180 / M_PI * asin(lat * 10000000 / 400 / b));
//获得程序状态栏对象的指针,AFX_IDW_STATUS_BAR就是状态栏的ID
//它的功能是通过指定的ID来获得子孙窗口
GetDlgItem(IDC_EDIT_Location)->SetWindowText(str);
}
else
{
SetDlgItemText(IDC_EDIT_Location, _T("超出地图范围"));
}
CDialogEx::OnMouseMove(nFlags, point);
}
反解计算鼠标所在位置对应投影图的经纬度值。
float CMFCMercatorDlg::AntiCal(double latdis)
{
double qtemp;
qtemp = latdis / a + Qcal(0, sqrt((pow(a, 2) - pow(b, 2)) / pow(a, 2)));
//q= Qcal(lat_1, e)
//直接反解法
double B2 = 0.33560695588 * 1e-2, B4 = 0.65700353 * 1e-5, B6 = 0.176221 * 1e-7, B8 = 0.608 * 1e-10;
double phi, phi_;
phi_ = 2 * atan(pow(M_E, qtemp)) - M_PI / 2;
phi = phi_ + B2 * sin(2 * phi_) + B4 * sin(4 * phi_) + B6 * sin(6 * phi_) + B8 * sin(8 * phi_);
return (180 / M_PI) * phi;
}
4.窗口外观设置
// TODO: 在此添加额外的初始化代码
//设置样式
//改按钮样式
CFont* font = new CFont();//开一个空间
font->CreateFont(24, // nHeight
0, // nWidth
0, // nEscapement
0, // nOrientation
FW_NORMAL, // nWeight
FALSE, // bItalic
FALSE, // bUnderline
0, // cStrikeOut
ANSI_CHARSET, // nCharSet
OUT_DEFAULT_PRECIS, // nOutPrecision
CLIP_DEFAULT_PRECIS, // nClipPrecision
DEFAULT_QUALITY, // nQuality
DEFAULT_PITCH | FF_SWISS, // nPitchAndFamily
_T("黑体"));
GetDlgItem(IDC_EDIT_Location)->SetFont(font);//放入字体格式
GetDlgItem(IDC_BUTTON_Draw)->SetFont(font);//放入字体格式
GetDlgItem(IDC_EDIT_PLocation)->SetFont(font);//放入字体格式
this->MoveWindow(100, 100, 1800, 1200, true);