Android 绘制中国地图

最近的版本有这样一个需求:

有 3 个要素:

  1. 中国地图
  2. 高亮省区
  3. 中心显示数字

面对这样一个需求,该如何实现呢?

高德地图

因为项目是基于高德地图来做的,所以很自然而然的想到了高德。但是当查阅高德地图相关 Api 后,发现并没有能够实现这样需求的方法,所以只能另寻他法了。

图片叠加

让设计师出图,实现第一个要素开发成本极低。至于高亮省区,也是继续让设计师出图,与全国地图分辨率保持一致,为每个省区设计一张高亮的图,其他地方透明,这样算下来设计师得出 35 张图。若不考虑性能,将图片无脑叠加倒也可以实现。但是作为 Android 开发都知道,这样的一张不算小的图片加载到手机里,占用的内存怕是个庞然大物,更别谈极端情况下要叠加 35 张这样的大图了。
优化下叠加方案:将高亮的省区做成小图,一个包含了省区所有区域的矩形,省区内部高亮,其他区域透明,这样图变小了,但是就得计算小图相对于全国大图的相对位置,对于每个小图都得计算一个比例。同时,绘制高亮省区时可以每次都只取2张图进行叠加,叠加完后释放一张图再加载另一张图,而不用一次性全部加载在内存中。这种方案想想是 ok 的,但是感觉依然还是很麻烦。于是继续探索~

SVG Path

其实网上有很多文章也是有类似的需求,简单搜一下就发现了 SVG 这个解决方案了。看了一眼,便决定就是它了!
SVG:可缩放矢量图形(英语:Scalable Vector Graphics,SVG)是一种基于可扩展标记语言(XML),用于描述二维矢量图形的图形格式。元素是 SVG 基本形状中最强大的一个,它不仅能创建其他基本形状,还能创建更多其他形状。

SVG Path 用 Android 绘制

这里先贴一下我找的北京市的 Path 数据:

123
<svg height="475" width="565">    <path id="Beijing" d="M421.139,189.75L420.782,186.894L419.95,184.989L425.045,182.863L425.426,181.18L424.23699999999997,176.413H422.56899999999996L415.90299999999996,172.964L412.21299999999997,176.654C412.21299999999997,176.654,411.08799999999997,183.239,411.381,181.534C411.66999999999996,179.82999999999998,407.688,185.822,407.688,185.822L407.094,190.108L407.926,192.371L412.807,191.537L416.5,192.608L418.284,190.941L421.139,189.75Z"/></svg>

这里要注意一点:SVG Path 里的数据都是在一个固定宽高的矩形里的坐标集合,所以当 Android View 与 SVG 的宽高不一致时,需要进行缩放。注意下面代码中的 scale 属性:

123456789101112131415161718192021222324252627282930
 * 计算地图边界 * 1.黑龙江是中国最东,最北的省份 * 2.新疆是中国最西的省份 * 3.海南是中国最南的省份 */private fun () {    val hljRF = RectF()    xPaths[HEILONGJIANG_CODE]?.computeBounds(hljRF, true)

    val hnRF = RectF()    xPaths[HAINAN_CODE]?.computeBounds(hnRF, true)

    mapWidth = hljRF.right    mapHeight = hnRF.bottom}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {    super.onMeasure(widthMeasureSpec, heightMeasureSpec)    val speSize = View.MeasureSpec.getSize(widthMeasureSpec)    scale = speSize / mapWidth    setMeasuredDimension(speSize, (speSize * mapHeight / mapWidth).toInt())}

