精华内容
下载资源
问答
  • 本文基于百度地图API和...(附数据和源码)一文的第二,百度地图开放平台就发表了篇【教程】如何快速构建一张周边疫情地图,一度让我同事以为是我投的稿。 罗孚确实没投稿,可能是百度地图不爽,你用高德地图API...

    本文基于百度地图API和fangkai提供的疫情数据API制作,是一份真实并实时的疫情场所分布地图,演示地址:https://rovertang.com/map/ncov/

    花絮

    上次发表了如何制作一份疫情场所分布地图?(附数据和源码)一文的第二天,百度地图开放平台就发表了一篇【教程】如何快速构建一张周边疫情地图,一度让我同事以为是我投的稿。

    罗孚确实没投稿,可能是百度地图不爽,你用高德地图API做了份,那我也公开一份用百度地图API的地图吧,还附送了源码和模拟数据

    而高德地图API,至今也没有任何信息给到罗孚,反倒是腾讯地图,同样也是第二天,几乎同百度差不多的时间,给罗孚发了邮件,没搞懂百年不给罗孚发邮件的腾讯位置中心,怎么突然会有我的邮箱地址。

    腾讯位置中心发来的邮件主题“腾讯位置服务抗击疫情保障计划:免费配额提升至3倍!”,一听就是一个非常有吸引力的标题,点进去一看,还提供了“紧急上线疫情小区地图,开放给所有开发者接入”,不仅营销做的好,还实用啊,赶紧申请。

    本以为腾讯地图开放平台提供了数据API,结果实际仅仅是一个H5的页面(没搞懂这个链接给我开放了啥),再发邮件咨询数据API的事宜,就没人回复了,一度让罗孚想去扒腾讯的API数据。

    罗孚“将百度地图API和腾讯数据杂交成一份真实的疫情场所地图”的想法泡汤了,于是去GitHub搜索了一圈,虽然大部分都是扒丁香园的疫情数量数据,但也找到了一份疫情小区分布地图,是安徽大学的一名学生制作的,还开放了API(点赞点赞),最终罗孚用此数据API同百度地图API杂交成功。

    效果预览

    详情信息、城市点位、地图查询

    功能说明

    显示疫情小区

    当地图层级大于9时,显示疫情小区位置,疫情小区位置数据由数据API根据城市名获得,城市名为地图中心点所在的城市名,当移动地图后地图中心所对应的城市名发生变化,则加载新城市名对应的疫情小区位置数据。

    显示疫情小区详情

    点击疫情小区位置marker,从底部弹出疫情位置的详细信息,详细信息内容来自数据API,包括位置名称、省、市、县以及数据来源和来源链接。

    显示省市位置

    当地图层级在6到10之间,则显示城市点位数据,当地图层级小于7时,显示省份点位数据,此数据罗孚整理,增加了省、市名称。

    自定位和地图查询

    自定位功能用于显示自身位置信息,需要允许定位才能显示,如果定位失败则显示北京市地图和数据。

    地图查询功能,输入关键词后,自动显示最佳结果,点击某一结果可以将地图定位到该结果并显示周边疫情信息。

    技术点

    由于源码不是罗孚所写,原文也提供了概要的源码说明,所以罗孚在此仅介绍一下罗孚修改的几个关键点,以及这里的坑,顺便再吐槽一下百度地图提供的源码。

    根据不同比例尺显示不同数据

    显示省份、城市和小区

    数据加载有两个问题,数据量和美观度。

    一次性加载全国数据,对于近万的数据量,地图API提供方表示无压力,但对开发人员来说,加载速度是有影响的,所以我们只加载一个城市的数据,以加快数据加载速度。
    地图数据API本身的速度

    如果只加载一个城市的数据,那么在全国地图或世界地图时显示,你会发现所有数据缩在了一坨,这样的美观度我们是无法接受的。所以我们要分层级(不同比例尺)的来显示地图。
    全国地图下显示一个城市的效果

    源码中本身设计了不同比例尺下显示不同数据的逻辑:

    不同比例尺不同数据JS

    罗孚修改了省份和城市数据,带名称,同时将详细数据改成API获取方式。

    更新数据的逻辑放在了地图缩放过程中:

    缩放完成后更新地图数据JS

    罗孚不得不吐槽,地图缩放跨一层级就重新加载一次地图数据,既影响了速度,又增加了加载次数,被调用的API要哭,原本在详图层级只需加载一次的数据,最后要被多次加载,每次加载就会产生一次访问,就会增加服务器的访问量,浪费人家服务器的资源。

    所以,罗孚修改为当进入详图时,加载该城市的疫情小区数据,只要不缩小到城市或省份地图,那么该城市的数据就不会重复加载,降低了API的访问量。

    城市发生变化后重新加载数据

    拖动地图,城市发生变化,自动加载新城市数据

    当用户拖动或缩放地图时,如果地图中心的城市发生了变化,那就加载该城市的数据,这样的体验应该是最棒的。

    罗孚本想在点击城市marker时根据城市名加载该城市数据,但此方法无法解决移动地图过程中的城市变化加载不同城市数据问题。浏览代码时,发现在移动和缩放地图事件中就存在城市名的判断:

    根据地图中心位置获取城市名js

    罗孚吐槽一下Geocoder类,此类可能是异步加载(罗孚不确认),所以城市名的获取要慢于后续执行的代码,导致后面使用该类的城市名为空,表现为选择从城市地图缩放进入到详情地图时,地图第一次无法加载城市数据。于是我在城市marker的click事件中,在centerAndZoom方法前先将currentCity变量变更为城市marker的name,解决了点击城市marker后不加载该城市数据的问题。

    另外,如果每移动一次地图就加载一次该城市的数据,那数据API的访问量也将无法想象,所以罗孚定义了一个beforeCity,当获得了currentCity后,同beforeCity比较,如果城市名发生了变化,那就加载新城市名的数据,加载完成后更新beforeCity为currentCity,这样就做到了城市名不变就不重新加载数据。同时,增加了zoom > 9的判断,在城市图和省份图下移动地图,无需加载城市详图数据。

    显示详情和隐藏详情

    点击小区marker显示详情,拖动地图隐藏详情

    源码中定义了显示详情卡片showinfoCard和隐藏详情卡片hideInforCard函数,但详情卡片中的内容没有传递,所以罗孚稍加改造,在读取城市详图数据时,将详情内容组织好后传给showinfoCard函数。

    另外,发现隐藏详情卡片的时机有问题,没有及时调用hideInforCard函数,于是在移动结束后就隐藏详情卡片。当然,你也可以添加在地图点击事件中。

    显示详情卡片内容和隐藏详情卡片的时机js

    罗孚再吐槽一下,地图API本身配合marker点击就有信息窗口InfoWindow类,并且显示和隐藏都设计了时机或方法,那为什么还要自定义一个从底部弹出来的详情卡片?吃力不讨好还费劲。

    定位、搜索及周边疫情信息

    定位模块和搜索功能是现成的,罗孚未做任何修改,所以不在此赘述,但需要吐槽一下,源码中的定位模块感觉是自己封装的(罗孚不确认),而高德地图API是直接封装成了一个公共的类,相比之下似乎有点不专业。

    显示疫情最近的距离数据以及1公里内或3公里内的疫情总数量,属于周边疫情信息模块,罗孚未去实现,简单说一下思路,两个方案吧。如果你本身有数据,可以做一个根据该位置搜索周边数据的API,可以直接提供结果,但可能需要服务端支撑。如果不想自己写服务端,那就根据数据API获取的数据,遍历该城市的数据,做距离排序并聚合数量,一样可以达到疫情信息展示的功能。有兴趣的可以尝试一下。

    小结和福利

    百度地图虽然提供了源码,但源码的质量实在是soso,逻辑思考不足,甚至语法都存在不规范(比如缺少代码结尾符号)。同时,对于JS API的GL版和V3.0版,虽然存在继承,但在文档中未能很好体现,导致看起来GL版功能很少。另外,对于百度地图API的类,感觉封装的数量少了点,而在可定制性上又欠缺了一些,这仅仅是罗孚的感觉吧,毕竟未通读百度地图API和示例。

    最后的福利依然是源码放送啦,只是罗孚想测试一下公众号新的付费阅读功能,所以本文需要付费1元查看全文,包括下面的下载链接哦。下载文件简要说明如下:

    疫情地图源码说明

    对了,还会多送一份地图,一个html文件就可以搞定的地图,自己拿到后再修改布局元素相关的内容吧,数据和地图的代码都可以不用动哦,罗孚仅保存了html,未做任何修改哦,具体效果如下:

    一个html文件的疫情地图

    下载地址:在“罗孚传说”公众号后台回复"疫情场所地图"地图试试。

    展开全文
  • 最初是因为看到我所在的城市(上海)疫情场所位置不准,想自己做一份数据并显示在地图上,结果一拖再拖然后疫情都快结束了[尴尬],不过我还是花了一天时间研究了一下,于是有了这篇《如何制作一份疫情场所分布地图?...

    罗孚想要制作一份疫情场所分布地图,最初是因为看到我所在的城市(上海)疫情场所位置不准,想自己做一份数据并显示在地图上,结果一拖再拖然后疫情都快结束了[尴尬],不过我还是花了一天时间研究了一下,于是有了这篇《如何制作一份疫情场所分布地图?》,要不就当罗孚手把手教你制作一份疫情场所分布地图吧[捂脸]。
    本文是以上海地图数据为基础、基于高德地图API设计制作的一份城市地图,如果你收集了你所在城市的地图数据,那么按照本文文末提供的源码和数据结构,你也可以直接复制出一份城市疫情场所地图。要是你真实现了,记得要回来show给我看一下哦。

    功能和效果预览


    上图是几个主要功能的预览
    不过只给个图不给体验,这不是罗孚的风格,直接上体验网址:https://rovertang.com/map/shncov/
    疫情场所分布地图现有的功能:
    1. 地图显示
    2. 疫情场所显示
    3. 个人位置显示
    4. 地图搜索
    5. 其他:缩放控件显示、地图范围限定等
    由于是初版,仅仅研究探讨,所以更强大的功能还无法实现,不过我会在下文中展望一下更强大的功能。

    概要设计

    两个文件就实现了全部功能,还要做个概要设计?不好意思,请让罗孚装一下[捂脸]。实际上,罗孚在这事情上确实走了弯路。

    实现框架

    罗孚最开始考虑的实现方案,超级简单,简单到甚至不需要写代码和制作数据文件。
    不管是高德、百度还是腾讯,都提供了地图数据管理方案,高德的云图、百度地图的LBS云、腾讯地图的地点云,直接使用他们的云存储,然后在地图上添加一个自定义云图层或者通过数据检索API取出数据后marker到地图就行了。
    云数据方案是好的,但罗孚使用下来,竟然都不好用,要么无法在地图上显示,要么API接口不返回数据,只能说罗孚作为一个非专业开发人员,确实太菜了一些。当然,也要批评一下高德,地图如果是3D模式时,无法使用云地图图层,这一信息是在某一文档的一行小字中找到的,擦,这坑挖的够深。
    自建数据库成了唯一的选择。
    不过,为了让方案简单易复制,使用MySQL似乎重了一点,要不使用sqlite吧。但一想到要写数据库的增删改查,又增加了不少麻烦,为了快速实现,直接将最终数据做成了json文本,数据库的操作,以后再考虑吧。
    大言不惭的说,最后的实现框架是:json+js。嗯,两个文件,一个json数据文件,一个js文件(写在html文件里了)。[憨笑]

    数据的定义和来源

    对于地图数据,必须的字段可能有:
    - 名称
    - 经度
    - 纬度
    - 地址(可选)
    对于疫情,可能的字段有:
    - 次数 (有些省市会提供这个地址发生的次数)
    - 类型 (可以细分为小区、商场等)
    - 时间 (发现时间)
    - 来源 (数据源来自哪里)
    呃,考虑的是不是有点复杂?罗孚实际上也没有用这么多数据内容,数据字段越多,内容越丰富,实现难度也就越大,作为一个DEMO,就只考虑必须的名称(实际是用地址代替的)和经纬度吧。
    定义完字段,就要制作数据了,这些点位数据怎么来呢?逃不出两种方式:
    1. 自己收集和标注
    2. 扒别人的位置数据
    罗孚的初衷是想自己收集和标注,自力更生是我国人民的传统美德,但本人懒癌严重,所以最后也是取了个巧,扒了份别人的数据(仅使用了上海部分啊,其他城市的没要)。
    当然,如何扒其他平台的数据,似乎又可以写一篇文章,考虑同本教程有较大出入,加上这见不得人的伎俩,以后再说吧。

    地图平台的选择

    确定好框架,定义好数据,可以开始开发了,但由于和地图相关,在此多讨论一下地图平台的问题。
    一般来说,使用商业公司成熟的地图API是最佳选择:
    高德和百度地图的API:推荐。毕竟是国内不分伯仲的顶尖API。
    腾讯地图API:可用。可能可以算国内老三,但用的人真少,看在微信、小程序都是鹅厂的产品,也是值得考虑的,看了下API,也基本能用。
    mapbox API:可用,有难度。mapbox是开源地图解决方案公司,属于国际顶尖(在地图上甚至比Google还强大哦),现在正在大力发展国内市场,此次制作的疫情地图也超级漂亮(欣赏一下)。
    Google maps API:不能用。虽然国际顶尖(地图API鼻祖),但不能用的道理,你懂得。(有兴趣的可以了解一下我十年前拆解的Google Maps API v3离线开发包)
    除了商业公司的方案,当然也可以使用开源方案,比如leaflet。我看到天地图上海做的地方疫情地图就是用了leaflet。开源方案主要是功能框架,但地图底图数据,要么用商业公司的,要么就还是自己扒了(罗孚也扒过:Google Maps瓦片(tile)地图文件下载(1-11层级),今年过年太无聊,又扒了更多更细的数据[捂脸])。
    地图平台的探讨,真可以再写几篇文章,对于本文的地图平台,最终选择了高德,主要原因是站在别人肩膀上的选择。百度地图API虽然我想用,但考虑到百度地图坐标是在GCJ02坐标系下又做了一次加密,想到后续的数据坐标混乱,还是罢了罢了。

    详细设计(功能实现)

    继续大言不惭的说说功能实现的详细设计吧,我们针对每一项功能做详细的介绍以及实现上的注意事项,顺便把我参考的地图示例贴出来供大家参考。

    地图显示

    地图显示是最基本的功能,有一些地方需要注意,谈谈我在地图上的一些考虑。
    1. 考虑到是地方地图,那么全国地图、世界地图层级就不要显示啦,所以我把地图层级设置在了10-18之间。
    2. 既然是专题地图,那么杂项就不要显示了,比如室内地图、地图上的热点等,都设置为false吧。
    3. 而地图的风格,normal的色调太亮,换一个官方的远山黛whitesmoke主题吧。
    参考示例:
    地图创建:https://lbs.amap.com/api/javascript-api/example/map-lifecycle/map-show
    设置地图中心点/级别:https://lbs.amap.com/api/javascript-api/example/map/change-map-center
    标准样式主题:https://lbs.amap.com/api/javascript-api/example/personalized-map/set-theme-style
    地图类:https://lbs.amap.com/api/javascript-api/reference/map

    添加疫情场所marker并弹出气泡框


    这一步算是本地图DEMO的最核心功能了,虽说比较简单,就是遍历json,然后批量添加marker到地图就可以了,但这里遇到了一个坑,在marker上绑定click事件后,竟然click任何一个marker,都是响应最后一个marker的气泡框。
    调整半天、搜索半天都没能解决,罗孚差点想把整个DEMO都放弃,最后还是搜索到了一个小小的提示,才解决了这个问题。感慨百度搜索真心不灵。
    这个问题的解决方法:使用marker的extData。
    参考示例:
    覆盖物的添加与移除:https://lbs.amap.com/api/javascript-api/example/map-componets/map-overlays
    点标记:https://lbs.amap.com/api/javascript-api/example/marker/marker-content
    自定义图标:https://lbs.amap.com/api/javascript-api/example/marker/custom-icon
    获取某个覆盖物(关于extData的使用):https://lbs.amap.com/api/javascript-api/example/common/ext-data/
    默认样式信息窗体:https://lbs.amap.com/api/javascript-api/example/infowindow/default-style-infowindow
    Marker类:https://lbs.amap.com/api/javascript-api/reference/overlay#marker
    InfoWindow 类:https://lbs.amap.com/api/javascript-api/reference/infowindow#InfoWindow

    添加自定位功能


    此功能是显示自己的位置,如果定位成功,则基于自身位置显示半径为1公里和3公里的圈,以了解附近范围内大概有多少疫情场所marker。
    需要注意,本处使用的是AMap.Geolocation,是高德地图API的定位插件。
    定位的一些细节逻辑:当定位成功后,原则上是需要将地图的中心点移动到定位位置的。但如果定位信息中的citycode不是021,那么就不要移动地图中心点。对于无法定位和定位成功返回的citycode不是021,那么仍然是以上海市人民广场为地图中心点。
    参考示例:
    浏览器精确定位:https://lbs.amap.com/api/javascript-api/example/location/browser-location
    圆的绘制和编辑:https://lbs.amap.com/api/javascript-api/example/overlayers/circle-draw-and-edit
    AMap.Geolocation插件:https://lbs.amap.com/api/javascript-api/reference/location#m_AMap.Geolocation

    添加查询功能


    可以查看自身位置信息了解自身周边的疫情情况,那么想看看某一小区或商场附近的疫情情况是否可以实现呢?这就需要用到查询功能了。
    这里我们使用高德的AMap.Autocomplete,是输入提示插件,虽然不是地点搜索,但已经足够用了,能出现在提示中的结果都是搜索频率较高的结果,同时这个插件的使用也超级简单,输入绑定到某一输入框,比如id为search的input,结果绑定到某一div,比如id为result的div,功能就实现了,你只要输入内容,result中就出现结果了(简单到让罗孚瞠目结舌)。
    完成上述操作,结果内容是无法选择的,好在插件提供了返回结果功能,在AMap中设置一个监听事件,选择某一结果后就可以得到该结果的详细信息,我主要是取经纬度信息,然后在地图上显示一个marker就好了。
    参考示例:
    输入提示:https://lbs.amap.com/api/javascript-api/example/input/input-prompt

    其他小功能

    添加简易控件,就是在地图右下角的放大和缩小按钮,使用AMap.ToolBar即可,是地图操作工具条插件。
    为了让地图范围在某一范围中显示,可以给地图设置一个bound,使用map.setLimitBounds方法即可。
    参考示例:
    地图控件:https://lbs.amap.com/api/javascript-api/example/map-componets/map-with-function-control
    限制地图显示范围:https://lbs.amap.com/api/javascript-api/example/map/limit-map-show-range
    地图操作工具条插件:https://lbs.amap.com/api/javascript-api/reference/map-control#AMap.ToolBar

    时间顺序和事件的考虑

    本demo中使用的数据,总量100+,不算很多,但若数据量达到1000+,那么加载的时间顺序就显得尤为重要,所以我们在地图加载完成(使用map.on 'complete')后,再去加载json数据。
    这样做的优势:先让地图显示,然后看到地图上出现marker,避免地图长时间等待加载,毕竟用户的耐心是有限的。
    关于事件,由于主要是针对地图或覆盖物类,所以使用这些类的on成员方法实现对事件的绑定。比如上面加载json数据是绑定在map的complete事件上的,比如气泡框的显示是绑定在marker的click事件上的。
    对于非地图或覆盖物类的事件,那就只能增加addListener事件了,比如对输入提示结果的操作。
    参考示例:
    地图加载完成:https://lbs.amap.com/api/javascript-api/example/map-lifecycle/map-complete
    事件:https://lbs.amap.com/api/javascript-api/reference/event
    输入提示后查询:https://lbs.amap.com/api/javascript-api/example/poi-search/search-after-enter-prompt/

    延伸思考

    产品思考

    本文真的仅仅是一个DEMO,还有甚多的功能未能实现:
    1. 数据管理后台
    直接使用json文本作为数据源,无法增删改查确实是一个缺憾,需要多数据做一个管理,可以查询地图数据、添加数据、修改数据,这个功能下也包含了不少的小功能。
    2. 更多地图显示方式
    仅仅显示marker,显示方法太粗糙,marker按理是在详图层级(比如16级及以上)才显示的内容,可以考虑引入聚合图和热力图。
    比如10到12层级,可以考虑按照地区对疫情场所数据进行聚合显示,按地区显示每一区域总量总量的marker。
    比如13到15层级,可以考虑显示热力图,因为在此层级下查看疫情出现的热度可能反而更直观一些。
    3. 计算距离和提醒
    在当前位置或查询结果数据上,显示距离最近的疫情场所和距离信息,以让自身了解危险的程度。
    4. 引入其他地图API
    使用高德地图API,虽然实现了此DEMO,但毕竟有自身的局限性,绑定在了此平台上,可以考虑使用同一份数据制作基于百度地图API、腾讯地图API以及leaflet库的地图。
    还有很多很多,无法一一叙述,后续可以考虑写一篇疫情地图功能对比的文章,看看别人在产品制作上的优劣。

    开发思考

    不是开发人员,但用代码堆起了这个DEMO,那么也说说在开发上的思考吧。
    1. 不要重复造轮子
    站在巨人肩膀上,用成熟的API,其实挺好的。
    有时候你的目标是实现功能,而不是创造一个有竞争力的平台,那么你就没有必要思考自己开发一个平台,甚至连选择哪个平台都不需要太多的顾虑。
    2. 专业的人做专业的事
    罗孚本人不是程序猿,也应该有10年以上没有碰过代码了,这次做个DEMO都费了老半天的劲,只能感慨专业的事还是由专业的人来做吧。
    不过上面产品思考的功能开发,如果在屏幕前的你,有兴趣帮助完成,那也欢迎联系我。不管是互相学习交流,还是互相帮助提升,罗孚都愿意,毕竟罗孚也是热心肠嘛[憨笑]。

    福利时间

    本文太啰嗦,一篇技术文章写成了记叙文,罗孚自身也在感慨,看来以后可以考虑做成PPT或视频的形式,要么更简单明了,要么更详细清晰。
    本文标题和开篇均提到了源码和数据,如果是懂技术的你,应该是不屑的,或者Ctrl+U一下就拿到了(加密版本)。如果确实需要下载,可以在“罗孚传说”公众号上回复“疫情场所地图”即可(未加密版本)。如果需要带详细注释的文件来研究,请加入罗孚的知识星球来取(带注释版本),当然还可以附送超详细瓦片地图离线文件哦,后续也有更多内容分享。
    好了,用地图API制作一份疫情场所分布地图,今天,你会了吗?欢迎联系罗孚,欢迎关注罗孚的公众号(罗孚传说)做更多交流,更欢迎加入罗孚的知识星球赞助罗孚获得更多有价值内容,我们下一篇文章中见。

    展开全文
  • 一张新型肺炎地区分布地图是怎么制作的?

    万次阅读 多人点赞 2020-02-01 17:12:24
    一张新型肺炎地区分布地图是怎么制作的? 前言 让我们从环境开始 创建工程,添加引用 GIS数据 剩下的工作就很简单了 叠加世界数据 叠加中国数据 调整可视范围 连接动态数据 样式化地图 写在最后 前言 2020年刚...
    
    

    前言

    2020年刚开始,各钟不幸的消息满天飞。新型肺炎的蔓延,科比去世… 无时无刻让我感到痛楚。为了不给国家添乱,新年几天都在窝在家里。时不时拿起手机,观察一下现在病情蔓延情况。下面这张地图就是一张典型的GIS应用。

    infection-cover-status-demo

    每当我看到这张静态图时,很想要知道几个信息无法获知。

    1. 我们能通过颜色和图例比较一个省的确诊人数范围,看不到一个省具体患病人数。
    2. 由于是一张静态图,我们没法获市级数据。如果地图可以拖动,放大缩小就简单多了。
    3. 每次看到红色,心里都很焦虑。能换成其他颜色,我自己更加能接受点。

    基于这两个小功能,我准备介绍一下怎么去制作一张地图。我准备分两个阶段来做介绍。

    1. 先用最简洁的代码来生成一张静态图片。通过这个阶段,让我们认识一下一般地图应用开发的流程。
    2. 当我们了解流程以后,我们就把这个程序改造成地图服务,让她和知名的地图前端库Leaflet合并开发一个可交互的地图,集成点有趣的功能。

    这篇文章,我准备先从制作一张静态图片开始。

    让我们从环境开始

    以前开发地图应用软件,可能需要掌握很多编程语言技能,才能胜任一个完整的项目。比如一个典型的GIS B/S应用一般会使用Java, C#或其他后端编程语言来开发后端,然后用JavaScript + HTML来开发前端展现。

    今天用我们熟悉的JavaScript;即使是前端开发人员也可以开发后端地图应用了。追求极简开发环境的话,我们只需要2个工具。Node.js (推荐8以上,或者直接安装最新版本都是兼容的)和 vscode.

    这篇文章照顾新手,写的比较多。老鸟请自行过滤。勿喷。

    创建工程,添加引用

    接着,我们创建一个工程目录。用以下命令就可以了。(我个人比较喜欢使用命令行,由于平时都是用macOS做日常使用机器。所以以下命令行都是macOS执行验证的)。

    # 创建项目目录
    cd [your workspace]
    md nCoV-map
    cd nCoV-map
    
    # 创建功能,添加引用
    npm init -y
    npm i --save ginkgoch-map canvas lodash
    
    # 新建一个文件,这个将是我们写代码的地方
    touch tutorial-01.js
    

    这里引用了canvas库,是因为Node.js没有提供绘图API,我们只能引用一个第三方Node.js库来替代使用。

    到这里,我们的工程已经建立好了。

    GIS数据

    GIS应用里面数据是很重要的。我把她分为静态数据和动态数据。静态数据就是我们的几何图形以及她们特定的特征数据。如地区的名字等。动态数据就是我们实时关注的疫情变化。

    一般静态数据比较容易找到。百度搜索中国地图数据csv, json, shapefile都可以找到。这个项目里面,我准备使用shapefile作为我的静态数据。这里你可以找到以下数据,我们一会儿会使用到。把上面数据下载下来以后,放到工程的data目录下面。

    • chn/
      • gadm36_CHN_1_3857.shp - 省级数据
      • gadm36_CHN_2_3857.shp - 市级数据
    • cntry02.shp - 世界国家数据

    动态数据会麻烦点。我是写了一个爬虫,定时爬取。有兴趣可以私聊。不过作为例子,我放上了几份疫情数据在data/infected目录里面以便做示例。

    剩下的工作就很简单了

    叠加世界数据

    首先,我们定义一个函数来创建一个地图的图层,一个数据源即一个数据图层,多个数据图层叠加起来就可以构成我们期望的样式。使用ginkgoch-map,我们是这样定义一个图层的。

    function createLayerWithDefaultStyle(filePath) {
        // create a source with the specified shapefile file path
        let source = new GK.ShapefileFeatureSource(path.resolve(__dirname, filePath));
    
        // wrap the source as a world layer
        let layer = new GK.FeatureLayer(source);
    
        // set a style on the layer
        layer.styles.push(new GK.FillStyle('#f0f0f0', '#636363', 1));
    
        return layer;
    }
    

    有了layer, 我们可以简单查看我们数据图层的样子。比如对于数据cntry02.shp:

    let worldLayer = createLayerWithDefaultStyle('../data/cntry02.shp');
    await worldLayer.open();
    let worldImage = await worldLayer.thumbnail(512, 512);
    fs.writeFileSync(path.resolve(__dirname, './images/tutorial-01-world.png'), worldImage.toBuffer());
    

    我们通过命令行执行下面的语句。我们可以找到图片:

    node tutorial-01.js
    

    tutorial-01-world

    叠加中国数据

    当然这个不是我们想要的样子,我们还需要把省份的数据叠加在上面,才能看到我们中国详细一点的数据。我们接着做。

    const [imageWidth, imageHeight] = [512, 512];
    
    // create a world layer with cntry02.shp
    let worldLayer = createLayerWithDefaultStyle('../data/cntry02.shp');
    
    // create a province layer with gadm36_CHN_1_3857.shp
    let provinceLayer = createLayerWithDefaultStyle('../data/chn/gadm36_CHN_1_3857.shp');
    
    let mapEngine = new GK.MapEngine(imageWidth, imageHeight);
    mapEngine.srs = new GK.Srs('EPSG:900913');
    mapEngine.pushLayer(worldLayer);
    mapEngine.pushLayer(provinceLayer);
    
    let image = await mapEngine.image();
    fs.writeFileSync(path.resolve(__dirname, './images/tutorial-01-default.png'), image.toBuffer());
    

    现在再打开图片tutorial-01-default.png, 注意查看中国的数据已经叠加成功了。

    tutorial-01-default

    调整可视范围

    哦?太小了~ 好,我们调整下地图的可视范围。

    await provinceLayer.open();
    let chinaEnvelope = await provinceLayer.envelope();
    chinaEnvelope = GK.ViewportUtils.adjustEnvelopeToMatchScreenSize(chinaEnvelope, imageWidth, imageHeight);
    
    let image = await mapEngine.image(chinaEnvelope);
    fs.writeFileSync(path.resolve(__dirname, './images/tutorial-01-china.png'), image.toBuffer());
    

    tutorial-01-china

    连接动态数据

    我们对静态数据进行了渲染,同时对中国省份级别的边框进行绘制。接下来,我们将连接动态数据,把动态的感染人数和地图对应起来。我们怎么做呢?

    首先,我们先看下静态数据的特征数据。Shapefile的特征数据存放在*.dbf文件里面。你可以选择使用你常用的工具打开dbf文件。我个人一般使用的是shapefile viewer来查看。参考这里获取程序及使用说明

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NUD1zDlD-1580548031579)(./tutorials/images/china-attributes.png)]

    NL_NAME_1就是我们需要的省份名字,然后我们来看看动态数据的一个片段。

    [
        {
            "provinceName": "湖北省",
            "provinceShortName": "湖北",
            "confirmedCount": 4586,
            "suspectedCount": 0,
            "curedCount": 90,
            "deadCount": 162,
            "comment": "待明确地区:治愈 30",
            "cities": [
                {
                    "cityName": "武汉",
                    "confirmedCount": 2261,
                    "suspectedCount": 0,
                    "curedCount": 54,
                    "deadCount": 129
                },
                {
                    "cityName": "黄冈",
                    "confirmedCount": 496,
                    "suspectedCount": 0,
                    "curedCount": 5,
                    "deadCount": 12
                },
                ...
    

    有趣的是,动态数据也包含省份的名字provinceShortName;以及感染,疑似,治愈以及死亡的人数。现在,我们要做的就是通过静态数据的NL_NAME_1和动态数据的provinceShortName相等的数据相关联。在ginkgoch-map里面是这样做的。

    首先,我们定义一个函数来帮助我们定义一个关系。

    /**
     * field - the dynamic field value to return.
     * infectedData - the infected data in JSON format.
     */
    function _getDynamicFieldForProvince(field, infectedData) {
        return {
            name: field, fieldsDependOn: ['NL_NAME_1'], mapper: feature => {
                const fullName = feature.properties.get('NL_NAME_1');
                const infectionInfo = _.find(infectedData, d => {
                    return fullName.includes(d.provinceShortName);
                });
    
                if (infectionInfo === undefined) {
                    return undefined;
                } else {
                    return infectionInfo[field];
                }
            }
        };
    }
    

    然后建立4个列的映射函数。

    function connectDynamicData(layer) {
        // load dynamic data
        let dynamicData = fs.readFileSync(path.resolve(__dirname, '../data/infected/1580376765333.json')).toString();
        dynamicData = JSON.parse(dynamicData);
        
        // connect 4 dynamic attribute fields to the source.
        const source = layer.source;
        source.dynamicFields.push(_getDynamicFieldForProvince('confirmedCount', dynamicData));
        source.dynamicFields.push(_getDynamicFieldForProvince('suspectedCount', dynamicData));
        source.dynamicFields.push(_getDynamicFieldForProvince('curedCount', dynamicData));
        source.dynamicFields.push(_getDynamicFieldForProvince('deadCount', dynamicData));
    }
    

    最后,我们调用这个函数进行映射。

    //...省略前后重复的代码
    let provinceLayer = createLayerWithDefaultStyle('../data/chn/gadm36_CHN_1_3857.shp');
    connectDynamicData(provinceLayer);
    

    至此,我们可以认为provinceLayer已经包含了动态数据。她将在需要的时候动态的去通过映射关系找到需要的数据来使用。

    样式化地图

    做到这里,大家可以去休息一下。迎接最后一步:地图样式化。我们把感染人数分成几个等级,根据等级渲染不同的颜色来表示严重程度。比较好的是,ginkgoch-map提供了对应的API来渲染。

    我们先定义个函数来创建样式化对象Style.

    function _getClassBreakStyle(field) {
        const strokeColor = '#636363';
        const strokeWidth = 1;
    
        let countStops = [1, 10, 50, 100, 300, 500, 750, 1000, Number.MAX_SAFE_INTEGER];
        let activePallette = ['#fff5f0', '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2c', '#cb181d', '#67000d']
        let style = new GK.ClassBreakStyle(field);
    
        for (let i = 1; i < countStops.length; i++) {
            style.classBreaks.push({ minimum: countStops[i - 1], maximum: countStops[i], style: new GK.FillStyle(activePallette[i - 1], strokeColor, strokeWidth) });
        }
    
        return style;
    }
    

    再应用到layer上即可看到效果。

    let confirmedCountStyle = _getClassBreakStyle('confirmedCount');
    provinceLayer.styles.push(confirmedCountStyle);
    
    // 顺便我们把文字渲染上去,即可完成。
    let provinceLabelStyle = new GK.TextStyle('[NL_NAME_1]', 'black', 'Arial 12px');
    provinceLabelStyle.lineWidth = 2;
    provinceLabelStyle.strokeStyle = 'white';
    provinceLabelStyle.location = 'interior';
    provinceLayer.styles.push(provinceLabelStyle);
    

    chn-confirmed-map.png

    是不是很有趣?我们现在可以随意替换调色板,让她变成蓝色系的。替换这一句即可。

    // let activePallette = ['#fff5f0', '#fee0d2', '#fcbba1', '#fc9272', '#fb6a4a', '#ef3b2c', '#cb181d', '#67000d'];
    let activePallette = ['#f7fbff', '#deebf7', '#c6dbef', '#9ecae1', '#6baed6', '#4292c6', '#2171b5', '#08519c'];
    

    final-infection-map.png

    写在最后

    最终的代码可以在这里下载:https://github.com/ginkgoch/nCoV-map/tree/develop/tutorials

    看起来很多,大多数代码都是业务代码,和对样式的设置。了解起来还是挺简单的。今天就到这里,后面有时间,我再写一篇搭建一个地图服务,制作一个可以交互的地图应用。有问题可以随时联系我, ginkgoch@outlook.com

    Happy Mapping!

    展开全文
  • 这几总有人问我关于Web前端设计的作业怎么做呀,有什么代码可以实现啊。 我看了下作业内容,说是...关于如何实现图文在分布且图片在左侧而文字在右侧 <table border="0"> <tr> <td rowspan =...

    这几天总有人问我关于Web前端设计的作业怎么做呀,有什么代码可以实现啊。
    我看了下作业内容,说是难点在于让图文在同一行且图片在左侧而文字在右侧且进行布局。
    在这里插入图片描述
    看完作业内容后我想说的是:“有问题能尽量百度就百度,能谷歌就谷歌,别问这么基础的问题啊洗吧!

    关于如何实现图文在一行分布且图片在左侧而文字在右侧

    萌新写代码

    <table border="0">
    <tr>
    	<td rowspan ="2">
    	<img src="图片.jpg" height="250" width="250"/></td><td>
    <ul>
    	<li>一口价:45.00</li>
    	<li>剩余时间:323小时</li>
    	<li>本期售出:8</li>
    	<li>累计售出:24</li>
    	<li>宝贝类型:全新</li>
    	<li>所在地:北京海淀区</li>
    	<li>宝贝数量:3</li>
    	<li>浏览量:15</li>
    </ul>
    </td>
    <tr>
    <td><img src="2-2.jpg"/></td>
    </tr>
    </table>
    

    这一段是通过表格的跨行(跨列)操作实现<td rowspan="value">
    表格单元横跨两行:<td rowspan="2">;
    这个属性的属性值不能为0(所有浏览器都不支持)
    以下就是上面的代码实现的样子:
    在这里插入图片描述
    由代码行

    <td rowspan ="2">
    	<img src="2.jpg" height="250" width="250"/>
    </td>
    

    我们可以知道开始标签<td>和结束标签</td>之中有<img src="2.jpg" height="250" width="250"/>
    这是在表格中插入图片了,而<td>的属性是rowspan,也就是跨越的行数。
    注意:这里说的是一个图片占据两个行,不是真的将两个行合在一块了。
    换句话说,这里可以看成有三个单元格的表格,但其中一个单元格的两行都被图片占据了

    tr 元素定义表格行,th 元素定义表头,td 元素定义表格单元

    td使一个表格分成三个表格单元,tr使表格分成两行,其中一个图片占据两行,另两个个占据一行。
    不要问我为啥表格会因为三个td定义表格单元代码分成左边一个大的右边两小部分
    因为这里是表格,定义表格单元代码时都是默认向左右增加删减表格单元,代码中已经定义第一个表格单元跨行2个,第二个在第一行,第三个被定义为第二行,但因为第一个表格单元定义跨行两行,所以放在右边
    如下图才是正常三个单元表格现象,如果第一个单元格占据两行且定义在第一行时候,则第3单元格会移到右边。
    在这里插入图片描述
    跨行的情况如二图:

    在这里插入图片描述
    重点提醒:这里如果你没有定义过列有多少个,则在分割两个以上单元格时候,列数都默认为向左右增加,也就是说如果我是五个单元格,但没有定义行列的数量,则列数有五个行数有一个

    好了,以上就是解决方案,供给我同学解答。

    展开全文
  • 现代人的时间越来越碎片化,因此我们准备抛弃那种...接下来,我们目标不大,一天学习并掌握一个实用的小案例就够了! 回归 今天,我们学习的是使用seaborn绘制回归。 回归主要用于表现两个变量之间的线性关...
  • 如何通俗理解泊松分布

    万次阅读 多人点赞 2019-04-12 14:48:40
    1 甜在心馒头店 公司楼下有家馒头店: 每天早上六点到十点营业,生意挺好,就是发愁个事情...),但是如果每天准备5个馒头的话,从统计表来看,至少有两不够卖,的时间不够卖: 你“甜在心馒头店”又不是...
  • 幂律分布

    万次阅读 2017-05-08 09:39:27
    最近开始学习社交网络方面的知识,作为个入门级的菜鸟,先了解一下基础的只是理论和方法是很有必要的,今天在社交网络图分布块看到了个幂律分布,感觉很陌生因为之前的概率论和数理统计课程是不学习这个东西...
  • 泊松分布

    千次阅读 2020-09-08 10:04:20
    在实际事例中,当个事件以固定的平均速率出现时随机且独立地出现时,那么这个时间在单位时间(面积或体积等)内出现的次数或个数近似服从泊松分布。 如: 某医院平均每小时出生3个婴儿;(单位时间) 某公司...
  • matlab处理时间数据绘图

    千次阅读 2020-09-07 16:03:41
    直接绘制图像 举例 绘制横坐标为年月日的数据
  • 如果下个婴儿要间隔时间 t ,就等同于 t 之内没有任何婴儿出生。 反过来,事件在时间 t 之内发生的概率,就是1减去上面的值。 接下来15分钟,会有婴儿出生的概率是52.76%。 接下来的15分钟到30分钟,会有...
  • 指数分布

    千次阅读 2017-04-21 11:34:02
    利用几何分布的无记忆性来理解指数分布的无记忆性,事实上证明这种思路是完全可行的。其实几何分布就是在已经试验n次尚未成功的...易理解这也是种等待分布,只不过随机变量是离散型的。显然,n次投不中后,再投k次
  • 如何理解指数分布

    万次阅读 多人点赞 2019-05-06 10:34:30
    1 泊松分布 指数分布和泊松分布息息相关,所以先简单回忆下之前介绍过的...这个规律显然不够好,如果把营业时间抽象为根线段,把这段时间用来表示: 然后把卖出的馒头数画在这根线段上(节约篇幅,只画出周一...
  • 离散分布——泊松分布、指数分布

    万次阅读 2016-05-11 16:27:17
    泊松分布先解释一下泊松分布:假设过去很长时间内的跟踪统计数据表明,某医院每个小时会出生3个小孩儿,这个数据就属于泊松分布。因为这个数据满足几个条件:1、这个数据是在很长的时间段内来统计得到的;2...
  • 通俗理解指数分布

    千次阅读 2019-09-14 18:49:12
    1 泊松分布 指数分布和泊松分布息息相关,所以先简单回忆下之前...这个规律显然不够好,如果把营业时间抽象为根线段,把这段时间用 来表示: 然后把卖出的馒头数画在这根线段上(节约篇幅,只画出周周二作...
  • 本文以亚马逊rope bag商品为例,共采集到1989条商品评论时间数据,并选取15年1月——17年7月的1809条数据来绘制分月销量。采集数据的python代码如下所示:# -*- coding: utf-8 -*- """ Created on Mon Jul 24 15:...
  • 泊松分布和指数分布:通俗易懂

    万次阅读 多人点赞 2018-05-10 10:12:43
    传送门:1、泊松分布和指数分布:10分钟教程 2、如何理解指数分布的无记忆性?如果某事件以固定强度λ,随机且独立地出现,该事件在单位事件内出现的次数(个数)可以看成是服从泊松分布。...
  • 先简单提下马尔科夫链(具体描述有很多,不赘述): 1. 每个时间点处在某一个状态,时间是离散的...现在想说明一下,不管初始状态是神马概率分布,拿这个状态转移矩阵没完没了去乘它,总有一天这个状态向量肯定会达到
  • 通俗理解泊松分布

    万次阅读 多人点赞 2018-11-02 12:01:20
      每天早上六点到十点营业,生意挺好,就是发愁个事情,应该准备多少个馒头才能既不浪费又能充分供应?   老板统计了周每日卖出的馒头(为了方便计算和讲解,缩小了数据):   均值为: Xˉ=3+7+4+6+55=5...
  • 时间序列分析

    万次阅读 多人点赞 2017-03-22 17:04:51
    http://blog.csdn.net/pipisorry/article/details/62053938时间序列简介时间序列是时间间隔不变的情况下收集的时间点集合。这些集合被分析用来了解长期发展趋势,为了预测未来或者表现分析的其他形式。但是什么时间...
  • 人类行为时空特性的统计力学()——认识幂律分布 郑梓豪 · 10 个月前 从第次看到 人类行为时空特性的统计力学 时接触Power-Law Distribution,距这次工作上真正要利用起来的时候,已快一年。这...
  • 知识点:伯努利分布、二项式分布、多项式分布、先验概率,后验概率,共轭分布、贝塔分布、贝塔-二项分布、负二项分布、狄里克雷分布,伽马函数、分布 ,伯努利分布(bernouli distribution) 又叫做0-1分布,...
  • 对于这组电影数据,如果我们想runtime(电影时长)的分布情况,应该如何呈现数据? 数据分析 练习2 全球食品数据分析 步骤分析 1.数据清洗 2.获取国家列表 3.对各个国家进行统计 4.保存统计结果 ...
  • K线技术指标实现详解—筹码分布

    千次阅读 2018-06-26 09:21:31
    下图展示了筹码分布图通常的展示形式: 在这幅图中我们可以看到当前股票的大致筹码分布情况:图中红色筹码为获利盘,绿色筹码为亏损盘,最大部分的筹码集中在42.21-44.74价格区间内。此时获利的股民...
  • Matlab时间序列分析

    万次阅读 多人点赞 2018-11-13 18:53:46
    在引入时间序列前,先介绍几个matlab函数 matlab中的gallery函数简析 Matlab 中的 gallery 函数是个测试矩阵生成函数。当我们需要对某些算法进行测试的时候,可以利用gallery函数来生成各种性质的测试矩阵。其用法...
  • 我的uni框架APP(共享充电投放)独立开发日记项目说明及介绍练习阶段APP开发第一天(设计和功能归类)首页物品我的APP开发第二天(UI实现到创作)Tap_Bar:首页充电榜邀请赚钱项目说明物品我的会员资料下载APP我的...
  • 本文介绍六种概率分布的原理、举例、均值、方差、期望、概率分布图等,分布分别为伯努利分布、均匀分布、二项分布、正态分布、泊松分布、指数分布;另外介绍各种分布之间的关系和不同
  • 常用随机变量及其概率分布

    万次阅读 2017-05-20 10:20:40
    、常用的离散型随机变量及其概率分布 1、(0-1)分布(伯努利分布(Bernoulli distribution)、两点分布) 如果随机变量X 只可能取0与1两个值,其概率分布为: 或写成 则称随机变量X 服从(0-1)分布或...
  • 泊松分布–计算概率分布的公式

    千次阅读 2020-08-17 17:18:36
    泊松分布概率公式Probability Distributions play an important role in our daily lives. We commonly use them when trying to summarise and gain insights from different forms of data. 概率分布在我们的日常...
  • 数模国赛前一天总结

    千次阅读 多人点赞 2011-11-16 23:09:05
    (问题重述,问题分析,模型假设,符号说明,模型形式,以及参考文献都已经在第一天的讨论中有了一定的共识。) 其余两个人(在不清楚时3人讨论),开始考虑第一个问题的模型的求解,即研究模型的解法。查找文献...
  • 结构 非常得类似我们之前讲到过的树结构,但前者没有很多的...在数学的概念中,后者是前者的种,不过在数据结构中我们还是认为两者有所区别,尽管如此,我们在学习结构的时候仍可以稍微借鉴一下树结构的思想。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 73,496
精华内容 29,398
关键字:

一天时间分布图