【实战篇】39 # 如何实现世界地图的新冠肺炎疫情可视化?

news2025/2/26 1:56:23

说明

【跟月影学可视化】学习笔记。

世界地图新冠肺炎疫情可视化

下面将实现世界地图新冠肺炎疫情可视化。数据用的是从 2020 年 1 月 22 日到 3 月 19 日这些天的新冠肺炎疫情进展。效果类似下图:https://covid19.who.int/

在这里插入图片描述

步骤一:准备世界地图可视化需要的数据

先需要准备新冠肺炎的官方数据( https://www.who.int/ )

这里直接使用大佬整理好的:https://github.com/akira-cn/graphics/blob/master/covid-vis/assets/data/covid-data.json

另外需要准备地图的 JSON 文件:

  • GeoJSON:基础格式,它包含了描述地图地理信息的坐标数据。
  • TopoJSON:GeoJSON 格式经过压缩之后得到的,它通过对坐标建立索引来减少冗余,能够大大减少 JSON 文件的体积。但在使用的时候还需要对它解压,把它变成 GeoJSON 数据,可以使用https://github.com/topojson/topojson这个JavaScript 模块来处理 TopoJSON 数据。

什么是 geojson 数据可以去看我下面这篇文章,这里不多做介绍。

  • 怎么获取echarts需要的geoJson数据去渲染地图:以广州市白云区24镇街为例(内附资源)

推荐阅读:

  • GeoJSON 规范
  • TopoJSON 规范
  • GEOJSON标准格式学习

这里直接使用大佬整理好 geojson 以及 topojson 的 json 数据:

  • https://github.com/akira-cn/graphics/blob/master/covid-vis/assets/data/world-geojson.json
  • https://github.com/akira-cn/graphics/blob/master/covid-vis/assets/data/world-topojson.json

步骤二:利用 GeoJSON 数据绘制地图

下面使用 Canvas2D 来绘制地图,先了解一下墨卡托投影

什么是墨卡托投影?

墨卡托投影,是正轴等角圆柱投影。由荷兰地图学家墨卡托(G.Mercator)于1569年创立。假想一个与地轴方向一致的圆柱切或割于地球,按等角条件,将经纬网投影到圆柱面上,将圆柱面展为平面后,即得本投影。

在这里插入图片描述

下面利用墨卡托投影将 GeoJSON 数据中,coordinates 属性里的经纬度信息转换成画布坐标。

经纬度投影示意图:

  • longitude:经度,经度范围是 360 度
  • latitude:纬度,维度范围是 180 度
  • width:Canvas 的宽度
  • height:Canvas 的高度

在这里插入图片描述

换算公式如下:

x = width * (180 + longitude) / 360;
y = height * (1.0 - (90 + latitude) / 180); // Canvas坐标系y轴朝下

具体实现如下:

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>利用 GeoJSON 数据绘制地图</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <canvas width="1024" height="512"></canvas>
        <script>
            const width = 1024;
            const height = 512;

            // 投影函数
            function projection([longitude, latitude]) {
                const x = (width * (180 + longitude)) / 360;
                const y = height * (1.0 - (90 + latitude) / 180); // Canvas坐标系y轴朝下
                return [x, y];
            }
            // 绘制
            function drawPoints(ctx, points) {
                ctx.beginPath();
                ctx.moveTo(...points[0]);
                for (let i = 1; i < points.length; i++) {
                    ctx.lineTo(...points[i]);
                }
                ctx.fill();
            }

            const canvas = document.querySelector("canvas");
            const ctx = canvas.getContext("2d");
            ctx.fillStyle = "salmon";

            (async function () {
                // 用 fetch 来读取 JSON 文件
                const worldData = await (await fetch("./data/world-geojson.json")).json();
                const features = worldData.features;
                // 遍历数据
                features.forEach(({ geometry }) => {
                    if (geometry.type === "MultiPolygon") {
                        const coordinates = geometry.coordinates;
                        if (coordinates) {
                            coordinates.forEach((contours) => {
                                contours.forEach((path) => {
                                    // 进行投影转换
                                    const points = path.map(projection);
                                    // 进行绘制
                                    drawPoints(ctx, points);
                                });
                            });
                        }
                    } else if (geometry.type === "Polygon") {
                        const contours = geometry.coordinates;
                        contours.forEach((path) => {
                            // 进行投影转换
                            const points = path.map(projection);
                            // 进行绘制
                            drawPoints(ctx, points);
                        });
                    }
                });
            })();
        </script>
    </body>
</html>

在这里插入图片描述

步骤三:将疫情的 JSON 数据整合进地图数据里

地图数据中,properties 只有一个 name 属性,对应着不同国家的名字。

在这里插入图片描述
疫情数据中的 contry 属性和 GeoJSON 数据里面的国家名称是一一对应的。
在这里插入图片描述

下面建立一个数据映射关系,将疫情数据中的每个国家的疫情数据直接写入到 GeoJSON 数据的 properties 字段里面。这里我们使用 topojson 处理。

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">

<head>
	<meta charset="UTF-8" />
	<meta http-equiv="X-UA-Compatible" content="IE=edge" />
	<meta name="viewport" content="width=device-width, initial-scale=1.0" />
	<title>将疫情的 JSON 数据整合进地图数据里</title>
</head>

<body>
	<script src="https://lib.baomitu.com/topojson/3.0.2/topojson.min.js"></script>
	<script>
		// 数据映射函数
		function mapDataToCountries(geoData, covidData) {
			const covidDataMap = {};
			covidData.dailyReports.forEach((d) => {
				const date = d.updatedDate;
				const countries = d.countries;
				countries.forEach((country) => {
					const name = country.country;
					covidDataMap[name] = covidDataMap[name] || {};
					covidDataMap[name][date] = country;
				});
			});
			geoData.features.forEach((d) => {
				const name = d.properties.name;
				d.properties.covid = covidDataMap[name];
			});
		}

		(async function () {
			// 使用 topojson 数据
			const worldData = await (await fetch('./data/world-topojson.json')).json();
			const countries = topojson.feature(worldData, worldData.objects.countries);

			const covidData = await (await fetch('./data/covid-data.json')).json();
			mapDataToCountries(countries, covidData);

			console.log("将疫情的 JSON 数据整合进地图数据里--->", countries)
		})();
	</script>
</body>

</html>

整合数据如下:可以看到疫情数据已经整进去地图里面了
在这里插入图片描述

步骤四:将数据与地图结合

这里用7个不同的颜色来表示疫情的严重程度,填充地图,确诊人数越多的区域颜色越红。

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>世界地图新冠肺炎疫情可视化</title>
        <style>
            canvas {
                border: 1px dashed salmon;
            }
        </style>
    </head>
    <body>
        <div id="dateInfo"></div>
        <canvas width="1200" height="600"></canvas>
        <script src="https://lib.baomitu.com/topojson/3.0.2/topojson.min.js"></script>
        <script>
            const width = 1200;
            const height = 600;

            // 投影函数
            function projection([longitude, latitude]) {
                const x = (width * (180 + longitude)) / 360;
                const y = height * (1.0 - (90 + latitude) / 180); // Canvas坐标系y轴朝下
                return [x, y];
            }
            // 绘制
            function drawPoints(ctx, points) {
                ctx.beginPath();
                ctx.moveTo(...points[0]);
                for (let i = 1; i < points.length; i++) {
                    ctx.lineTo(...points[i]);
                }
                ctx.fill();
            }

            // 颜色映射
            function mapColor(confirmed) {
                // 无人感染
                if (!confirmed) {
                    return "rgb(80, 255, 80)";
                }
                // 小于 10
                if (confirmed < 10) {
                    return "rgb(250, 247, 171)";
                }
                // 感染人数 10~99 人
                if (confirmed < 100) {
                    return "rgb(255, 186, 66)";
                }
                // 感染人数 100~499 人
                if (confirmed < 500) {
                    return "rgb(234, 110, 41)";
                }
                // 感染人数 500~999 人
                if (confirmed < 1000) {
                    return "rgb(224, 81, 57)";
                }
                // 感人人数 1000~9999 人
                if (confirmed < 10000) {
                    return "rgb(192, 50, 39)";
                }
                // 感染人数超 10000 人
                return "rgb(151, 32, 19)";
            }

            // 日期格式
            function formatDate(date) {
                const year = date.getFullYear();
                let month = date.getMonth() + 1;
                month = month > 9 ? month : `0${month}`;
                let day = date.getDate();
                day = day > 9 ? day : `0${day}`;
                return `${year}-${month}-${day}`;
            }

            // 数据映射函数
            function mapDataToCountries(geoData, covidData) {
                const covidDataMap = {};
                covidData.dailyReports.forEach((d) => {
                    const date = d.updatedDate;
                    const countries = d.countries;
                    countries.forEach((country) => {
                        const name = country.country;
                        covidDataMap[name] = covidDataMap[name] || {};
                        covidDataMap[name][date] = country;
                    });
                });
                geoData.features.forEach((d) => {
                    const name = d.properties.name;
                    d.properties.covid = covidDataMap[name];
                });
            }

            // 绘制地图
            function drawMap(ctx, countries, date) {
                date = formatDate(date);
                dateInfo.innerHTML = date;
                countries.features.forEach(({ geometry, properties }) => {
                    const covid = properties.covid
                        ? properties.covid[date]
                        : null;
                    let confirmed;
                    if (covid) {
                        confirmed = covid.confirmed;
                        properties.lastConfirmed = confirmed;
                    } else if (properties.lastConfirmed) {
                        confirmed = properties.lastConfirmed;
                    }
                    ctx.fillStyle = mapColor(confirmed);
                    if (geometry.type === "MultiPolygon") {
                        const coordinates = geometry.coordinates;
                        if (coordinates) {
                            coordinates.forEach((contours) => {
                                contours.forEach((path) => {
                                    const points = path.map(projection);
                                    drawPoints(ctx, points);
                                });
                            });
                        }
                    } else if (geometry.type === "Polygon") {
                        const contours = geometry.coordinates;
                        contours.forEach((path) => {
                            const points = path.map(projection);
                            drawPoints(ctx, points);
                        });
                    }
                });
            }

            const canvas = document.querySelector("canvas");
            const ctx = canvas.getContext("2d");

            (async function () {
                // 使用 topojson 数据
                const worldData = await (
                    await fetch("./data/world-topojson.json")
                ).json();
                const countries = topojson.feature(
                    worldData,
                    worldData.objects.countries
                );
                // 疫情数据
                const covidData = await (
                    await fetch("./data/covid-data.json")
                ).json();
                mapDataToCountries(countries, covidData);

                // 开始日期
                const startDate = new Date("2020/01/22");
                let i = 0;
                // 自动绘制
                const timer = setInterval(() => {
                    const date = new Date(startDate.getTime() + 86400000 * ++i);
                    drawMap(ctx, countries, date);
                    if (date.getTime() + 86400000 > Date.now()) {
                        clearInterval(timer);
                    }
                }, 100);
                drawMap(ctx, countries, startDate);
            })();
        </script>
    </body>
</html>

效果实现如下:

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/151384.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[ 数据结构 ] 迪杰斯特拉算法(最短路径问题)

0 最短路径问题 战争时期&#xff0c;胜利乡有 7 个村庄(A, B, C, D, E, F, G) &#xff0c;现在有六个邮差&#xff0c;从 G 点出发&#xff0c;需要分别把邮件分别送到 A, B, C , D, E, F 六个村庄各个村庄的距离用边线表示(权) &#xff0c;比如 A – B 距离 5 公里问&#…

不透明度和填充的区别

提纲 1、不透明度和填充的相同之处 2、不透明度和填充的不同之处 3、从字面意思理解不透明度和填充 1、不透明度和填充的相同之处 在初学PS时&#xff0c;一定对“不透明度”和“填充”非常迷惑&#xff0c;它们在图层面板的这个位置 这篇就来详细聊聊这两个滑块&#xff0…

SSR是什么?Vue中怎么实现?

一、是什么 Server-Side Rendering 称其为SSR&#xff0c;意为服务端渲染 指由服务侧完成页面的 HTML 结构拼接的页面处理技术&#xff0c;发送到浏览器&#xff0c;然后为其绑定状态与事件&#xff0c;成为完全可交互页面的过程 先来看看Web3个阶段的发展史&#xff1a; 传…

Dart基础

一、dart概述 Dart简介 Dart 是谷歌开发的&#xff0c;类型安全的&#xff0c;面向对象的编程语言&#xff0c;被应用于Web、服务器、移动应用和物联网等领域。Dart 诞生于 2011 年 10 月 10 日Dart简单易学(类似TypeScript, 是强类型的语言)运行方式 原生虚拟机(Dart 代码可…

从执行者到管理者的角色转变

前言 在职场中因为岗位职责的差异&#xff0c;我们通过被分为两种角色&#xff0c;即执行者和管理者&#xff1b;大部分管理者也是从执行者晋升来的。 因为思维的惯性&#xff0c;导致我们会很容易带着执行者的意识去做管理&#xff0c;遇到问题就会想着自己动手去做&#xff0…

智慧防雷+智能防雷的综合应用方案

随着物联网时代的到来&#xff0c;信息共享成为社会运转的动力&#xff0c;伴随着现代建筑、交通、医疗以及工业制造等行业的智能化&#xff0c;大量微电子网络、自动化设备、计算机等投入使用&#xff0c;其集成度高、工作电压小、工作电流低、绝缘强度低、耐过电压和过电流能…

HDMI接口电路设计

HDMI是一个能传输高清视频和多声道音频的接口&#xff0c;常用的有TYPE A&#xff0c;TYPEC&#xff0c;和TYPE D的HDMI&#xff0c;最常用的是这种TYPE A的HDMI接口&#xff0c;这个是母座HDMI TYPE A插座的引脚信号定义大家可以看下&#xff0c;总共包含19个引脚。其中TMDS d…

Hudi的核心概念 —— 索引(Index)

文章目录原理索引选项全局索引与非全局索引索引的选择策略原理 Hudi 通过索引机制提供高效的 upserts&#xff0c;具体是将给定的 hoodie key(record key&#xff08;记录键&#xff09; partition path)与文件 id&#xff08;文件组&#xff09;建立唯一映射。这种映射关系&…

Axure原型模板大全(100多款超高清高保真原型),APP+WEB精美版,绝对大神出品

LIB012 - Axure交互设计常用素材组件包(界面模型、流程图素材)LIB001 - Axure交互原型Web元件库完整版LIB001v2 - Axure WEB前后端交互原型通用元件库 v2LIB003 - Axure交互原型移动端元件库完整版LIB005 - Axure手机移动端交互原型通用元件库 v2LIB006 - Axure IPAD移动端交互…

Java工作流详解(附6大工作流框架对比)

目录1.什么是工作流2.工作流应用场景3.工作流实现方式4.有哪些工作流框架什么是工作流工作流(Worklow)工作流是对工作流程及其各操作步骤之间业务规则的抽象、概括描述。工作流建模&#xff0c;即将工作流程中的工作如前后组织在一起的逻辑和规则&#xff0c;在计算机中以恰当的…

C语言缓冲区与重定向

目录 什么是缓冲区&#xff1f; 刷新策略 模拟实现重定向 标准输出和标准错误有什么区别&#xff1f; 上文提到关闭1号文件&#xff08;标准输出文件&#xff09;&#xff0c;根据文件描述符分配规则&#xff0c;再打开的文件的描述符就是1&#xff0c;看以下代码&#xf…

差分数组详解

目录1.概述2.代码实现3.应用本文参考&#xff1a; LABULADONG 的算法网站 1.概述 &#xff08;1&#xff09;差分数组的思想与前缀和算法的非常近似&#xff08;有关前置和算法的具体细节可以参考前缀和算法这篇文章&#xff09;&#xff0c;其主要适用于频繁地对原始数组的某…

为民服务 智慧政务数据可视化大屏一体化系统

为顺应全球发展趋势&#xff0c;以及我国当前经济社会发展进步的需要&#xff0c;加快政府服务信息化、数字化建设紧跟国际步伐的同时也需要开拓引领。今天给大家分享一个基于 数维图 的 SovitChart编辑器 构建大屏可视化场景的案例——智慧政务数据可视化大屏一体化平台。建设…

RabbitMQ 总结二(MQ原理 通信方式 消息应答机制)

目录 MQ的构成 生产者 交换机 队列 消费者 通信方式 Producer -> Broker (包含Exchange) Exchange -> Binding -> Queue -> Consumer 消息应答 为什么引入消息应答 消息自动重新入队 如何进行消息应答 案例Demo MQ的构成 生产者 消费者 交换机和队列…

【学习笔记之Linux】工具之yum

yum是Linux的软件包管理器。   什么是软件包&#xff1f;在Linux中安装软件&#xff0c;可以通过下载程序源码&#xff0c;然后编译得到可执行程序。但是这样非常麻烦&#xff0c;于是就有人把常用的软件编译好之后做成软件包&#xff0c;然后把软件包放在一个服务器上。   …

redis常见面试题

redis常见面试题 redis集群转载于&#xff1a;https://blog.csdn.net/sun_lm/article/details/123467103 redis的几个数据结构的应用场景借鉴于&#xff1a;https://blog.csdn.net/weixin_51299478/article/details/125204374 1. redis的作用 redis的作用主要就是两个&…

数据结构——串

串又称字符串&#xff0c;是由零个或多个字符组成的有限序列&#xff0c;是一种特殊的线性表。由串中若干个连续字符组成的子序列称为子串。 利用字符数组或字符指针表示串&#xff1a; char str1[] { a,b,c,d,\0 }; char str2[] "abcdef"; char* str3 str1; 上…

Java设计模式之单例模式

这一篇&#xff0c;我们来介绍下设计模式最简单的一个模式&#xff0c;单例模式。 二、释义以及实战 2.1 单例模式的定义 单例模式&#xff0c;英文&#xff1a;Singleton Pattern,英文解释&#xff1a;Ensure a class has only instance,and provide a global point of acce…

黑马2022新版SSM框架教程(SpringMVC_day02)

SpringMVC_day02 文章目录SpringMVC_day021&#xff0c;SSM整合1.1 流程分析1.2 整合配置步骤1&#xff1a;创建Maven的web项目步骤2:添加依赖步骤3:创建项目包结构步骤4:创建SpringConfig配置类步骤5:创建JdbcConfig配置类步骤6:创建MybatisConfig配置类步骤7:创建jdbc.proper…

Vue(十二)

1. TodoList案例自定义事件 //App.vue <template><div id"root"><div class"todo-container"><div class"todo-wrap"><!-- addTodo添加自定义事件 --><MyHeader addTodo"addTodo"/><MyList …