阅读 2001

不依赖任何库打造属于自己的可视化数据地图

前言

在数据日益增长的今天,数据可视化的需求也越来越大。

随便打开一个招聘软件,稍微好一点的岗位十个里面至少有三个以上都要求最好有数据可视化的经验。

我就曾经遇到过这么一个需求:青岛银行希望把自己往智慧银行的那个方向去转型,所以提出了很多个展项需求。

其中有个项目叫做文化体验桌,里面有一项是青岛地图的展示,在地图上有几个后台传过来的坐标点,点击坐标点就会显示出详细信息。

如果屏幕前面的你是青岛人或者恰好身处青岛地区 可以去青岛银行看看

里面的文化体验桌就是我做的

技术选型

百度地图

一开始肯定是想拿百度地图开放平台提供的API来做,毕竟人家是专业做地图的,用这个方法既省时省力同时效果还好。

而且不是只能显示成咱们平时搜索出来那种专业地图,还可以使用它提供的个性化地图:

但银行的安全性大家是知道的,为了防止黑客攻击,所有银行都禁止连接外网。

那么你可能说了,网上不有那种离线版的地图瓦片么,做个离线地图试试?

找了半天发现这些地图瓦片很不好找,花钱下载了好多但都只是些残废版,功能不齐全还有各种各样的坑,基本都是盗版,说到盗版就不得不提到版权的问题了:

眼尖的朋友可能注意到了,地图的左下角有一个百度地图的Logo,询问过甲方后人家明确提出不希望出现除青岛银行或者后台传过来的广告商数据以外的任何Logo。

那想个办法把它去除或者隐藏掉不就得了?

这还真不行,一般它出现这个Logo的目的就是为了向大家展示:看!这是百度做的地图!

毕竟假如哪个公司做出来个什么地图软件,直接调用百度地图的API,然后把 Logo 一删,谁还看得出这是百度地图?还以为这就是这个公司做的地图呢。

带着猜想,我仔细查阅了百度地图的使用条款:

那么你可能会想:不是还有高德地图、腾讯地图、谷歌地图等各种地图吗?

其实大家都差不多,没人希望自己辛辛苦苦做出来的地图被别人抹去 Logo 后当作自己的地图,所以都没办法去除这个 Logo 的(强行去除只会被递律师函 然后上法庭 最后败诉 赔钱到破产)。

ECharts

在经过和甲方的沟通我了解到:这个文化体验桌并不是真的要像一款专业地图那样为人指路的,它的目的主要是为了展示山东文化的同时顺便在地图上显示几个坐标点(广告商的坐标),点击坐标后显示对应的广告商信息,里面就包含了具体位置。

其实仔细想想也很合理,毕竟就算用百度这么专业的地图,人家也不会仔细的去看这个坐标究竟位于哪条街哪号楼,而是直接点击坐标弹出信息:青岛XX有限公司,位于XX大街XX号XX楼…

那这事可就简单多啦!只需有一个抽象地图描绘出青岛市大概的轮廓就可以啦!于是首先想到的就是 ECharts:

领导看完效果图后觉得很满意:这样既简洁又炫酷,就它吧!

于是赶紧用 ECharts 写完一版拿到拼接屏上去测试(文化体验桌是由两块 1920 * 1080 的屏幕横着拼接在一起的),居然发现一个令人崩溃的事情:

拼接屏是触摸的!

你可能会觉得:那又怎么了,触摸就触摸呗,至于这么大惊小怪的嘛!

可是在触摸屏上我才发现 EChart 地图是靠鼠标滚轮来控制地图缩放的,在触摸屏上根本没效果,而目前的需求是要像在手机上操作地图那样用双指放大地图:

你可能会想:这个需求在移动端很常见啊,所以肯定有现成的库能用。

确实是有,但是那样的话放大后会变得模糊,因为 ECharts 底层是靠 Canvas 来绘制的,并不是矢量图,所以导致强行放大会失真。

而且强制放大后连地图的描边也一起放大了,按理说那个边只是划分边界用的,现实中并不存在,所以应该越细越好,这样的话只能魔改 ECharts 然后靠底层的 Canvas 去重绘放大缩小。

没办法了,直接去看 ECharts 源码吧!看看能不能把它靠鼠标滚轮而重绘的那段逻辑改成双指缩放的…

看了半天源码,发现这段逻辑并不在 ECharts 里,而是在它依赖的 ZRender 里,我太难了!再去 ZRender 里看看源码吧:

找了半天发现它其实是有定义移动端事件的,那为什么地图就不好使,难道说 ECharts 在地图那又进行了什么封装?正当我又去看 ECharts 的源码时,甲方领导不知从哪找来个视频:

反正大概就是类似的这么一种效果,当时那个视频描边的形状是一个类似于心电图然后描边到中间时描出来个爱心:

具体的记不太清楚了,反正不是上面那种就是下面这种:

当时那个视频比上面这些效果都炫,但是这个"炫"却炫到了自己的头上,那边非要这种效果,怎么沟通都不行,人家甚至表示:我们愿意加钱!

但加钱也没加在我这里啊,加进了老板的口袋里去了,老板当然高兴,让我必须做出来。

他加钱、我加班…

SVG

不过可以肯定的是,不能再看 ECharts 源码了,像这种成熟的开源项目不仅代码量很多、依赖的库也很多。

挨个翻源码的话得翻到猴年马月,而且改成这种描边效果得大幅度改动源码才行,最后还不一定能成功,所以赶紧放弃了这个念头。

不过还好我的 Canvas 和 SVG 功底都不错,描边这种效果用 SVG 来做简直再合适不过了!

但是有个巨大的问题摆在我眼前:

  • 去哪找青岛地图的 SVG 坐标点?

自己挨个坐标画吗?就像 Canvas 的地图数据那样?

于是乎我从网上找了个带描边的青岛地图:

想用代码把它描出来,但是描了仅仅不到四分之一,眼睛就已经花掉了。而且跟图片一对比,还存在着一定的肉眼可见的误差。

不行,这么做效率太低下了!肯定有什么别的办法,去问问公司的 UI 设计师吧!

人家看完之后表示:这没问题,你给我一张青岛的图,我用AI(不是人工智能那个AI,而是Adobe Illustrator)给你描出来然后导出SVG。

我这边代码写了一整天眼睛都花了,而人家那边只用了半小时就描出来了,而且肉眼根本看不出来误差:

青岛地图

但毕竟导出来的只是svg格式的图片,想要让它能够受到程序的控制还是需要再处理一下:

