×

使用交互式地图协调对COVID-19的响应

消耗积分:0 | 格式:zip | 大小:0.03 MB | 2022-11-28

张霞

分享资料个

描述

在大流行期间管理社区响应需要提供信息和教育的工具。几个世纪以来,地图已经做到了这一点,我们现在拥有了惊人的数字工具,只需点击几个按钮即可完成。以下是我们的社区如何应对危机,并希望其他人可以利用这些工具来构建自己的应用程序,以帮助通知和动员他们的社区。

第 1 阶段 - 启动并运行工具。快速地!

那是 2020 年 3 月,新闻似乎每天都在恶化。COVID-19 出现在我们的社区中,人们正在寻求行动。启动了一个项目,允许来自整个社区的志愿者“采用他们居住的街区”,并在需要的时候帮助他们的邻居。一个名为 ForRichmond 的非营利组织开始了这项工作,它跨越了弗吉尼亚州中部的地理边界。由于我们也被隔离,该项目将需要依赖数字工具。那是我自愿提供帮助的地方。

鉴于这种巨大的影响,我建议让应用程序尽快启动并运行。虽然我们发现了一些很棒的应用程序可供建模,但我们认为开始这项工作的时间非常宝贵。所以我们从 MVP 模型开始努力,从一个基本的应用程序开始运行。

第 1 步 - 建立志愿者表格

我们合作的非营利组织已经拥有一个由 Squarespace 托管的基本静态网站。能够获得可以用于注册志愿者并直接加载到 g-sheet 的表单页面设置。该页面随后被链接到他们的主网站,并在多个社交媒体渠道上共享。

pYYBAGOAcYCAbfHsAABIFADckq4648.png
使用 G-Sheets 的在线 Web 表单
 

第 2 步 - 构建地图

这家非营利组织的创始人已经听说过费城的一个类似项目,并且能够将我与该项目的负责人联系起来。这是基于开放街道地图。根据研究,我发现还有一个名为 Mapbox 的商业产品,它构建了添加到它的工具和服务。我注册了该服务,并能够使用免费套餐快速构建地图。每当有人想通过在网站上填写表格来“采用区块”时,我们就可以在地图上绘制该区块以供参考。然后将该地图集成到 Squarespace 网站上托管的网页中,以供其他人查看采用了多少块的进度。

第 3 步(进行中)- 手动维护地图

当志愿者在表格中注册时,需要有人从 G-sheet 中取出数据,然后进入 Mapbox 的 Studio 界面并绘制志愿服务的街道。这需要通过文本和电子邮件进行一些协调。进行更改后,将发布新版本的地图,然后将其反映在站点中。这有点手动,但很容易启动和运行,因此达到了 MVP 的目标。这是一个供参考的流程。

poYBAGOAcYKAH9_pAAD_epHw7Pg594.png

第 2 阶段 - 数字地图

在启动并运行 MVP 后,我开始着手研究下一个版本。两个关键限制是(1)在某人注册一个区块的时间和由于手动输入而出现的时间之间存在延迟,以及(2)试图根据文本输入字段理解志愿者的意图。第二点与地理有关。在经典的市中心街道网格中,很容易描述什么是街区。例如,Main Street 的 3300 街区,或 K & L Street 之间的 Centre Ave。在郊区,街区更加模糊,因为它们不遵循网格,而是由弯曲的道路和死胡同组成。要解决此问题,需要更改界面,并预定义用户可以明确选择的块。这是同样使用 Mapbox 构建的新版本的视图,以及它是如何构建的。

poYBAGOAcYiAF2t1AAK87wBx4HQ497.png
采用街区的互动版(采用蓝色街道)
 

第 1 步 - 获取数据

大里士满都会区有超过一百万人居住在其中,分布在数百平方英里的土地上。手动绘制所有这些数据需要数年时间,但幸运的是,这些信息已经存在——只是找出位置的问题。我们的研究表明,当地市政当局的 GIS 部门已经为其他目的构建了数字地图,并将其作为公共数据发布。这是里士满市的其中一个站点的示例。

poYBAGOAcY6AUozTAAv79mRGWv4903.png
来自市政当局的 GIS 街道数据
 

https://richmond-geo-hub-cor.hub.arcgis.com/maps/edit?content=cor%3A%3Acenterlines

