第一步, 获取百度瓦片地图,下载到本地
网上有很多相关的工具自行查找一个使用即可
实在懒得找的我把我用的分享出来:https://download.youkuaiyun.com/download/qq_35080168/12411438
然后查看本地的地图目录结构. 搞清楚目录接口之后, 之后加载图片的路径问题就搞定了
如下图 大概的目录结构 1-18级地图 每一级内的文件夹名称是行数 打开进去的图名称是列数
也就是说每一片的瓦片地图层级+行数+列数 是唯一的 这就好办了
第二步,写一个读取本地地图的类
坐标的问题后边再说
using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;
/// <summary>
/// 地图图片管理
/// </summary>
public class MapTextureManager : MonoBehaviour
{
//避免重复加载 搞一个缓存
private Dictionary<string, Sprite> Sprites = new Dictionary<string, Sprite>();
public MapAxesTransfer mapAxesTransfer;
private List<LoadTextureInfo> LoadInfos = new List<LoadTextureInfo>();
/// <summary>
/// 图片地址拼接
/// </summary>
/// <param name="zoom">层级</param>
/// <param name="pieceX">行数</param>
/// <param name="pieceY">列数</param>
/// <returns></returns>
string StitchingPath(int zoom,float pieceX,float pieceY)
{
return @"file:///D:/TaiAnGround/maptile/" + zoom + "/" + (int)pieceX + "/" + (int)pieceY + ".png";
}
/// <summary>
/// 预加载
/// </summary>
/// <param name="Pieces">加载中心的块坐标</param>
public void Preload(Dictionary<int, List<Vector2>> Pieces)
{
//先加载小层级
Dictionary<int, Vector4> zoomToLRPiece = mapAxesTransfer.ZoomToLRPiece(10, 15);
foreach (var key in zoomToLRPiece.Keys)
{
Vector4 bound = zoomToLRPiece[key];
Vector2 left = new Vector2(bound.x, bound.y);
Vector2 right = new Vector2(bound.z, bound.w);
for (int i = (int)left.x; i < right.x + 1; i++)
{
for (int j = (int)left.y; j < right.y + 1; j++)
{
string path = StitchingPath(key, i, j);
LoadTextureInfo info = new LoadTextureInfo(path, null);
LoadInfos.Add(info);
}
}
}
//加载附近层级以及大层级
foreach (int zoom in Pieces.Keys)
{
foreach (var piece in Pieces[zoom])
{
string path = StitchingPath(zoom, piece.x, piece.y);
LoadTextureInfo info = new LoadTextureInfo(path,null);
LoadInfos.Add(info);
}
}
LoadSingle();
}
/// <summary>
/// 队列内添加一个加载任务
/// </summary>
public void AddLoadTask(int zoom, float pieceX, float pieceY, Action<Sprite> setSprite)
{
string path = StitchingPath(zoom, pieceX, pieceY);
if (Sprites.ContainsKey(path))
{
setSprite(Sprites[path]);
}
else
{
LoadTextureInfo info = new LoadTextureInfo(path, setSprite);
if (LoadInfos.Count == 0)
{
if (!LoadInfos.Contains(info))
{
LoadInfos.Add(info);
LoadSingle();
}
}
else
{
LoadInfos.Add(info);
}
}
}
/// <summary>
/// 开始加载
/// </summary>
void LoadSingle()
{
if (LoadInfos.Count > 0)
StartCoroutine(LoadSpritebywww(LoadInfos[0]));
else
Resources.UnloadUnusedAssets();
}
private IEnumerator LoadSpritebywww(LoadTextureInfo info)
{
using (WWW www = new WWW(info.path))
{
yield return www;
if (www.isDone)
{
if (string.IsNullOrEmpty(www.error))
{
var sprite = Sprite.Create(www.texture, new Rect(0, 0, 256, 256), new Vector2(0, 0), 100);
if (info.setSprite != null)
info.setSprite(sprite);
Sprites[info.path] = sprite;
}
else
{
WD_Debug._Print("错误: "+www.error);
}
if (LoadInfos.Count > 0) LoadInfos.RemoveAt(0);
LoadSingle();
}
}
}
public class LoadTextureInfo
{
public string path;
public Action<Sprite> setSprite;
public LoadTextureInfo(string path,Action<Sprite> act)
{
this.path = path;
setSprite = act;
}
}
}
第三,坐标转换
这里要说到几个坐标系和他们之间的关系
经纬度坐标系
经线的定义:地球表面连接南北两极的大圆线上的半圆弧,任两根经线的长度相等
纬度的定义:地球表面某点随地球自转所形成的轨迹。任何一根纬线都是圆形而且两两平行。纬线的长度是赤道的周长乘以纬线的纬度的余弦,所以赤道最长,离赤道越远的纬线,周长越短,到了两极就缩为0墨卡托坐标/平面坐标
百度提供了经纬度坐标和墨卡托坐标转换的方法我们拿来直接用,经纬度要先转换成墨卡托坐标 才能拿来计算转换坐标,原因是经纬度表示的是个球,直接以经纬度去计算距离是肯定有偏差的.
像素坐标
在18级地图下 像素坐标等于墨卡托坐标,17级就要除以2 16级除以4以此类推,基于此 就可以计算出经纬度对应的块坐标,
块坐标
就是下载的瓦片地图 图片的行列数坐标, 我写的方法是以中心图块为基准查找周围图块的 根据块坐标的原理 查找周围图块就很简单了
世界坐标
图块在unity 的世界坐标,以块坐标左下角的那个图块作为000点.又知道每一块图块的unity内的长度,基于此 可以计算出任意层级下任意图块的unity世界坐标
一个图块(使用的是spriterenderer组件) 的大小计算方式: 每单位像素量除以分辨率 每单位像素量就是下边截图中的属性,分辨率是已知的
米坐标
同理的,在由一个点 已知的经纬度坐标和米坐标以及偏移量 计算出的坐标 可以用来测距 两点之间实际距离
好的 搞清楚他们之间的关系一切都好办了
这是百度提供的坐标转换方法
里边有一堆乱七八糟的参数 咱也不是很懂.但能用就行了
计算的时候大多数时候 float的精度是不够的 所以下边定义了一个double类型的vector2
using System;
using System.Collections.Generic;
/// <summary>
/// 经纬度墨卡托转换
/// </summary>
public class MCObject
{
public MCObject()
{
}
//经纬度 偏移一定的像素量 取另一个经纬度
public Vector2D Point_Offset(double lng, double lat, double offx, double offy, int zoom)
{
//经纬度转墨卡托
var re = convertLL2MC(lng, lat);
//转换到zoom级别下
re.x *= Math.Pow(2, zoom - 18);
re.y *= Math.Pow(2, zoom - 18);
//像素坐标
re.x += offx/*/2*/;//256
re.y += offy/*/2*/;
//转回平面坐标
//像素坐标.
re.x /= Math.Pow(2, zoom - 18);
re.y /= Math.Pow(2, zoom - 18);
//转回经纬度
convertMC2LL(re.x, re.y);
re.x = Longitude;
re.y = Latitude;
return re;
}
static double DEF_PI = 3.14159265359; // PI
static double DEF_2PI = 6.28318530712; // 2*PI
static double DEF_PI180 = 0.01745329252; // PI/180.0
static double DEF_R = 6370693.5; // radius of earth
//适用于近距离 求两个经纬度之间的实际距离
public static double GetShortDistance(double lon1, double lat1, double lon2, double lat2)
{
double ew1, ns1, ew2, ns2;
double dx, dy, dew;
double distance;
// 角度转换为弧度
ew1 = lon1 * DEF_PI180;
ns1 = lat1 * DEF_PI180;
ew2 = lon2 * DEF_PI180;
ns2 = lat2 * DEF_PI180;
// 经度差
dew = ew1 - ew2;
// 若跨东经和西经180 度,进行调整
if (dew > DEF_PI)
dew = DEF_2PI - dew;
else if (dew < -DEF_PI)
dew = DEF_2PI + dew;
dx = DEF_R * Math.Cos(ns1) * dew; // 东西方向长度(在纬度圈上的投影长度)
dy = DEF_R * (ns1 - ns2); // 南北方向长度(在经度圈上的投影长度)
// 勾股定理求斜边长
distance = Math.Sqrt(dx * dx + dy * dy);
return distance;
}
/// <summary>
/// 经纬度坐标和墨卡托坐标互转
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="OutLongitudeAndLatitude">trueMC坐标转经纬度,false经纬度转MC坐标</param>
public MCObject(double x, double y, bool OutLongitudeAndLatitude = false)
{
if (OutLongitudeAndLatitude)
{
_MCx = x;
_MCy = y;
convertMC2LL(_MCx, _MCy);
}
else
{
_Longitude = x;
_Latitude = y;
convertLL2MC(_Longitude, _Latitude);
}
}
private double _MCx;
private double _MCy;
public double MCx
{
get
{
return _MCx;
}
set { _MCx = value; }
}
public double MCy
{
get
{
return _MCy;
}
set { _MCy = value; }
}
public double _Longitude;
public double _Latitude;
public double Longitude
{
get
{
if (_Longitude != null)
{
return _Longitude;
}
else
{
return 0;
}
}
set { _Longitude = value; }
}
public double Latitude
{
get
{
if (_Latitude != null)
{
return _Latitude;
}
else
{
return 0;
}
}
set { _Latitude = value; }
}
private List<List<double>> LL2MC = new List<List<double>>(){new List<double>(){-0.0015702102444, 111320.7020616939, 1704480524535203, -10338987376042340, 26112667856603880, -35149669176653700, 26595700718403920, -10725012454188240, 1800819912950474, 82.5},
new List<double>() {0.0008277824516172526, 111320.7020463578, 647795574.6671607, -4082003173.641316, 10774905663.51142, -15171875531.51559, 12053065338.62167, -5124939663.577472, 913311935.9512032, 67.5},
new List<double>() {0.00337398766765, 111320.7020202162, 4481351.045890365, -23393751.19931662, 79682215.47186455, -115964993.2797253, 97236711.15602145, -43661946.33752821, 8477230.501135234, 52.5},
new List<double>() {0.00220636496208, 111320.7020209128, 51751.86112841131, 3796837.749470245, 992013.7397791013, -1221952.21711287, 1340652.697009075, -620943.6990984312, 144416.9293806241, 37.5},
new List<double>() {-0.0003441963504368392, 111320.7020576856, 278.2353980772752, 2485758.690035394, 6070.750963243378, 54821.18345352118, 9540.606633304236, -2710.55326746645, 1405.483844121726, 22.5},
new List<double>() {-0.0003218135878613132, 111320.7020701615, 0.00369383431289, 823725.6402795718, 0.46104986909093, 2351.343141331292, 1.58060784298199, 8.77738589078284, 0.37238884252424, 7.45}
};
private List<List<double>> MC2LL = new List<List<double>>() { new List<double>() { 1.410526172116255e-8, 0.00000898305509648872, -1.9939833816331, 200.9824383106796, -187.2403703815547, 91.6087516669843, -23.38765649603339, 2.57121317296198, -0.03801003308653, 17337981.2 }, new List<double>() { -7.435856389565537e-9, 0.000008983055097726239, -0.78625201886289, 96.32687599759846, -1.85204757529826, -59.36935905485877, 47.40033549296737, -16.50741931063887, 2.28786674699375, 10260144.86 }, new List<double>() { -3.030883460898826e-8, 0.00000898305509983578, 0.30071316287616, 59.74293618442277, 7.357984074871, -25.38371002664745, 13.45380521110908, -3.29883767235584, 0.32710905363475, 6856817.37 }, new List<double>() { -1.981981304930552e-8, 0.000008983055099779535, 0.03278182852591, 40.31678527705744, 0.65659298677277, -4.44255534477492, 0.85341911805263, 0.12923347998204, -0.04625736007561, 4482777.06 }, new List<double>() { 3.09191371068437e-9, 0.000008983055096812155, 0.00006995724062, 23.10934304144901, -0.00023663490511, -0.6321817810242, -0.00663494467273, 0.03430082397953, -0.00466043876332, 2555164.4 }, new List<double>() { 2.890871144776878e-9, 0.000008983055095805407, -3.068298e-8, 7.47137025468032, -0.00000353937994, -0.02145144861037, -0.00001234426596, 0.00010322952773, -0.00000323890364, 826088.5 } };
private Double[] MCBAND = { 12890594.86, 8362377.87, 5591021d, 3481989.83, 1678043.12, 0d };
private int[] LLBAND = { 75, 60, 45, 30, 15, 0 };
private double getRange(double cf, double ce, double t)
{
if (ce != 0)
{
cf = cf > ce ? cf : ce;
}
if (t != 0)
{
cf = cf > t ? t : cf;
}
return cf;
}
private double getLoop(double cf, double ce, double t)
{
while (cf > t)
{
cf -= t - ce;
}
while (cf < ce)
{
cf += t - ce;
}
return cf;
}
private Vector2D convertor(double longitude, double latitude, double[] cg)
{
if (cg == null)
{
return null;
}
double t = cg[0] + cg[1] * longitude;
double ce = Math.Abs(latitude) / cg[9];
double ch = cg[2] + cg[3] * ce + cg[4] * Math.Pow(ce, 2) + cg[5] * Math.Pow(ce, 3) + cg[6] * Math.Pow(ce, 4) + cg[7] * Math.Pow(ce, 5) + cg[8] * Math.Pow(ce, 6);
t = t * (longitude < 0 ? -1 : 1);
ch = ch * (latitude < 0 ? -1 : 1);
var re = new Vector2D(t,ch);
//re.Item1 = t;
//re.Item2 = ch;
return re;
}
//经纬度转墨卡托坐标
public Vector2D convertLL2MC(double longitude, double latitude)
{
try
{
longitude = getLoop(longitude, -180, 180);
latitude = getRange(latitude, -74, 74);
int countLLBAND = 7;
double[] cg = null;
for (int cf = 0; cf < countLLBAND; cf++)
{
if (latitude >= LLBAND[cf])
{
cg = LL2MC[cf].ToArray();
break;
}
}
if (cg == null)
{
for (int cf = countLLBAND - 1; cf >= 0; cf--)
{
if (longitude <= -LLBAND[cf])
{
cg = LL2MC[cf].ToArray();
break;
}
}
}
Vector2D result = convertor(longitude, latitude, cg);
_MCx = result.x;
_MCy = result.y;
return result;
}
catch (System.Exception ex)
{
Vector2D result = new Vector2D(); ;
//result.Item1 = 0;
//result.Item2 = 0;
return result;
}
}
//墨卡托坐标转经纬度坐标
public Vector2D convertMC2LL(double x, double y)
{
try
{
double[] cF = null;
x = Math.Abs(x);
y = Math.Abs(y);
for (int cE = 0; cE < MCBAND.Length; cE++)
{
if (y >= MCBAND[cE])
{
cF = MC2LL[cE].ToArray();
break;
}
}
Vector2D result = convertor(x, y, cF);
_Longitude = result.x;
_Latitude = result.y;
return result;
}
catch (System.Exception ex)
{
_Longitude = 0;
_Latitude = 0;
return new Vector2D();
}
}
}
/// <summary>
/// double类型的一个vector2
/// </summary>
public class Vector2D
{
/// <summary>
/// 如果是经纬度 lat值
/// </summary>
public double x;
/// <summary>
/// 如果是经纬度 lng值
/// </summary>
public double y;
public Vector2D()
{
x = 0;
y = 0;
}
public Vector2D(double X, double Y)
{
x = X;
y = Y;
}
}
然后是各种坐标之间的转换
需要拿到下载地图的时候 左下角和右上角的经纬度,
using System;
using System.Collections.Generic;
using UnityEngine;
//地图坐标轴转换
public class MapAxesTransfer : MonoBehaviour
{
public static MapAxesTransfer ins;
// public MapPicData mapPicData = new MapPicData();
MCObject MC = new MCObject();
public int zoom = 17;
/// <summary>
/// 图片大小 分辨率
/// </summary>
public Vector2 picSize = new Vector2(256, 256);
/// <summary>
/// 图片的每单位像素 图片资源的设置
/// </summary>
public int PixelsPerUnit = 100;
#region 初始坐标
public Vector2D InitleftLL
{
get
{
return new Vector2D()
{
x = 左下角经度,
y = 左下角纬度,
};
}
}
public Vector2D rightLL
{
get
{
return new Vector2D()
{
x = 右上角经度,
y = 右上角纬度,
};
}
}
public double 左下角经度 = 117.811422d, 左下角纬度 = 36.655215d;//117.811422d, 36.655215d
public double 右上角经度 = 118.214941d, 右上角纬度 = 36.888737d;//118.214941d, 36.888737d
#endregion
public Vector2D leftLL = new Vector2D();
//左下角的MC坐标
public Vector2D leftDownMC = new Vector2D();
//全图左下角块坐标
public Vector2 leftPiece;
public Vector2 rightPiece;
/// <summary>
/// 单个图块unity世界坐标的长度
/// </summary>
public float pixel;
/// <summary>
/// 单块平面坐标长度
/// </summary>
double pieceLengh;
public Vector3 GetRightPos()
{
return new Vector3(rightPiece.x - leftPiece.x, 20, rightPiece.y - leftPiece.y) * pixel;
}
public void Init()
{
ins = this;
pixel = picSize.x / PixelsPerUnit;
leftDownMC = MC.convertLL2MC(InitleftLL.x, InitleftLL.y);
leftPiece = LLToPiece(InitleftLL, zoom);
rightPiece = LLToPiece(rightLL, zoom);
ChangeLayer(zoom);
WD_Debug._Print(Math.Pow(2, zoom - 18));
//MCToWorld(null);
TestSet();
}
//更换层级 一些东西要重新计算定义
public void ChangeLayer(int zoom)
{
this.zoom = zoom;
leftPiece = LLToPiece(InitleftLL, zoom);
rightPiece = LLToPiece(rightLL, zoom);
pieceLengh = picSize.x / Math.Pow(2, zoom - 18);
WD_Debug._Print("左下角1 " + leftPiece + " 单块平面坐标的长度" + pieceLengh + " 左下角MC " + leftDownMC.x);
//下边两行先后顺序不能变 否则计算有误差 计算的时候是以leftDownMC为基准计算的 leftDownMC必须由修正后的经纬度计算
ReSetLL();//修正经纬度
TestSet();
}
//测试 更换层级之后的参考点位置
GameObject g;
void TestSet()
{
Vector2D mcpos = MC.convertLL2MC(referencePointLngLat.x, referencePointLngLat.y);
Vector3 world = MCToWorld(mcpos);
if (g == null)
g = new GameObject("参考点");
g.transform.position = world;
}
//修正经纬度到左下角 原因 : 在网页上取的经纬度 和实际上左下角块坐标的经纬度肯定是有偏差的, 需要重新计算
private void ReSetLL()
{
//块坐标转平面坐标 块坐标乘以ppu
Vector2D mcPos = new Vector2D()
{
x = leftPiece.x * picSize.x + 1,
y = leftPiece.y * picSize.x + 1
};
mcPos.x /= Math.Pow(2, zoom - 18);
mcPos.y /= Math.Pow(2, zoom - 18);
//MC坐标转经纬度
leftLL = MC.convertMC2LL(mcPos.x, mcPos.y);
}
//取层级对应的所有块坐标的左下角和右上角
public Dictionary<int, Vector4> ZoomToLRPiece(int startZoom, int endZoom)
{
Dictionary<int, Vector4> zoomtoPiece = new Dictionary<int, Vector4>();
for (int i = startZoom; i < endZoom; i++)
{
zoomtoPiece[i] = new Vector4()
{
x = LLToPiece(InitleftLL, i).x,
y = LLToPiece(InitleftLL, i).y,
z = LLToPiece(rightLL, i).x,
w = LLToPiece(rightLL, i).y,
};
}
// WD_Debug._Error(zoomtoPiece.Count);
return zoomtoPiece;
}
//取一个坐标附近各个层级的块坐标
public Dictionary<int, List<Vector2>> PieceToZoom(Vector2 CenterPiece, int zoom)
{
Dictionary<int, List<Vector2>> zoomtoPiece = new Dictionary<int, List<Vector2>>();
for (int i = 15; i < 18 + 1; i++)
{
float coe = (float)Math.Pow(2, zoom - i);
Vector2 newPiece = new Vector2((int)(CenterPiece.x / coe),
(int)(CenterPiece.y / coe));
// WD_Debug._Print(zoom + " " + i + " 新中点" + newPiece + "系数? " + (float)Math.Pow(2, zoom - i));
zoomtoPiece[i] = GetDic(newPiece, new Vector2(10, 10));
}
return zoomtoPiece;
}
List<Vector2> GetDic(Vector2 CenterPiece, Vector2 colraw)
{
List<Vector2> ListPieces = new List<Vector2>();
for (int i = 1; i < ((int)(colraw.x / 2)) + 1; i++)
{
for (int j = 1; j < ((int)(colraw.y / 2)) + 1; j++)
{
ListPieces.Add(CenterPiece + new Vector2(i, j));
ListPieces.Add(CenterPiece + new Vector2(i, -j));
ListPieces.Add(CenterPiece + new Vector2(-i, -j));
ListPieces.Add(CenterPiece + new Vector2(-i, j));
ListPieces.Add(CenterPiece + new Vector2(-i, 0));
ListPieces.Add(CenterPiece + new Vector2(i, 0));
ListPieces.Add(CenterPiece + new Vector2(0, -j));
ListPieces.Add(CenterPiece + new Vector2(0, j));
}
}
return ListPieces;
}
/// <summary>
/// 经纬度取块坐标
/// </summary>
public Vector2 LLToPiece(Vector2D LL, int Zoom)
{
//先转墨卡托坐标
Vector2D piece = MC.convertLL2MC(LL.x, LL.y);
//转换到zoom级别下
piece.x *= Math.Pow(2, Zoom - 18);
piece.y *= Math.Pow(2, Zoom - 18);
//墨卡托取块坐标
return new Vector2((int)(piece.x / picSize.x), (int)(piece.y / picSize.x));
}
/// <summary>
/// 世界坐标转经纬度
/// </summary>
/// <returns></returns>
public Vector2D WorldToLL(Vector3 worldPos)
{
//计算偏移像素量 世界坐标乘以ppu
Vector2D MCOffset = new Vector2D()
{
x = worldPos.x * PixelsPerUnit,
y = worldPos.z * PixelsPerUnit,
};
return MC.Point_Offset(leftLL.x, leftLL.y, MCOffset.x, MCOffset.y, zoom);
}
/// <summary>
/// 经纬度转世界坐标
/// </summary>
public Vector3 LLToWorld(Vector2D LL)
{
Vector2D TempMC = MC.convertLL2MC(LL.x, LL.y);
Vector3 world = MCToWorld(TempMC);
return world;
}
/// <summary>
/// 世界坐标转墨卡托坐标
/// </summary>
public Vector2D WorldToMC(Vector3 worldPos)
{
Vector2D LL = WorldToLL(worldPos);
Vector2D leftDownMCs = MC.convertLL2MC(LL.x, LL.y);
return leftDownMCs;
}
/// <summary>
/// 墨卡托转世界坐标
/// </summary>
public Vector3 MCToWorld(Vector2D MC)
{
string[] X = (MC.x / pieceLengh).ToString().Split('.');
string[] Y = (MC.y / pieceLengh).ToString().Split('.');
//小数点之前的是块坐标
long pieceX = long.Parse(X[0]);
long pieceY = long.Parse(Y[0]);
Vector2 piecePos = new Vector2(pieceX, pieceY);
double s = MC.x / pieceLengh;
//小数点之后的是偏移量
float ratioX = float.Parse("0." + X[1]);
float ratioY = float.Parse("0." + Y[1]);
//块偏移量
Vector2 offsettt = (piecePos - leftPiece);
Vector3 center = new Vector3(ratioX + offsettt.x, 0, ratioY + offsettt.y);
center *= pixel;
return center;
}
/// <summary>
/// 墨卡托转块
/// </summary>
public Vector2 MCToPiece(Vector2D centerMC)
{
WD_Debug._Print("zoom = " + zoom + " " + Math.Pow(2, zoom - 18) + " " + centerMC.x);
centerMC.x *= Math.Pow(2, zoom - 18);
centerMC.y *= Math.Pow(2, zoom - 18);
//墨卡托取块坐标
return new Vector2((int)(centerMC.x / picSize.x), (int)(centerMC.y / picSize.x));
}
/// <summary>
/// 米坐标转世界坐标
/// </summary>
public Vector3 CMToWorld(float x, float y)
{
//转经纬度
Vector2D LL = CMToLatLng(x, y);
//经纬度转世界坐标
Vector2D MCs = MC.convertLL2MC(LL.x, LL.y);
if (MCs.x == 0) return new Vector3();
return MCToWorld(MCs);
}
//参考点
private Vector2D referencePointLngLat = new Vector2D()
{
x = 118.050357d,
y = 36.860605d
};
//米坐标
private Vector2D referencePointXY = new Vector2D()
{
x = -3576280,
y = -1258750,
};
/// <summary>
/// 米坐标 转经纬度
/// </summary>
public Vector2D CMToLatLng(float x, float y)
{
double Dx = x * 10.0f - referencePointXY.x / 1000.0f;
double Dy = y * 10.0f - referencePointXY.y / 1000.0f;
double c = 6371393f * 2f * Math.PI;
double ss = c * Math.Cos(referencePointLngLat.y * Mathf.Deg2Rad);//当前纬度的周长
double d_m = 360f / ss;//度每米
double cd = 360f / c;//赤道度每米
Vector2D LL = new Vector2D()
{
//经度
x = Dx * d_m + referencePointLngLat.x,
//纬度
y = Dy * cd + referencePointLngLat.y
};
return LL;
}
}
转换的方法写好了 终于要到了使用的时候了
上代码
using System;
using UnityEngine;
using System.Collections.Generic;
using System.Linq;
using UnityEngine.EventSystems;
public class MapManager : MonoBehaviour
{
public static MapManager ins;
[Header("是否打开日志")]
public bool isDebug = false;
public Dictionary<Vector2, TempPic> tempPics = new Dictionary<Vector2, TempPic>();
public int zoom = 15;
private MCObject MC = new MCObject();
public MapAxesTransfer mapAxesTransfer;
private MapTextureManager LoadTexture;
/// <summary>
/// 尺寸
/// </summary>
private Vector2 numSize;
[Header("初始化后视角是否在中心")]
public bool iscenter;
void Start()
{
WD_Debug.ins = isDebug;
ins = this;
mapAxesTransfer = GetComponentInChildren<MapAxesTransfer>();
LoadTexture = GetComponent<MapTextureManager>();
mapAxesTransfer.Init();
int sizes = (int)(1920 / mapAxesTransfer.picSize.x);
numSize = new Vector2(sizes + 3, sizes + 3);//决定地图几圈的地方
WD_Debug._Print("计算排列 " + numSize + " " + Screen.width);
//InitMap();
InitMap(iscenter);
}
Vector2D CameraLL;
//更换层级
public void ChangeLayer(int zoom)
{
Vector3 Screensize = new Vector3(Screen.width / 2, Screen.height / 2);
ray = Camera.main.ScreenPointToRay(Screensize);
if (Physics.Raycast(ray, out hit)) //
{
//换之前先记录下当前中心位置的平面坐标 换之后 没有正确的块坐标不好找
Vector2D centerMC = mapAxesTransfer.WorldToMC(hit.point);
CameraLL = mapAxesTransfer.WorldToLL(hit.point);
//WD_Debug._Print("换之前的MC坐标" + centerMC.x + " " + centerMC.y);
this.zoom = zoom;
mapAxesTransfer.ChangeLayer(zoom);//这里已经将左下角块坐标重新计算了
tempPics.Remove(centerTemppic.PieceCoordinates);//这里删除之前的中心位置 后边设置得是新的中心块
centerTemppic.PieceCoordinates = mapAxesTransfer.MCToPiece(centerMC);
//计算出下一个中心快的坐标之后 在计算其位置 以左下角为00点结算
Vector2 offset = centerTemppic.PieceCoordinates - mapAxesTransfer.leftPiece;
Vector3 InitPosition = new Vector3(offset.x, 0, offset.y);
InitPosition *= mapAxesTransfer.pixel;
// WD_Debug._Print("转换之后的块坐标" + centerTemppic.PieceCoordinates + " 坐标差 " + offset + " 位置" + InitPosition);
centerTemppic.transform.position = InitPosition;
centerTemppic.isUpdate = true;
centerPos = centerTemppic.PieceCoordinates;
InitMap(true,true);
}
}
Camera cameramain;
public void SetMapEnable(bool isEnable)
{
foreach (var map in tempPics.Values)
map.SetEnable(isEnable);
}
void Update()
{
TileUpdate();
}
/// <summary>
/// 中心图块周围坐标对应图块的信息
/// </summary>
private Dictionary<Vector2, Vector3> Groundpoints;
/// <summary>
/// 中心图块
/// </summary>
public TempPic centerTemppic;
private Ray ray;
private RaycastHit hit;
//刷新地图
void TileUpdate()
{
ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (!EventSystem.current.IsPointerOverGameObject())
{
if (Physics.Raycast(ray, out hit, 1000, 1 << 8)) //
{
if (Input.GetMouseButtonUp(0))
{
mapAxesTransfer.WorldToMC(hit.point);
}
}
}
if (Input.GetMouseButtonUp(2)|| Input.GetMouseButtonUp(1))
{
//找新的中心图 并刷新其周围图块 新的中心块一定是距离相机最近的一个 需要计算
UpdateCamera();
}
}
public void UpdateCamera()
{
Vector3 Screensize = new Vector3(Screen.width / 2, Screen.height / 2);
ray = Camera.main.ScreenPointToRay(Screensize);
if (Physics.Raycast(ray, out hit))//
{
if (hit.transform.gameObject.layer == 8)
{
if (hit.transform != centerTemppic.transform)
{
Groundpoints[centerTemppic.PieceCoordinates] = centerTemppic.transform.position;
centerTemppic = hit.transform.GetComponent<TempPic>();
centerTemppic.isUpdate = true;
Groundpoints.Remove(centerTemppic.PieceCoordinates);
MapUpdate();
}
}
}
}
[Header("是否使用输入得到中心点位置")]
public bool IsInputCenter = false;
[Header("输入的中心点经纬度")]
public double inputlat = 118.050357d, inputlng = 36.860605d;
void GetInputCenter()
{
centerPos = mapAxesTransfer.LLToPiece(new Vector2D(inputlat, inputlng), zoom);
}
public Vector2 centerPos;//中心块的块坐标
//临时存储要删掉的图
public List<GameObject> TempDeleteMaps = new List<GameObject>();
/// <summary>
/// 初始化地图 以平面坐标左下角的图块的世界坐标为000;
/// </summary>
/// <param name="Center"></param>
void InitMap(bool Center,bool isRefresh = false)
{
Vector3 InitPosition = Vector3.zero;
var TempPicPre = Resources.Load<GameObject>("prefabgis/mapresources/TempPic");
if (Center)
{
if (centerPos == Vector2.zero) //调用之前 赋值中心点.则 是更新地图层级.
centerPos = new Vector2(Convert.ToInt32((mapAxesTransfer.leftPiece.x + mapAxesTransfer.rightPiece.x) / 2),
Convert.ToInt32((mapAxesTransfer.leftPiece.y + mapAxesTransfer.rightPiece.y) / 2));
if (IsInputCenter) GetInputCenter();
Vector2 offset = centerPos - mapAxesTransfer.leftPiece;
// Debug.Log(offset);
InitPosition = new Vector3(/*(int)numSize.x / 2 + 1 + */offset.x, 0, /*(int)numSize.y / 2 + 1 +*/ offset.y);
InitPosition *= mapAxesTransfer.pixel;
}
else
{
//新的初始化 以图块左下角的图为原点 旧的是全图块中间的图作为原点
//中心图块的块坐标 x是列数 y是行数
centerPos = new Vector2(Convert.ToInt32((mapAxesTransfer.leftPiece.x + numSize.x / 2 + 1)), Convert.ToInt32((mapAxesTransfer.leftPiece.y + numSize.y / 2 + 1)));
//初始化生成 具体数量是否要可变得 ?
InitPosition = new Vector3((int)numSize.x / 2 + 1, 0, (int)numSize.x / 2 + 1);
InitPosition *= mapAxesTransfer.pixel;
//设置相机
}
//初始位置放到目标点
if (isRefresh)
RefreshMapLayer();
else
SetMap(TempPicPre, centerPos, InitPosition);
Vector3 cameraPos;
if (isRefresh)
cameraPos = mapAxesTransfer.LLToWorld(CameraLL);
else
cameraPos = new Vector3(InitPosition.x + mapAxesTransfer.pixel / 2, 20, InitPosition.z + mapAxesTransfer.pixel / 2);
Camera.main.transform.position = new Vector3(cameraPos.x, 20, cameraPos.z);
}
/// <summary>
/// 以左下角为零点计算地图
/// </summary>
void SetMap(GameObject TempPicPre, Vector2 centerPos, Vector3 InitPosition)
{
WD_Debug._Print("初始化");
//新的初始化 以图块左下角的图为原点 旧的是全图块中间的图作为原点
GameObject centerTemp = Instantiate(TempPicPre, InitPosition, Quaternion.Euler(90, 0, 0)) as GameObject;
centerTemp.transform.SetParent(transform);
centerTemp.name = centerPos.ToString();
centerTemppic = centerTemp.GetComponent<TempPic>();
centerTemppic.PieceCoordinates = centerPos;
LoadTexture.AddLoadTask(zoom, (int)centerPos.x, (int)centerPos.y, tex =>
{
centerTemppic.SetSprite(tex);
});
tempPics[centerPos] = centerTemppic;
Groundpoints = centerTemppic.BoundsMapPos(numSize, mapAxesTransfer.pixel);
foreach (var pos in Groundpoints.Keys)
{
GameObject temp = Instantiate(TempPicPre, Groundpoints[pos], Quaternion.Euler(90, 0, 0)) as GameObject;
temp.transform.SetParent(transform);
TempPic temppic = temp.GetComponent<TempPic>();
LoadTexture.AddLoadTask(zoom, (int)pos.x, (int)pos.y, tex =>
{
temppic.SetSprite(tex);
});
// temppic.LoadPicSingleInit(GetPathZoom(pos));
temppic.name = pos.ToString();
temppic.PieceCoordinates = pos;
tempPics[pos] = temppic;
}
LoadTexture.Preload(mapAxesTransfer.PieceToZoom(centerPos, zoom));
}
/// <summary>
/// 中心图片更换之后调用 刷新周围 算法需要优化
/// </summary>
void MapUpdate()
{
Dictionary<Vector2, Vector3> newGroundpoints = centerTemppic.BoundsMapPos(numSize, mapAxesTransfer.pixel);
List<Vector2> SamePoint = new List<Vector2>();//相同的点
foreach(var pos in Groundpoints.Keys)
{
foreach (var poss in newGroundpoints.Keys)
{
if (!SamePoint.Contains(pos) && !SamePoint.Contains(poss))
{
if (!newGroundpoints.ContainsKey(pos) && !Groundpoints.ContainsKey(poss))
{
SamePoint.Add(poss);
SamePoint.Add(pos);
if(tempPics.ContainsKey(pos))
{
TempPic temp = tempPics[pos];
temp.transform.position = newGroundpoints[poss];
temp.PieceCoordinates = poss;
temp.name = poss.ToString();
LoadTexture.AddLoadTask(zoom, (int)poss.x, (int)poss.y, tex =>
{
temp.SetSprite(tex);
});
//temp.LoadPicSingleInit(GetPathZoom(poss));
tempPics[poss] = temp;
tempPics.Remove(pos);
}
}
}
}
}
SamePoint.Clear();
Groundpoints = newGroundpoints;
}
/// <summary>
/// 刷新地图层级
/// </summary>
void RefreshMapLayer()
{
Dictionary<Vector2, TempPic> Vec_Temps = new Dictionary<Vector2, TempPic>();
Vec_Temps[centerTemppic.PieceCoordinates] = centerTemppic;
centerTemppic.name = centerTemppic.PieceCoordinates.ToString();
LoadTexture.AddLoadTask(zoom, (int)centerTemppic.PieceCoordinates.x, (int)centerTemppic.PieceCoordinates.y, tex =>
{
centerTemppic.SetSprite(tex);
});
Dictionary<Vector2, Vector3> newGroundpoints = centerTemppic.BoundsMapPos(numSize, mapAxesTransfer.pixel);
int index = 0;
foreach (var poss in newGroundpoints.Keys)
{
var obj = tempPics.ElementAt(index);
index++;
TempPic temp = obj.Value;
if (temp.transform.position != newGroundpoints[poss])//刷层级的时候其实,15级以下不会有重复
temp.transform.position = newGroundpoints[poss];
temp.PieceCoordinates = poss;
temp.name = poss.ToString();
LoadTexture.AddLoadTask(zoom, (int)poss.x, (int)poss.y, tex =>
{
temp.SetSprite(tex);
});
Vec_Temps[poss] = temp;
}
tempPics = Vec_Temps;
}
}
还有一个类 图片预制体上需要挂的
主要用来计算周围图块的坐标
using UnityEngine;
using System.Collections.Generic;
public class TempPic : MonoBehaviour
{
public bool isUpdate = true;//是否需要刷新位置
/// <summary>
/// 块坐标
/// </summary>
public Vector2 PieceCoordinates;
/// <summary>
/// 四周八个相邻图片的位置 图位置 对应块坐标
/// </summary>
/// <returns></returns>
public Dictionary<Vector2, Vector3> BoundsMapPos(Vector2 pieceNum,float size)
{
if (isUpdate || _boundsMapPos.Count == 0)
{
isUpdate = false;
_boundsMapPos.Clear();
GetDic(pieceNum, size);
}
return _boundsMapPos;
}
private readonly Dictionary<Vector2, Vector3> _boundsMapPos = new Dictionary<Vector2, Vector3>();
public void SetColor(Color col)
{
if (spriterender == null)
spriterender = GetComponent<SpriteRenderer>();
spriterender.color = col;
}
void GetDic(Vector2 colraw, float size)
{
for (int i = 1; i < ((int)(colraw.x / 2))+1; i++)
{
for (int j = 1; j < ((int)(colraw.y / 2))+1; j++)
{
_boundsMapPos[PieceCoordinates + new Vector2(i, j)] = transform.position + new Vector3(size*i, 0, size*j);
_boundsMapPos[PieceCoordinates + new Vector2(i, -j)] = transform.position + new Vector3(size * i, 0, -size * j);
_boundsMapPos[PieceCoordinates + new Vector2(-i, -j)] = transform.position + new Vector3(-size * i, 0, -size * j);
_boundsMapPos[PieceCoordinates + new Vector2(-i, j)] = transform.position + new Vector3(-size * i, 0, size * j);
_boundsMapPos[PieceCoordinates + new Vector2(-i, 0)] = transform.position + new Vector3(-size * i, 0, 0);
_boundsMapPos[PieceCoordinates + new Vector2(i, 0)] = transform.position + new Vector3(size * i, 0, 0);
_boundsMapPos[PieceCoordinates + new Vector2(0, -j)] = transform.position + new Vector3(0, 0, -size * j);
_boundsMapPos[PieceCoordinates + new Vector2(0, j)] = transform.position + new Vector3(0, 0, size * j);
}
}
}
private bool isshow = true;
public void SetEnable(bool enable)
{
isshow = enable;
spriterender.enabled = enable;
}
private SpriteRenderer spriterender;
public void SetSprite(Sprite sp)
{
if (spriterender == null)
spriterender = GetComponent<SpriteRenderer>();
spriterender.sprite = sp;
if (!spriterender.enabled && isshow)
spriterender.enabled = true;
if (GetComponent<BoxCollider>() == null)
{
BoxCollider box = gameObject.AddComponent<BoxCollider>();
box.isTrigger = false;
}
}
}
再来一个相机上挂的
主要就是移动和范围控制 以防拖动到了范围外的地方去
using UnityEngine;
using UnityEngine.EventSystems;
public class CameraMove : MonoBehaviour
{
public float speed = 2;
private Camera cameraMain;
private Vector2 screenSize;
public Vector3 oldpos;
void Start ()
{
cameraMain = Camera.main;
oldpos = cameraMain.transform.position;
}
void LateUpdate ()
{
ControlCamera();
}
/// <summary>
/// 控制相机
/// </summary>
void ControlCamera()
{
//视角拉伸
if (Input.GetAxis("Mouse ScrollWheel") != 0&&!EventSystem.current.IsPointerOverGameObject())
{
cameraMain.orthographicSize += Input.GetAxis("Mouse ScrollWheel") * -1;
if (MapManager.ins == null) return;
if (cameraMain.orthographicSize<3f)
{
if (MapManager.ins.zoom<18)
{
cameraMain.orthographicSize = 5f;
MapManager.ins.ChangeLayer(MapManager.ins.zoom + 1);
}
else
{
if (cameraMain.orthographicSize < 2f) cameraMain.orthographicSize = 2f;
}
}
if (cameraMain.orthographicSize>6f)
{
if (MapManager.ins.zoom > 13)
{
cameraMain.orthographicSize = 4f;
MapManager.ins.ChangeLayer(MapManager.ins.zoom - 1);
}
else
{
if (cameraMain.orthographicSize > 7f) cameraMain.orthographicSize = 7f;
}
}
}
//视角滑动
//非精准移动
if (Input.GetMouseButton(2)||Input.GetMouseButton(1))
{
//调用
float x = Input.GetAxis("Mouse X");
float y = Input.GetAxis("Mouse Y");
transform.Translate(new Vector3(-x, -y, 0) * Time.deltaTime * 90 * speed);
//范围圈定
GetOffset();
MaxPos = MapManager.ins.mapAxesTransfer.GetRightPos();
if (transform.position.x > MaxPos.x- Screenoffset.x)
{
transform.position = new Vector3(MaxPos.x - Screenoffset.x, 20, transform.position.z);
}
if (transform.position.z > MaxPos.z- Screenoffset.z)
{
transform.position = new Vector3(transform.position.x, 20, MaxPos.z - Screenoffset.z);
}
if (transform.position.x < Screenoffset.x)
{
transform.position = new Vector3(Screenoffset.x, 20, transform.position.z);
}
if (transform.position.z < Screenoffset.z)
{
transform.position = new Vector3(transform.position.x, 20, Screenoffset.z);
}
}
}
public Vector3 MaxPos;
private Vector3 Screenoffset;
RaycastHit hit;
Ray ray;
void GetOffset()
{
ray = Camera.main.ScreenPointToRay(screenSize);
if(Physics.Raycast(ray,out hit))
{
Screenoffset = transform.position- hit.point;
}
}
}
ok 到这里呢 就基本大功告成了
效果图的话 录得gif太大了 这里超出了限制 上传了资源可以下载看下https://download.youkuaiyun.com/download/qq_35080168/12411872