<svg id="tsing-tao-map" data-name="tsing-tao-map" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 892 994">
  <defs>
    <defs>
      <filter id="filter" x="0" y="0" width="200%" height="200%">
        <feOffset result="offOut" in="SourceGraphic" dx="0" dy="2" />
        <feColorMatrix result="matrixOut" in="offOut" type="matrix"
        values=".15 0 0 0 0 0 .15 0 0 0 0 0 .15 0 0 0 0 0 1 0" />
        <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="1" />
        <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
      </filter>
    </defs>
  </defs>
  <polyline class="cls-1" points="428.5 668.5 429.5 668.5 429.5 672.5 430.5 672.5 430.5 676.5 429.5 676.5 429.5 677.5 426.5 677.5 426.5 679.5 427.5 679.5 429.5 682.5 429.5 684.5 431.5 686.5 431.5 690.5"/>
  <g class="cls-2">
    <polyline class="cls-3" points="328.75 65.13 330.5 67.5 334.5 67.5 336.5 66.5 337.5 64.5 339.5 64.5 342.5 66.5 343.5 69.5 343.5 73.5 344.5 74.5 348.5 74.5 351.5 71.5 355.5 71.5 355.5 69.5 363.5 69.5 369.5 73.5 370.5 74.5 371.5 75.5 371.5 76.5 369.5 79.5 366.5 79.5 364.5 80.5 364.5 81.5 360.5 81.5 360.5 83.5 359.5 83.5 359.5 87.5 363.5 91.5 364.5 91.5 364.5 92.5 369.5 92.5 369.5 91.5 370.5 91.5 371.5 88.5 374.5 88.5 374.5 89.5 376.5 91.5 378.5 91.5 378.5 90.5 382.5 90.5 383.5 91.5 383.5 93.5 384.5 93.5 384.5 94.5 383.5 94.5 379.5 96.5 378.5 97.5 376.5 99.5 374.5 100.5 374.5 103.5 381.5 113.5 382.5 114.5 382.5 119.5 381.5 119.5 381.5 121.5 377.5 127.5 376.5 128.5 376.5 130.5 375.5 130.5 375.5 133.5 374.5 135.5 373.5 138.5 370.5 141.5 367.5 141.5 364.5 143.5 363.5 144.5 362.5 145.5 362.5 148.5 363.5 148.5 364.5 151.5 366.5 152.5 368.5 155.5 370.5 159.5 370.5 161.5 368.5 161.5 368.5 164.5 367.5 164.5 366.5 167.5 366.5 171.5 365.5 171.5 365.5 179.5 364.5 180.5 364.5 185.5 363.5 185.5 363.5 190.5 362.5 192.5 362.5 195.5 361.5 195.5 361.5 197.5 360.5 197.5 360.5 201.5 359.5 206.5 359.5 209.5 357.5 212.5 357.5 214.5 356.5 214.5 355.5 218.5 354.5 218.5 354.5 221.5 354.5 232.5 355.5 232.5 355.5 236.5 357.5 238.5 357.5 240.5 356.5 240.5 356.5 243.5 358.5 246.5 359.5 249.5 360.5 253.5 359.5 256.5 360.5 257.5 360.5 261.5 359.5 261.5 359.5 268.5 359.5 282.5 360.5 283.5 360.5 289.5 361.5 290.5 362.5 292.5 362.5 295.5 363.5 296.5 365.5 298.5 368.5 299.5 370.5 301.5 371.5 303.5 371.5 307.5 370.5 308.5 369.5 310.5 369.5 314.5 368.5 314.5 367.5 320.5 365.5 321.5 363.5 322.5 360.5 322.5 360.5 321.5 357.5 321.5 357.5 323.5 356.5 323.5 356.5 325.5 355.5 325.5 355.5 329.5 351.5 329.5 349.5 329.5 349.5 327.5 345.5 327.5 345.5 326.5 342.5 326.5 337.5 330.5 333.5 330.5 333.5 332.5 331.5 332.5 327.5 337.5 326.5 340.5 326.5 342.5 327.5 342.5 327.5 349.5 323.5 352.5 319.5 355.5 318.5 356.5 314.5 356.5 310.5 359.5 308.5 359.5 308.5 358.5 306.5 358.5 304.5 363.5 304.5 368.5 303.5 368.5 303.5 371.5 302.5 371.5 301.5 376.5 298.5 381.5 297.5 383.5 297.5 385.5 298.5 385.5 305.5 393.5 306.5 393.5 306.5 398.5 304.5 398.5 304.5 404.5 308.5 410.5 309.5 410.5 309.5 417.5 310.5 417.5 310.5 418.5 313.5 419.5 314.5 419.5 314.5 425.5 313.5 425.5 313.5 426.5 308.5 426.5 308.5 425.5 304.5 425.5 304.5 427.5 303.5 427.5 303.5 433.5 301.5 435.5 298.5 435.5 298.5 436.5 296.5 436.5 296.5 437.5 294.5 439.5 294.5 445.5 296.5 446.5 305.5 446.5 306.5 447.5 306.5 450.5 308.5 450.5 308.5 455.5 305.5 455.5 302.5 459.5 302.5 467.5 300.5 470.5 300.5 471.5 301.5 471.5 301.5 476.5 302.5 476.5 302.5 479.5 305.5 483.5 309.5 483.5 316.5 487.5 317.5 487.5 317.5 491.5 319.5 492.5 323.5 492.5 323.5 494.5 324.5 494.5 324.5 495.5 326.5 496.5 329.5 496.5 329.5 497.5 330.5 497.5 332.5 498.5 332.5 500.5 331.5 501.5 331.5 503.5 330.5 503.5 330.5 510.5 327.5 510.5 327.5 514.5 326.5 514.5 326.5 515.5 324.5 515.5 324.5 518.5 322.5 518.5 321.5 521.5 320.5 521.5 319.5 524.5 316.5 527.5 314.5 527.5 314.5 532.5 308.5 537.5 308.5 541.5 301.5 541.5 301.5 543.5 294.5 549.5 293.5 550.5 290.5 550.5 286.5 552.5 286.5 555.5 287.5 556.5 288.5 556.5 288.5 557.5 289.5 559.5 289.5 560.5 288.5 560.5 288.5 564.5 286.5 564.5 284.5 561.5 284.5 559.5 283.5 559.5 283.5 562.5 282.5 562.5 281.5 563.5 281.5 568.5 282.5 569.5 282.5 574.5 284.5 575.5 284.5 582.5 281.5 582.5 280.5 582.5 280.5 583.5 281.5 584.5 281.5 585.5 283.5 586.5 283.5 587.5 286.5 587.5 286.5 586.5 288.5 586.5 288.5 587.5 289.5 589.5 289.5 590.5 286.5 590.5 284.5 592.5 284.5 595.5 286.5 596.5 289.5 596.5 289.5 595.5 291.5 595.5 293.5 598.5 293.5 601.5 288.5 601.5 286.5 603.5 286.5 606.5 288.5 607.5 291.5 609.5 294.5 612.5 295.5 614.5 296.66 615.37"/>
    <polyline class="cls-3" ref="line2" points="235.5 419.97 237.5 419.5 237.5 416.5 238.5 415.5 239.5 415.5 243.5 419.5 244.5 420.5 246.5 420.5 247.5 419.5 247.5 416.5 250.5 416.5 255.5 423.5 255.5 424.5 259.5 424.5 259.5 422.5 262.5 419.5 266.5 419.5 266.5 417.5 267.5 416.5 270.5 416.5 270.5 417.5 275.5 417.5 275.5 418.5 278.5 418.5 279.5 419.5 280.5 419.5 281.5 417.5 282.5 417.5 282.5 418.5 283.5 418.5 283.5 419.5 285.5 419.5 285.5 416.5 287.5 416.5 290.5 414.5 291.5 414.5 291.5 416.5 292.5 416.5 292.5 417.5 296.5 417.5 296.5 419.5 303.5 419.5 303.5 420.5 306.5 420.5 307.5 422.5 310.5 422.5 310.5 426.5"/>
    <polyline class="cls-3" ref="line3" points="351.5 329.5 351.5 331.5 355.5 335.5 358.5 335.5 358.5 333.5 359.5 332.5 360.5 332.5 360.5 333.5 361.5 333.5 362.5 335.5 364.5 335.5 365.5 333.5 366.5 333.5 367.5 334.5 367.5 335.5 374.5 335.5 374.5 332.5 379.5 332.5 379.5 330.5 384.5 330.5 384.5 332.5 386.5 332.5 389.5 329.5 392.5 329.5 392.5 332.5 398.5 332.5 398.5 333.5 402.5 333.5 402.5 331.5 403.5 331.5 403.5 327.5 406.5 327.5 406.5 332.5 405.5 333.5 405.5 335.5 410.5 335.5 413.5 330.5 417.5 330.5 417.5 329.5 420.5 329.5 424.5 334.5 426.5 334.5 429.5 337.5 431.5 337.5 431.5 336.5 436.5 336.5 436.5 335.5 441.5 335.5 441.5 336.5 443.5 336.5 443.5 337.5 444.5 337.5 444.5 335.5 447.5 335.5 447.5 339.5 448.5 341.5 450.5 341.5 450.5 340.5 453.5 340.5 453.5 341.5 456.5 341.5 456.5 339.5 459.5 339.5 459.5 336.5 460.5 335.5 463.5 335.5 463.5 334.5 467.5 328.5 468.5 327.5 472.5 327.5 477.5 332.5 477.5 334.5 479.5 334.5 479.5 333.5 483.5 333.5 483.5 338.5 486.5 341.5 486.5 344.5 487.5 345.5 487.5 346.5 486.5 346.5 486.5 349.5 484.5 350.5 484.5 354.5 486.5 354.5 487.5 356.5 487.5 363.5 488.5 363.5 489.5 364.5 494.5 364.5 494.5 363.5 497.5 359.5 502.5 359.5 503.5 358.5 503.5 355.5 501.5 355.5 501.5 352.5 504.5 352.5 506.5 350.5 506.5 348.5 505.5 347.5 505.5 345.5 508.5 345.5 508.5 344.5 510.5 344.5 510.5 349.5 511.5 351.5 512.5 351.5 512.5 353.5 518.5 353.5 520.5 355.5 520.5 356.5 522.5 356.5 522.5 355.5 523.5 355.5 523.5 352.5 528.5 352.5 528.5 353.5 530.5 353.53"/>
    <polyline class="cls-3" points="331.93 498.21 334.5 496.5 334.5 492.5 333.5 492.5 333.5 478.5 331.5 478.5 332.5 475.5 336.5 472.5 337.5 472.5 336.5 477.5 339.5 478.5 343.5 479.5 344.5 481.5 345.5 480.5 346.5 479.5 349.5 479.5 349.5 482.5 350.5 484.5 350.5 491.5 346.5 492.5 346.5 493.5 350.5 493.5 350.5 494.5 352.5 494.5 352.5 497.5 353.5 498.5 357.5 498.5 357.5 500.5 362.5 500.5 362.5 504.5 367.5 510.5 371.5 513.5 372.5 514.5 374.5 514.5 374.5 508.5 378.5 508.5 378.5 507.5 384.5 507.5 385.5 509.5 391.5 509.5 391.5 517.5 395.5 522.5 399.5 522.5 399.5 527.5 400.5 528.5 400.5 529.5 403.5 529.5 403.5 523.5 404.5 523.5 405.5 525.5 407.5 525.5 411.5 518.5 413.5 518.5 413.5 519.5 415.5 519.5 422.5 509.5 422.5 505.5 416.5 502.5 416.5 498.5 417.5 494.5 421.5 494.5 422.5 497.5 422.5 501.5 427.5 502.5 427.5 508.5 433.5 508.5 437.5 511.5 443.5 511.5 448.5 508.5 452.5 508.5 452.5 512.5 452.5 519.5 456.5 519.5 456.5 520.5 459.5 520.5 461.5 517.5 466.5 517.5 466.5 519.5 468.5 519.5 468.5 517.5 471.5 517.5 471.5 515.5 470.5 515.5 470.5 513.5 475.5 513.5 476.5 511.5 479.5 511.5 482.5 519.5 485.5 520.5 489.5 520.5 489.5 514.5 487.5 513.5 488.5 510.5 490.5 510.5 497.5 518.5 502.5 518.5 503.5 520.5 505.5 520.5 505.5 518.5 510.5 518.5 510.5 519.5 519.5 519.5 520.5 521.5 523.5 521.5 523.5 513.5 526.5 510.5 527.5 510.5 528.5 511.5 530.5 511.5 530.5 522.5 542.5 522.5"/>
    <polyline class="cls-3" points="391.77 592.77 393.5 592.5 393.5 588.5 394.5 588.5 394.5 585.5 395.5 585.5 395.5 583.5 398.5 583.5 398.5 584.5 401.5 584.5 401.5 583.5 403.5 583.5 403.5 581.5 405.5 580.5 405.5 576.5 406.5 576.5 406.5 575.5 410.5 575.5 410.5 574.5 412.5 574.5 415.5 578.5 415.5 579.5 424.5 579.5 424.5 580.5 426.5 580.5 426.5 583.5 428.5 583.5 431.5 587.5 432.5 588.5 432.5 590.5 431.5 590.5 430.5 591.5 430.5 593.5 431.5 593.5 431.5 596.5 435.5 596.5 435.5 595.5 437.5 595.5 437.5 594.5 438.5 593.5 440.5 593.5 443.5 596.5 444.5 597.5 444.5 599.5 446.5 599.5 447.5 598.5 447.5 597.5 451.5 597.5 453.5 596.5 458.5 590.5 459.5 589.5 459.5 585.5 460.5 585.5 464.5 580.5 465.5 577.5 470.5 570.5 470.5 565.5 471.5 565.5 472.5 564.5 476.5 564.5 476.5 563.5 477.5 562.5 496.5 562.5 499.5 560.5 500.5 556.5 500.5 552.5 501.5 552.5 501.5 550.5 504.5 543.5 504.5 541.5 503.5 541.5 503.5 533.5 499.5 526.5 499.5 523.5 498.5 523.5 498.52 518.5"/>
    <polyline class="cls-3" points="459.35 589.65 462.5 589.5 462.5 590.5 464.5 592.5 470.5 592.5 472.5 594.5 472.5 596.5 471.5 596.5 471.5 598.5 470.5 599.5 470.5 605.5 468.5 607.5 468.5 612.5 463.5 615.5 463.5 618.5 462.5 618.5 462.5 621.5 457.5 625.5 455.5 625.5 451.5 627.5 444.5 634.5 433.5 634.5 433.5 636.5 425.5 643.5 425.5 645.5 428.5 648.5 431.5 654.5 434.5 661.5 435.5 663.5 435.5 664.5 431.5 664.5 428.5 666.5 428.5 668.5 424.5 668.5 423.5 668.5 423.5 669.5 420.5 669.5 420.5 668.5 412.5 668.5 412.5 670.5 407.5 670.5 406.5 671.5 406.5 674.5 405.5 674.5 405.5 679.5 404.5 679.5 404.5 680.5 402.5 681.5 402.5 682.5 400.5 682.5 400.5 681.5 399.5 680.5 390.5 680.5 389.5 680.5 388.5 678.5 386.5 678.5 386.5 679.5 384.5 679.5 384.5 680.5 381.5 680.5 381.5 678.5 379.5 678.5 379.5 679.5 378.5 679.5 374.44 675.56"/>
    <polyline class="cls-3" points="428.81 640.6 427.5 640.5 425.5 640.5 424.5 639.5 422.5 639.5 421.5 636.5 421.5 631.5 420.5 631.5 420.5 629.5 418.5 627.5 411.5 627.5 411.5 629.5 406.5 629.5 396.98 624.15"/>
    <polyline class="cls-3" points="96.5 699.15 98.5 699.5 98.5 701.5 102.5 701.5 102.5 703.5 104.5 703.5 104.5 708.5 105.5 708.5 105.5 709.5 108.5 709.5 108.5 713.5 110.5 713.5 112.5 711.5 114.5 711.5 115.5 713.5 117.5 714.5 117.5 717.5 122.5 717.5 122.5 718.5 126.5 718.5 126.5 715.5 127.5 714.5 127.5 709.5 131.5 705.5 133.5 705.5 133.5 707.5 138.5 707.5 138.5 708.5 142.5 708.5 142.5 706.5 145.5 702.5 145.5 700.5 146.5 699.5 146.5 697.5 144.5 697.5 144.5 694.5 146.5 692.5 150.5 692.5 151.5 691.5 153.5 691.5 153.5 692.5 155.5 692.5 160.5 687.5 165.5 687.5 167.5 691.5 170.5 693.5 170.5 696.5 173.5 696.5 173.5 694.5 175.5 693.5 179.5 693.5 179.5 692.5 184.5 689.5 186.5 689.5 186.5 690.5 188.5 690.5 188.5 688.5 189.5 687.5 192.5 687.5 192.5 686.5 201.5 686.5 206.5 681.5 211.5 681.5 214.5 679.5 216.5 679.5 216.5 681.5 217.5 684.5 219.5 684.5 222.5 681.5 222.5 678.5 223.5 676.5 223.5 674.5 222.5 673.5 222.5 670.5 220.5 669.5 220.5 667.5 222.5 667.5 224.5 671.5 229.5 671.5 229.5 673.5 230.5 673.5 230.5 668.5 233.5 668.5 235.5 667.5 235.5 663.5 234.5 663.5 234.5 657.5 233.5 656.5 232.5 655.5 228.5 655.5 228.5 654.5 232.5 654.5 232.5 653.5 235.5 653.5 235.5 650.5 234.5 650.5 234.5 648.5 235.5 646.5 237.5 646.5 237.5 642.5 234.5 642.5 234.5 637.5 235.5 635.5 235.5 633.5 240.5 633.5 243.5 637.5 248.5 637.5 249.5 636.5 250.5 635.5 254.5 635.5 256.5 637.5 256.5 638.5 261.5 638.5 262.5 636.5 264.5 636.5 275.5 643.5 279.5 643.5 279.5 644.5 282.98 644.5"/>
  </g>
  <polygon class="cls-4" points="655.5 477.5 655.5 478.5 656.5 478.5 656.5 479.5 657.5 479.5 657.5 478.5 657.5 477.5 656.5 477.5 655.5 477.5"/>
  <polygon class="cls-4" points="48.5 82.5 44.5 87.5 44.5 93.5 40.5 100.5 38.5 104.5 33.5 123.5 33.5 125.5 40.5 141.5 40.5 184.5 24.5 217.5 21.5 217.5 16.5 214.5 14.5 214.5 11.5 216.5 11.5 217.5 16.5 217.5 16.5 219.5 12.5 219.5 12.5 222.5 11.5 222.5 11.5 228.5 8.5 233.5 8.5 238.5 7.5 238.5 7.5 243.5 8.5 243.5 10.5 256.5 18.5 269.5 22.5 269.5 25.5 274.5 29.5 274.5 34.5 283.5 39.5 289.5 39.5 293.5 45.5 304.5 47.5 304.5 47.5 310.5 48.5 310.5 48.5 313.5 52.5 320.5 64.5 331.5 64.5 333.5 66.5 333.5 66.5 331.5 80.5 342.5 86.5 342.5 101.5 356.5 101.5 358.5 101.5 364.5 101.5 370.5 103.5 370.5 108.5 364.5 110.5 364.5 118.5 369.5 124.5 373.5 133.5 376.5 142.5 381.5 150.5 384.5 159.5 386.5 176.5 389.5 192.5 394.5 192.5 404.5 193.5 404.5 193.5 411.5 194.5 412.5 200.5 412.5 200.5 402.5 209.5 395.5 217.5 395.5 218.5 399.5 228.5 406.5 230.5 406.5 230.5 403.5 235.5 403.5 235.5 406.5 233.5 406.5 233.5 413.5 232.5 413.5 232.5 419.5 235.5 419.5 235.5 420.5 232.5 422.5 232.5 426.5 233.5 427.5 233.5 429.5 236.5 429.5 236.5 433.5 235.5 433.5 235.5 438.5 232.5 438.5 232.5 442.5 227.5 442.5 227.5 440.5 220.5 440.5 220.5 441.5 214.5 441.5 209.5 443.5 208.5 445.5 207.5 445.5 207.5 444.5 205.5 444.5 198.5 460.5 194.5 460.5 194.5 468.5 203.5 477.5 203.5 479.5 204.5 479.5 204.5 482.5 200.5 484.5 197.5 482.5 186.5 482.5 185.5 484.5 185.5 487.5 184.5 489.5 184.5 492.5 182.5 493.5 182.5 502.5 181.5 503.5 181.5 515.5 178.5 519.5 178.5 524.5 166.5 530.5 166.5 533.5 165.5 534.5 161.5 534.5 160.5 538.5 151.5 548.5 151.5 556.5 150.5 556.5 150.5 560.5 149.5 560.5 149.5 562.5 144.5 562.5 144.5 567.5 145.5 567.5 145.5 571.5 139.5 577.5 139.5 580.5 141.5 580.5 141.5 585.5 142.5 585.5 144.5 590.5 145.5 591.5 148.5 591.5 148.5 595.5 146.5 597.5 146.5 601.5 147.5 601.5 148.5 607.5 149.5 608.5 150.5 608.5 150.5 611.5 145.5 613.5 145.5 616.5 144.5 616.5 141.5 614.5 141.5 619.5 138.5 618.5 134.5 618.5 134.5 617.5 131.5 617.5 131.5 619.5 129.5 619.5 129.5 620.5 123.5 620.5 123.5 619.5 119.5 619.5 119.5 620.5 114.5 625.5 110.5 625.5 109.5 623.5 105.5 623.5 103.5 620.5 103.5 615.5 99.5 615.5 99.5 614.5 91.5 614.5 90.5 614.5 88.5 612.5 87.5 612.5 84.5 614.5 80.5 611.5 80.5 610.5 79.5 610.5 77.5 612.5 74.5 612.5 74.5 613.5 73.5 613.5 73.5 619.5 69.5 623.5 69.5 628.5 68.5 628.5 68.5 632.5 64.5 637.5 64.5 642.5 62.5 642.5 62.5 646.5 65.5 651.5 65.5 654.5 68.5 657.5 68.5 658.5 67.5 658.5 67.5 661.5 56.5 666.5 56.5 676.5 56.5 682.5 62.5 682.5 63.5 683.5 68.5 683.5 72.5 688.5 75.5 688.5 75.5 685.5 77.5 685.5 82.5 690.5 86.5 690.5 86.5 693.5 87.5 693.5 87.5 691.5 88.5 691.5 88.5 690.5 92.5 690.5 93.5 694.5 95.5 696.5 96.5 697.5 96.5 698.5 96.5 699.5 94.5 699.5 92.5 701.5 92.5 705.5 88.5 708.5 86.5 708.5 86.5 711.5 79.5 716.5 79.5 718.5 82.5 722.5 82.5 725.5 81.5 725.5 81.5 732.5 81.5 741.5 82.5 742.5 82.5 747.5 88.5 757.5 88.5 761.5 87.5 761.5 87.5 764.5 89.5 765.5 89.5 773.5 92.5 773.5 92.5 770.5 95.5 770.5 97.5 775.5 97.5 778.5 96.5 778.5 96.5 781.5 101.5 787.5 103.5 791.5 103.5 795.5 104.5 796.5 104.5 801.5 105.5 802.5 105.5 808.5 103.5 811.5 101.5 811.5 98.5 811.5 97.5 812.5 99.5 813.5 100.5 814.5 97.5 817.5 95.5 817.5 89.5 810.5 85.5 810.5 76.5 821.5 76.5 824.5 75.5 825.5 70.5 825.5 67.5 822.5 65.5 822.5 54.5 830.5 54.5 832.5 55.5 832.5 55.5 834.5 46.5 844.5 45.5 846.5 45.5 853.5 49.5 857.5 49.5 859.5 46.5 863.5 46.5 866.5 40.5 866.5 38.5 869.5 38.5 873.5 36.5 874.5 36.5 880.5 39.5 882.5 42.5 883.5 45.5 887.5 46.5 890.5 53.5 898.5 53.5 902.5 52.5 903.5 51.5 907.5 48.5 904.5 47.5 904.5 42.5 908.5 41.5 908.5 39.5 905.5 35.5 905.5 34.5 906.5 32.5 906.5 29.5 906.5 28.5 905.5 26.5 905.5 25.5 905.5 24.5 903.5 20.5 900.5 18.5 899.5 15.5 899.5 15.5 898.5 14.5 897.5 13.5 897.5 13.5 898.5 12.5 899.5 10.5 900.5 7.5 900.5 6.5 900.5 6.5 898.5 5.5 898.5 5.5 895.5 5.5 894.5 3.5 894.5 3.5 898.5 1.5 899.5 1.5 900.5 3.5 903.5 3.5 906.5 2.5 907.5 3.5 908.5 3.5 911.5 2.5 911.5 2.5 914.5 1.5 914.5 0.5 915.5 0.5 919.5 2.5 919.5 2.5 924.5 6.5 928.5 6.5 944.5 5.5 946.5 5.5 950.5 4.5 950.5 4.5 954.5 2.5 954.5 2.5 955.5 4.5 957.5 1.5 961.5 1.5 968.5 7.5 973.5 10.5 973.5 11.5 974.5 11.5 981.5 10.5 981.5 10.5 983.5 12.5 984.5 14.5 984.5 15.5 983.5 18.5 983.5 18.5 982.5 22.5 982.5 22.5 983.5 26.5 983.5 26.5 986.5 29.5 986.5 29.5 982.5 32.5 982.5 37.5 977.5 39.5 977.5 40.5 980.5 40.5 983.5 42.5 983.5 44.5 983.5 44.5 982.5 46.5 980.5 46.5 977.5 47.5 977.5 47.5 973.5 50.5 973.5 50.5 974.5 52.5 976.5 57.5 976.5 57.5 979.5 65.5 984.5 78.5 984.5 99.5 965.5 112.5 965.5 112.5 984.5 116.5 988.5 118.5 992.5 120.5 992.5 124.5 987.5 126.5 987.5 126.5 990.5 129.5 993.5 131.5 993.5 135.5 989.5 135.5 987.5 132.5 982.5 132.5 980.5 134.5 980.5 134.5 979.5 135.5 979.5 135.5 976.5 132.5 971.5 132.5 967.5 135.5 964.5 135.5 961.5 147.5 949.5 149.5 949.5 149.5 955.5 150.5 955.5 150.5 966.5 152.5 966.5 156.5 962.5 159.5 963.5 167.5 971.5 171.5 971.5 183.5 956.5 192.5 956.5 192.5 955.5 195.5 951.5 195.5 944.5 187.5 938.5 187.5 930.5 190.5 926.5 191.5 925.5 191.5 920.5 193.5 917.5 196.5 914.5 202.5 911.5 206.5 908.5 207.5 906.5 208.5 905.5 208.5 897.5 207.5 897.5 206.5 895.5 204.5 895.5 204.5 896.5 196.5 896.5 192.5 890.5 192.5 882.5 195.5 876.5 198.5 874.5 203.5 874.5 203.5 875.5 208.5 875.5 209.5 876.5 215.5 882.5 216.5 885.5 215.5 886.5 215.5 890.5 217.5 890.5 218.5 889.5 220.5 889.5 223.5 894.5 223.5 897.5 222.5 898.5 222.5 901.5 221.5 901.5 221.5 905.5 224.5 905.5 230.5 902.5 233.5 902.5 233.5 903.5 234.5 905.5 236.5 905.5 239.5 900.5 239.5 896.5 242.5 892.5 242.5 888.5 246.5 880.5 248.5 880.5 248.5 873.5 250.5 873.5 250.5 865.5 253.5 863.5 253.5 858.5 246.5 847.5 246.5 839.5 251.5 826.5 257.5 812.5 260.5 804.5 265.5 798.5 268.5 796.5 270.5 797.5 273.5 799.5 278.5 799.5 281.5 798.5 284.5 797.5 286.5 795.5 287.5 793.5 288.5 791.5 288.5 787.5 289.5 783.5 291.5 783.5 293.5 785.5 294.5 785.5 297.5 782.5 297.5 781.5 295.5 778.5 295.5 776.5 295.5 775.5 297.5 775.5 297.5 778.5 298.5 780.5 299.5 783.5 302.5 783.5 304.5 783.5 305.5 784.5 307.5 782.5 306.5 779.5 308.5 776.5 310.5 773.5 312.5 770.5 314.5 765.5 316.5 766.5 317.5 767.5 318.5 765.5 317.5 764.5 319.5 762.5 325.5 759.5 329.5 758.5 330.5 758.5 331.5 761.5 330.5 762.5 328.5 765.5 325.5 766.5 322.5 765.5 322.5 771.5 320.5 771.5 318.5 774.5 317.5 775.5 317.5 779.5 311.5 785.5 311.5 790.5 309.5 792.5 309.5 795.5 310.5 796.5 318.5 796.5 326.5 792.5 327.5 792.5 327.5 787.5 326.5 787.5 326.5 781.5 332.5 774.5 334.5 773.5 338.5 765.5 343.5 758.5 347.5 758.5 347.5 752.5 354.5 747.5 355.5 746.5 370.5 746.5 371.5 745.5 373.5 745.5 375.5 740.5 376.5 736.5 377.5 732.5 377.5 729.5 378.5 725.5 379.5 724.5 379.5 721.5 378.5 721.5 378.5 717.5 374.5 716.5 371.5 715.5 368.5 713.5 367.5 713.5 365.5 716.5 362.5 719.5 360.5 720.5 360.5 723.5 361.5 726.5 361.5 728.5 357.5 728.5 354.5 724.5 353.5 725.5 353.5 730.5 352.5 731.5 351.5 731.5 351.5 738.5 347.5 738.5 347.5 729.5 350.5 725.5 351.5 721.5 353.5 719.5 355.5 716.5 355.5 714.5 352.5 709.5 350.5 709.5 329.5 727.5 324.5 727.5 324.5 725.5 326.5 725.5 336.5 714.5 336.5 711.5 340.5 707.5 340.5 703.5 341.5 703.5 341.5 701.5 338.5 699.5 338.5 696.5 343.5 696.5 344.5 694.5 344.5 687.5 340.5 684.5 321.5 684.5 317.5 684.5 312.5 679.5 312.5 676.5 309.5 673.5 306.5 673.5 304.5 667.5 299.5 663.5 296.5 663.5 294.5 662.5 287.5 662.5 285.5 661.5 285.5 653.5 284.5 653.5 282.5 645.5 282.5 644.5 290.5 644.5 295.5 639.5 296.5 637.5 297.5 635.5 297.5 624.5 296.5 615.5 308.5 605.5 311.5 601.5 314.5 598.5 321.5 594.5 329.5 591.5 333.5 591.5 335.5 595.5 335.5 603.5 337.5 606.5 341.5 606.5 346.5 598.5 354.5 599.5 355.5 601.5 354.5 603.5 352.5 606.5 352.5 607.5 355.5 611.5 358.5 611.5 360.5 608.5 362.5 608.5 362.5 612.5 365.5 612.5 368.5 607.5 378.5 607.5 378.5 600.5 373.5 596.5 370.5 596.5 370.5 593.5 369.5 592.5 369.5 584.5 371.5 582.5 375.5 580.5 382.5 579.5 383.5 579.5 383.5 581.5 390.5 591.5 395.5 596.5 400.5 599.5 402.5 602.5 405.5 611.5 405.5 612.5 401.5 613.5 399.5 616.5 398.5 621.5 397.5 623.5 393.5 628.5 387.5 642.5 386.5 643.5 386.5 651.5 385.5 651.5 385.5 654.5 382.5 654.5 381.5 655.5 381.5 657.5 380.5 657.5 380.5 658.5 381.5 658.5 381.5 660.5 378.5 662.5 377.5 666.5 376.5 667.5 375.5 667.5 375.5 669.5 377.5 669.5 377.5 671.5 376.5 671.5 375.5 674.5 372.5 677.5 372.5 680.5 371.5 681.5 371.5 685.5 368.5 685.5 367.5 687.5 367.5 693.5 366.5 693.5 365.5 695.5 365.5 698.5 371.5 698.5 371.5 695.5 370.5 695.5 370.5 692.5 374.5 692.5 374.5 693.5 378.5 693.5 378.5 689.5 380.5 687.5 384.5 687.5 384.5 692.5 388.5 692.5 389.5 690.5 391.5 690.5 391.5 691.5 392.5 691.5 392.5 694.5 390.5 697.5 390.5 698.5 393.5 698.5 396.5 694.5 400.5 694.5 402.5 699.5 403.5 699.5 406.5 696.5 406.5 693.5 405.5 693.5 405.5 690.5 413.5 687.5 414.5 687.5 414.5 690.5 413.5 690.5 413.5 693.5 422.5 693.5 425.5 689.5 428.5 689.5 429.5 690.5 432.5 690.5 432.5 692.5 435.5 692.5 435.5 689.5 434.5 688.5 434.5 685.5 437.5 683.5 438.5 683.5 438.5 684.5 439.5 685.5 440.5 685.5 440.5 682.5 455.5 667.5 461.5 667.5 463.5 670.5 467.5 670.5 471.5 668.5 477.5 668.5 477.5 666.5 483.5 666.5 483.5 667.5 484.5 668.5 485.5 668.5 487.5 666.5 489.5 666.5 490.5 666.5 490.5 664.5 491.5 664.5 491.5 660.5 488.5 657.5 488.5 656.5 489.5 656.5 489.5 655.5 492.5 655.5 492.5 656.5 493.5 657.5 497.5 657.5 497.5 658.5 500.5 658.5 500.5 656.5 501.5 656.5 501.5 653.5 504.5 652.5 505.5 654.5 504.5 657.5 504.5 659.5 506.5 661.5 513.5 661.5 518.5 656.5 519.5 653.5 519.5 651.5 522.5 649.5 525.5 649.5 525.5 650.5 529.5 653.5 534.5 653.5 534.5 652.5 541.5 649.5 548.5 643.5 551.5 643.5 555.5 646.5 565.5 646.5 570.5 641.5 571.5 638.5 570.5 636.5 569.5 635.5 567.5 635.5 566.5 636.5 563.5 636.5 563.5 628.5 560.5 627.5 560.5 625.5 564.5 625.5 564.5 624.5 565.5 624.5 565.5 620.5 560.5 618.5 560.5 616.5 559.5 616.5 559.5 606.5 558.5 606.5 558.5 601.5 559.5 601.5 559.5 590.5 558.5 590.5 558.5 586.5 557.5 586.5 557.5 582.5 556.5 578.5 552.5 574.5 552.5 571.5 553.5 571.5 556.5 565.5 556.5 562.5 555.5 562.5 555.5 559.5 554.5 558.5 554.5 556.5 555.5 556.5 555.5 549.5 552.5 547.5 550.5 547.5 545.5 551.5 542.5 551.5 539.5 548.5 539.5 544.5 545.5 536.5 546.5 535.5 546.5 531.5 542.5 525.5 542.5 522.5 541.5 521.5 541.5 519.5 542.5 519.5 544.5 515.5 546.5 515.5 547.5 516.5 547.5 518.5 555.5 518.5 555.5 519.5 561.5 519.5 564.5 518.5 571.5 518.5 577.5 521.5 579.5 521.5 579.5 519.5 583.5 518.5 583.5 516.5 580.5 513.5 580.5 510.5 575.5 504.5 575.5 500.5 574.5 500.5 574.5 497.5 569.5 497.5 564.5 491.5 560.5 489.5 560.5 487.5 559.5 487.5 559.5 477.5 566.5 470.5 566.5 467.5 567.5 467.5 567.5 466.5 569.5 466.5 572.5 463.5 574.5 463.5 574.5 456.5 575.5 456.5 575.5 454.5 579.5 452.5 589.5 452.5 590.5 449.5 591.5 448.5 591.5 445.5 589.5 444.5 589.5 439.5 588.5 438.5 588.5 434.5 589.5 432.5 612.5 432.5 613.5 431.5 626.5 431.5 629.5 435.5 629.5 441.5 629.5 446.5 627.5 448.5 627.5 451.5 631.5 451.5 638.5 458.5 638.5 464.5 632.5 469.5 632.5 473.5 634.5 477.5 634.5 482.5 636.5 486.5 642.5 488.5 644.5 490.5 643.5 492.5 643.5 493.5 644.5 493.5 645.5 492.5 651.5 489.5 653.5 489.5 655.5 485.5 655.5 483.5 652.5 480.5 652.5 477.5 655.5 473.5 658.5 468.5 663.5 462.5 666.5 461.5 667.5 462.5 667.5 466.5 669.5 466.5 672.5 464.5 673.5 462.5 673.5 459.5 670.5 459.5 670.5 460.5 667.5 460.5 664.5 456.5 664.5 449.5 660.5 444.5 660.5 441.5 666.5 437.5 672.5 437.5 674.5 442.5 678.5 442.5 680.5 439.5 680.5 435.5 684.5 433.5 688.5 428.5 688.5 423.5 682.02 415.17 681.5 414.5 683.5 412.5 683.5 409.5 681.5 406.5 685.5 399.5 688.5 393.5 693.5 388.5 696.5 383.5 696.5 381.5 689.5 372.5 686.5 370.5 683.5 369.5 675.5 369.5 671.5 367.5 667.5 363.5 665.5 364.5 663.5 366.5 660.5 366.5 652.5 359.5 652.5 351.5 648.5 344.5 642.5 343.5 635.5 339.5 632.5 339.5 602.5 353.5 600.5 352.5 599.5 348.5 593.5 341.5 589.5 342.5 588.5 342.5 583.5 335.5 578.5 335.5 577.5 336.5 576.5 335.5 576.5 331.5 570.5 331.5 565.5 336.5 565.5 338.5 562.5 340.5 561.5 343.5 563.5 346.5 563.5 347.5 557.5 347.5 551.5 353.5 547.5 353.5 540.5 359.5 537.5 359.5 534.5 361.5 533.5 363.5 531.5 361.5 531.5 356.5 530.5 354.5 530.5 349.5 533.5 346.5 533.5 337.5 535.5 335.5 535.5 330.5 538.5 328.5 542.5 328.5 542.5 323.5 541.5 319.5 542.5 314.5 540.5 313.5 537.5 313.5 537.5 311.5 537.5 310.5 538.5 310.5 538.5 306.5 536.5 306.5 534.5 304.5 533.5 305.5 528.5 308.5 526.5 308.5 526.5 301.5 528.5 301.5 529.5 300.5 529.5 299.5 523.5 293.5 523.5 290.5 521.5 289.5 519.5 289.5 519.5 288.5 513.5 288.5 509.5 287.5 508.5 286.5 508.5 283.5 512.5 278.5 512.5 274.5 510.5 274.5 510.5 271.5 509.5 270.5 509.5 267.5 507.5 265.5 506.5 263.5 506.5 260.5 505.5 259.5 503.5 259.5 502.5 261.5 500.5 261.5 500.5 260.5 496.5 260.5 494.5 256.5 492.5 256.5 490.5 254.5 488.5 254.5 488.5 248.5 490.5 248.5 490.5 247.5 488.5 246.5 488.5 243.5 487.5 243.5 487.5 239.5 489.5 239.5 490.5 236.5 493.5 232.5 496.5 224.5 497.5 218.5 511.5 218.5 513.5 218.5 514.5 216.5 516.5 215.5 516.5 211.5 517.5 208.5 515.5 207.5 518.5 205.5 519.5 204.5 519.5 200.5 518.5 198.5 516.5 198.5 514.5 197.5 512.5 196.5 508.5 195.5 508.5 194.5 508.5 192.5 509.5 191.5 509.5 189.5 511.5 189.5 511.5 186.5 510.5 185.5 509.5 183.5 504.5 183.5 503.5 181.5 503.5 169.5 506.5 169.5 506.5 168.5 513.5 168.5 518.5 167.5 518.5 165.5 522.5 165.5 524.5 162.5 525.5 160.5 525.5 150.5 514.5 150.5 513.5 149.5 511.5 149.5 502.5 139.5 501.5 139.5 501.5 128.5 500.5 126.5 498.5 126.5 496.5 123.5 496.5 117.5 498.5 115.5 499.5 105.5 500.5 100.5 502.5 100.5 502.5 93.5 505.5 92.5 509.5 95.5 509.5 98.5 512.5 98.5 512.5 99.5 514.5 99.5 514.5 96.5 516.5 96.5 516.5 93.5 517.5 93.5 517.5 86.5 515.5 86.5 515.5 85.5 516.5 83.5 520.5 81.5 520.5 75.5 519.5 75.5 519.5 71.5 516.5 71.5 516.5 68.5 518.5 68.5 518.5 65.5 516.5 63.5 507.5 63.5 507.5 58.5 506.5 56.5 504.5 56.5 504.5 62.5 500.5 64.5 494.5 64.5 491.5 67.5 489.5 67.5 482.5 59.5 482.5 57.5 484.5 57.5 486.5 55.5 486.5 50.5 485.5 49.5 486.5 48.5 486.5 44.5 484.5 44.5 484.5 36.5 485.5 36.5 485.5 28.5 488.5 28.5 488.5 24.5 489.5 24.5 489.5 18.5 488.5 15.5 487.5 15.5 487.5 11.5 487.5 10.5 483.5 10.5 480.5 7.5 480.5 4.5 479.5 4.5 479.5 3.5 476.5 3.5 476.5 0.5 470.5 0.5 464.5 9.5 464.5 12.5 462.5 15.5 456.5 15.5 450.5 20.5 449.5 21.5 446.5 19.5 441.5 19.5 441.5 17.5 439.5 16.5 439.5 18.5 437.5 18.5 437.5 20.5 434.5 20.5 432.5 21.5 431.5 23.5 430.5 23.5 428.5 21.5 427.5 22.5 426.5 24.5 423.5 23.5 424.5 26.5 425.5 27.5 425.5 31.5 423.5 33.5 419.5 33.5 414.5 28.5 411.5 28.5 411.5 27.5 405.5 27.5 401.5 30.5 401.5 34.5 400.5 34.5 399.5 37.5 399.5 40.5 397.5 40.5 397.5 43.5 394.5 45.5 394.5 42.5 393.5 42.5 393.5 38.5 389.5 34.5 389.5 28.5 388.5 25.5 386.5 23.5 384.5 23.5 382.5 26.5 381.5 26.5 378.5 22.5 375.5 22.5 374.5 21.5 372.5 19.5 372.5 15.5 373.5 15.5 373.5 10.5 371.5 10.5 370.5 11.5 366.5 11.5 365.5 13.5 363.5 13.5 363.5 11.5 361.5 11.5 358.5 17.5 356.5 19.5 355.5 21.5 352.5 21.5 350.5 18.5 348.5 18.5 347.5 19.5 346.5 18.5 344.5 15.5 342.5 14.5 341.5 14.5 341.5 18.5 340.5 18.5 340.5 21.5 340.5 25.5 339.5 26.5 339.5 27.5 340.5 29.5 340.5 33.5 338.5 33.5 338.5 35.5 340.5 35.5 339.5 37.5 336.5 37.5 334.5 40.5 333.5 41.5 333.5 46.5 331.5 47.5 331.5 50.5 333.5 53.5 333.5 56.5 330.5 58.5 330.5 62.5 328.5 65.5 327.5 68.5 325.5 70.5 322.5 72.5 320.5 71.5 319.5 70.5 317.5 70.5 316.5 72.5 316.5 73.5 314.5 73.5 313.5 72.5 311.5 72.5 311.5 76.5 310.5 77.5 309.5 77.5 308.5 77.5 308.5 82.5 305.5 85.5 304.5 86.5 301.5 85.5 297.5 84.5 297.5 79.5 293.5 79.5 289.5 83.5 283.5 83.5 283.5 85.5 275.5 85.5 274.5 82.5 263.5 82.5 260.5 81.5 252.5 81.5 251.5 85.5 251.5 89.5 248.5 91.5 246.5 94.5 244.5 94.5 244.5 93.5 241.5 93.5 239.5 90.5 235.5 90.5 230.5 85.5 229.5 85.5 228.5 86.5 226.5 86.5 223.5 83.5 220.5 83.5 218.5 86.5 216.5 86.5 216.5 85.5 211.5 85.5 194.5 96.5 194.5 97.5 184.5 97.5 181.5 94.5 175.5 94.5 175.5 93.5 151.5 93.5 150.5 92.5 147.5 92.5 147.5 94.5 145.5 94.5 139.5 85.5 133.5 84.5 133.5 87.5 127.5 89.5 122.5 89.5 122.5 93.5 120.5 93.5 120.5 96.5 117.5 98.5 107.5 98.5 104.5 101.5 102.5 101.5 98.5 97.5 98.5 94.5 97.5 93.5 96.5 89.5 94.5 89.5 90.5 92.5 86.5 93.5 82.5 95.5 79.5 95.5 79.5 91.5 64.5 86.5 60.5 86.5 57.5 83.5 54.5 83.5 53.5 85.5 49.5 85.5 49.5 82.5 48.5 82.5"/>
  <polygon class="cls-4" points="311.5 859.5 310.5 865.5 306.5 871.5 303.5 877.5 303.5 883.5 308.5 888.5 312.5 888.5 320.5 881.5 321.5 877.5 317.5 872.5 317.5 868.5 316.5 867.5 316.5 859.5 311.5 859.5"/>
  <polygon class="cls-4" points="377.5 758.5 377.5 763.5 380.5 763.5 382.5 762.5 384.5 763.5 387.5 761.5 387.5 760.5 385.5 760.5 381.5 761.5 381.5 759.5 380.5 759.5 380.5 757.5 377.5 757.5 377.5 758.5"/>
  <polygon class="cls-4" points="465.5 748.5 463.5 748.5 463.5 749.5 464.5 751.5 467.5 751.5 467.5 748.5 465.5 748.5 465.5 748.5"/>
  <polygon class="cls-4" points="450.5 681.5 450.5 683.5 452.5 686.5 454.5 685.5 452.5 681.5 450.5 681.5"/>
  <polygon class="cls-4" points="505.5 664.5 505.5 667.5 506.5 668.5 509.5 668.5 509.5 669.5 510.5 669.5 512.5 667.5 509.5 664.5 509.5 662.5 507.5 662.5 507.5 664.5 505.5 664.5"/>
  <polygon class="cls-4" points="523.5 660.5 523.5 663.5 525.5 663.5 525.5 661.5 523.5 660.5"/>
  <line class="cls-4" x1="508.5" y1="727.5" x2="509.5" y2="728.5"/>
  <polygon class="cls-4" points="644.5 795.5 644.5 794.5 648.5 789.5 650.5 789.5 651.5 790.5 651.5 791.5 650.5 791.5 647.5 794.5 646.5 794.5 645.5 795.5 644.5 795.5"/>
  <polygon class="cls-4" points="679.5 610.5 679.5 612.5 680.5 613.5 682.5 613.5 682.5 611.5 681.5 610.5 679.5 610.5"/>
  <polygon class="cls-4" points="678.5 614.5 678.5 615.5 679.5 616.5 681.5 616.5 681.5 615.5 679.5 614.5 678.5 614.5"/>
  <polygon class="cls-4" points="594.5 574.5 594.5 578.5 593.5 578.5 593.5 581.5 596.5 584.5 598.5 584.5 599.5 583.5 599.5 581.5 598.5 580.5 598.5 579.5 597.5 579.5 597.5 574.5 594.5 574.5"/>
  <polygon class="cls-4" points="617.5 576.5 617.5 580.5 618.5 580.5 618.5 582.5 619.5 581.5 619.5 579.5 620.5 579.5 620.5 575.5 618.5 575.5 617.5 576.5 617.5 576.5"/>
  <polygon class="cls-4" points="576.5 582.5 576.5 580.5 579.5 579.5 580.5 579.5 580.5 581.5 578.5 581.5 577.5 581.5 576.5 582.5"/>
  <polygon class="cls-4" points="582.5 590.5 583.5 590.5 583.5 591.5 584.5 591.5 584.5 592.5 583.5 592.5 582.5 592.5 582.5 590.5 582.5 590.5"/>
  <polygon class="cls-4" points="571.5 543.5 571.5 547.5 570.5 547.5 570.5 548.5 572.5 549.5 573.5 549.5 574.5 548.5 574.5 543.5 571.5 543.5"/>
  <polygon class="cls-4" points="570.5 551.5 570.5 553.5 571.5 553.5 571.5 550.5 570.5 550.5 570.5 551.5"/>
  <polygon class="cls-4" points="635.5 488.5 634.5 490.5 634.5 491.5 637.5 493.5 639.5 493.5 639.5 490.5 637.5 488.5 635.5 488.5"/>
  <polygon class="cls-4" points="623.5 484.5 624.5 485.5 625.5 486.5 625.5 488.5 626.5 488.5 627.5 485.5 626.5 485.5 624.5 483.5 623.5 484.5"/>
  <polygon class="cls-4" points="670.5 471.5 671.5 471.5 671.5 468.5 672.5 468.5 672.5 467.5 673.5 467.5 673.5 465.5 675.5 465.5 676.5 467.5 676.5 468.5 674.5 468.5 673.5 469.5 673.5 471.5 672.5 472.5 670.5 472.5 670.5 471.5"/>
  <polygon class="cls-4" points="678.5 460.5 678.5 461.5 679.5 461.5 679.5 462.5 680.5 463.5 684.5 463.5 684.5 465.5 686.5 465.5 686.5 463.5 687.5 463.5 688.5 462.5 692.5 462.5 692.5 461.5 695.5 461.5 697.5 463.5 698.5 463.5 698.5 464.5 699.5 464.5 699.5 463.5 700.5 463.5 700.5 459.5 696.5 459.5 691.5 453.5 687.5 453.5 687.5 457.5 685.5 457.5 685.5 456.5 682.5 456.5 679.5 459.5 678.5 460.5"/>
  <polygon class="cls-4" points="709.5 448.5 709.5 449.5 708.5 449.5 708.5 450.5 709.5 451.5 709.5 453.5 711.5 453.5 712.5 452.5 712.5 450.5 711.5 450.5 711.5 448.5 711.5 447.5 709.5 447.5 709.5 448.5"/>
  <line class="cls-4" x1="709.5" y1="457.5" x2="710.5" y2="458.5"/>
  <polygon class="cls-4" points="697.5 415.5 697.5 418.5 698.5 418.5 698.5 419.5 699.5 419.5 700.5 418.5 701.5 418.5 701.5 417.5 705.5 417.5 705.5 416.5 704.5 415.5 703.5 414.5 702.5 414.5 701.5 415.5 700.5 415.5 699.5 414.5 698.5 414.5 697.5 415.5"/>
  <polygon class="cls-4" points="731.5 382.5 731.5 384.5 732.5 384.5 732.5 385.5 733.5 385.5 734.5 384.5 735.5 384.5 735.5 382.5 734.5 382.5 734.5 381.5 734.5 380.5 732.5 380.5 732.5 381.5 731.5 381.5 731.5 382.5"/>
  <polygon class="cls-4" points="887.5 553.5 887.5 557.5 888.5 557.5 888.5 558.5 889.5 559.5 890.5 559.5 891.5 558.5 891.5 557.5 890.5 557.5 890.5 555.5 889.5 554.5 889.5 553.5 887.5 553.5"/>
  <polygon class="cls-4" points="194.5 962.5 194.5 959.5 195.5 959.5 195.5 957.5 197.5 954.5 199.5 954.5 199.5 957.5 198.5 959.5 197.5 959.5 197.5 962.5 197.5 963.5 194.5 963.5 194.5 962.5"/>
  <polygon class="cls-4" points="154.5 967.5 154.5 970.5 155.5 970.5 155.5 971.5 157.5 971.5 157.5 970.5 157.5 969.5 155.5 969.5 155.5 967.5 154.5 967.5"/>
  <polygon class="cls-4" points="104.5 979.5 104.5 982.5 107.5 986.5 107.5 988.5 108.5 988.5 108.5 980.5 107.5 980.5 107.5 977.5 105.5 977.5 105.5 979.5 104.5 979.5"/>