可以下载这些数据,然后在其他项目中使用。我下载了我们想要获取街区级别数据的我们地区所有四个城市的数据,因此这使得我们能够推进构建所有可以采用的住宅街区的区域地图的新方法。

第 2 步 - 将街道数据转换为 GeoJSON

虽然我能够获得数据版本,但一致的主题是数据集的格式与 Mapbox 中所需的格式不同。KML 数据是 GIS 部门常用的格式,但需要的是 geojson。这需要转换。这些下载的文件也很大——在 10-100MB 范围内,考虑到我们最终需要使用 API 来加载这些数据,我需要保持大小可控。我使用在线实用程序将 KML 结构转换为更常见的 csv 格式(参见下面的链接),然后创建多个分片,将文件分解成更易于管理的文件(每个分片大约 5-10MB)。

https://www.convertcsv.com/xml-to-csv.htm

第 3 步 - 过滤和丰富数据

接下来是减少数据集以获取我们需要的信息的步骤。这将有助于提高应用程序的可用性和性能。这个应用程序只需要住宅街道。我们从市政当局收到的地图用于街道维护,因此包括高速公路和立交桥之类的东西都被删除了。这是通过在 AWS 中编写为 lambda 函数的数据过滤过程来完成的,该函数可以处理暂存在 S3 存储桶中的数据。

交互式地图还需要为每个块添加属性。这使得在 Mapbox 中更容易渲染地图。为了实现这一点,有一个布尔值指示该块是否已被采用,以及谁采用了它。这些属性在上一步中添加到过滤后的数据中,继续使用 json 格式。

第 4 步 - 将数据加载到 Mapbox

要创建将由浏览器呈现的地图,我需要将数据导入 Mapbox 到唯一的数据集中。这可以通过 API 或 Studio 界面来完成,我选择了后者。一次可以处理多大的上传文件是有限制的,因此文件大小需要实用。这是针对上一步中处理的所有四个数据集完成的。

第 5 步 - 构建地图数据视图

将数据加载到 Mapbox 后,需要创建反映不同街道数据组的瓦片集。这些瓦片集是浏览器在地图中呈现的用户可以与之交互的图层,并且也在浏览器代码中的 SDK 中引用。每个图层都有自己的属性——颜色、字体大小、线宽等。这一切都在 Mapbox Studio 中完成,称为样式。

第 6 步 - 添加区域图层以使地图可用

我需要添加的一个意外范围是地图的汇总层,我们称之为区域层。前面步骤中的基础数据集总共有 10 万个城市街区(特征),所以当试图一次显示它们时,用户体验很糟糕,因为它只是变成了一个单一颜色的大块,因为所有街道都混合在一起。

为了解决这个问题,我创建了一个区域的摘要视图,在尝试获取整个内容时可用。这是通过使用 Mapbox studio 创建一个数据集来概述这些区域的。然后地图的起点在这个级别被缩小,显示更广泛的区域。

pYYBAGOAcZCAeSiSAAE1YdT5Blo774.png
地图的顶级视图
 

网页中嵌入了 javascript,允许用户单击其中一个区域,然后放大该区域。从那里可以单击更用户友好的地图版本。

第 7 步 - 构建可以更新地图的 API

Mapbox 已经有可以管理更新的 API。要调用 API,将需要要更新的功能的唯一标识符。鉴于 Mapbox 设置了唯一标识符,需要在加载地图后从 Mapbox 中提取地图,并在调用 API 之前构建一个用作包装器的查找实用程序,以应用唯一标识符。这是用 NodeJS 编写的,并托管在 AWS 中。

8 步 - 将 API 集成到数字地图中

一旦我们有 API 工作,然后将其集成到我们的。我构建了一个简单的 AngularJS Web 应用程序,然后调用 API 将块分配给单个志愿者。这使多个人能够直接更新地图,从而使项目能够扩大规模。

poYBAGOAcZOAZKUXAAAyK_ezgx0047.png
更新地图的管理工具
 

 


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

评论(0)
发评论

下载排行榜

全部0条评论

快来发表一下你的评论吧 !

'+ '

'+ '

