(地圖瓦片糾偏最好的方法在這:https://www.cnblogs.com/s0611163/p/15606460.html)
對于地圖坐标偏移,以leaflet為例,有如下解決辦法
方法1、修改leaflet源碼,解決地圖坐标偏移問題
方法2、将點位真實的經緯度經過偏移算法,添加到加密的地圖上
方法3、直接對離線地圖瓦片進行糾偏
方法1需要修改源碼
方法2有缺陷,地圖依然是偏移的,如果把地圖經緯度顯示出來,經緯度也是不對的
我使用的是方法3,原理是:雖然偏移不是線性的,我也不知道糾偏算法,但是在市級或者縣級區域,偏移近似線性的,是以對于市級或縣級地圖應用,可以對地圖瓦片進行簡單的糾編,優點是得到的地圖瓦片可以認為是沒有偏移的。
我用C#寫了一個高德地圖瓦片糾偏程式,代碼如下:
1、配置檔案

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<appSettings>
<add key="inputPath" value="D:\_臨時檔案\GISMap\1818940751"/>
<add key="outputPath" value="D:\_臨時檔案\GISMapOutput\1818940751"/>
<add key="deltaPixcelX" value="1031"/>
<add key="deltaPixcelY" value="421"/>
<add key="fromMapZoom" value="1"/>
<add key="toMapZoom" value="16"/>
</appSettings>
</configuration>
View Code
deltaPixcelX和deltaPixcelY根據不同城市而不同,機關是像素,在太樂地圖下載下傳器上,開啟網絡,放大到18級,使用下載下傳器的糾偏計算,定位點位和糾偏後的點位,基本用眼可以看出來,差一個格就是256像素,不足一格的自己算一下,就把deltaPixcelX和deltaPixcelY參數算出來了。
2、糾偏代碼

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Configuration;
using System.Data;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Utils;
namespace TileProcess
{
public partial class Form1 : Form
{
private int _count = 0;
private int _deltaPixcelX;
private int _deltaPixcelY;
private string _inputPath;
private string _outputPath;
private int _fromMapZoom;
private int _toMapZoom;
private DateTime _startTime;
private int _lastCount;
private object _lock = new object();
public Form1()
{
InitializeComponent();
_deltaPixcelX = Convert.ToInt32(ConfigurationManager.AppSettings["deltaPixcelX"]);
_deltaPixcelY = Convert.ToInt32(ConfigurationManager.AppSettings["deltaPixcelY"]);
_inputPath = ConfigurationManager.AppSettings["inputPath"];
_outputPath = ConfigurationManager.AppSettings["outputPath"];
_fromMapZoom = Convert.ToInt32(ConfigurationManager.AppSettings["fromMapZoom"]);
_toMapZoom = Convert.ToInt32(ConfigurationManager.AppSettings["toMapZoom"]);
}
private void btnTileProcess_Click(object sender, EventArgs e)
{
this.btnTileProcess.Enabled = false;
Task.Factory.StartNew(() =>
{
LogUtil.Log("開始處理");
Process();
});
Thread thread = new Thread(new ThreadStart(() =>
{
int sleepInterval = 1000;
while (true)
{
Thread.Sleep(sleepInterval);
this.BeginInvoke(new Action(() =>
{
double totalSeconds = DateTime.Now.Subtract(_startTime).TotalSeconds;
int avg = (int)(_count / totalSeconds);
lblMsg.Text = string.Format("已處理 {0} 張瓦片圖", _count);
if (_count - _lastCount > 0)
{
lblSpeed.Text = string.Format("目前速度:{0} 張/每秒,平均速度:{1} 張/每秒", (_count - _lastCount) * 1000.0 / sleepInterval, avg);
}
_lastCount = _count;
}));
}
}));
thread.IsBackground = true;
thread.Start();
}
/// <summary>
/// 瓦片糾偏處理
/// </summary>
private void Process()
{
_startTime = DateTime.Now;
Regex regex = new Regex(@"\\(\d+)\\(\d+).png", RegexOptions.IgnoreCase);
for (int i = _fromMapZoom; i <= _toMapZoom; i++)
{
int deltaPixcelX = (int)Math.Round(_deltaPixcelX / Math.Round(Math.Pow(2, 18 - i)));
int deltaPixcelY = (int)Math.Round(_deltaPixcelY / Math.Round(Math.Pow(2, 18 - i)));
string[] fileArr = Directory.GetFiles(_inputPath + "\\" + i, "*.*", SearchOption.AllDirectories);
foreach (string file in fileArr)
{
ThreadData data = new ThreadData();
data.File = file;
data.I = i;
data.DeltaPixcelX = deltaPixcelX;
data.DeltaPixcelY = deltaPixcelY;
ThreadUtil.Run((obj) =>
{
ThreadData d = obj as ThreadData;
Match match = regex.Match(d.File);
if (match.Success)
{
int x = Convert.ToInt32(match.Groups[1].Value);
int y = Convert.ToInt32(match.Groups[2].Value);
string pathTarget = string.Format(string.Format(@"{0}\{1}\{2}\{3}.png", _outputPath, d.I, x, y));
if (!File.Exists(pathTarget))
{
if (!Directory.Exists(Path.GetDirectoryName(pathTarget)))
{
Directory.CreateDirectory(Path.GetDirectoryName(pathTarget));
}
Bitmap bmpNew = new Bitmap(256, 256, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
bmpNew.SetResolution(96, 96);
Graphics graph = Graphics.FromImage(bmpNew);
int deltaX = data.DeltaPixcelX / 256;
int deltaY = data.DeltaPixcelY / 256;
//臨時變量定義
string pathSource = null;
FileStream fs = null;
byte[] bArr = null;
MemoryStream ms = null;
Bitmap bmpSource = null;
//起始
pathSource = string.Format(@"{0}\{1}\{2}\{3}.png", _inputPath, d.I, x + deltaX, y + deltaY);
if (File.Exists(pathSource))
{
fs = new FileStream(pathSource, FileMode.Open, FileAccess.Read);
bArr = new byte[fs.Length];
int readCount = fs.Read(bArr, 0, bArr.Length);
ms = new MemoryStream(bArr, 0, readCount);
bmpSource = new Bitmap(ms);
bmpSource.SetResolution(96, 96);
graph.DrawImage(bmpSource, 0, 0, new RectangleF(data.DeltaPixcelX % 256, data.DeltaPixcelY % 256, 256 - data.DeltaPixcelX % 256, 256 - data.DeltaPixcelY % 256), GraphicsUnit.Pixel);
graph.Flush();
fs.Close();
fs = null;
ms.Close();
ms = null;
bmpSource.Dispose();
bmpSource = null;
}
//右
pathSource = string.Format(@"{0}\{1}\{2}\{3}.png", _inputPath, d.I, x + deltaX + 1, y + deltaY);
if (File.Exists(pathSource) && (data.DeltaPixcelX > 0 || data.DeltaPixcelY > 0))
{
fs = new FileStream(pathSource, FileMode.Open, FileAccess.Read);
bArr = new byte[fs.Length];
int readCount = fs.Read(bArr, 0, bArr.Length);
ms = new MemoryStream(bArr, 0, readCount);
bmpSource = new Bitmap(ms);
bmpSource.SetResolution(96, 96);
graph.DrawImage(bmpSource, 256 - data.DeltaPixcelX % 256, 0, new RectangleF(0, data.DeltaPixcelY % 256, data.DeltaPixcelX % 256, 256 - data.DeltaPixcelY % 256), GraphicsUnit.Pixel);
graph.Flush();
fs.Close();
fs = null;
ms.Close();
ms = null;
bmpSource.Dispose();
bmpSource = null;
}
//下
pathSource = string.Format(@"{0}\{1}\{2}\{3}.png", _inputPath, d.I, x + deltaX, y + deltaY + 1);
if (File.Exists(pathSource) && (data.DeltaPixcelX > 0 || data.DeltaPixcelY > 0))
{
fs = new FileStream(pathSource, FileMode.Open, FileAccess.Read);
bArr = new byte[fs.Length];
int readCount = fs.Read(bArr, 0, bArr.Length);
ms = new MemoryStream(bArr, 0, readCount);
bmpSource = new Bitmap(ms);
bmpSource.SetResolution(96, 96);
graph.DrawImage(bmpSource, 0, 256 - data.DeltaPixcelY % 256, new RectangleF(data.DeltaPixcelX % 256, 0, 256 - data.DeltaPixcelX % 256, data.DeltaPixcelY % 256), GraphicsUnit.Pixel);
graph.Flush();
fs.Close();
fs = null;
ms.Close();
ms = null;
bmpSource.Dispose();
bmpSource = null;
}
//右下
pathSource = string.Format(@"{0}\{1}\{2}\{3}.png", _inputPath, d.I, x + deltaX + 1, y + deltaY + 1);
if (File.Exists(pathSource) && (data.DeltaPixcelX > 0 || data.DeltaPixcelY > 0))
{
fs = new FileStream(pathSource, FileMode.Open, FileAccess.Read);
bArr = new byte[fs.Length];
int readCount = fs.Read(bArr, 0, bArr.Length);
ms = new MemoryStream(bArr, 0, readCount);
bmpSource = new Bitmap(ms);
bmpSource.SetResolution(96, 96);
graph.DrawImage(bmpSource, 256 - data.DeltaPixcelX % 256, 256 - data.DeltaPixcelY % 256, new RectangleF(0, 0, data.DeltaPixcelX % 256, data.DeltaPixcelY % 256), GraphicsUnit.Pixel);
graph.Flush();
fs.Close();
fs = null;
ms.Close();
ms = null;
bmpSource.Dispose();
bmpSource = null;
}
bmpNew.Save(pathTarget);
//bmpNew.Save("d:\\_臨時檔案\\1234.png"); //測試用
bmpNew.Dispose();
bmpNew = null;
graph.Dispose();
graph = null;
} //end if (!File.Exists(pathTarget))
lock (_lock)
{
_count++;
}
} //end if (match.Success)
}, data, (ex) =>
{
this.BeginInvoke(new Action(() =>
{
lblErrorMsg.Text = "出錯:" + ex.Message + "\r\n" + ex.StackTrace;
LogUtil.LogError(ex, "出錯");
}));
}); //end ThreadUtil.Run
} //end foreach (string file in fileArr)
} //end for (int i = _fromMapZoom; i <= _toMapZoom; i++)
}
}
}
輔助類ThreadUtil:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace Utils
{
/// <summary>
/// 線程工具類
/// </summary>
public class ThreadUtil
{
/// <summary>
/// 使用的邏輯處理器數
/// </summary>
private static int _ProcessorCount;
private static Semaphore _semaphore;
private static List<Task> _TaskList = new List<Task>();
private static object _lock = new object();
static ThreadUtil()
{
_ProcessorCount = Environment.ProcessorCount * 2 / 4; //使用的邏輯處理器數
if (_ProcessorCount < 1) _ProcessorCount = 1;
_semaphore = new Semaphore(_ProcessorCount, _ProcessorCount);
}
public static void Run(Action<object> doWork, object arg, Action<Exception> errorAction)
{
Task task = null;
task = Task.Factory.StartNew((obj) =>
{
_semaphore.WaitOne();
try
{
doWork(obj);
}
catch (Exception ex)
{
errorAction(ex);
}
_semaphore.Release();
lock (_lock)
{
_TaskList.Remove(task);
}
}, arg);
lock (_lock)
{
_TaskList.Add(task);
}
}
public static void WaitAll()
{
Task.WaitAll(_TaskList.ToArray());
}
}
}
輔助類ThreadData:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TileProcess
{
public class ThreadData
{
public int I { get; set; }
public string File { get; set; }
public int DeltaPixcelX { get; set; }
public int DeltaPixcelY { get; set; }
}
}
寫日志工具類就不貼了,可以用其它日志工具代替
處理速度大約每稱300張瓦片,具體根據電腦性能不同,一個城市的瓦片大約1個小時左右能處理完。
糾偏後的地圖做最佳路徑分析,顯示的路徑和道路基本吻合,略有誤差。
還有另一種糾偏方法,通過修改leaflet源碼進行糾偏:https://www.cnblogs.com/s0611163/p/13396622.html