</svg>
复制代码

看着晕吧?可见当初我想的自己用代码打出这个地图是有多么的荒谬。不过看不懂没关系,只需要把这段代码复制到编辑器然后保存成xxx.svg,再用浏览器打开的话就会看到这样一幅景象:

免费放出青岛地图的代码有木有,如果屏幕前的你有类似的需求的话,希望这段代码可以帮到你。

加入动画

光有青岛的矢量地图还不够,整理完SVG代码后还要用程序来控制一下动画:

<template>
  <svg id="tsing-tao-map" ref="map" data-name="tsing-tao-map" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 892 994" @touchend="checkScale">
    <defs>
      <defs>
        <filter id="filter" x="0" y="0" width="200%" height="200%">
          <feOffset result="offOut" in="SourceGraphic" dx="0" dy="2" />
          <feColorMatrix result="matrixOut" in="offOut" type="matrix"
          values=".15 0 0 0 0 0 .15 0 0 0 0 0 .15 0 0 0 0 0 1 0" />
          <feGaussianBlur result="blurOut" in="matrixOut" stdDeviation="1" />
          <feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
        </filter>
      </defs>
    </defs>
    <polyline class="cls-1" ref="line0" points=" "/>
    <g class="cls-2">
      <polyline class="cls-3" ref="line1" points=" "/>
      <polyline class="cls-3" ref="line2" points=" "/>
      <polyline class="cls-3" ref="line3" points=" "/>
      <polyline class="cls-3" ref="line4" points=" "/>
      <polyline class="cls-3" ref="line5" points=" "/>
      <polyline class="cls-3" ref="line6" points=" "/>
      <polyline class="cls-3" ref="line7" points=" "/>
      <polyline class="cls-3" ref="line8" points=" "/>
    </g>
    <polygon class="cls-4" ref="line9" points=" "/>
    <polygon class="cls-4" ref="line10" points=" "/>
    <polygon class="cls-4" ref="line11" points=" "/>
    <polygon class="cls-4" ref="line12" points=" "/>
    <polygon class="cls-4" ref="line13" points=" "/>
    <polygon class="cls-4" ref="line14" points=" "/>
    <polygon class="cls-4" ref="line15" points=" "/>
    <polygon class="cls-4" ref="line16" points=" "/>
    <line class="cls-4" ref="line38" x1=" " y1=" " x2=" " y2=" "/>
    <polygon class="cls-4" ref="line17" points=" "/>
    <polygon class="cls-4" ref="line18" points=" "/>
    <polygon class="cls-4" ref="line19" points=" "/>
    <polygon class="cls-4" ref="line20" points=" "/>
    <polygon class="cls-4" ref="line21" points=" "/>
    <polygon class="cls-4" ref="line22" points=" "/>
    <polygon class="cls-4" ref="line23" points=" "/>
    <polygon class="cls-4" ref="line24" points=" "/>
    <polygon class="cls-4" ref="line25" points=" "/>
    <polygon class="cls-4" ref="line26" points=" "/>
    <polygon class="cls-4" ref="line27" points=" "/>
    <polygon class="cls-4" ref="line28" points=" "/>
    <polygon class="cls-4" ref="line29" points=" "/>
    <polygon class="cls-4" ref="line30" points=" "/>
    <line class="cls-4" ref="line39" x1=" " y1=" " x2=" " y2=" "/>
    <polygon class="cls-4" ref="line31" points=" "/>
    <polygon class="cls-4" ref="line32" points=" "/>
    <polygon class="cls-4" ref="line33" points=" "/>
    <polygon class="cls-4" ref="line34" points=" "/>
    <polygon class="cls-4" ref="line35" points=" "/>
    <polygon class="cls-4" ref="line36" points=" "/>
  </svg>