'+ ''+ '
'+ ''+ ''+ '
'+ ''+ '' ); $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code ==5){ $(pop_this).attr('href',"/login/index.html"); return false } if(data.code == 2){ //跳转到VIP升级页面 window.location.href="//m.obk20.com/vip/index?aid=" + webid return false } //是会员 if (data.code > 0) { $('body').append(htmlSetNormalDownload); var getWidth=$("#poplayer").width(); $("#poplayer").css("margin-left","-"+getWidth/2+"px"); $('#tips').html(data.msg) $('.download_confirm').click(function(){ $('#dialog').remove(); }) } else { var down_url = $('#vipdownload').attr('data-url'); isBindAnalysisForm(pop_this, down_url, 1) } }); }); //是否开通VIP $.get('/article/vipdownload/aid/'+webid,function(data){ if(data.code == 2 || data.code ==5){ //跳转到VIP升级页面 $('#vipdownload>span').text("开通VIP 免费下载") return false }else{ // 待续费 if(data.code == 3) { vipExpiredInfo.ifVipExpired = true vipExpiredInfo.vipExpiredDate = data.data.endoftime } $('#vipdownload .icon-vip-tips').remove() $('#vipdownload>span').text("VIP免积分下载") } }); }).on("click",".download_cancel",function(){ $('#dialog').remove(); }) var setWeixinShare={};//定义默认的微信分享信息,页面如果要自定义分享,直接更改此变量即可 if(window.navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){ var d={ title:'使用交互式地图协调对COVID-19的响应',//标题 desc:$('[name=description]').attr("content"), //描述 imgUrl:'https://'+location.host+'/static/images/ele-logo.png',// 分享图标,默认是logo link:'',//链接 type:'',// 分享类型,music、video或link,不填默认为link dataUrl:'',//如果type是music或video,则要提供数据链接,默认为空 success:'', // 用户确认分享后执行的回调函数 cancel:''// 用户取消分享后执行的回调函数 } setWeixinShare=$.extend(d,setWeixinShare); $.ajax({ url:"//www.obk20.com/app/wechat/index.php?s=Home/ShareConfig/index", data:"share_url="+encodeURIComponent(location.href)+"&format=jsonp&domain=m", type:'get', dataType:'jsonp', success:function(res){ if(res.status!="successed"){ return false; } $.getScript('https://res.wx.qq.com/open/js/jweixin-1.0.0.js',function(result,status){ if(status!="success"){ return false; } var getWxCfg=res.data; wx.config({ //debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。 appId:getWxCfg.appId, // 必填,公众号的唯一标识 timestamp:getWxCfg.timestamp, // 必填,生成签名的时间戳 nonceStr:getWxCfg.nonceStr, // 必填,生成签名的随机串 signature:getWxCfg.signature,// 必填,签名,见附录1 jsApiList:['onMenuShareTimeline','onMenuShareAppMessage','onMenuShareQQ','onMenuShareWeibo','onMenuShareQZone'] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2 }); wx.ready(function(){ //获取“分享到朋友圈”按钮点击状态及自定义分享内容接口 wx.onMenuShareTimeline({ title: setWeixinShare.title, // 分享标题 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享给朋友”按钮点击状态及自定义分享内容接口 wx.onMenuShareAppMessage({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 type: setWeixinShare.type, // 分享类型,music、video或link,不填默认为link dataUrl: setWeixinShare.dataUrl, // 如果type是music或video,则要提供数据链接,默认为空 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ”按钮点击状态及自定义分享内容接口 wx.onMenuShareQQ({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到腾讯微博”按钮点击状态及自定义分享内容接口 wx.onMenuShareWeibo({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); //获取“分享到QQ空间”按钮点击状态及自定义分享内容接口 wx.onMenuShareQZone({ title: setWeixinShare.title, // 分享标题 desc: setWeixinShare.desc, // 分享描述 link: setWeixinShare.link, // 分享链接 imgUrl: setWeixinShare.imgUrl, // 分享图标 success: function () { setWeixinShare.success; // 用户确认分享后执行的回调函数 }, cancel: function () { setWeixinShare.cancel; // 用户取消分享后执行的回调函数 } }); }); }); } }); } function openX_ad(posterid, htmlid, width, height) { if ($(htmlid).length > 0) { var randomnumber = Math.random(); var now_url = encodeURIComponent(window.location.href); var ga = document.createElement('iframe'); ga.src = 'https://www1.elecfans.com/www/delivery/myafr.php?target=_blank&cb=' + randomnumber + '&zoneid=' + posterid+'&prefer='+now_url; ga.width = width; ga.height = height; ga.frameBorder = 0; ga.scrolling = 'no'; var s = $(htmlid).append(ga); } } openX_ad(828, '#berry-300', 300, 250);