语音播报
地图切片思想和技术
受服务器处理能力和网络传输带宽等多方面因素影响, 响应速度一度成为互 联网电子地图的发展瓶颈, 随着 瓦片地图技术 的出现, 地图的拖动、 缩放以及不 同比例尺下的快速浏览都有了很大的改善。
Google 、高德、 Baidu 、 Mapbar 、灵图等互联网电子地图供应商都使用了这 一技术。 瓦片地图本质上 就是把人们通用的地图作为主要地图背景, 并采用预先 生成的方法存放在服务器端, 然后根据用户提交的不同请求, 把相应的地图瓦片 发送给客户端的过程。 由于客户端请求的地图是预先生成, 不需像传统的 WebGIS 那样对用户的请求进行实时计算和绘图, 所以瓦片地图技术能够在地图的显示方 面具有速度的优越性。
地图瓦片是如何生成的?如何根据用户的请求范围实时地将相关瓦片反馈 给用户?这不但需要做好地图切片工作,更需要建立一个良好的索引机制。 本文主要探讨地图切片及其加载显示、变形问题修正等技术问题。
1. 地图切片的命名方式
地图切片采用类似四叉树的方式进行切割, 每一层的图幅数皆为前一层的四 倍,如下图所示:
Level 1 Level 2 Level 3 0_0
0_0
1_01_1
1_01_1
0_11_01_1
按上述切割方式,对各级别的图幅进行命名,以 1级和 2级为例,如下所示: Level 1:
Level 2:
地图切片保存格式:png
地图命名方式:
地图采用 level (缩放级别 ) + row(行号 ) + column(列号 ) 方式
具体规则为
1. row 和 column 按数字进行分解每个数字为一个文件夹 , 如:123分解 为 1/2/3。
2. level 单独为一个文件夹。
3. 命名示例:
level :10 , row:123, column :256命名为 10/1/2/3/2/5/6.png
level :11 , row:1123, column :2356命名为 10/1/1/2/3/2/3/5/6.png
2. 地图切片各级别图片数量
地图切片中,每个切割的小图片是 256*256(像素) ,共分为 12级,下表为 各比例尺下的图片数量及图片大小:
3. 地图切片的坐标范围:
经度:70°—— 137°
维度:0°—— 67°
4. 地图切片显示方案: 4.1. 地图分层
4.2. 配色
4.3. 各层显示范围
因为每个地区的道路和 POI 数量、 分布情况不同, 所以为了达到比较好的显 示效果,在设置图层显示的比例尺范围时会不同,应视具体情况而定。一般,道 路分级显示不要超过 6个层次, POI 分级显示不要超过 3个层次。
5. 一种基于矢量数据的瓦片金字塔算法
李海亭
武汉市勘测设计研究院
工程师,博士
摘要
由于响应速度一度成为互联网电子地图的发展瓶颈, 随着瓦片地图技术的出 现, 地图的拖动、 缩放以及不同比例尺下的快速浏览都有了很大的改善。 近年来, 许多互联网电子地图供应商(包括 Google 、 Baidu 、 Mapbar 、灵图等)都使用了 这一技术。 瓦片地图本质上就是把人们通用的地图作为主要地图背景,并采用 预先生成的方法存放在服务器端,然后根据用户提交的不同请求,把相应的地 图瓦片发送给客户端的过程。 由于客户端请求的地图是预先生成, 不需像传统的
WebGIS 那样对用户的请求进行实时计算和绘图,所以瓦片地图技术能够在地图 的显示方面具有速度的优越性。 地图瓦片是如何生成的, 如何根据用户的请求范 围实时地将相关瓦片反馈给用户, 这需要建立一个良好的索引机制。 本文根据基 于瓦片地图机制的武汉市公益地图网(www.vrwuhan.com )的实际开发应用,提 出了一种基于矢量数据的 瓦片金字塔算法 , 并探讨了该算法引发的地图变形问题 及其修正方法。
关键词:瓦片金字塔;网格索引;地图变形;步长修正
1 前言
瓦片索引是当今网络电子地图发布的主要技术手段, 它采用预生成思想将地 图进行横向分幅和纵向分级, 然后根据用户请求动态检索相应的图块并自动完成 拼接。对全球进行空间划分的方法归纳起来主要有以下两种 :等间隔空间划分和 等面积空间划分。 但在平面电子地图的表达中, 瓦片索引在本质上则是地图投影 变换和空间索引的融合运用, 该索引模型的建立过程须根据其应用特点参考不同 地图投影的变形规律。 因此, 瓦片索引方法研究同样也是适应新型地图产品而派 生的新的研究领域, 它是地图投影学研究的一个延伸。 本文首先介绍基于矢量数 据的地图瓦片金字塔概念, 然后提出了一种采用网格索引的瓦片金字塔算法。 本 文还在分析该算法在特定区域引发的地图变形问题的同时进一步探讨了如何通 过地图瓦片的长宽修正和经纬度步长修正两种方法解决变形问题。
2 基于矢量数据的瓦片金字塔
将指定范围内由矢量数据绘制并符号化的地图,进行纵向分级和横向分幅, 根据不同的比例尺等级,按照指定尺寸 (如 300×240等)和指定格式 (如 JPEG , PNG 等 ) 进行切割,得到若干行和列的矩形图片库,这些矩形地图切片也称为地 图瓦片 (Map Tile) ,这些若干行和列的地图瓦片库呈现正金字塔形的数据结构。 将预先生成的金字塔式地图瓦片库放置于服务器的虚拟目录中, 服务器接收到客 户端请求及验证后, 根据客户端请求的地理范围及比例尺的大小, 将预先生成的 地图瓦片返回并显示在不同的客户端。
3 具体算法描述及步骤
现以世界地图为例,阐述该瓦片金字塔的主要算法。具体如下:
3.1 地图瓦片的基本约定
假设把世界地图按比例尺的大小分为 16个等级,也就是说要在以后的地图 切片中完成 16套地图瓦片的制作。为了方便快捷的进行地图切片,同时考虑到 要反映整个世界地图的特征,拟采用 WGS84大地坐标系作为地图瓦片库的坐标 系统,即将需要切片的矢量数据全部转换为 WGS84的大地坐标系统。 WGS84大地坐标系以经度和纬度反映地球上任意一点的具体位置,在相同等级的前提 下, 假定每张地图瓦片跨越的经纬度是相同的, 并将地图瓦片的经纬度步长分为 16个等级。根据实际计算的精度需要,拟采用非等比数列的步长数组。用 工作单位:武汉市勘测设计研究院,工程师
Javascript 语言描述如下:
//定义地图缩放的级别
//定义地图瓦片跨越的经度步长数组
//定义地图瓦片跨越的纬度步长数组
3.2 地图瓦片的存放约束
由于地图瓦片是基于矢量数据生成的栅格图片, 其本身并不具有空间位置信 息。 但是每一张地图瓦片在特定的比例尺下跨越了固定的经纬度步长, 所以很容 易计算出该地图瓦片的具体位置信息。 本文拟采用地图瓦片的文件名来标识其所 在的地理位置。 需要指出的是:由于地图瓦片的数量巨大, 若用同一个文件夹来 存放所有的地图瓦片, 不仅会引起瓦片数据的管理混乱, 而且对于存放地图瓦片 的操作系统而言, 每一个文件夹中文件的存放数量受限, 很难达到地图瓦片库的 存放要求。 由于不同比例尺级别的地图瓦片库相对独立, 故分别建立相应比例尺 级别的文件夹用以存放该级别的地图瓦片, 然后再根据网格索引的具体算法对该
级别的地图瓦片进一步分类存放。
3.3 瓦片金字塔算法
地图瓦片的文件夹命名不是唯一的, 为了方便计算的需要, 拟采用缩放级别 的数值作为每一套地图瓦片库文件夹的名称。 实验证明:如果简单地将每一级别 的地图瓦片都存放在同一个文件夹下, 那么除了会遇到管理混乱和存放受限的问 题外, 客户端对地图瓦片的访问速度也会明显减慢。 因此需要建立一种索引机制 来提高这种金字塔形海量地图瓦片库的搜索速度。 因为每张地图瓦片的文件名都 是其地理位置信息的标识, 在地图瓦片的切割过程中, 假定以 x_y的方式来命名, current_longitude, current_latitude分别表示当前的经度和纬度, current_level表 示当前的缩放级别,用 Javascript 语言描述,那么最简单的索引算法是:
为避免上述瓦片管理混乱、 存放受限及访问速度缓慢等问题, 本文采用网格 索引为海量地图瓦片库提供位置搜索服务, 即假定每一个网格中包含若干行和列 的地图瓦片, 然后根据特定级别的瓦片数量, 确定每一个网格具体的行和列的数 值值。 为了方便计算的需要, 采用方阵来描述每一个网格。 假设某一个级别方阵 行数的值为 M , 根据该级别的地图瓦片数量设定 M 值。 因为随着比例尺的增大, 瓦片文件的数量呈非线性剧增,根据实际的计算结果,比例尺较小的 M 值设为 10,比例尺较大的 M 值设为 50。用 Javascript 语言描述如下:
//定义相应级别的网格方阵阶数数组
这样, 每一个级别的瓦片库文件夹下就可以存放若干个用于索引的引擎文件 夹, 即引擎文件夹的名称将为地图瓦片库提供位置索引服务。 上述瓦片文件名称 x_y分别代表地图瓦片相对于经纬度原点 (0,0)和相应经纬度步长的零点坐标值, 分别用 Origin_x和 Origin_y来描述。对于某一级别的地图瓦片库,假设它的引 擎文件夹名称为 folder_x_folder_y,用 Javascript 语言描述,则满足:
假定应用网格索引后的地图瓦片名称仍然用 x_y表示,用 Javascript 语言描 述,则 x 和 y 的具体数值可以通过如下计算获得(%表示取余计算) :
或者是:
这样,在客户端浏览特定范围的地图瓦片库或定位某一个具体的地理位置 时, 就可以根据此引擎机制调用相应的地图瓦片, 并返回给客户端并无缝拼接显 示。 引擎机制调用的地图瓦片如图 1所示。 地图瓦片无缝拼接的计算方法同样采 用此索引原理。 由于地图瓦片的索引计算是在客户端完成的, 属于一种胖客户端 的结构, 这样便大大减轻了服务器的负担, 并很好地提升了地图请求的响应速度。
引擎机制调用的地图瓦片
3.4 瓦片的定位与拼接
以定位某一个具体的地理位置来说明该引擎机制在地图瓦片库中的具体应 用 。 根 据 上 述 设 定 , 当 前 需 要 定 位 的 地 理 坐 标 为 (current_longitude, current_latitude),调用的地图瓦片文件路径为 Tile_path,则满足:
Tile_path = 地图瓦片库文件夹 + current_level + folder_x_folder_y + "/" + x_y + .扩展名
定位某一个具体的地理要素并使该要素在地图上完整居中显示, 则根据其质 心及整个要素所跨越的地图瓦片计算获得。
4 算法引发的地图变形问题及其修正方法
在瓦片地图的发布过程中, 所有的地图瓦片从形状来讲往往是全等的。 但地 球是一个不规则的椭球体, 在同一个地点, 跨越相同的经度和纬度, 距离一般并 不相等。并且,在不同的纬度带,跨越同样的经度差,距离也往往是不相等的。 因此, 利用上述计算方法, 只有在同时满足上述两种条件的理想情况下, 地图瓦 片上所表示的地理要素与实际地物的长宽比才会一致。 也就是说, 假设地球是一 个标准的球体, 这种瓦片金字塔算法在赤道地区附近相对来讲是适用的。 如何使 该算法适用于世界范围或某一特定的区域呢?本文尝试通过地图瓦片修正和经 纬度步长修正两种方法对上述算法进行改进, 使其能够较真实的反映某一区域而 不产生大的变形。
4.1瓦片长宽修正
瓦片长宽修正方法是指根据其特定在区域位置, 在该区域范围内, 通过计算 同一经度和纬度所跨越的实际距离来调整地图瓦片的长宽比例关系, 以此来修正 瓦片上所表示地理要素的真实形状及要素间的相对位移。 以北京市为例, 同样假 设地球为一标准的球体(这样假设的误差并不大) ,假设经纬度每变化一度跨越 的实际地理距离为 x_perlongitude、 y_perlatitude,地球半径为 radius_Earth,北京 市的经纬度坐标为 BJ_LONGITUDE, BJ_LATITUDE。则满足:
北京市的纬度坐标大约为北纬 39.92度,因此,每块地图瓦片的长宽比为 cos(39.92°) =0.767。为了计算方便的需要,将瓦片的长宽比调整为 4:5。在实 际计算中,设定每块瓦片大小为 240像素×300像素,并同北京 1954坐标系下 公里格网的北京市地图相比较,没有发生大的变形,基本满足公众需要。 4.2经纬度步长修正
经纬度步长修正方法是指地图瓦片保持正方形特征, 而修正每块瓦片跨越的 经度或纬度步长。 以纬度步长修正为例, 根据上述计算, 每一块瓦片的大小设定 为 300像素×300像素, 调整每块瓦片跨越的纬度和经度的比值为 4:5。 即将上 述地图瓦片跨越的纬度步长数组修改为:
经过上述方法进行修正, 可以基本解决瓦片地图技术在特定区域范围内引发 的地图变形问题。 对于世界范围内的地图瓦片表达, 可以通过对经纬度实行分带, 然后再通过步长修正的方法来尽量减小地图变形。 分带的数值越大, 引发的地图 变形将越小。
5 结 语
本文采用网格索引为矢量地图的瓦片金字塔图库提供引擎服务, 该引擎算法 可以快速实现客户端地图瓦片的调用及无缝拼接显示,大大提升地图的响应速 度; 另外, 本文还探讨了瓦片金字塔算法引发的地图变形问题, 并提出了两种修 正方法:即在特定的区域范围内,根据计算同一经度和纬度所跨越的实际距离, 对地图瓦片自身的长宽比例关系进行修正; 或者对地图瓦片跨越的经纬度步长进 行修正。
1. 为什么写这些 ?
今天开会 , 听同事主讲了 <怎么样写出高质量代码 ?>.里面同事说到了 " 仿照国外 的博客 , 公开自己写的有意思的代码或者思想 " 等等的建议 .
因为很佩服他所讲的这些 , 所以决定尝试下 抛玉引砖 ,不再潜水了 .
顺便记录一下自己的编程成长经历 .
2. 抛玉引砖
抛出自己认为是玉的那部分 , 当然也希望在别人眼里也能够是玉 . 或者通过别人 指点 , 能将这个璞玉雕琢的更加完美 .
3. 绪论
主要写一下地图的加载算法和想法 . 本人接触 AS3 比较晚 , 去年 7月毕业后才开 始接触 , 到现在快满一年了 . 所以对 AS 甚至编程中的很多东西都不明了 , 希望高 手能指点 .
最开始的地图算法是去年 8月参照一个策划的方法 (多加载一行一列地图切片 , 移动时将最左边不用的移动到最右边更新 ) 写的 , 然后自创了一套新的制作思想 (关键字 做标记 ) 和算法 .
这套方法 做个简单的比喻下就是拿代数题的方法去解决几何问题 .
这个思想 初中老师就已经教过了 , 所以非常简单
比如用代数方法解决几何学题目
I. 建立直角坐标系
II. 利用三角函数进行计算解题
准备分 A,B 两个部分写 A: 1.地图的预加载 ; (纯粹的多加载一圈地图切片 )
2. 地图的移动 ; (这里就讲我自己想到的方法 )
3. 极限值 ; (利用极限值 其实可以制作出很 AI 的东西 , 但这里只 讲下自己怎么控制极限值让程序不报错 ...)
B: 还没优化完 , 暂时保密 .A 部分是以前写的 ,B 部分是最近优化 A 部分的衍生物 .
如果对此类思想感兴趣的话可以 email 我 互相讨论下
AS3地图加载 ---A1. 地图的预加载
1. 为什么要多预加载一圈 ?
见图
...
蓝色的就是地图了 , 由一块一块的大小为 100 x 100 px的切片组成
红色的框就代表 swf的显示框 大小为 300 x 300 px ,用户只能看到红框里面 的内容
黑色的框 表示 必须加载的切片范围 4 x 4 枚 切片 (少于这个数量 用户会在红 框内看到空白背景 )
绿色的框 表示 预加载多一圈的范围 6 x 6 (左右上下 各多加 1个切片 ) 青色的是辅助线 请无视
当移动地图后 swf 在如图所示的位置
因为地图往往会比 swf 的显示范围大的多 , 一次全部加载速度很慢
而且很占用资源 , 所以为了满足需求最少要在 swf 显示范围上预加载一圈
因为不知道用户向四周哪个方向移动地图
在移动地图时 会需要加载新的图片
预加载一圈的话 , 用户会先看到预加载的外围贴片 ,
如果不预加载 , 新的图片还在加载中没有显示
用户就会直接看到 swf 的空白背景 , 这样的使用体验不太友善
-------------------------------------------------------------------- 例图里都是纯色切片 , 实际地图肯定每个切片都不一样的
可以根据 swf 在地图上的相对坐标 加载 6 x 6 张 对应地图切片
简单介绍下当时的需求
接下来就是重点了
→ A2.地图的移动
http://home.verycd.com/space.php?uhttps://img.ggdoc.com/56b5tlLW%2BrEbiBrVGRtJi8DekaqQ7Go6BDW6Ix3NCW3QHUilcQ12GRuuY6JsQEZY7XXcHpY%2FO%2FgalE6MrxA8X0K%2FXr6iWMOUo7ZY7qrkiGs5azB01SWm3gc13ivA%2FSLvV0SMLzWy4dtnooZURYiKjBEbQNBa2SyxTIHFOlKtYU1XQEFiRpF4wvOfONLUqTTR4i%2FOtklYZ0gsdn1epa%2FKnaNzZjOjMdeiI%2B2UfZMV8nb0VQj0N3b6hka5kOOpVttTpdWbe2t%2BalFHSYcdO8Y1WADZvBpwiPK5v6Ehn6nGpoNaHRup8hE20QNFsNW52fAU7KpdScYqtDLZoOaPLZmKQx1XVAmz9YvYfAUd2tL8duE6XLFPzeK5xi01oHq5Lg.jpg' alt='地图切片思想和技术'>
举例 : 移动前 需要加载的切片位置 标记为 白色的 A 设其范围为 RectA 移动后 需要加载的切片位置 标记为 黑色的 B 设其范围为 RectB
在移动地图后就需要加载只有 B 标记的切片地图 , 然后将只有 A 标记的切片从显 示中删除
标记有 AB 切片的地图不需要做操作 .
实现上述需求的老方法 (几何方法 ) 是
1. 获取地图坐标
2. 通过对比坐标来确定新切片满足 不在 RectA 中
3. 而在 RectB 中 得到需要加载的切片对象
4和 5. 然后同理得到需要从先是列表中删除的切片对象 .
(当然不一定是删除 , 根据项目不同的需求设置显示对象载体的变更方式 , 删除或 者移动位置 )
下面说说新方法 (代数方法 ) 方法主要利用了集合和逻辑思想 .
AS3中数组类型没那么多方便的集合操作方法 , 所以这里还要加上标记才能更简 单的实现集合操作 .
在 地图加载 的最开始设置一个 Boolean 型的标记 flag, 标记当前位置的切片是否 显示 .
即当前 RectA 中的切片的 flag 全部标记为 true;
1. 获取地图坐标
2. 判断 RectB 中的 flag , if(flag) 则 flag = false , else flag = true; 并 加载切片 .(当然里面省略了很多缓存的判断操作 , 如果有缓存则直接拿缓存 , 没 有则用 loader 加 .. 老方法也会判断所以这里不做比较 )
3. 判断 RectA 中的 flag, if(flag) flag = false;并删除切片显示对象 ,else
flag = true;
到这里去年 8月新方法结束 .
经过逻辑判断 , 不管用户怎么样拖动地图 , 最后也只有三种情况
RectA 和 RectB 相同 , 相交 , 完全不相交
因为有预加载部分 所以第三种可能也比较少 .
新方法也完全能用于这三种情况 , 而且不复杂可以方便的扩展新的功能 . 写成数字描述会更简单
但是 地图加载 在性能上还是有别的地方可以进行优化
如 难道每次 mouseMove 都要进行新方法的判断吗 ?
是可以灵活的操作极限值来减少的 .
简单的说明区别就是
过去 mouseMove 中进行新方法判断是以 X = n轴 或 Y = n轴 即用一条线的比 较来取舍是否来进行新方法 .
灵活利用极限值的话可以将这一方法扩展为 用多条线组成的一个范围区域来取 舍是否进行新方法 .
使用线的话 在线的临界 来回拖动的话就会每次都进行新方法 .
使用范围的话 在固定范围内不再会对新方法做 2次执行 , 同样也能满足需求 详细请见
→ A3. 极限值 //等待写好后在这里添加链接
AS3地图加载 ---A3. 极限值
地图操作中会碰到很多临界情况
要设置很多极限值去防止溢出的操作 .
多预加载一圈切片将带来了更多新的问题
.
在上图的示例中 , 预加载的绿色方框包含了没有地图切片的地方 .
为了不使程序报错 , 必须要设置一个极限值去限制加载切片的位置 .
但这里主要要说的不是这个问题 , 而是要控制 mouseMove 事件中要发生的事情 想象一下绿色的预加载框开始向左移动 100px,
假设它的注册点是左上角 .
那么在移动前它的位置就是 (100,0) ,那么它的目标就是 (0,0)
在 mouseMove 中 , 我们会去加载图片 , 如果用我上一篇日志中的标记的方法 . 那么在移动过程中的多次 mouseMove 中只要判断新切片位置的标记 就可以达到 目标切片只加载一次的目的了 .
但是假如出现下图中的情况 , 程序仍可能会一直处于 "忙 ".
青色的辅助线就表示我们的临界值 ,
我们判断地图坐标时
会将地图坐标和临界值对比 , 然后决定是否加载新的切片 .
但是绿色的预加载框在图中 白线 左右移动时 , 会不断的触发我们的加载方法 .
特别是当绿色的预加载框在需求中要非常大的时候 ... 程序可能会因为忙而未响 应 , 甚至导致程序出错 .
(图片停止加载 , 请注意这时原本的切片很可能已经在逻辑判断中删除了 , 那么用户就会看到地图上的一块或几块 " 空地 "".
如果没有删除 , 用户则会看到位置错误的切片 .)
为了提高性能 , 我们需要在使用临界值判断之前增加一个范围判断 .
增加一个鼠标相对位移是否在这个范围内的判断 ,
假如在这个的范围内 , 则不继续执行复杂的加载方法 . 反之 , 则执行 .
请注意 :这里的范围不能是一个固定值 !
因为如果是固定值的话 , 每次鼠标相对位移都在范围内 , 程序将永远都不加载新 的图片 .
这个范围的大小将和地图坐标有密切的关系 , 而且最大不能超过一个切片大小 . 这里的判断讲起来比较复杂 , 但实际上非常简单 . 所以只写下大概的思路 . 鼠标的位移是根据地图的左右移动决定的 , 是有正负之分的 .
正负情况要分开考虑 , 决定这个范围靠近切片的左边界还是右边界 .
范围大小由地图坐标和临界值决定 .
以上以左右移动地图为例讨论了极限值 , 但请不要忘记了实际操作地图还有上下 移动 .
但因为很相似所以就写到这里吧 ..
A 部分结束 .
-----------------------------------------------------
实际上极限值这里部分可以运用 AS3的 startDrag API 代替
地图分块加载的原理
首先我们确定几个关键点:
窗口大小:windowW:Number,windowH:Number; 指的是可视区域的大小 我们可以 把他想成客户端的分别率
单位地图大小: uintPicW:Number , uintPicH :Number; 指的是你切割的地图 图片的单元大小。
当前地图坐标:position :point; 这里大家就可以想成是你人物的当前所处坐标 清楚了上面的几个关键点,我们再来看下面的这个图:
阴影区域就是可视区域 A、 B 、 C 、 D 就是切割的单元地图
我们可以很容易的求出窗口的 4个点,所占的地图的索引: x0 = position.x - windowW/2;
x1 = position.x + windowW/2;
y0 = position.y - windowH/2;
y1 = position.y + windowH/2;
mapIndexX0 = uint(x0/256);
mapIndexX1 = uint(x1/256);
mapIndexY0 = uint(y0/256);
mapIndexY1 = uint(y1/256);
明白了上面的这些 下面这个类 相信大家都能看明白
1.
2. package com.heptaFish.common.game.map.layers
3. {
4. import com.heptaFish.common.config.Config;
5. import com.heptaFish.common.core.BaseDisplayObject;
6. import com.heptaFish.common.game.map.impl.GameMap;
7. import com.heptaFish.common.hack.HeptaFishGC;
8. import com.heptaFish.common.loader.impl.ImageLoader;
9. import com.heptaFish.common.map.impl.HashMap;
10.
11. import flash.display.Bitmap;
12. import flash.events.Event;
13. import flash.events.IOErrorEvent;
14. import flash.events.ProgressEvent;
15. import flash.geom.Point;
16. //地图层 图片
17. public class MapLayer extends BaseDisplayObject 18. {
19. //图片读取器
20. private var _imageLoader:ImageLoader;
21. //地图图片 用于整块加载模式
22. private var _image:Bitmap;
23. //地图图片数组 用于栅格式加载地图模式
24. private var _imageMap:HashMap;
25. //小地图图片
26. private var _simage:Bitmap;
27. //
28. private var _map:GameMap;
29. private var _loadType:int;//加载类型 0:整块加载 1:栅格加载 30. private var _visualWidth:Number;//地图可视宽度
31. private var _visualHeight:Number;//地图可视高度
32. private var _sliceWidth:Number;//地图切割单元宽度
33. private var _sliceHeight:Number;//地图切割单元高度
34. private var _preloadX:Number;//横向预加载屏数
35. private var _preloadY:Number;//纵向预加载屏数
36. private var _loadingMap:HashMap;//正在加载的屏 map
37. private var _waitLoadingArr:Array;//等待加载的 loadermap 38.
39. private var _loadingNo:int = Config.getInt("concurrencyImag eLoader");
40.
41. private var _screenImageRow:int;//一屏需要加载的横向图片数 42. private var _screenImageCol:int;//一屏需要加载的纵向图片数 43. private var _row:int;//总横向节点数
44. private var _col:int;//总纵向节点数
45.
46. private var _nowPlayerPointoint;//当前人物所处的屏
47.
48. public function MapLayer(map:GameMap)
49. {
50. _map = map;
51. _loadType = parseInt([email protected]);
52. }
53. //读取地图图片
54. public function load():void{
55. //加载小地图
56. var imageLoader:ImageLoader = new ImageLoader();
57. var fileName:String =Config.getValue("mapLib") + _map.name + "/map_s.jpg";
58. imageLoader.load(fileName);
59. imageLoader.addEventListener(Event.COMPLETE,loadSmallSucce ss);
gHandler);
61. imageLoader.addEventListener(IOErrorEvent.IO_ERROR,ioError Handler);
62.
63. }
64. //读取大地图成功
65. private function loadBigSuccess(evet:Event):void{
66. var imageLoader:ImageLoader = ImageLoader(evet.target);
67. var image:Bitmap = new Bitmap(imageLoader._data);
68. addChild(image);
69. if(_simage != null && _simage.parent == this){
70. removeChild(_simage);
71. _simage = null;
72. }
73. this.width = image.width;
74. this.height = image.height;
75. imageLoader.removeEventListener(Event.COMPLETE,loadBigSucc ess);
76. imageLoader.removeEventListener(ProgressEvent.PROGRESS,loa dingHandler);
77. imageLoader.removeEventListener(IOErrorEvent.IO_ERROR,ioEr rorHandler);
78. imageLoader = null;
79. dispatchEvent(evet);
80. HeptaFishGC.gc();
81. }
82. //读取小地图成功
83. private function loadSmallSuccess(evet:Event):void{
84. var imageLoader:ImageLoader = ImageLoader(evet.target);
85. var image:Bitmap = new Bitmap(imageLoader._data);
86. image.width = _map.mapWidth;
87. image.height = _map.mapHeight;
88. addChild(image);
89. this.width = image.width;
90. this.height = image.height;
91. imageLoader.removeEventListener(Event.COMPLETE,loadSmallSu ccess);
dingHandler);
93. imageLoader.removeEventListener(IOErrorEvent.IO_ERROR,ioEr rorHandler);
94. imageLoader = null;
95. dispatchEvent(evet);
96. HeptaFishGC.gc();
97. switch(_loadType){
98. case 0://整块加载
99. //加载大地图
100. var bfileName:String =Config.getValue("mapLib") + _map. name + "/map.jpg";
101. var bLoader:ImageLoader = new ImageLoader();
102. bLoader.load(bfileName);
103. bLoader.addEventListener(Event.COMPLETE,loadBigSucces s);
104. bLoader.addEventListener(ProgressEvent.PROGRESS,loadin gHandler);
105. bLoader.addEventListener(IOErrorEvent.IO_ERROR,ioError Handler);
106. break;
107. case 1:
108. _loadingMap = new HashMap();
109. _imageMap = new HashMap();
110. _waitLoadingArr = new Array();
111. _visualWidth = _map.app.screen.size.x;
112. _visualHeight = _map.app.screen.size.y;
113. _sliceWidth = parseFloat([email protected]);
114. _sliceHeight = parseFloat([email protected]); 115. _preloadX = parseFloat([email protected]);
116. _preloadY = parseFloat([email protected]);
117. _screenImageRow = Math.round(_visualWidth/_sliceWidt h);
118. _screenImageCol = Math.round(_visualHeight/_sliceHeigh t);
119. _row = Math.ceil(_map.mapWidth/_sliceWidth);
120. _col = Math.ceil(_map.mapHeight/_sliceHeight);
121. loadSliceImage(_map.initPlayerPoint);
123. default:
124. break;
125.
126. }
127. }
128.
129. //根据 player 坐标读取周边指定屏数地图
130. private function loadSliceImage(playerPointoint):void{ 131. var nowX:int = Math.floor(playerPoint.x/_sliceWidth);//现 在所处的索引 X
132. var nowY:int = Math.floor(playerPoint.y/_sliceHeight);//现 在所处的索引 Y
133. var nowScreenX:int = Math.floor(nowX/_screenImageRow);//现 在所处的屏索引 X
134. var nowScreenY:int = Math.floor(nowY/_screenImageCol);//现 在所处的屏索引 Y
135. // trace("nowScreenX:" + nowScreenX);
136. // trace("nowScreenY:" + nowScreenY);
137. _nowPlayerPoint = new Point(nowScreenX,nowScreenY);
138. loadScreenImage(nowScreenX,nowScreenY);
139. // removeScreenImage(nowScreenX,nowScreenY);
140. var startX:int = (nowScreenX - _preloadX < 0 ? 0 : nowScre enX - _preloadX);
141. var startY:int = (nowScreenY - _preloadY < 0 ? 0 : nowScre enY - _preloadY);
142.
143. var endX:int = (nowScreenX + _preloadX > _row ? _row : now ScreenX + _preloadX);
144. var endY:int = (nowScreenY + _preloadY > _col ? _col : now ScreenY + _preloadY);
145.
146. for(var xx:int = startX; xx < endX;xx++){
147. for(var yy:int = startY; yy < endY;yy++){
148. if(xx == nowScreenX && yy == nowScreenY){
149. continue;
150. }else{
151. loadScreenImage(xx,yy);
153. }
154. }
155. }
156. //加载指定屏的地图图片
157. private function loadScreenImage(screenX:int,screenY:int):v oid{
158. var starX:int = _screenImageRow*screenX < 0 ? 0 : _screenI mageRow*screenX;
159. var starY:int = _screenImageCol*screenY < 0 ? 0 : _screenI mageCol*screenY;
160. var endX:int = _screenImageRow*(screenX+1) > _row - 1 ? _r ow -1 : _screenImageRow*(screenX+1);
161. var endY:int = _screenImageCol*(screenY+1) > _col-1 ? _ col-1 : _screenImageCol*(screenY+1);
162. for(var yy:int=starY;yy<endy+1;yy++){
</endy+1;yy++){
163. for(var xx:int = starX;xx<endx+1;xx++){
</endx+1;xx++){
164. var tempKey:String = yy+"_"+xx;
165. if(!_loadingMap.containsValue(tempKey) && !_imageMap.c ontainsKey(tempKey)){
166. _waitLoadingArr.push(tempKey);
167. }
168. }
169. _waitLoadingArr.reverse();
170. loadImage();
171. }
172. }
173.
174. private function loadImage():void{
175. if(_waitLoadingArr.length > 0){
176. for(var i:int = 0;i<_loadingNo - _loadingMap.size();i++){ 177. var key:String = _waitLoadingArr.pop();
178. var imageLoader:ImageLoader = new ImageLoader();
179. var fileName:String = Config.getValue("mapLib") + _map. name +"/" + key + ".jpg";
180. // trace("fileName:" + fileName);
182. imageLoader.addEventListener(Event.COMPLETE,loadScreen ImageSuccess);
183. imageLoader.addEventListener(ProgressEvent.PROGRESS,lo adingHandler);
184. imageLoader.addEventListener(IOErrorEvent.IO_ERROR,ioE rrorHandler);
185. imageLoader.load(fileName);
186. }
187. }
188. }
189.
190. //成功加载某屏的图片
191. private function loadScreenImageSuccess(evet:Event):void{ 192. var imageLoader:ImageLoader = ImageLoader(evet.target);
193. var tempStr:String = String(_loadingMap.getValue(imageLoad er));
194. var tempStrArr:Array = tempStr.split("_");
195. var yy:int = tempStrArr[0];
196. var xx:int = tempStrArr[1];
197. _loadingMap.remove(imageLoader);
198. var image:Bitmap = new Bitmap(imageLoader._data);
199. image.x = _sliceWidth*xx;
200. image.y = _sliceHeight*yy;
201. this.addChild(image);
202. _imageMap.put(yy+"_"+xx,image);
203. imageLoader.removeEventListener(Event.COMPLETE,loadScreenI mageSuccess);
204. imageLoader.removeEventListener(ProgressEvent.PROGRESS,loa dingHandler);
205. imageLoader.removeEventListener(IOErrorEvent.IO_ERROR,ioEr rorHandler);
206. imageLoader = null;
207. loadImage();
208. }
209. //卸载指定屏的地图图片
210. private function removeScreenImage(screenX:int,screenY:int): void{
211. var startX:int = (screenX - _preloadX < 0 ? 0 : screenX - _preloadX);
212. var startY:int = (screenY - _preloadY < 0 ? 0 : screenY - _preloadY);
213.
214. var endX:int = (screenX + _preloadX > _row ? _row : screen X + _preloadX);
215. var endY:int = (screenY + _preloadY > _col ? _col : screen Y + _preloadY);
216. var keyArr:Array = _imageMap.keys();
217. for(var i:int = 0;i < keyArr.length;i++){
218. var key:String = keyArr;
219. var tempStrArr:Array = key.split("_");
220. var yy:int = tempStrArr[0];
221. var xx:int = tempStrArr[1];
222. if(xx < startX*_screenImageRow || xx > endX * _screenImag eRow || yy < startY*_screenImageCol || yy > endY*_screenImageCo l){
223. var image:Bitmap = Bitmap(_imageMap.getValue(key)); 224. this.removeChild(image);
225. image = null;
226. _imageMap.remove(key);
227. }
228. }
229. HeptaFishGC.gc();
230. }
231.
232. //检查是否需要加载
233. public function checkLoad(pointoint):void{
234. var nowX:int = Math.floor(point.x/_sliceWidth);//现在所处 的索引 X
235. var nowY:int = Math.floor(point.y/_sliceHeight);//现在所处 的索引 Y
236. var nowScreenX:int = Math.floor(nowX/_screenImageRow);//现 在所处的屏索引 X
237. var nowScreenY:int = Math.floor(nowY/_screenImageCol);//现 在所处的屏索引 Y
238. if(nowScreenX != _nowPlayerPoint.x || nowScreenY != _nowPl ayerPoint.y){
239. loadSliceImage(point);
240. }
241.
242. }
243. }
244. }