</template>

<script>
export default {
  data () {
    return {
      list: []
    }
  },
  mounted () {
    Object.entries(this.$refs).forEach(([k, v]) => k.includes('line') && this.list.push(v))
    this.clearOffset(this.list)
  },
  methods: {
    clearOffset (list) {
      list.forEach(i => {
        i.setAttribute('stroke-dasharray', i.getTotalLength())
        i.setAttribute('stroke-dashoffset', i.getTotalLength())
        setTimeout(() => i.classList.contains('cls-4') && i.getTotalLength() > 100 && i.classList.add('no-offset'), 0)
        setTimeout(() => i.classList.add('no-offset'), 6000)
      })
    }
  }
}
</script>

<style lang="scss">
:root {
  background: black;
}
#tsing-tao-map {
  --stroke: white;
  --opacity: .8;
  --width: 1.2;
  height: 100vh;
  padding-left: 6vw;
  overflow: visible;
}
.cls- {
  &1, &3, &4 {
    fill: none;
    stroke: var(--stroke) { miterlimit: 10; width: var(--width) }
  }
  &1, &2 { opacity: var(--opacity) }
  &1, &2, &3 { stroke-width: calc(var(--width) * .8) }
}
.no-offset {
  transition: stroke-dashoffset 6s ease-in-out;
  stroke-dashoffset: 0
}
</style>
复制代码