override fun onDraw(canvas: Canvas) {    super.onDraw(canvas)    // 缩放画布    canvas.scale(scale, scale)    ...}

再来看到 Path 里有一些 M、L、Z 等字符,这些都是 Path 元素里的指令,后面紧跟的数字即是坐标。

M x,y 移动指令,映射 Path 中的 moveTo
L x,y 画直线指令,映射 Path 中的 lineTo
H x 画水平线指令,映射 Path 中的 lineTo,不过要使用上一个坐标的 y
V y 画垂直线指令,映射 Path 中的 lineTo,不过要使用上一个坐标的 x
C x1,y1,x2,y2,x,y 三次贝塞尔曲线指令,映射 Path 中的 cubicTo
S x2,y2,x,y 跟在 C 指令后面使用,用 C 指令的结束点做控制点,映射 cubicTo
Q x1,y1,x,y 二次贝塞尔曲线指令,映射 quadTo
T x,y 跟在 Q 指令后面使用,使用 Q 的 x,y 做控制点,映射 quadTo
Z path 关闭指令,映射 close

注意小写指令为使用相对坐标,下面 2 行 Path 得到的结果是一样的:

12
M421.139,189.75L420.782,186.894M421.139,189.75l-0.357,-2.856

基于 Android Path 实现不了小写指令的那种效果,所以只能使用大写指令。这里贴一下一个将 SVG Path 转成 Android Path 的工具类:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596大专栏  Android 绘制中国地图"line">979899100101102103104105
 * 仅限大写指令转换 */public class SvgPathToAndroidPath {    private int svgPathLenght = 0;    private String svgPath = null;    private int mIndex;    private List<Integer> cmdPositions = new ArrayList<>();

     * M x,y     * L x,y     * H x     * V y     * C x1,y1,x2,y2,x,y     * Q x1,y1,x,y     * S x2,y2,x,y     * T x,y     * */    public Path parser(String svgPath) {        this.svgPath = svgPath;        svgPathLenght = svgPath.length();        mIndex = 0;        Path lPath = new Path();        lPath.setFillType(Path.FillType.WINDING);        //记录最后一个操作点        PointF lastPoint = new PointF();        findCommand();        for (int i = 0; i < cmdPositions.size(); i++) {            Integer index = cmdPositions.get(i);            switch (svgPath.charAt(index)) {                case 'M': {                    String ps[] = findPoints(i);                    lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));                    lPath.moveTo(lastPoint.x, lastPoint.y);                }                break;                case 'L': {                    String ps[] = findPoints(i);                    lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));                    lPath.lineTo(lastPoint.x, lastPoint.y);                }                break;                case 'H': {//基于上个坐标在水平方向上划线,因此y轴不变                    String ps[] = findPoints(i);                    lastPoint.set(Float.parseFloat(ps[0]), lastPoint.y);                    lPath.lineTo(lastPoint.x, lastPoint.y);                }                break;                case 'V': {//基于上个坐标在水平方向上划线,因此x轴不变                    String ps[] = findPoints(i);                    lastPoint.set(lastPoint.x, Float.parseFloat(ps[0]));                    lPath.lineTo(lastPoint.x, lastPoint.y);                }                break;                case 'C': {//3次贝塞尔曲线                    String ps[] = findPoints(i);                    lastPoint.set(Float.parseFloat(ps[4]), Float.parseFloat(ps[5]));                    lPath.cubicTo(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3]), Float.parseFloat(ps[4]), Float.parseFloat(ps[5]));                }                break;                case 'S': {//一般S会跟在C或是S命令后面使用,用前一个点做起始控制点                    String ps[] = findPoints(i);                    lPath.cubicTo(lastPoint.x,lastPoint.y,Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));                    lastPoint.set(Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));                }                break;                case 'Q': {//二次贝塞尔曲线                    String ps[] = findPoints(i);                    lastPoint.set(Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));                    lPath.quadTo(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]), Float.parseFloat(ps[2]), Float.parseFloat(ps[3]));                }                break;                case 'T': {//T命令会跟在Q后面使用,用Q的结束点做起始点                    String ps[] = findPoints(i);                    lPath.quadTo(lastPoint.x,lastPoint.y,Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));                    lastPoint.set(Float.parseFloat(ps[0]), Float.parseFloat(ps[1]));                }                break;                break;                case 'Z': {//结束                    lPath.close();                }                break;            }        }        return lPath;    }

    private String[] findPoints(int cmdIndexInPosition) {        int cmdIndex = cmdPositions.get(cmdIndexInPosition);        String pointString = svgPath.substring(cmdIndex + 1, cmdPositions.get(cmdIndexInPosition + 1));        return pointString.split(",");    }

    private void findCommand() {        cmdPositions.clear();        while (mIndex < svgPathLenght) {            char c = svgPath.charAt(mIndex);            if ('A' <= c && c <= 'Z') {                cmdPositions.add(mIndex);            }            ++mIndex;        }    }}

实现

  1. 利用工具类获取每个省区的 Android Path,全部绘制一遍,即可绘制出全国地图(优化:高亮的省区这一步不绘制,避免绘制两次)。
  2. 针对高亮省区,调整画笔颜色再绘制一遍即可。
  3. 显示数量:这个目前没想到什么好方法,只能让设计师参照地图宽高比标出每个中心点的位置,就像这样:

    然后手动算出每个点的横纵坐标占比,再进行绘制。绘制数量计算坐标时仍要考虑 scale 属性。

参考

  1. Android 上绘制中国省份地图
  2. SVG 转 Android Canvas Path

原文地址:https://www.cnblogs.com/lijianming180/p/12227283.html

时间: 2024-11-09 00:57:24

Android 绘制中国地图的相关文章

利用d3.js绘制中国地图

d3.js是一个比较强的数据可视化js工具.利用它画了一幅中国地图,如下图所示: 源码如下: <!DOCTYPE html> <html> <head> <script type="text/javascript" src="d3.js"></script> <script type="text/javascript" src="d3.csv.js">&l

[Echarts可视化] 一.入门篇之简单绘制中国地图和贵州地区

最近发生了很多事情,去到了一个新环境学习.但是不论在哪里,我都需要不忘初心,坚持做自己喜欢的事情,在CSDN写博客.教学.爱娜.生活等等.        这篇文章主要是通过Echarts可视化介绍入门知识.中国地图和贵州地区各省份的数据分析.其中贵州地图才是这篇文章的核心内容.这是一篇入门文章,希望对您有所帮助,如果文章中存在不足之处,还请海涵~        官网地址:http://echarts.baidu.com/index.html 一. 入门介绍-第一张图 强烈推荐大家阅读官网的教程进

Javascript实战开发:教你使用raphael.js绘制中国地图

最近的数据统计项目中要用到中国地图,也就是在地图上动态的显示某个时间段某个省份地区的统计数据,我们不需要flash,仅仅依靠raphael.js以及SVG图像就可以完成地图的交互操作.在本文中,我给大家分享如何使用js来完成地图交互. 先简单介绍下raphael.js,raphael.js是一个很小的javascript库,它可以在网页中实现绘制各种矢量图.各类图表.以及图像裁剪.旋转.运动动画等等功能.此外raphael.js还跨浏览器兼容,而且还兼容老掉牙的IE6啊.raphael.js的官

基于D3JS绘制中国地图

仿照D3JS官网上的美国地图制作了一个中国版的地图. D3JS官网上的版本: http://bl.ocks.org/NPashaP/a74faf20b492ad377312 中国版的地图效果: 如要制作其他国家或其他具体省份-市区级的地图,只需替换掉javascript中的SVG地图数据即可.地图上每个省份的统计数据是随机生成的,可根据需求导入实际业务数据. 分享一个SVG地图数据的网站:https://commons.wikimedia.org/wiki/Category:SVG_maps_o

vue中使用echarts来绘制中国地图,NuxtJS制作疫情地图,内有详细注释,我就懒得解释了,vue cli制作疫情地图 代码略有不同哦~~~

我的代码自我感觉----注释一向十分详细,就不用过多解释都是什么了~~ 因为最近疫情期间在家实在是没事干,想找点事,就练手了个小demo 首先上 NuxtJs版本代码,这里面 export default { mode: 'universal', /* ** Headers of the page */ head: { title: process.env.npm_package_name || '', meta: [ { charset: 'utf-8' }, { name: 'viewpor

PHP+Mysql+jQuery实现中国地图区域数据统计(raphael.js)

使用过百度统计或者cnzz统计的童鞋应该知道,后台有一个地图统计,不同访问量的省份显示的颜色也不一样,今天我将带领大家开发一个这样的案例.上一篇<使用raphael.js绘制中国地图>文章中,我给大家介绍了如何使用raphael.js绘制中国地图,今天我要给大家介绍在实际应用中,如何把数据载入到地图中.本文结合实例,使用PHP+Mysql+jQuery实现中国地图各省份数据统计效果. 本例以统计某产品在各省份的活跃用户数为背景,数据来源于mysql数据库,根据各省份的活跃用户数,分成不同等级,

PHP+Mysql+jQuery实现中国地图区域数据统计

使用过百度统计或者cnzz统计的童鞋应该知道,后台有一个地图统计,不同访问量的省份显示的颜色也不一样,今天我将带领大家开发一个这样的案例.上一篇<使用raphael.js绘制中国地图>文章中,我给大家介绍了如何使用raphael.js绘制中国地图,今天我要给大家介绍在实际应用中,如何把数据载入到地图中.本文结合实例,使用PHP+Mysql+jQuery实现中国地图各省份数据统计效果. 本例以统计某产品在各省份的活跃用户数为背景,数据来源于mysql数据库,根据各省份的活跃用户数,分成不同等级,

R语言与中国地图

一个合格的数据分析师必定能够从大量数据中洞察需求,并把数据和需求展示给团队人员,获得资源支持.数据可视化技术对于产品经理来说也是相当重要的一项技能,通过数据可视化技术我们可以把数据分析结果以"人性化"."友好"的方式反馈给各个合作方,使接收者能够较为愉快地了解整个行业.产品或其他方面的相关发展状况,从而有利于项目的顺利进行.在调研过程中我们经常会遇到与中国地区区域有关的数据显示问题,今天笔者在此讨论一下如何通过R软件来绘制中国地图及与之相关的类似于热力图/密度图等相

matlab利用m_map工具包画中国地图及散点云图

开始之前需要准备好malab,中国地图shp文件,m_map工具包. 中国地图shp文件可以在下面的链接中下载: https://gadm.org/download_country_v3.html 本文借鉴了下面链接中教程,该方法为matlab自带的画图工具包绘制方法,在我电脑上geoshow命令运行时间特别长,不知道为什么,感兴趣的同学可以试试: https://my.oschina.net/chengwei426/blog/674280 利用m_map绘制中国地图,代码如下: close a