等等、不是说不依赖任何框架或库的吗?这怎么看起来那么像 Vue 的代码?

这个地图以及后续的一系列确实都是原生 SVG 做的,但是如果写成纯 JS 与 SVG 进行交互的话不是看起来不够直观嘛。为了让大家看得更加直观,我把 SVG 里面的那些坐标点都删掉了,如果需要用到的话可以直接去上个 SVG 代码中把坐标点复制过来。

实际上在这个例子中 Vue 做的都是一些很普通的事

你随时都可以将其改造成 React、Angular 甚至 jQuery 代码
只不过 Vue 在做简单逻辑时看起来会更加的直观

运行结果:

由于动态图的大小限制,看起来好像一卡一卡的,但实际运行在电脑上时效果很流畅( 后面所有动态图也是一样的道理 )。

虽然描边动画是写出来了,但是又有另一个比较棘手的问题:坐标系没了!

坐标系换算

坐标系没了是啥意思呢?由于 ECharts 也是百度出品的,所以 ECharts 地图的坐标系就是百度坐标系,百度搜"百度坐标",第一个出现的就是百度拾取坐标系统:

鼠标放在哪个位置就会出现那个位置对应的坐标,比如:

但是 SVG 没有百度坐标系啊!不过我们还是可以自己模仿的,来分析一下思路:
假如把 SVG 想象成一张图片,那么这张图片就有宽和高对吧(这不废话么!)
那么左上角为坐标原点(计算机眼中的原点都在左上角),宽就是横坐标,高就是纵坐标
对应到百度地图就是要找到 SVG 最左上角的那个位置:

不过当前 SVG 图片有点大,缩小一点然后再对齐:

差不多就是这样,虽然并没有对的太齐 ( 美工图片多少存在一点误差,无法完全重合 ),但是明白意思就行。
然后给个边框方便查看左上角在哪个具体位置:

这回知道该怎么写算法了吧,后台是按照百度坐标系传的点位。
传过来是百度的坐标点,你就把它减去左上角的坐标点,得出来的就是相对于左上角的位置了。然后再乘以这个图片缩放到百度地图的那个比例,算法如下:

location ([x, y]) {
      return [(x - 119.87) * 773, (37.13 - y) * 637.445]
}
复制代码

为什么横坐标是 x 减去左上角横坐标,到了纵坐标就变成了左上角纵坐标减去 y 了呢?(* x 和 y 为传进来的坐标* )

这是因为百度地图的原点并不是世界地图的最左上角,而是世界地图的中心位置:

这就和计算机的左上角为原点不一样了,这才是我们中学学过的平面直角坐标系。还记得我国著名思想家、文学家鲁迅曾经说过的那句话吗:

  • 奇变偶不变 符号看象限

定睛一看,咱们国家正处于第一象限:

横坐标都是越往右越大,所以是传进来的值减去左上角。
而纵坐标刚好和计算机坐标系相反,第一象限是越往下走纵坐标越小、计算机坐标是越往下走纵坐标越大。
所以需要用左上角的值来减去传进来的纵坐标。

当然这个算法是为了我做的青岛地图量身定做的,如果你做的是别的地区的地图,那么只要算好左上角坐标以及图片比例然后稍微改一下数值即可,原理都是不变的。

边框放大后变细

虽然 SVG 是矢量图,放大缩小都不会失真,但放大后地图边框也跟着一起放大了啊:

这种效果看起来就不太好了,我们来加入一个 touchend 事件:

<template>
  <svg ref="map" @touchend="checkScale"></svg>
</template>

<script>
import Panzoom from '@panzoom/panzoom'
  
export default {
  data () {
  	panzoom: null,
  	list: []
  },
  mounted () {
  	Object.entries(this.$refs).forEach(([k, v]) => k.includes('line') && this.list.push(v))
    this.clearOffset(this.list)
  	this.panzoom = Panzoom(this.$refs.map, { minScale: 1, maxScale: 100 })
  },
  methods: {
    clearOffset (list) {
        list.forEach(i => {
          i.setAttribute('stroke-dasharray', i.getTotalLength())
          i.setAttribute('stroke-dashoffset', i.getTotalLength())
          setTimeout(() => i.classList.contains('cls-4') && i.getTotalLength() > 100 && i.classList.add('no-offset'), 0)
          setTimeout(() => i.classList.add('no-offset'), 6000)
        })
      }
    },
  	checkScale () {
      const scale = this.panzoom.getScale()
  	  const map = this.$refs.map
  
      if (scale > 36) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .02;')
      } else if (scale > 25) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .05;')
      } else if (scale > 16) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .1;')
      } else if (scale > 6) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .2;')
      } else if (scale > 2) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .5;')
      } else if (scale > 1.2) {
        map.setAttribute('style', map.getAttribute('style') + '--width: 1;')
      } else {
        map.setAttribute('style', map.getAttribute('style') + '--width: 1.2;')
      }
    }
  }
}
</script>
复制代码

效果如下:

这里的 --widthCSS 变量,如果不太了解什么是 CSS 变量 的同学可以点击 CSS 变量 这个链接。

加入坐标点

我们随便在 拾取坐标系统 找几个青岛的点然后放进 data 中,用来模拟后台传过来的数据:

<template>
  <svg ref="map" @touchend="checkScale">
  	<!-- 这里省略之前前面那些一大堆xml 把新代码放在svg标签里的末尾即可 -->
    <template v-for="(i, j) of points">
      <circle class="point" :cx="i[0]" :cy="i[1]" :r="pointStyle.size" :key="`point${j}`"
        :fill="pointStyle.color" :stroke="pointStyle.border.color" :stroke-width="pointStyle.border.width"
      />
      <text :key="`txt${j}`" :x="i[0]" :y="i[1] - 18" class="txt">{{i[2]}}</text>
    </template>
  </svg>
</template>

<script>
import Panzoom from '@panzoom/panzoom'
  
export default {
  data () {
  	panzoom: null,
  	list: [],
  	pointStyle: {
      size: 10,
      color: 'rgb(255, 66, 88)',
      border: { width: 2, color: 'rgb(255, 255, 255)' }
    },
    pointsData: [{
      id: 0,
      title: '中国xx公司',
      point: [120.111, 36.8003]
    }, {
      id: 1,
      title: '山东xx公司',
      point: [120.094693, 35.928573]
    }, {
      id: 2,
      title: '青岛xx公司',
      point: [120.459023, 36.399622]
    }]
  },
  mounted () {
  	Object.entries(this.$refs).forEach(([k, v]) => k.includes('line') && this.list.push(v))
    this.clearOffset(this.list)
  	this.panzoom = Panzoom(this.$refs.map, { minScale: 1, maxScale: 100 })
  },
  methods: {
    clearOffset (list) {
        list.forEach(i => {
          i.setAttribute('stroke-dasharray', i.getTotalLength())
          i.setAttribute('stroke-dashoffset', i.getTotalLength())
          setTimeout(() => i.classList.contains('cls-4') && i.getTotalLength() > 100 && i.classList.add('no-offset'), 0)
          setTimeout(() => i.classList.add('no-offset'), 6000)
        })
      }
    },
  	checkScale () {
      const scale = this.panzoom.getScale()
  	const map = this.$refs.map
  
      if (scale > 36) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .02;')
      } else if (scale > 25) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .05;')
      } else if (scale > 16) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .1;')
      } else if (scale > 6) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .2;')
      } else if (scale > 2) {
        map.setAttribute('style', map.getAttribute('style') + '--width: .5;')
      } else if (scale > 1.2) {
        map.setAttribute('style', map.getAttribute('style') + '--width: 1;')
      } else {
        map.setAttribute('style', map.getAttribute('style') + '--width: 1.2;')
      }
    }
  },
  computed: {
    points () {
      return this.pointsData.map(({ point: [x, y], title }) => [(x - 119.87) * 773, (37.13 - y) * 637.445, title])
    }
  }
}
</script>

<style lang="scss">
:root {
  background: black;
}
#tsing-tao-map {
  --stroke: white;
  --opacity: .8;
  --width: 1.2;
  height: 100vh;
  padding-left: 6vw;
  overflow: visible;
}
.cls- {
  &1, &3, &4 {
    fill: none;
    stroke: var(--stroke) { miterlimit: 10; width: var(--width) }
  }
  &1, &2 { opacity: var(--opacity) }
  &1, &2, &3 { stroke-width: calc(var(--width) * .8) }
}
.no-offset {
  transition: stroke-dashoffset 6s ease-in-out;
  stroke-dashoffset: 0
}
.txt {
  stroke: white { width: .6 }
  fill: none;
  filter: url(#filter);
  text: { anchor: middle }
  font: { size: 18px; weight: bold }
}
.point, .txt {
  animation: show-points 1s 12s both;
}
@keyframes show-points {
  from {
  	transform: translateY(-10%);
    opacity: 0;
    pointer-events: none
  }
  to {
    opacity: 1;
    pointer-events: auto;
  }
}
</style>
复制代码

运行结果:

当然这里还没写缩放地图时,坐标点也随之放大缩小的逻辑,因为这段逻辑和描边缩放的逻辑差不多,就不在这里浪费篇幅了。

之后你只需在 <circle> 这个标签中加入点击事件,然后做你想做的逻辑即可,给大家看看青岛银行的点击效果:

这里面没写数据,所以是个空版面。

结语

怎么样是不是很炫呢?虽然说这篇博客写的是青岛地图,但是思路都是相通的。
如果你有一个需求要做别的什么地图,就按照我说的一步步来:

  • 先在网上找个带描边的地图
  • 让美工用 AI 照着描一遍
  • 导出 SVG 格式的矢量图
  • 用编辑器打开就可以看到代码
  • 用 JS 和 CSS 来控制 SVG
  • 写一个坐标系转换算法
  • 缩放地图时描边和坐标点要动态缩小放大

青岛银行这个项目不止这一处的动画很炫,几乎处处都很炫,给大家放几张动态图:

喜欢哪个效果可以直接在评论区留言,如果某一效果大家特别感兴趣的话我会再出一篇博客来讲解。

往期精彩文章: