diff --git a/.eslintrc.js b/.eslintrc.cjs similarity index 95% rename from .eslintrc.js rename to .eslintrc.cjs index 2925873c0e4..86e239483c2 100644 --- a/.eslintrc.js +++ b/.eslintrc.cjs @@ -8,6 +8,7 @@ module.exports = { extends: [ 'eslint:recommended', 'plugin:react/recommended', + 'plugin:react/jsx-runtime', 'plugin:prettier/recommended', ], overrides: [], diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 612b4bf6aad..c67c4d26ef3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,13 +8,13 @@ jobs: strategy: max-parallel: 4 matrix: - python-version: ['3.8', '3.9', '3.10'] + python-version: ['3.9', '3.10', '3.11', '3.12'] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} id: setup_python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} cache: 'pip' diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml index 6850b560885..1c1db5eada6 100644 --- a/.github/workflows/gh-pages.yml +++ b/.github/workflows/gh-pages.yml @@ -46,11 +46,14 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 + with: + # if your default branches is not master, please change it here + ref: master - name: Cache Data Files if: inputs.save_data_in_github_cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | activities @@ -68,11 +71,11 @@ jobs: ${{ inputs.data_cache_prefix }}- - name: Setup Node.js environment - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: '18' + node-version: '20' - - uses: pnpm/action-setup@v2 + - uses: pnpm/action-setup@v3 name: Install pnpm with: version: 8 @@ -83,7 +86,7 @@ jobs: run: | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV - - uses: actions/cache@v3 + - uses: actions/cache@v4 name: Setup pnpm cache with: path: ${{ env.STORE_PATH }} @@ -97,10 +100,10 @@ jobs: run: PATH_PREFIX=/${{ github.event.repository.name }} pnpm build - name: Upload artifact - uses: actions/upload-pages-artifact@v1 + uses: actions/upload-pages-artifact@v3 with: # Upload dist repository path: './dist' - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/run_data_sync.yml b/.github/workflows/run_data_sync.yml index a81889d89be..065a175262f 100644 --- a/.github/workflows/run_data_sync.yml +++ b/.github/workflows/run_data_sync.yml @@ -13,15 +13,19 @@ on: - run_page/strava_sync.py - run_page/gen_svg.py - run_page/garmin_sync.py + - run_page/coros_sync.py - run_page/keep_sync.py - run_page/gpx_sync.py - run_page/tcx_sync.py + - run_page/tcx_to_garmin_sync.py - run_page/garmin_to_strava_sync.py + - run_page/keep_to_strava_sync.py + - run_page/oppo_sync.py - requirements.txt env: # please change to your own config. - RUN_TYPE: pass # support strava/nike/garmin/garmin_cn/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon, Please change the 'pass' it to your own + RUN_TYPE: pass # support strava/nike/garmin/coros/garmin_cn/garmin_sync_cn_global/keep/only_gpx/only_fit/nike_to_strava/strava_to_garmin/tcx_to_garmin/strava_to_garmin_cn/garmin_to_strava/garmin_to_strava_cn/codoon/oppo, Please change the 'pass' it to your own ATHLETE: yihong0618 TITLE: Yihong0618 Running MIN_GRID_DISTANCE: 10 # change min distance here @@ -49,11 +53,11 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Set up Python id: setup_python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.10' cache: pip @@ -65,7 +69,7 @@ jobs: - name: Cache Data Files if: env.SAVE_DATA_IN_GITHUB_CACHE == 'true' - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | activities @@ -97,6 +101,17 @@ jobs: run: | python run_page/keep_sync.py ${{ secrets.KEEP_MOBILE }} ${{ secrets.KEEP_PASSWORD }} --with-gpx + - name: Run sync Coros script + if: env.RUN_TYPE == 'coros' + run: | + python run_page/coros_sync.py ${{ secrets.COROS_ACCOUNT }} ${{ secrets.COROS_PASSWORD }} + + - name: Run sync Keep_to_strava script + if: env.RUN_TYPE == 'keep_to_strava_sync' + run: | + python run_page/keep_to_strava_sync.py ${{ secrets.KEEP_MOBILE }} ${{ secrets.KEEP_PASSWORD }} ${{ secrets.STRAVA_CLIENT_ID }} ${{ secrets.STRAVA_CLIENT_SECRET }} ${{ secrets.STRAVA_CLIENT_REFRESH_TOKEN }} --sync-types running cycling hiking + # If you only want to sync `type running` modify args --sync-types running, default script is to sync all data (rides, hikes and runs). + - name: Run sync Strava script if: env.RUN_TYPE == 'strava' run: | @@ -108,6 +123,12 @@ jobs: run: | python run_page/codoon_sync.py ${{ secrets.CODOON_MOBILE }} ${{ secrets.CODOON_PASSWORD }} + - name: Run sync tcx to Garmin script + if: env.RUN_TYPE == 'tcx_to_garmin' + run: | + # python run_page/tcx_to_garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING }} + python run_page/tcx_to_garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING_CN }} --is-cn + # for garmin if you want generate `tcx` you can add --tcx command in the args. - name: Run sync Garmin script if: env.RUN_TYPE == 'garmin' @@ -125,6 +146,15 @@ jobs: # If you only want to sync `type running` add args --only-run, default script is to sync all data (rides and runs). # python run_page/garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING_CN }} --only-run --is-cn + - name: Run sync Garmin CN to Garmin script + if: env.RUN_TYPE == 'garmin_sync_cn_global' + run: | + # make garimin secret string `python run_page/garmin_sync_cn_global.py ${email} ${password} --is-cn + python run_page/garmin_sync_cn_global.py ${{ secrets.GARMIN_SECRET_STRING_CN }} ${{ secrets.GARMIN_SECRET_STRING }} + # If you only want to sync `type running` add args --only-run, default script is to sync all data (rides and runs). + # python run_page/garmin_sync_cn_global.py ${{ secrets.GARMIN_SECRET_STRING_CN }} ${{ secrets.GARMIN_SECRET_STRING }} --only-run + + - name: Run sync Only GPX script if: env.RUN_TYPE == 'only_gpx' run: | @@ -165,6 +195,13 @@ jobs: run: | python run_page/tulipsport_sync.py ${{ secrets.TULIPSPORT_TOKEN }} --with-gpx + - name: Run sync Oppo heytap script, note currently this script is not worked + if: env.RUN_TYPE == 'oppo' + run: | + python run_page/oppo_sync.py ${{ secrets.OPPO_ID }} ${{ secrets.OPPO_CLIENT_SECRET }} ${{ secrets.OPPO_CLIENT_REFRESH_TOKEN }} --with-tcx + # If you want to sync fit activity in gpx format, please consider the following script: + # python run_page/oppo_sync.py ${{ secrets.OPPO_ID }} ${{ secrets.OPPO_CLIENT_SECRET }} ${{ secrets.OPPO_CLIENT_REFRESH_TOKEN }} --with-gpx + - name: Make svg GitHub profile if: env.RUN_TYPE != 'pass' run: | diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 70% rename from .prettierrc.js rename to .prettierrc.cjs index c4e31cdad73..e2507d6d9f3 100644 --- a/.prettierrc.js +++ b/.prettierrc.cjs @@ -3,4 +3,5 @@ module.exports = { semi: true, bracketSpacing: true, singleQuote: true, + plugins: ['prettier-plugin-tailwindcss'] }; diff --git a/.vercelignore b/.vercelignore index 979f16f7011..8e294ae2f2e 100644 --- a/.vercelignore +++ b/.vercelignore @@ -1 +1,2 @@ requirements.txt +requirements-dev.txt diff --git a/Dockerfile b/Dockerfile index 99024c32f25..9e9ff275037 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,9 @@ FROM python:3.10.5-slim AS develop-py WORKDIR /root/running_page COPY ./requirements.txt /root/running_page/requirements.txt +# Add proxy for apt. +# ENV http_proxy http://ip_address:port +# ENV https_proxy http://ip_address:port RUN apt-get update \ && apt-get install -y --no-install-recommends git \ && apt-get purge -y --auto-remove \ @@ -14,22 +17,21 @@ FROM node:18 AS develop-node WORKDIR /root/running_page COPY ./package.json /root/running_page/package.json COPY ./pnpm-lock.yaml /root/running_page/pnpm-lock.yaml -RUN npm config set registry https://registry.npm.taobao.org \ +RUN npm config rm proxy&&npm config set registry https://registry.npmjs.org/ \ &&npm install -g corepack \ &&corepack enable \ - &&pnpm install - - + &&yarn install FROM develop-py AS data ARG app ARG nike_refresh_token -ARG email -ARG password +ARG secret_string ARG client_id ARG client_secret ARG refresh_token ARG YOUR_NAME +ARG keep_phone_number +ARG keep_password WORKDIR /root/running_page COPY . /root/running_page/ @@ -39,13 +41,15 @@ RUN DUMMY=${DUMMY}; \ if [ "$app" = "NRC" ] ; then \ python3 run_page/nike_sync.py ${nike_refresh_token}; \ elif [ "$app" = "Garmin" ] ; then \ - python3 run_page/garmin_sync.py ${email} ${password}; \ + python3 run_page/garmin_sync.py ${secret_string} ; \ elif [ "$app" = "Garmin-CN" ] ; then \ - python3 run_page/garmin_sync.py ${email} ${password} --is-cn ; \ + python3 run_page/garmin_sync.py ${secret_string} --is-cn ; \ elif [ "$app" = "Strava" ] ; then \ python3 run_page/strava_sync.py ${client_id} ${client_secret} ${refresh_token};\ elif [ "$app" = "Nike_to_Strava" ] ; then \ python3 run_page/nike_to_strava_sync.py ${nike_refresh_token} ${client_id} ${client_secret} ${refresh_token};\ + elif [ "$app" = "Keep" ] ; then \ + python3 run_page/keep_sync.py ${keep_phone_number} ${keep_password};\ else \ echo "Unknown app" ; \ fi @@ -57,8 +61,8 @@ RUN python3 run_page/gen_svg.py --from-db --title "my running page" --type grid FROM develop-node AS frontend-build WORKDIR /root/running_page COPY --from=data /root/running_page /root/running_page -RUN pnpm run build +RUN yarn run build FROM nginx:alpine AS web -COPY --from=frontend-build /root/running_page/public /usr/share/nginx/html/ +COPY --from=frontend-build /root/running_page/dist /usr/share/nginx/html/ COPY --from=frontend-build /root/running_page/assets /usr/share/nginx/html/assets diff --git a/README-CN.md b/README-CN.md index 23f11e1518a..7086a8cecfd 100644 --- a/README-CN.md +++ b/README-CN.md @@ -42,7 +42,7 @@ R.I.P. 希望大家都能健康顺利的跑过终点,逝者安息。 | [zhubao315](https://github.com/zhubao315) | | Strava | | [shaonianche](https://github.com/shaonianche) | | Strava | | [yihong0618](https://github.com/yihong0618) | | Nike | -| [superleeyom](https://github.com/superleeyom) | | Nike | +| [superleeyom](https://github.com/superleeyom) | | Strava | | [geekplux](https://github.com/geekplux) | | Nike | | [guanlan](https://github.com/guanlan) | | Strava | | [tuzimoe](https://github.com/tuzimoe) | | Nike | @@ -95,7 +95,15 @@ R.I.P. 希望大家都能健康顺利的跑过终点,逝者安息。 | [deepinwine](https://github.com/deepinwine) | | Garmin-cn | | [Jeffggmm](https://github.com/Jeffggmm) | | Garmin | | [s1smart](https://github.com/s1smart) | | Strava | - +| [Ryan](https://github.com/85Ryan) | | Strava | +| [PPZ](https://github.com/8824PPZ) | | Strava | +| [Yer1k](https://github.com/Yer1k) | | Strava | +| [AlienVision](https://github.com/weaming) | | Strava | +| [Vensent](https://github.com/Vensent) | | Garmin | +| [Zeonsing](https://github.com/NoonieBao) | | Coros | +| [yaoper](https://github.com/yaoper) | | codoon | +| [NoZTurn](https://github.com/NoZTurn) | | Strava | +| [laqieer](https://github.com/laqieer) | | Strava | ## 它是怎么工作的 @@ -136,12 +144,14 @@ R.I.P. 希望大家都能健康顺利的跑过终点,逝者安息。 - **[GPX](#gpx)** - **[TCX](#tcx)** - **[FIT](#fit)** +- **[佳明国内同步国际](#Garmin-CN-to-Garmin)** - **[Tcx+Strava(upload all tcx data to strava)](#tcx_to_strava)** +- **[Tcx+Garmin(upload all tcx data to Garmin)](#tcx_to_garmin)** - **[Gpx+Strava(upload all tcx data to strava)](#gpx_to_strava)** - **[Nike+Strava(Using NRC Run, Strava backup data)](#nikestrava)** - **[Garmin_to_Strava(Using Garmin Run, Strava backup data)](#garmin_to_strava)** - **[Strava_to_Garmin(Using Strava Run, Garmin backup data)](#strava_to_garmin)** - +- **[Coros高驰](#Coros高驰)** ## 视频教程 - https://www.youtube.com/watch?v=reLiY9p8EJk @@ -181,6 +191,9 @@ docker build -t running_page:latest . --build-arg app=Strava --build-arg client_ #Nike_to_Strava docker build -t running_page:latest . --build-arg app=Nike_to_Strava --build-arg nike_refresh_token="" --build-arg client_id="" --build-arg client_secret="" --build-arg refresh_token="" +# Keep +docker build -t running_page:latest . --build-arg app=Keep --build-arg keep_phone_number="" --build-arg keep_password="" + #启动 docker run -itd -p 80:80 running_page:latest @@ -230,6 +243,11 @@ siteMetadata: { const USE_DASH_LINE = true; // styling: 透明度:[0, 1] const LINE_OPACITY = 0.4; +// styling: 开启隐私模式(不显示地图仅显示轨迹): 设置为 `true` +// 注意:此配置仅影响页面显示,数据保护请参考下方的 "隐私保护" +const PRIVACY_MODE = false; +// styling: 默认关灯: 设置为 `false`, 仅在隐私模式关闭时生效(`PRIVACY_MODE` = false) +const LIGHTS_ON = true; ``` > 隐私保护:设置下面环境变量: @@ -319,7 +337,7 @@ python3(python) run_page/keep_sync.py ${your mobile} ${your password} python3(python) run_page/keep_sync.py 13333xxxx example ``` -> 我增加了 keep 可以导出 gpx 功能(因 keep 的原因,距离和速度会有一定缺失), 执行如下命令,导出的 gpx 会加入到 GPX_OUT 中,方便上传到其它软件 +> 我增加了 keep 可以导出 gpx 功能(因 keep 的原因,距离和速度会有一定缺失), 执行如下命令,导出的 gpx 会加入到 GPX_OUT 中,方便上传到其它软件。 ```bash python3(python) run_page/keep_sync.py ${your mobile} ${your password} --with-gpx @@ -328,9 +346,22 @@ python3(python) run_page/keep_sync.py ${your mobile} ${your password} --with-gpx 示例: ```bash -python3(python) run_page/keep_sync.py 13333xxxx example --with-gpx +python3(python) run_page/keep_sync.py 13333xxxx example --with-gpx +``` + +> 增加了 keep 对其他运动类型的支持,目前可选的有running, cycling, hiking,默认的运动数据类型为running。 + +```bash +python3(python) run_page/keep_sync.py ${your mobile} ${your password} --with-gpx --sync-types running cycling hiking +``` + +示例: + +```bash +python3(python) run_page/keep_sync.py 13333xxxx example --with-gpx --sync-types running cycling hiking ``` +
@@ -569,6 +600,43 @@ python3(python) run_page/garmin_sync.py xxxxxxxxxx --is-cn --only-run
+### Garmin-CN to Garmin + +
+ 同步佳明 CN 数据到 佳明国际区 + +
+ +- 如果你只想同步 `type running` 使用参数 --only-run +**The Python version must be >=3.10** + +#### 获取佳明 CN 的密钥 + +在终端中输入以下命令 + +```bash +python3(python) run_page/get_garmin_secret.py ${your email} ${your password} --is-cn +``` + +#### 获取佳明全球的密钥 + +在终端中输入以下命令 + +```bash +python3(python) run_page/get_garmin_secret.py ${your email} ${your password} +``` + +#### 同步 佳明 CN 到 佳明全球 + +在终端中输入以下命令 + +```bash +python3(python) run_page/garmin_sync_cn_global.py ${garmin_cn_secret_string} ${garmin_secret_string} +``` + +
+ + ### Nike Run Club
@@ -580,10 +648,15 @@ python3(python) run_page/garmin_sync.py xxxxxxxxxx --is-cn --only-run 获取 Nike 的 refresh_token -1. 登录 [Nike](https://www.nike.com) 官网 -2. In Developer -> Application-> Storage -> https:unite.nike.com 中找到 refresh_token +**全部需要在大陆以外的全局 ip 下进行** + +![example img](https://user-images.githubusercontent.com/67903793/282300381-4e7437d0-65a9-4eed-93d1-2b70e360215f.png) + +1. 在这里登陆[website](https://unite.nike.com/s3/unite/mobile.html?androidSDKVersion=3.1.0&corsoverride=https%3A%2F%2Funite.nike.com&uxid=com.nike.sport.running.droid.3.8&backendEnvironment=identity&view=login&clientId=VhAeafEGJ6G8e9DxRUz8iE50CZ9MiJMG), 打开 F12 在浏览器抓 login -> XHR -> get the `refresh_token` from login api -![image](https://user-images.githubusercontent.com/15976103/94448123-23812b00-01dd-11eb-8143-4b0839c31d90.png) 3. 在项目根目录下执行: +2. 复制 `refresh_token` 之后可以添加在GitHub Secrets 中,也可以直接在命令行中使用 + +> Chrome 浏览器:按下 F12 打开浏览器开发者工具,点击 Application 选项卡,来到左侧的 Storage 面板,点击展开 Local storage,点击下方的 https://unite.nike.com。接着点击右侧的 com.nike.commerce.nikedotcom.web.credential Key,下方会分行显示我们选中的对象,可以看到 refresh_token ,复制 refresh_token 右侧的值。Safari 浏览器:在 Safari 打开 Nike 的网页后,右击页面,选择「检查元素」,打开浏览器开发者工具。点击「来源」选项卡,在左侧找到 XHR 文件夹,点击展开,在下方找到 login 文件并单击,在右侧同样可以看到 refresh_token ,复制 refresh_token 右侧的值。 ```bash python3(python) run_page/nike_sync.py ${nike refresh_token} @@ -710,6 +783,33 @@ python3(python) run_page/tcx_to_strava_sync.py xxx xxx xxx --all
+### TCX_to_Garmin + +
+上传所有的 tcx 格式的跑步数据到 Garmin + +
+ +1. 完成 garmin 的步骤 +2. 把 tcx 文件全部拷贝到 TCX_OUT 中 +3. 在项目根目录下执行: + +```bash +python3 run_page/tcx_to_garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING_CN }} --is-cn +``` + +示例: + +```bash +python run_page/tcx_to_garmin_sync.py xxx --is-cn +或佳明国际 +python run_page/tcx_to_garmin_sync.py xxx +``` + +> 如果你已经上传过需要跳过判断增加参数 `--all` + +
+ ### GPX_to_Strava
@@ -815,6 +915,53 @@ python3(python) run_page/strava_to_garmin_sync.py ${{ secrets.STRAVA_CLIENT_ID }
+### Coros高驰 + +
+获取您的 Coros高驰 数据 + +#### 在终端中输入以下命令 + +```bash +python run_page/coros_sync.py ${{ secrets.COROS_ACCOUNT }} ${{ secrets.COROS_PASSWORD }} +``` + +#### 修改 `run_data_sync.yml` 中 `env.RUN_TYPE: coros` + +#### 设置 github action中Coros高驰信息 + +- 在github action中配置`COROS_ACCOUNT`,`COROS_PASSWORD`参数 + + ![github-action](https://img3.uploadhouse.com/fileuploads/30980/3098042335f8995623f8b50776c4fad4cf7fff8d.png) + +
+ +### Keep_to_Strava +
+获取您的Keep数据,然后同步到Strava + +示例: +```bash +python3(python) run_page/keep_to_strava_sync.py ${your mobile} ${your password} ${client_id} ${client_secret} ${strava_refresh_token} --sync-types running cycling hiking +``` + +#### 解决的需求: +1. 适用于由Strava总览/展示数据,但是有多种运动类型,且数据来自不同设备的用户。 +2. 适用于期望将华为运动健康/OPPO健康等数据同步到Strava的用户(前提是手机APP端已经开启了和Keep之间的数据同步)。 +3. 理论上华为/OPPO等可以通过APP同步到Keep的设备,均可通过此方法自动同步到Strava,目前已通过测试的APP有 + - 华为运动健康: 户外跑步,户外骑行,户外步行。 + +#### 特性以及使用细节: +1. 与Keep相似,但是由keep_to_strava_sync.py实现,不侵入data.db 与 activities.json。因此不会出现由于同时使用keep_sync和strava_sync而导致的数据重复统计/展示问题。 +2. 上传至Strava时,会自动识别为Strava中相应的运动类型, 目前支持的运动类型为running, cycling, hiking。 +3. run_data_sync.yml中的修改: + + ```yaml + RUN_TYPE: keep_to_starva_sync + ``` + +
+ ### Total Data Analysis
@@ -906,6 +1053,13 @@ python3(python) run_page/gen_svg.py --from-db --type circular --use-localtime 4. 为 GitHub Actions 添加代码提交权限,访问仓库的 `Settings > Actions > General`页面,找到 `Workflow permissions` 的设置项,将选项配置为 `Read and write permissions`,支持 CI 将运动数据更新后提交到仓库中。 + +5. 如果想把你的 running_page 部署在 xxx.github.io 而不是 xxx.github.io/run_page 亦或是想要添加自定义域名于 GitHub Pages,需要做三点 + +- 修改你的 fork 的 running_page 仓库改名为 xxx.github.io, xxx 是你 github 的 username +- 修改 gh-pages.yml 中的 Build 模块,删除 `${{ github.event.repository.name }}` 改为`run: PATH_PREFIX=/ pnpm build` 即可 +- 修改 src/static/site-metadata.ts 中 `siteUrl: ''` 或是添加你的自定义域名,`siteUrl: '[your_own_domain]'`, 即可 +
## GitHub Actions @@ -1033,6 +1187,10 @@ curl https://api.github.com/repos/yihong0618/running_page/actions/workflows -H " 谢谢就够了 +# Raycast 插件 + + + # FAQ - Strava 100 每 15 分钟的请求,1000 每日限制 @@ -1049,7 +1207,7 @@ curl https://api.github.com/repos/yihong0618/running_page/actions/workflows -H " - vercel git - 如果想 ignpre gh-pages 可以在 `settings` -> `build` -> `Ignored Build Step` -> `Custom` 输入命令: + 如果想 ignore gh-pages 可以在 `settings` -> `build` -> `Ignored Build Step` -> `Custom` 输入命令: ```bash if [ "$VERCEL_GIT_COMMIT_REF" != "gh-pages" ]; then exit 1; else exit 0; diff --git a/README.md b/README.md index d0d21176c13..a32fcae22e1 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ English | [简体中文](https://github.com/yihong0618/running_page/blob/master/ | [zhubao315](https://github.com/zhubao315) | | Strava | | [shaonianche](https://github.com/shaonianche) | | Strava | | [yihong0618](https://github.com/yihong0618) | | Nike | -| [superleeyom](https://github.com/superleeyom) | | Nike | +| [superleeyom](https://github.com/superleeyom) | | Strava | | [geekplux](https://github.com/geekplux) | | Nike | | [guanlan](https://github.com/guanlan) | | Strava | | [tuzimoe](https://github.com/tuzimoe) | | Nike | @@ -91,7 +91,16 @@ English | [简体中文](https://github.com/yihong0618/running_page/blob/master/ | [Echo](https://github.com/donghao526) | | JoyRun | | [Jeffggmm](https://github.com/Jeffggmm) | | Garmin | | [s1smart](https://github.com/s1smart) | | Strava | - +| [XmchxUp](https://github.com/XmchxUp) | | Strava | +| [Ryan](https://github.com/85Ryan) | | Strava | +| [PPZ](https://github.com/8824PPZ) | | Strava | +| [Yer1k](https://github.com/Yer1k) | | Strava | +| [AlienVision](https://github.com/weaming) | | Strava | +| [闻笑忘](https://wenxiaowan.com) | | 苹果健身 | +| [Vensent](https://github.com/Vensent) | | Garmin | +| [Zeonsing](https://github.com/NoonieBao) | | Coros | +| [yaoper](https://github.com/yaoper) | | codoon | +| [laqieer](https://github.com/laqieer) | | Strava | ## How it works @@ -119,12 +128,14 @@ English | [简体中文](https://github.com/yihong0618/running_page/blob/master/ - **[GPX](#gpx)** - **[TCX](#tcx)** - **[FIT](#fit)** +- **[Garmin-CN_to_Garmin(Sync Garmin-CN activities to Garmin Global)](#garmin-cn-to-garmin)** - **[Nike_to_Strava(Using NRC Run, Strava backup data)](#nike_to_strava)** - **[Tcx_to_Strava(upload all tcx data to strava)](#tcx_to_strava)** +- **[Tcx_to_Garmin(upload all tcx data to Garmin)](#tcx_to_garmin)** - **[Gpx_to_Strava(upload all gpx data to strava)](#gpx_to_strava)** - **[Garmin_to_Strava(Using Garmin Run, Strava backup data)](#garmin_to_strava)** - **[Strava_to_Garmin(Using Strava Run, Garmin backup data)](#strava_to_garmin)** - +- **[Coros](#Coros)** ## Download Clone or fork the repo. @@ -162,6 +173,9 @@ docker build -t running_page:latest . --build-arg app=Strava --build-arg client_ # Nike_to_Strava docker build -t running_page:latest . --build-arg app=Nike_to_Strava --build-arg nike_refresh_token="" --build-arg client_id="" --build-arg client_secret="" --build-arg refresh_token="" +# Keep +docker build -t running_page:latest . --build-arg app=Keep --build-arg keep_phone_number="" --build-arg keep_password="" + # run docker run -itd -p 80:80 running_page:latest @@ -212,6 +226,18 @@ siteMetadata: { const USE_DASH_LINE = true; // styling: route line opacity: [0, 1] const LINE_OPACITY = 0.4; +// styling: set to `true` if you want to display only the routes without showing the map +// Note: This config only affects the page display; please refer to "privacy protection" below for data protection +const PRIVACY_MODE = false; +// styling: set to `false` if you want to make light off as default, only effect when `PRIVACY_MODE` = false +const LIGHTS_ON = true; +``` + +- To use Google Analytics, you need to modify the configuration in the `src/utils/const.ts` file. + +```typescript +const USE_GOOGLE_ANALYTICS = false; +const GOOGLE_ANALYTICS_TRACKING_ID = ''; ``` > privacy protection,setting flowing env: @@ -370,6 +396,45 @@ python3(python) run_page/garmin_sync.py xxxxxxxxxxxxxx(secret_string) --is-cn - +### Garmin-CN to Garmin + +
+ Sync your Garmin-CN data to Garmin + +
+ +- If you only want to sync `type running` add args --only-run +**The Python version must be >=3.10** + +#### Get Garmin CN Secret + +Enter the following command in the terminal + +```bash +# to get secret_string +python3(python) run_page/get_garmin_secret.py ${your email} ${your password} --is-cn +``` + +#### Get Garmin Secret + +Enter the following command in the terminal + +```bash +# to get secret_string +python3(python) run_page/get_garmin_secret.py ${your email} ${your password} +``` + +#### Sync Garmin CN to Garmin + +Enter the following command in the terminal + +```bash +# to sync garmin-cn to garmin-global +python3(python) run_page/garmin_sync_cn_global.py ${garmin_cn_secret_string} ${garmin_secret_string} +``` + +
+ ### Nike Run Club
@@ -381,12 +446,13 @@ python3(python) run_page/garmin_sync.py xxxxxxxxxxxxxx(secret_string) --is-cn - Get Nike's `refresh_token` -1. Login [Nike](https://www.nike.com) website -2. In Develop -> Application-> Storage -> https:unite.nike.com look for `refresh_token` +**ALL need to do outside GFW** -
+![example img](https://user-images.githubusercontent.com/67903793/282300381-4e7437d0-65a9-4eed-93d1-2b70e360215f.png) -![image](https://user-images.githubusercontent.com/15976103/94448123-23812b00-01dd-11eb-8143-4b0839c31d90.png) +1. Login from this [website](https://unite.nike.com/s3/unite/mobile.html?androidSDKVersion=3.1.0&corsoverride=https%3A%2F%2Funite.nike.com&uxid=com.nike.sport.running.droid.3.8&backendEnvironment=identity&view=login&clientId=VhAeafEGJ6G8e9DxRUz8iE50CZ9MiJMG), open F12 -> XHR -> get the `refresh_token` from login api. + +2. copy this `refresh_token` and use it in GitHub Secrets or in command line 3. Execute in the root directory: @@ -523,6 +589,33 @@ python3(python) run_page/tcx_to_strava_sync.py xxx xxx xxx --all
+### TCX_to_Garmin + +
+upload all tcx files to garmin + +
+ +1. follow the garmin steps +2. copy all your tcx files to TCX_OUT +3. Execute in the root directory: + +```bash +python3 run_page/tcx_to_garmin_sync.py ${{ secrets.GARMIN_SECRET_STRING_CN }} --is-cn +``` + +example: + +```bash +python run_page/tcx_to_garmin_sync.py xxx --is-cn +or Garmin Global +python run_page/tcx_to_garmin_sync.py xxx +``` + +4. if you want to all files add args `--all` + +
+ ### GPX_to_Strava
@@ -628,6 +721,29 @@ ps: **when initializing for the first time, if you have a large amount of strava
+ + +### Coros + +
+Get your Coros data + +#### Enter the following command in the terminal + +```bash +python run_page/coros_sync.py 'your coros account' 'your coros password' +``` + +#### Modify `run_data_sync.yml` env.RUN_TYPE: _coros_ + +#### Set the Coros account information in github action + +- configure the `COROS_ACCOUNT` , `COROS_PASSWORD` + + ![github-action](https://img3.uploadhouse.com/fileuploads/30980/3098042335f8995623f8b50776c4fad4cf7fff8d.png) + +
+ ### Total Data Analysis
@@ -720,6 +836,12 @@ For more display effects, see: 4. make sure you have write permissions in Workflow permissions settings. +5. If you want to deploy your running_page to xxx.github.io instead of xxx.github.io/running_page or redirect your GitHub Pages to a custom domain, you need to do three things: + +- Rename your forked running_page repository to `xxx.github.io`, where xxx is your GitHub username +- Modify the Build module in gh-pages.yml, remove `${{ github.event.repository.name }}` and change to `run: PATH_PREFIX=/ pnpm build` +- In `src/static/site-metadata.ts`, set siteUrl: '' or your custom domain URL +
## GitHub Actions @@ -850,6 +972,10 @@ Before submitting PR: Just enjoy it~ +# Raycast Extension + + + # FAQ - Strava Api limit @@ -862,7 +988,7 @@ Just enjoy it~ Strava API Rate Limit Timeout. Retry in 799.491622 seconds ``` -- vercel git ignpre gh-pages: +- vercel git ignore gh-pages: you can change settings -> build -> Ignored Build Step -> Custom command diff --git a/assets/github.svg b/assets/github.svg index 8ec8a0cf529..879f2a2e897 100644 --- a/assets/github.svg +++ b/assets/github.svg @@ -1,2 +1,2 @@ -Yihong0618 RunningATHLETEyihong0618STATISTICSNumber: 2533Weekly: 5.2Total: 9850.0 kmAvg: 3.9 kmMin: 0.5 kmMax: 43.5 km2023521.9 kmJanFebMarAprMayJunJulAugSepOctNovDec2022-12-262022-12-272022-12-282022-12-292022-12-302022-12-312023-01-01 1.0 km2023-01-022023-01-03 1.6 km2023-01-04 1.6 km2023-01-05 2.1 km2023-01-06 2.3 km2023-01-07 2.6 km2023-01-08 2.1 km2023-01-09 3.4 km2023-01-10 3.1 km2023-01-11 2.0 km2023-01-12 3.1 km2023-01-13 1.0 km2023-01-142023-01-152023-01-162023-01-172023-01-18 1.3 km2023-01-19 2.1 km2023-01-202023-01-212023-01-222023-01-232023-01-242023-01-252023-01-262023-01-272023-01-282023-01-292023-01-302023-01-312023-02-012023-02-02 1.0 km2023-02-032023-02-042023-02-052023-02-06 1.2 km2023-02-07 1.1 km2023-02-08 2.1 km2023-02-09 2.5 km2023-02-10 2.4 km2023-02-11 2.3 km2023-02-12 1.6 km2023-02-13 2.0 km2023-02-14 3.8 km2023-02-15 3.9 km2023-02-16 2.5 km2023-02-17 3.5 km2023-02-18 5.3 km2023-02-19 3.1 km2023-02-20 3.1 km2023-02-21 4.7 km2023-02-22 5.0 km2023-02-23 3.1 km2023-02-24 1.5 km2023-02-25 5.6 km2023-02-26 5.5 km2023-02-27 2.4 km2023-02-28 3.1 km2023-03-01 3.1 km2023-03-02 2.0 km2023-03-03 2.6 km2023-03-04 5.2 km2023-03-05 3.2 km2023-03-06 3.1 km2023-03-07 3.1 km2023-03-08 4.3 km2023-03-09 2.5 km2023-03-10 4.5 km2023-03-11 6.2 km2023-03-12 5.7 km2023-03-13 2.2 km2023-03-14 2.7 km2023-03-15 7.7 km2023-03-16 2.6 km2023-03-17 5.9 km2023-03-18 5.0 km2023-03-19 6.8 km2023-03-20 2.6 km2023-03-21 3.1 km2023-03-22 3.1 km2023-03-23 2.1 km2023-03-24 3.2 km2023-03-25 6.2 km2023-03-26 4.5 km2023-03-27 3.1 km2023-03-28 2.1 km2023-03-29 3.1 km2023-03-30 2.6 km2023-03-31 2.6 km2023-04-01 10.3 km2023-04-02 3.1 km2023-04-03 4.6 km2023-04-04 2.6 km2023-04-05 3.6 km2023-04-06 2.8 km2023-04-07 3.1 km2023-04-08 4.1 km2023-04-09 6.3 km2023-04-10 4.7 km2023-04-11 1.4 km2023-04-12 1.4 km2023-04-132023-04-14 3.1 km2023-04-152023-04-16 3.0 km2023-04-17 3.1 km2023-04-18 4.6 km2023-04-192023-04-20 2.3 km2023-04-21 2.6 km2023-04-22 2.3 km2023-04-232023-04-24 2.6 km2023-04-25 3.1 km2023-04-262023-04-27 2.1 km2023-04-28 2.4 km2023-04-29 2.0 km2023-04-30 3.1 km2023-05-012023-05-02 1.0 km2023-05-032023-05-042023-05-052023-05-062023-05-072023-05-08 2.3 km2023-05-092023-05-10 2.2 km2023-05-11 0.8 km2023-05-12 2.4 km2023-05-13 3.2 km2023-05-142023-05-15 2.7 km2023-05-16 2.0 km2023-05-17 2.6 km2023-05-18 4.6 km2023-05-19 2.1 km2023-05-202023-05-21 2.6 km2023-05-222023-05-23 1.2 km2023-05-24 2.5 km2023-05-25 2.5 km2023-05-26 2.6 km2023-05-272023-05-282023-05-29 2.2 km2023-05-30 3.2 km2023-05-31 3.6 km2023-06-01 5.3 km2023-06-02 3.1 km2023-06-03 4.1 km2023-06-04 5.1 km2023-06-05 4.1 km2023-06-062023-06-072023-06-082023-06-092023-06-102023-06-112023-06-122023-06-132023-06-14 0.6 km2023-06-15 0.6 km2023-06-16 1.0 km2023-06-172023-06-182023-06-19 1.0 km2023-06-202023-06-21 2.2 km2023-06-222023-06-232023-06-242023-06-252023-06-262023-06-27 1.1 km2023-06-28 1.3 km2023-06-29 1.1 km2023-06-30 2.2 km2023-07-012023-07-022023-07-03 4.9 km2023-07-04 2.9 km2023-07-052023-07-062023-07-07 2.7 km2023-07-08 4.5 km2023-07-092023-07-10 2.7 km2023-07-11 3.7 km2023-07-12 2.6 km2023-07-132023-07-142023-07-152023-07-162023-07-17 1.5 km2023-07-18 2.2 km2023-07-19 2.3 km2023-07-202023-07-212023-07-222023-07-23 1.3 km2023-07-242023-07-25 2.3 km2023-07-262023-07-27 2.8 km2023-07-282023-07-292023-07-302023-07-31 1.4 km2023-08-012023-08-022023-08-03 1.6 km2023-08-04 2.3 km2023-08-05 2.1 km2023-08-06 3.1 km2023-08-07 1.1 km2023-08-08 2.1 km2023-08-092023-08-102023-08-112023-08-12 1.2 km2023-08-132023-08-142023-08-152023-08-16 1.2 km2023-08-17 1.6 km2023-08-182023-08-192023-08-202023-08-21 1.0 km2023-08-22 2.5 km2023-08-232023-08-24 1.6 km2023-08-252023-08-262023-08-27 2.4 km2023-08-282023-08-292023-08-30 0.6 km2023-08-31 2.1 km2023-09-01 1.0 km2023-09-022023-09-03 3.0 km2023-09-04 7.8 km2023-09-05 2.5 km2023-09-06 7.1 km2023-09-07 8.5 km2023-09-08 3.9 km2023-09-09 4.1 km2023-09-10 3.3 km2023-09-11 2.7 km2023-09-12 3.1 km2023-09-13 3.2 km2023-09-14 3.1 km2023-09-15 3.4 km2023-09-16 5.2 km2023-09-17 2.2 km2023-09-18 4.6 km2023-09-19 3.8 km2023-09-20 3.5 km2023-09-212023-09-222023-09-232023-09-242023-09-252023-09-262023-09-272023-09-282023-09-292023-09-302023-10-012023-10-022023-10-032023-10-042023-10-052023-10-062023-10-072023-10-082023-10-092023-10-102023-10-112023-10-122023-10-132023-10-142023-10-152023-10-162023-10-172023-10-182023-10-192023-10-202023-10-212023-10-222023-10-232023-10-242023-10-252023-10-262023-10-272023-10-282023-10-292023-10-302023-10-312023-11-012023-11-022023-11-032023-11-042023-11-052023-11-062023-11-072023-11-082023-11-092023-11-102023-11-112023-11-122023-11-132023-11-142023-11-152023-11-162023-11-172023-11-182023-11-192023-11-202023-11-212023-11-222023-11-232023-11-242023-11-252023-11-262023-11-272023-11-282023-11-292023-11-302023-12-012023-12-022023-12-032023-12-042023-12-052023-12-062023-12-072023-12-082023-12-092023-12-102023-12-112023-12-122023-12-132023-12-142023-12-152023-12-162023-12-172023-12-182023-12-192023-12-202023-12-212023-12-222023-12-232023-12-242023-12-252023-12-262023-12-272023-12-282023-12-292023-12-302023-12-312022758.2 kmJanFebMarAprMayJunJulAugSepOctNovDec2021-12-272021-12-282021-12-29 1.4 km2021-12-30 5.4 km2021-12-31 2.6 km2022-01-01 3.2 km2022-01-02 4.4 km2022-01-03 3.1 km2022-01-04 3.2 km2022-01-05 3.2 km2022-01-06 3.2 km2022-01-07 5.1 km2022-01-08 3.2 km2022-01-09 4.3 km2022-01-10 3.1 km2022-01-11 1.6 km2022-01-12 4.0 km2022-01-13 6.1 km2022-01-14 1.5 km2022-01-15 4.1 km2022-01-16 3.7 km2022-01-17 3.1 km2022-01-18 7.2 km2022-01-192022-01-20 7.7 km2022-01-21 2.5 km2022-01-22 4.3 km2022-01-23 8.2 km2022-01-24 2.1 km2022-01-25 10.4 km2022-01-26 4.2 km2022-01-27 2.1 km2022-01-28 4.8 km2022-01-29 1.4 km2022-01-30 11.3 km2022-01-31 2.1 km2022-02-01 4.1 km2022-02-02 4.3 km2022-02-03 8.3 km2022-02-04 1.6 km2022-02-052022-02-06 2.0 km2022-02-07 4.7 km2022-02-08 3.3 km2022-02-092022-02-10 3.1 km2022-02-11 1.6 km2022-02-12 6.4 km2022-02-13 8.3 km2022-02-14 5.0 km2022-02-15 5.2 km2022-02-16 5.1 km2022-02-17 6.4 km2022-02-18 6.1 km2022-02-19 5.5 km2022-02-20 5.9 km2022-02-21 7.3 km2022-02-22 4.3 km2022-02-23 15.5 km2022-02-24 2.4 km2022-02-252022-02-26 4.1 km2022-02-27 2.1 km2022-02-28 2.4 km2022-03-01 3.4 km2022-03-02 6.7 km2022-03-03 2.1 km2022-03-04 3.1 km2022-03-05 1.6 km2022-03-06 3.3 km2022-03-072022-03-08 1.5 km2022-03-092022-03-10 2.0 km2022-03-11 3.5 km2022-03-12 1.4 km2022-03-13 5.3 km2022-03-14 1.5 km2022-03-152022-03-16 2.1 km2022-03-17 3.1 km2022-03-18 1.4 km2022-03-192022-03-20 3.7 km2022-03-212022-03-22 1.4 km2022-03-23 3.1 km2022-03-242022-03-252022-03-26 3.1 km2022-03-27 3.2 km2022-03-282022-03-29 3.5 km2022-03-302022-03-31 2.8 km2022-04-01 1.5 km2022-04-02 1.1 km2022-04-03 4.3 km2022-04-042022-04-052022-04-06 2.5 km2022-04-072022-04-082022-04-09 1.5 km2022-04-10 3.1 km2022-04-112022-04-12 2.1 km2022-04-13 3.1 km2022-04-142022-04-15 4.2 km2022-04-16 3.1 km2022-04-17 5.3 km2022-04-182022-04-19 2.1 km2022-04-20 3.1 km2022-04-212022-04-22 2.4 km2022-04-23 2.1 km2022-04-242022-04-25 2.1 km2022-04-262022-04-27 2.1 km2022-04-282022-04-29 2.7 km2022-04-30 2.2 km2022-05-01 3.1 km2022-05-02 3.3 km2022-05-03 3.1 km2022-05-04 4.2 km2022-05-05 3.1 km2022-05-062022-05-07 2.2 km2022-05-082022-05-09 3.8 km2022-05-102022-05-112022-05-122022-05-13 1.8 km2022-05-142022-05-152022-05-162022-05-172022-05-182022-05-192022-05-20 1.6 km2022-05-21 2.0 km2022-05-22 3.1 km2022-05-23 1.0 km2022-05-242022-05-25 1.1 km2022-05-262022-05-27 1.5 km2022-05-282022-05-292022-05-30 2.0 km2022-05-312022-06-01 2.1 km2022-06-022022-06-032022-06-042022-06-05 2.1 km2022-06-062022-06-07 2.4 km2022-06-08 2.1 km2022-06-09 2.1 km2022-06-10 2.4 km2022-06-112022-06-12 2.6 km2022-06-13 2.8 km2022-06-14 2.1 km2022-06-15 3.1 km2022-06-16 3.1 km2022-06-17 1.3 km2022-06-18 1.6 km2022-06-19 4.1 km2022-06-20 1.0 km2022-06-21 2.1 km2022-06-22 2.2 km2022-06-232022-06-24 2.7 km2022-06-252022-06-262022-06-27 1.1 km2022-06-28 1.2 km2022-06-29 1.9 km2022-06-30 2.6 km2022-07-01 3.1 km2022-07-02 3.2 km2022-07-032022-07-04 1.0 km2022-07-05 2.8 km2022-07-062022-07-07 2.0 km2022-07-08 1.1 km2022-07-092022-07-10 4.3 km2022-07-11 3.6 km2022-07-12 4.2 km2022-07-13 2.7 km2022-07-14 3.2 km2022-07-15 2.4 km2022-07-16 2.2 km2022-07-17 5.2 km2022-07-18 5.3 km2022-07-19 4.8 km2022-07-20 3.2 km2022-07-21 7.0 km2022-07-22 2.1 km2022-07-23 5.5 km2022-07-24 4.1 km2022-07-25 4.5 km2022-07-26 4.5 km2022-07-27 3.1 km2022-07-28 2.6 km2022-07-292022-07-302022-07-312022-08-01 2.3 km2022-08-02 2.5 km2022-08-03 2.4 km2022-08-042022-08-052022-08-06 2.3 km2022-08-072022-08-08 3.2 km2022-08-09 2.0 km2022-08-10 2.1 km2022-08-11 2.4 km2022-08-122022-08-132022-08-142022-08-15 1.3 km2022-08-16 1.4 km2022-08-17 2.1 km2022-08-18 2.5 km2022-08-192022-08-202022-08-21 2.1 km2022-08-22 1.6 km2022-08-23 1.2 km2022-08-24 2.8 km2022-08-25 1.4 km2022-08-26 2.7 km2022-08-27 3.1 km2022-08-282022-08-292022-08-302022-08-31 2.1 km2022-09-01 1.1 km2022-09-02 2.1 km2022-09-03 1.0 km2022-09-042022-09-05 2.6 km2022-09-06 3.0 km2022-09-07 2.1 km2022-09-08 3.1 km2022-09-09 1.0 km2022-09-10 2.1 km2022-09-11 2.2 km2022-09-122022-09-132022-09-142022-09-152022-09-16 1.2 km2022-09-17 2.8 km2022-09-18 2.2 km2022-09-19 1.1 km2022-09-20 3.1 km2022-09-21 2.0 km2022-09-22 1.0 km2022-09-23 1.1 km2022-09-24 3.2 km2022-09-252022-09-262022-09-272022-09-28 1.1 km2022-09-29 2.1 km2022-09-30 2.2 km2022-10-012022-10-02 2.1 km2022-10-032022-10-042022-10-05 1.1 km2022-10-06 1.6 km2022-10-07 3.4 km2022-10-082022-10-092022-10-10 3.1 km2022-10-11 4.8 km2022-10-12 3.8 km2022-10-13 4.2 km2022-10-14 4.4 km2022-10-15 4.3 km2022-10-16 3.1 km2022-10-17 6.5 km2022-10-18 5.4 km2022-10-19 4.8 km2022-10-20 3.5 km2022-10-21 7.5 km2022-10-22 1.5 km2022-10-232022-10-24 1.4 km2022-10-25 1.4 km2022-10-262022-10-272022-10-28 1.3 km2022-10-292022-10-302022-10-31 2.1 km2022-11-01 2.1 km2022-11-02 2.0 km2022-11-03 1.7 km2022-11-042022-11-052022-11-06 2.1 km2022-11-07 2.0 km2022-11-08 2.2 km2022-11-092022-11-10 1.6 km2022-11-11 2.1 km2022-11-122022-11-13 0.5 km2022-11-14 2.3 km2022-11-15 2.3 km2022-11-16 2.4 km2022-11-172022-11-182022-11-19 3.8 km2022-11-20 2.1 km2022-11-21 1.8 km2022-11-22 2.1 km2022-11-23 2.5 km2022-11-24 2.6 km2022-11-25 2.3 km2022-11-26 1.2 km2022-11-272022-11-282022-11-292022-11-302022-12-01 2.1 km2022-12-02 2.6 km2022-12-032022-12-042022-12-052022-12-062022-12-07 1.0 km2022-12-08 2.1 km2022-12-092022-12-102022-12-11 2.4 km2022-12-12 2.0 km2022-12-13 2.0 km2022-12-142022-12-152022-12-162022-12-172022-12-182022-12-19 2.1 km2022-12-20 2.0 km2022-12-212022-12-222022-12-232022-12-242022-12-252022-12-262022-12-272022-12-282022-12-292022-12-302022-12-3120211243.5 kmJanFebMarAprMayJunJulAugSepOctNovDec2020-12-282020-12-292020-12-302020-12-31 3.0 km2021-01-01 5.1 km2021-01-02 5.5 km2021-01-03 10.3 km2021-01-04 2.6 km2021-01-05 4.2 km2021-01-06 3.1 km2021-01-07 3.1 km2021-01-08 2.1 km2021-01-09 1.6 km2021-01-10 11.4 km2021-01-11 1.6 km2021-01-12 4.1 km2021-01-13 5.6 km2021-01-14 2.0 km2021-01-15 1.6 km2021-01-16 11.2 km2021-01-17 3.4 km2021-01-18 2.0 km2021-01-19 5.9 km2021-01-20 3.2 km2021-01-21 5.1 km2021-01-22 10.8 km2021-01-23 5.2 km2021-01-24 10.2 km2021-01-25 1.1 km2021-01-26 4.1 km2021-01-27 1.5 km2021-01-28 4.6 km2021-01-29 10.1 km2021-01-30 2.7 km2021-01-31 3.1 km2021-02-01 4.1 km2021-02-02 1.6 km2021-02-03 5.1 km2021-02-04 3.1 km2021-02-05 1.0 km2021-02-06 1.7 km2021-02-07 6.6 km2021-02-08 12.4 km2021-02-09 3.4 km2021-02-10 1.2 km2021-02-11 1.7 km2021-02-12 5.1 km2021-02-13 19.2 km2021-02-14 0.5 km2021-02-15 4.2 km2021-02-16 2.0 km2021-02-17 5.2 km2021-02-18 4.2 km2021-02-19 5.2 km2021-02-20 3.1 km2021-02-21 5.1 km2021-02-22 6.2 km2021-02-23 3.1 km2021-02-24 1.1 km2021-02-25 7.1 km2021-02-26 2.0 km2021-02-27 7.6 km2021-02-28 1.6 km2021-03-01 2.3 km2021-03-02 5.1 km2021-03-03 6.1 km2021-03-04 2.6 km2021-03-05 6.1 km2021-03-06 3.3 km2021-03-07 10.2 km2021-03-08 3.1 km2021-03-09 4.5 km2021-03-10 6.1 km2021-03-11 2.6 km2021-03-12 5.0 km2021-03-13 4.9 km2021-03-14 4.2 km2021-03-15 6.2 km2021-03-16 3.3 km2021-03-17 4.3 km2021-03-18 3.1 km2021-03-19 5.2 km2021-03-20 1.6 km2021-03-21 5.1 km2021-03-22 3.1 km2021-03-23 2.7 km2021-03-24 6.4 km2021-03-25 2.1 km2021-03-26 4.1 km2021-03-27 3.1 km2021-03-28 7.2 km2021-03-29 1.1 km2021-03-30 4.6 km2021-03-31 10.2 km2021-04-01 4.0 km2021-04-02 3.1 km2021-04-03 1.4 km2021-04-04 6.4 km2021-04-05 4.1 km2021-04-06 3.1 km2021-04-07 5.1 km2021-04-08 10.3 km2021-04-09 3.1 km2021-04-10 4.1 km2021-04-11 1.1 km2021-04-12 4.3 km2021-04-13 4.5 km2021-04-14 6.1 km2021-04-15 5.2 km2021-04-16 5.1 km2021-04-17 5.9 km2021-04-18 17.8 km2021-04-19 2.5 km2021-04-20 6.1 km2021-04-21 7.8 km2021-04-22 6.1 km2021-04-23 3.2 km2021-04-24 3.2 km2021-04-25 4.1 km2021-04-26 4.2 km2021-04-27 5.0 km2021-04-28 1.7 km2021-04-29 5.5 km2021-04-30 10.6 km2021-05-01 4.0 km2021-05-02 5.7 km2021-05-03 9.4 km2021-05-04 2.1 km2021-05-05 2.4 km2021-05-06 6.3 km2021-05-07 3.3 km2021-05-08 3.0 km2021-05-09 1.5 km2021-05-102021-05-11 5.1 km2021-05-12 2.2 km2021-05-13 5.5 km2021-05-14 3.1 km2021-05-15 2.6 km2021-05-16 2.2 km2021-05-17 4.2 km2021-05-18 5.7 km2021-05-19 3.7 km2021-05-20 1.0 km2021-05-21 2.3 km2021-05-22 6.4 km2021-05-23 2.5 km2021-05-24 1.5 km2021-05-25 1.2 km2021-05-26 4.3 km2021-05-27 2.1 km2021-05-28 6.9 km2021-05-29 10.3 km2021-05-30 5.2 km2021-05-31 1.1 km2021-06-01 3.2 km2021-06-02 4.3 km2021-06-03 4.1 km2021-06-04 3.3 km2021-06-05 4.3 km2021-06-06 1.5 km2021-06-07 5.4 km2021-06-08 3.4 km2021-06-09 3.2 km2021-06-10 2.2 km2021-06-112021-06-12 1.1 km2021-06-13 5.7 km2021-06-14 2.2 km2021-06-15 4.0 km2021-06-16 4.3 km2021-06-17 4.2 km2021-06-18 1.6 km2021-06-19 0.6 km2021-06-20 6.2 km2021-06-21 1.0 km2021-06-22 1.6 km2021-06-23 4.2 km2021-06-24 5.7 km2021-06-25 1.5 km2021-06-26 2.5 km2021-06-27 10.9 km2021-06-282021-06-29 3.1 km2021-06-30 1.2 km2021-07-01 4.3 km2021-07-02 1.8 km2021-07-03 5.1 km2021-07-04 3.2 km2021-07-05 1.0 km2021-07-06 2.6 km2021-07-072021-07-08 2.5 km2021-07-09 4.4 km2021-07-102021-07-11 5.2 km2021-07-12 1.1 km2021-07-13 2.7 km2021-07-14 3.1 km2021-07-152021-07-16 3.0 km2021-07-172021-07-18 5.2 km2021-07-192021-07-20 2.1 km2021-07-21 1.3 km2021-07-222021-07-232021-07-24 2.6 km2021-07-25 1.1 km2021-07-26 1.7 km2021-07-27 1.5 km2021-07-28 1.8 km2021-07-292021-07-30 3.1 km2021-07-31 2.1 km2021-08-01 6.5 km2021-08-02 1.1 km2021-08-032021-08-04 2.3 km2021-08-052021-08-062021-08-07 2.2 km2021-08-08 3.1 km2021-08-092021-08-10 1.2 km2021-08-112021-08-12 2.1 km2021-08-132021-08-14 3.1 km2021-08-15 3.9 km2021-08-16 3.2 km2021-08-17 3.3 km2021-08-18 3.3 km2021-08-19 2.2 km2021-08-20 3.2 km2021-08-21 5.3 km2021-08-22 2.1 km2021-08-23 3.6 km2021-08-24 1.1 km2021-08-25 3.2 km2021-08-26 5.3 km2021-08-27 4.2 km2021-08-28 6.4 km2021-08-29 3.0 km2021-08-30 5.1 km2021-08-31 3.1 km2021-09-01 4.4 km2021-09-022021-09-03 1.5 km2021-09-04 3.1 km2021-09-05 11.3 km2021-09-062021-09-07 1.9 km2021-09-08 2.0 km2021-09-09 2.6 km2021-09-10 4.3 km2021-09-11 4.2 km2021-09-12 15.2 km2021-09-132021-09-14 2.2 km2021-09-15 2.1 km2021-09-16 3.2 km2021-09-17 5.4 km2021-09-18 2.2 km2021-09-19 2.2 km2021-09-202021-09-21 4.5 km2021-09-22 3.1 km2021-09-232021-09-242021-09-25 8.5 km2021-09-262021-09-272021-09-28 3.2 km2021-09-292021-09-30 10.4 km2021-10-01 4.1 km2021-10-02 1.1 km2021-10-03 2.1 km2021-10-04 3.2 km2021-10-05 5.2 km2021-10-06 5.6 km2021-10-07 3.2 km2021-10-08 3.1 km2021-10-09 4.1 km2021-10-10 6.8 km2021-10-112021-10-12 3.1 km2021-10-13 3.1 km2021-10-14 3.1 km2021-10-15 0.6 km2021-10-162021-10-17 8.1 km2021-10-18 1.1 km2021-10-19 2.0 km2021-10-20 3.1 km2021-10-212021-10-222021-10-23 1.6 km2021-10-24 3.4 km2021-10-252021-10-26 2.8 km2021-10-27 3.2 km2021-10-282021-10-29 3.1 km2021-10-30 4.8 km2021-10-31 3.1 km2021-11-01 5.4 km2021-11-02 3.1 km2021-11-03 4.4 km2021-11-04 1.6 km2021-11-052021-11-062021-11-072021-11-08 1.7 km2021-11-09 3.1 km2021-11-10 5.1 km2021-11-11 1.0 km2021-11-12 2.1 km2021-11-13 1.1 km2021-11-14 5.2 km2021-11-152021-11-16 2.1 km2021-11-17 2.4 km2021-11-18 1.0 km2021-11-19 4.2 km2021-11-20 1.5 km2021-11-21 5.8 km2021-11-22 3.1 km2021-11-23 3.4 km2021-11-242021-11-252021-11-26 2.6 km2021-11-27 5.2 km2021-11-28 5.0 km2021-11-292021-11-30 1.6 km2021-12-01 3.6 km2021-12-02 5.1 km2021-12-032021-12-04 1.2 km2021-12-05 3.1 km2021-12-06 1.8 km2021-12-07 2.4 km2021-12-08 3.2 km2021-12-092021-12-10 1.2 km2021-12-11 5.1 km2021-12-12 2.1 km2021-12-132021-12-14 2.1 km2021-12-15 2.1 km2021-12-16 2.6 km2021-12-172021-12-18 2.4 km2021-12-19 4.1 km2021-12-20 1.5 km2021-12-21 3.1 km2021-12-22 2.1 km2021-12-232021-12-24 1.0 km2021-12-25 2.0 km2021-12-26 3.1 km2021-12-272021-12-282021-12-29 1.4 km2021-12-30 5.4 km2021-12-31 2.6 km20201283.5 kmJanFebMarAprMayJunJulAugSepOctNovDec2019-12-302019-12-31 4.0 km2020-01-01 10.0 km2020-01-02 5.1 km2020-01-03 5.0 km2020-01-04 9.3 km2020-01-05 10.1 km2020-01-06 4.0 km2020-01-072020-01-08 2.0 km2020-01-092020-01-102020-01-11 1.1 km2020-01-12 5.0 km2020-01-13 2.0 km2020-01-14 4.0 km2020-01-15 4.0 km2020-01-16 4.1 km2020-01-17 5.0 km2020-01-18 3.0 km2020-01-19 3.1 km2020-01-20 3.8 km2020-01-212020-01-22 3.0 km2020-01-23 1.5 km2020-01-24 3.0 km2020-01-25 2.0 km2020-01-26 2.0 km2020-01-27 3.0 km2020-01-282020-01-292020-01-302020-01-31 5.0 km2020-02-01 4.0 km2020-02-02 5.0 km2020-02-03 4.0 km2020-02-042020-02-05 3.1 km2020-02-06 3.0 km2020-02-07 4.0 km2020-02-08 3.7 km2020-02-092020-02-10 5.0 km2020-02-11 3.1 km2020-02-12 4.1 km2020-02-132020-02-142020-02-152020-02-162020-02-172020-02-182020-02-192020-02-20 1.2 km2020-02-21 3.0 km2020-02-22 4.3 km2020-02-23 5.0 km2020-02-242020-02-25 1.0 km2020-02-262020-02-27 1.0 km2020-02-282020-02-29 2.0 km2020-03-012020-03-02 1.0 km2020-03-032020-03-04 3.0 km2020-03-052020-03-062020-03-072020-03-082020-03-092020-03-10 2.1 km2020-03-11 1.0 km2020-03-12 1.4 km2020-03-132020-03-142020-03-152020-03-162020-03-17 1.2 km2020-03-18 2.0 km2020-03-192020-03-20 1.2 km2020-03-212020-03-222020-03-23 3.0 km2020-03-242020-03-25 2.3 km2020-03-262020-03-272020-03-282020-03-292020-03-30 1.0 km2020-03-312020-04-01 1.4 km2020-04-022020-04-032020-04-042020-04-052020-04-06 1.4 km2020-04-07 2.0 km2020-04-08 2.0 km2020-04-09 1.3 km2020-04-10 2.1 km2020-04-112020-04-122020-04-13 2.1 km2020-04-14 3.0 km2020-04-15 1.6 km2020-04-162020-04-172020-04-18 2.0 km2020-04-192020-04-202020-04-212020-04-222020-04-232020-04-242020-04-25 1.0 km2020-04-262020-04-27 1.1 km2020-04-28 2.0 km2020-04-29 1.0 km2020-04-30 0.6 km2020-05-012020-05-022020-05-032020-05-042020-05-052020-05-06 1.0 km2020-05-072020-05-082020-05-09 2.7 km2020-05-102020-05-112020-05-12 1.4 km2020-05-132020-05-142020-05-15 1.0 km2020-05-16 6.5 km2020-05-17 4.3 km2020-05-18 1.2 km2020-05-19 5.0 km2020-05-20 4.0 km2020-05-21 3.3 km2020-05-22 7.5 km2020-05-23 3.1 km2020-05-24 7.1 km2020-05-25 4.9 km2020-05-26 4.2 km2020-05-27 6.5 km2020-05-28 5.0 km2020-05-29 4.0 km2020-05-30 9.0 km2020-05-31 3.5 km2020-06-01 4.0 km2020-06-02 5.5 km2020-06-03 4.1 km2020-06-04 8.0 km2020-06-05 3.3 km2020-06-06 11.2 km2020-06-07 6.0 km2020-06-08 4.0 km2020-06-09 4.7 km2020-06-10 6.6 km2020-06-11 5.8 km2020-06-12 13.8 km2020-06-13 1.5 km2020-06-14 10.6 km2020-06-15 5.6 km2020-06-16 4.0 km2020-06-17 5.5 km2020-06-18 11.1 km2020-06-19 6.1 km2020-06-20 8.7 km2020-06-21 2.4 km2020-06-22 12.6 km2020-06-23 4.2 km2020-06-24 7.0 km2020-06-25 7.6 km2020-06-26 5.2 km2020-06-27 13.1 km2020-06-28 4.6 km2020-06-29 6.0 km2020-06-30 8.2 km2020-07-01 3.0 km2020-07-02 6.1 km2020-07-03 6.7 km2020-07-04 15.0 km2020-07-05 4.1 km2020-07-06 6.9 km2020-07-07 5.8 km2020-07-08 3.9 km2020-07-09 6.3 km2020-07-10 8.2 km2020-07-11 9.5 km2020-07-12 5.2 km2020-07-13 10.3 km2020-07-14 8.6 km2020-07-15 6.4 km2020-07-16 5.7 km2020-07-17 11.3 km2020-07-18 7.3 km2020-07-19 14.2 km2020-07-20 5.0 km2020-07-21 6.2 km2020-07-22 6.2 km2020-07-23 4.8 km2020-07-24 2.2 km2020-07-25 10.9 km2020-07-26 4.2 km2020-07-27 10.1 km2020-07-28 4.1 km2020-07-29 2.6 km2020-07-30 5.2 km2020-07-31 7.0 km2020-08-01 10.2 km2020-08-02 5.4 km2020-08-03 1.1 km2020-08-04 5.4 km2020-08-05 5.8 km2020-08-06 2.3 km2020-08-07 5.1 km2020-08-08 9.3 km2020-08-09 5.1 km2020-08-10 8.3 km2020-08-11 5.1 km2020-08-12 1.0 km2020-08-13 6.2 km2020-08-14 2.1 km2020-08-15 5.1 km2020-08-16 20.7 km2020-08-17 3.1 km2020-08-18 2.1 km2020-08-19 3.1 km2020-08-20 2.0 km2020-08-21 4.1 km2020-08-22 2.1 km2020-08-23 5.1 km2020-08-24 2.7 km2020-08-25 2.2 km2020-08-26 4.3 km2020-08-27 5.2 km2020-08-28 5.1 km2020-08-29 6.7 km2020-08-30 3.1 km2020-08-31 5.7 km2020-09-01 10.2 km2020-09-02 6.7 km2020-09-03 5.2 km2020-09-04 5.6 km2020-09-05 5.0 km2020-09-06 4.4 km2020-09-07 6.3 km2020-09-08 10.4 km2020-09-09 4.3 km2020-09-10 4.8 km2020-09-11 2.1 km2020-09-12 1.5 km2020-09-13 10.2 km2020-09-14 5.1 km2020-09-15 1.0 km2020-09-16 10.3 km2020-09-17 10.4 km2020-09-18 6.5 km2020-09-19 7.3 km2020-09-20 20.2 km2020-09-21 1.4 km2020-09-22 2.7 km2020-09-23 3.1 km2020-09-24 2.6 km2020-09-25 2.8 km2020-09-26 5.6 km2020-09-27 1.1 km2020-09-28 1.6 km2020-09-29 4.1 km2020-09-302020-10-01 4.3 km2020-10-02 3.2 km2020-10-03 1.4 km2020-10-04 1.0 km2020-10-05 3.1 km2020-10-06 5.2 km2020-10-07 5.1 km2020-10-08 5.2 km2020-10-09 3.1 km2020-10-10 2.7 km2020-10-11 3.2 km2020-10-12 4.3 km2020-10-13 1.0 km2020-10-14 4.4 km2020-10-15 1.5 km2020-10-162020-10-17 1.4 km2020-10-182020-10-19 2.2 km2020-10-20 3.0 km2020-10-21 5.6 km2020-10-22 3.0 km2020-10-23 3.3 km2020-10-24 5.0 km2020-10-25 3.0 km2020-10-26 3.2 km2020-10-27 4.0 km2020-10-28 2.7 km2020-10-29 1.7 km2020-10-30 5.3 km2020-10-31 11.2 km2020-11-01 1.2 km2020-11-02 3.1 km2020-11-032020-11-04 1.9 km2020-11-05 5.1 km2020-11-06 2.1 km2020-11-07 3.0 km2020-11-08 1.6 km2020-11-09 5.1 km2020-11-10 6.4 km2020-11-11 3.4 km2020-11-12 4.3 km2020-11-132020-11-14 7.0 km2020-11-15 6.2 km2020-11-16 1.5 km2020-11-172020-11-18 1.0 km2020-11-19 2.2 km2020-11-202020-11-21 4.5 km2020-11-22 5.6 km2020-11-23 4.2 km2020-11-24 1.2 km2020-11-25 2.1 km2020-11-26 1.1 km2020-11-27 3.3 km2020-11-28 7.0 km2020-11-292020-11-302020-12-012020-12-02 2.5 km2020-12-03 1.1 km2020-12-04 4.1 km2020-12-05 10.2 km2020-12-06 5.1 km2020-12-07 2.2 km2020-12-08 1.2 km2020-12-09 2.6 km2020-12-10 1.2 km2020-12-112020-12-12 2.6 km2020-12-13 5.1 km2020-12-14 1.0 km2020-12-15 2.2 km2020-12-16 3.1 km2020-12-17 2.5 km2020-12-182020-12-19 3.2 km2020-12-20 10.3 km2020-12-21 2.6 km2020-12-222020-12-23 2.5 km2020-12-24 4.1 km2020-12-252020-12-26 4.6 km2020-12-27 1.6 km2020-12-282020-12-292020-12-302020-12-31 3.0 km20191342.7 kmJanFebMarAprMayJunJulAugSepOctNovDec2018-12-312019-01-01 1.0 km2019-01-02 1.0 km2019-01-03 2.0 km2019-01-04 1.0 km2019-01-05 1.0 km2019-01-06 2.5 km2019-01-07 1.7 km2019-01-08 1.0 km2019-01-09 2.0 km2019-01-10 1.0 km2019-01-112019-01-12 1.2 km2019-01-132019-01-14 0.6 km2019-01-15 1.0 km2019-01-162019-01-17 1.0 km2019-01-18 1.7 km2019-01-192019-01-202019-01-212019-01-222019-01-232019-01-242019-01-252019-01-262019-01-272019-01-282019-01-292019-01-302019-01-312019-02-012019-02-022019-02-032019-02-042019-02-052019-02-062019-02-072019-02-082019-02-092019-02-102019-02-112019-02-122019-02-132019-02-142019-02-152019-02-162019-02-172019-02-182019-02-192019-02-202019-02-21 0.6 km2019-02-222019-02-232019-02-242019-02-252019-02-26 1.0 km2019-02-27 1.0 km2019-02-28 1.0 km2019-03-012019-03-022019-03-032019-03-042019-03-052019-03-062019-03-072019-03-082019-03-092019-03-102019-03-112019-03-122019-03-132019-03-142019-03-152019-03-16 0.6 km2019-03-172019-03-182019-03-192019-03-202019-03-21 1.0 km2019-03-222019-03-232019-03-242019-03-252019-03-262019-03-272019-03-282019-03-292019-03-302019-03-312019-04-012019-04-022019-04-032019-04-042019-04-052019-04-062019-04-072019-04-082019-04-092019-04-102019-04-112019-04-122019-04-132019-04-142019-04-152019-04-162019-04-172019-04-182019-04-192019-04-202019-04-212019-04-222019-04-232019-04-242019-04-252019-04-262019-04-272019-04-282019-04-292019-04-302019-05-012019-05-022019-05-032019-05-042019-05-05 1.0 km2019-05-06 1.0 km2019-05-07 1.6 km2019-05-08 2.0 km2019-05-09 2.0 km2019-05-10 2.2 km2019-05-11 3.0 km2019-05-12 2.0 km2019-05-13 3.0 km2019-05-14 3.5 km2019-05-15 4.2 km2019-05-16 3.0 km2019-05-17 5.0 km2019-05-18 5.3 km2019-05-19 3.5 km2019-05-20 4.2 km2019-05-21 3.0 km2019-05-22 4.2 km2019-05-23 6.0 km2019-05-24 1.6 km2019-05-25 6.0 km2019-05-26 2.7 km2019-05-27 4.0 km2019-05-28 4.0 km2019-05-29 4.0 km2019-05-30 7.0 km2019-05-31 4.2 km2019-06-01 3.0 km2019-06-02 8.0 km2019-06-03 4.0 km2019-06-04 4.0 km2019-06-05 3.0 km2019-06-06 8.0 km2019-06-07 4.0 km2019-06-08 4.0 km2019-06-09 8.4 km2019-06-10 5.0 km2019-06-11 5.0 km2019-06-12 5.0 km2019-06-13 10.0 km2019-06-14 5.0 km2019-06-15 10.0 km2019-06-16 3.0 km2019-06-17 5.0 km2019-06-18 5.3 km2019-06-19 5.0 km2019-06-20 12.0 km2019-06-21 5.0 km2019-06-22 5.0 km2019-06-23 11.5 km2019-06-24 5.0 km2019-06-25 5.0 km2019-06-26 4.3 km2019-06-27 12.0 km2019-06-28 5.0 km2019-06-29 5.0 km2019-06-30 10.0 km2019-07-01 5.0 km2019-07-02 5.0 km2019-07-03 5.0 km2019-07-04 8.0 km2019-07-05 15.1 km2019-07-06 11.0 km2019-07-07 5.0 km2019-07-08 5.0 km2019-07-09 5.0 km2019-07-10 5.0 km2019-07-11 15.0 km2019-07-12 5.0 km2019-07-13 5.0 km2019-07-14 14.0 km2019-07-15 5.0 km2019-07-16 5.0 km2019-07-17 5.0 km2019-07-18 10.0 km2019-07-19 5.0 km2019-07-20 10.0 km2019-07-21 5.0 km2019-07-22 5.0 km2019-07-23 5.0 km2019-07-24 5.0 km2019-07-25 10.0 km2019-07-26 5.0 km2019-07-27 6.4 km2019-07-28 10.0 km2019-07-29 5.0 km2019-07-30 5.0 km2019-07-31 5.0 km2019-08-01 10.0 km2019-08-02 5.0 km2019-08-03 15.4 km2019-08-04 6.3 km2019-08-05 5.0 km2019-08-06 7.2 km2019-08-07 6.0 km2019-08-08 10.2 km2019-08-092019-08-10 5.0 km2019-08-11 3.5 km2019-08-12 5.2 km2019-08-13 5.0 km2019-08-14 2.0 km2019-08-15 9.0 km2019-08-162019-08-172019-08-18 10.0 km2019-08-19 10.0 km2019-08-20 5.0 km2019-08-21 9.7 km2019-08-22 5.0 km2019-08-23 5.0 km2019-08-24 10.3 km2019-08-25 5.0 km2019-08-26 1.1 km2019-08-27 5.0 km2019-08-28 8.0 km2019-08-29 5.1 km2019-08-30 5.0 km2019-08-31 16.0 km2019-09-01 5.0 km2019-09-02 5.0 km2019-09-03 5.1 km2019-09-042019-09-05 2.5 km2019-09-06 5.0 km2019-09-07 12.1 km2019-09-08 6.7 km2019-09-09 5.5 km2019-09-10 5.0 km2019-09-11 6.1 km2019-09-12 5.0 km2019-09-13 12.0 km2019-09-14 5.0 km2019-09-15 15.0 km2019-09-16 5.0 km2019-09-17 4.4 km2019-09-18 5.1 km2019-09-19 6.0 km2019-09-20 4.3 km2019-09-21 5.1 km2019-09-22 14.7 km2019-09-23 4.0 km2019-09-24 8.0 km2019-09-25 5.0 km2019-09-26 8.0 km2019-09-27 3.0 km2019-09-28 20.0 km2019-09-29 3.1 km2019-09-30 9.1 km2019-10-01 5.0 km2019-10-02 2.0 km2019-10-03 10.0 km2019-10-04 6.0 km2019-10-05 2.0 km2019-10-06 18.6 km2019-10-07 1.0 km2019-10-08 3.0 km2019-10-09 5.2 km2019-10-10 2.1 km2019-10-11 5.0 km2019-10-12 2.0 km2019-10-13 21.5 km2019-10-14 1.0 km2019-10-15 5.0 km2019-10-16 8.0 km2019-10-17 3.2 km2019-10-18 5.0 km2019-10-19 24.0 km2019-10-20 2.0 km2019-10-21 5.1 km2019-10-22 3.3 km2019-10-23 6.0 km2019-10-24 3.1 km2019-10-25 4.2 km2019-10-26 20.4 km2019-10-27 3.0 km2019-10-28 2.0 km2019-10-29 5.0 km2019-10-30 2.0 km2019-10-31 3.0 km2019-11-01 4.2 km2019-11-02 1.0 km2019-11-03 43.5 km2019-11-04 5.0 km2019-11-05 1.3 km2019-11-06 5.0 km2019-11-07 2.3 km2019-11-08 3.0 km2019-11-09 1.2 km2019-11-10 4.5 km2019-11-11 5.0 km2019-11-12 10.0 km2019-11-13 1.1 km2019-11-14 2.1 km2019-11-15 4.5 km2019-11-16 5.0 km2019-11-172019-11-18 1.0 km2019-11-19 5.0 km2019-11-20 4.5 km2019-11-21 2.0 km2019-11-22 5.0 km2019-11-23 4.1 km2019-11-24 6.3 km2019-11-25 4.0 km2019-11-262019-11-27 4.0 km2019-11-28 2.4 km2019-11-29 2.1 km2019-11-30 5.0 km2019-12-01 11.6 km2019-12-02 4.0 km2019-12-03 5.0 km2019-12-04 2.5 km2019-12-05 3.0 km2019-12-06 3.5 km2019-12-07 3.2 km2019-12-08 5.0 km2019-12-09 2.0 km2019-12-10 3.0 km2019-12-11 4.0 km2019-12-12 2.4 km2019-12-132019-12-14 5.3 km2019-12-152019-12-16 1.7 km2019-12-172019-12-18 3.0 km2019-12-19 4.0 km2019-12-202019-12-212019-12-22 3.0 km2019-12-23 5.0 km2019-12-242019-12-25 5.0 km2019-12-26 3.0 km2019-12-27 4.0 km2019-12-28 2.0 km2019-12-29 7.0 km2019-12-302019-12-31 4.0 km2018403.9 kmJanFebMarAprMayJunJulAugSepOctNovDec2018-01-01 5.0 km2018-01-02 3.0 km2018-01-03 3.0 km2018-01-04 3.0 km2018-01-05 3.0 km2018-01-06 3.0 km2018-01-07 6.0 km2018-01-08 3.0 km2018-01-09 3.0 km2018-01-10 3.2 km2018-01-11 3.0 km2018-01-12 3.0 km2018-01-13 8.0 km2018-01-14 3.0 km2018-01-15 3.1 km2018-01-16 4.0 km2018-01-17 4.0 km2018-01-18 4.1 km2018-01-19 4.0 km2018-01-20 10.0 km2018-01-21 5.0 km2018-01-22 4.0 km2018-01-23 4.0 km2018-01-24 4.0 km2018-01-25 4.1 km2018-01-26 3.0 km2018-01-27 4.2 km2018-01-28 10.0 km2018-01-29 4.0 km2018-01-30 3.0 km2018-01-31 4.0 km2018-02-01 5.0 km2018-02-02 5.3 km2018-02-03 12.0 km2018-02-04 5.0 km2018-02-05 5.0 km2018-02-06 5.0 km2018-02-07 3.0 km2018-02-08 5.0 km2018-02-09 5.0 km2018-02-10 5.0 km2018-02-11 10.6 km2018-02-12 5.0 km2018-02-13 5.0 km2018-02-14 5.3 km2018-02-15 3.0 km2018-02-16 3.0 km2018-02-17 3.0 km2018-02-18 1.5 km2018-02-19 2.0 km2018-02-20 5.0 km2018-02-21 4.1 km2018-02-22 1.6 km2018-02-23 6.5 km2018-02-24 2.4 km2018-02-25 5.0 km2018-02-26 1.8 km2018-02-27 1.4 km2018-02-282018-03-01 8.4 km2018-03-02 3.8 km2018-03-03 2.3 km2018-03-042018-03-052018-03-062018-03-07 5.0 km2018-03-08 1.0 km2018-03-09 3.1 km2018-03-10 3.5 km2018-03-11 2.0 km2018-03-122018-03-13 2.0 km2018-03-142018-03-152018-03-162018-03-172018-03-18 1.0 km2018-03-19 1.0 km2018-03-202018-03-21 2.0 km2018-03-22 3.0 km2018-03-23 1.0 km2018-03-24 0.8 km2018-03-252018-03-262018-03-272018-03-28 1.0 km2018-03-292018-03-302018-03-312018-04-012018-04-02 1.0 km2018-04-032018-04-042018-04-052018-04-062018-04-072018-04-082018-04-092018-04-102018-04-11 1.0 km2018-04-122018-04-132018-04-142018-04-152018-04-162018-04-172018-04-18 1.0 km2018-04-192018-04-202018-04-212018-04-222018-04-23 1.0 km2018-04-242018-04-252018-04-262018-04-272018-04-282018-04-292018-04-302018-05-01 1.0 km2018-05-02 1.5 km2018-05-03 0.8 km2018-05-04 0.5 km2018-05-052018-05-062018-05-07 1.0 km2018-05-082018-05-092018-05-10 1.0 km2018-05-112018-05-12 1.0 km2018-05-132018-05-142018-05-152018-05-162018-05-172018-05-182018-05-192018-05-202018-05-212018-05-222018-05-23 1.0 km2018-05-242018-05-252018-05-262018-05-272018-05-282018-05-292018-05-302018-05-312018-06-012018-06-022018-06-032018-06-042018-06-052018-06-06 0.6 km2018-06-072018-06-082018-06-092018-06-102018-06-11 1.0 km2018-06-12 1.5 km2018-06-132018-06-14 1.0 km2018-06-15 1.0 km2018-06-162018-06-172018-06-182018-06-19 2.0 km2018-06-20 2.3 km2018-06-21 1.0 km2018-06-22 2.0 km2018-06-23 1.0 km2018-06-242018-06-25 1.1 km2018-06-262018-06-272018-06-282018-06-292018-06-302018-07-012018-07-022018-07-032018-07-042018-07-052018-07-062018-07-072018-07-082018-07-092018-07-102018-07-112018-07-122018-07-132018-07-142018-07-152018-07-162018-07-172018-07-18 0.5 km2018-07-192018-07-202018-07-212018-07-222018-07-232018-07-24 0.5 km2018-07-25 1.0 km2018-07-26 1.0 km2018-07-27 1.5 km2018-07-282018-07-292018-07-30 1.0 km2018-07-312018-08-01 0.5 km2018-08-022018-08-032018-08-042018-08-052018-08-062018-08-072018-08-082018-08-092018-08-102018-08-112018-08-122018-08-132018-08-142018-08-152018-08-162018-08-172018-08-182018-08-192018-08-202018-08-212018-08-22 1.0 km2018-08-23 1.5 km2018-08-24 2.0 km2018-08-252018-08-26 1.0 km2018-08-272018-08-282018-08-29 1.0 km2018-08-302018-08-31 2.0 km2018-09-012018-09-02 3.0 km2018-09-032018-09-042018-09-05 1.0 km2018-09-062018-09-07 1.0 km2018-09-082018-09-092018-09-10 1.0 km2018-09-112018-09-122018-09-132018-09-142018-09-152018-09-162018-09-172018-09-18 1.0 km2018-09-19 0.6 km2018-09-20 1.0 km2018-09-21 1.6 km2018-09-222018-09-232018-09-242018-09-252018-09-262018-09-272018-09-282018-09-292018-09-302018-10-012018-10-02 1.0 km2018-10-032018-10-042018-10-052018-10-062018-10-072018-10-082018-10-092018-10-102018-10-112018-10-122018-10-132018-10-14 1.0 km2018-10-152018-10-16 1.0 km2018-10-172018-10-18 1.1 km2018-10-19 1.5 km2018-10-20 2.0 km2018-10-21 2.0 km2018-10-222018-10-232018-10-24 1.0 km2018-10-252018-10-262018-10-27 1.0 km2018-10-282018-10-292018-10-302018-10-312018-11-012018-11-022018-11-032018-11-042018-11-05 1.0 km2018-11-06 1.0 km2018-11-07 2.0 km2018-11-08 2.0 km2018-11-09 3.0 km2018-11-10 2.2 km2018-11-11 2.1 km2018-11-12 2.0 km2018-11-13 2.0 km2018-11-142018-11-152018-11-16 1.0 km2018-11-172018-11-182018-11-19 1.0 km2018-11-202018-11-212018-11-222018-11-232018-11-242018-11-252018-11-26 1.0 km2018-11-272018-11-282018-11-29 1.0 km2018-11-30 1.0 km2018-12-01 1.0 km2018-12-022018-12-032018-12-042018-12-05 1.0 km2018-12-062018-12-072018-12-08 1.1 km2018-12-09 1.0 km2018-12-10 2.2 km2018-12-11 1.2 km2018-12-122018-12-132018-12-14 1.0 km2018-12-15 2.0 km2018-12-16 1.1 km2018-12-17 2.0 km2018-12-18 2.2 km2018-12-19 2.4 km2018-12-20 1.0 km2018-12-21 1.0 km2018-12-22 3.0 km2018-12-23 0.9 km2018-12-242018-12-25 1.0 km2018-12-262018-12-272018-12-282018-12-292018-12-302018-12-312017962.8 kmJanFebMarAprMayJunJulAugSepOctNovDec2016-12-26 6.7 km2016-12-27 2.0 km2016-12-28 4.9 km2016-12-29 6.1 km2016-12-30 4.0 km2016-12-31 18.0 km2017-01-01 3.5 km2017-01-02 5.0 km2017-01-03 4.0 km2017-01-04 6.4 km2017-01-05 7.0 km2017-01-06 2.4 km2017-01-07 18.1 km2017-01-08 1.0 km2017-01-09 5.0 km2017-01-10 3.4 km2017-01-11 5.3 km2017-01-12 5.1 km2017-01-13 4.0 km2017-01-14 13.8 km2017-01-15 6.6 km2017-01-16 7.6 km2017-01-17 7.3 km2017-01-18 5.8 km2017-01-19 4.5 km2017-01-20 7.7 km2017-01-21 15.8 km2017-01-22 3.5 km2017-01-23 9.6 km2017-01-24 7.2 km2017-01-25 5.3 km2017-01-26 6.0 km2017-01-27 8.0 km2017-01-28 4.5 km2017-01-29 3.0 km2017-01-30 5.0 km2017-01-31 10.0 km2017-02-01 4.0 km2017-02-02 6.0 km2017-02-03 8.1 km2017-02-04 5.5 km2017-02-05 7.4 km2017-02-06 2.6 km2017-02-07 3.0 km2017-02-08 5.0 km2017-02-09 2.4 km2017-02-10 5.1 km2017-02-11 8.0 km2017-02-12 8.9 km2017-02-13 2.1 km2017-02-14 5.9 km2017-02-15 5.3 km2017-02-16 8.0 km2017-02-17 4.9 km2017-02-18 17.1 km2017-02-19 6.2 km2017-02-20 1.0 km2017-02-21 5.4 km2017-02-22 5.0 km2017-02-23 13.1 km2017-02-24 3.9 km2017-02-25 15.2 km2017-02-26 2.0 km2017-02-27 5.0 km2017-02-28 5.2 km2017-03-01 8.6 km2017-03-02 4.0 km2017-03-03 5.2 km2017-03-04 10.0 km2017-03-05 4.0 km2017-03-06 4.0 km2017-03-07 4.0 km2017-03-08 9.2 km2017-03-09 8.0 km2017-03-10 4.0 km2017-03-11 7.3 km2017-03-12 9.1 km2017-03-13 3.0 km2017-03-14 5.2 km2017-03-15 8.8 km2017-03-16 7.0 km2017-03-17 6.0 km2017-03-18 6.7 km2017-03-19 17.8 km2017-03-20 6.8 km2017-03-212017-03-22 3.2 km2017-03-23 2.1 km2017-03-24 5.5 km2017-03-25 1.7 km2017-03-26 13.1 km2017-03-27 9.0 km2017-03-28 10.0 km2017-03-29 10.3 km2017-03-30 10.0 km2017-03-31 4.3 km2017-04-01 6.3 km2017-04-02 20.8 km2017-04-03 10.4 km2017-04-04 10.0 km2017-04-05 5.3 km2017-04-06 5.0 km2017-04-07 2.0 km2017-04-08 9.2 km2017-04-09 3.0 km2017-04-10 4.4 km2017-04-112017-04-12 5.2 km2017-04-13 11.3 km2017-04-14 2.5 km2017-04-15 8.2 km2017-04-16 5.0 km2017-04-17 5.8 km2017-04-18 8.0 km2017-04-19 0.5 km2017-04-20 4.5 km2017-04-21 3.0 km2017-04-22 5.0 km2017-04-23 28.3 km2017-04-242017-04-25 8.0 km2017-04-26 10.3 km2017-04-272017-04-28 3.0 km2017-04-292017-04-30 1.0 km2017-05-01 10.2 km2017-05-022017-05-03 5.3 km2017-05-04 7.6 km2017-05-052017-05-06 3.4 km2017-05-07 22.6 km2017-05-082017-05-092017-05-102017-05-11 5.0 km2017-05-12 2.2 km2017-05-13 42.8 km2017-05-142017-05-15 7.2 km2017-05-16 7.0 km2017-05-17 2.7 km2017-05-18 2.3 km2017-05-192017-05-20 2.1 km2017-05-21 3.0 km2017-05-222017-05-23 5.1 km2017-05-242017-05-25 2.7 km2017-05-26 3.0 km2017-05-272017-05-28 6.0 km2017-05-292017-05-302017-05-31 2.2 km2017-06-01 2.1 km2017-06-022017-06-032017-06-042017-06-052017-06-062017-06-07 3.0 km2017-06-082017-06-09 2.1 km2017-06-102017-06-11 5.6 km2017-06-122017-06-132017-06-14 2.0 km2017-06-152017-06-162017-06-17 2.0 km2017-06-182017-06-19 3.0 km2017-06-202017-06-212017-06-22 2.0 km2017-06-232017-06-242017-06-252017-06-262017-06-27 3.0 km2017-06-282017-06-292017-06-302017-07-012017-07-022017-07-032017-07-04 1.0 km2017-07-052017-07-062017-07-072017-07-082017-07-092017-07-102017-07-112017-07-122017-07-132017-07-142017-07-152017-07-162017-07-172017-07-182017-07-192017-07-202017-07-212017-07-222017-07-232017-07-242017-07-252017-07-26 1.0 km2017-07-272017-07-28 2.0 km2017-07-292017-07-302017-07-312017-08-012017-08-022017-08-032017-08-042017-08-052017-08-062017-08-072017-08-082017-08-092017-08-102017-08-112017-08-122017-08-132017-08-142017-08-152017-08-162017-08-172017-08-182017-08-192017-08-20 2.0 km2017-08-21 3.0 km2017-08-222017-08-232017-08-242017-08-252017-08-262017-08-272017-08-282017-08-292017-08-302017-08-312017-09-012017-09-022017-09-032017-09-042017-09-052017-09-062017-09-072017-09-082017-09-092017-09-102017-09-112017-09-122017-09-132017-09-142017-09-152017-09-162017-09-172017-09-182017-09-192017-09-202017-09-212017-09-222017-09-232017-09-242017-09-252017-09-262017-09-272017-09-282017-09-292017-09-302017-10-012017-10-022017-10-032017-10-042017-10-052017-10-062017-10-072017-10-082017-10-092017-10-102017-10-112017-10-122017-10-13 1.0 km2017-10-142017-10-15 1.4 km2017-10-16 2.0 km2017-10-172017-10-18 1.1 km2017-10-192017-10-202017-10-212017-10-222017-10-232017-10-242017-10-252017-10-262017-10-272017-10-282017-10-292017-10-302017-10-312017-11-012017-11-022017-11-032017-11-04 1.2 km2017-11-052017-11-062017-11-072017-11-082017-11-092017-11-102017-11-112017-11-122017-11-132017-11-142017-11-152017-11-162017-11-172017-11-182017-11-192017-11-202017-11-212017-11-222017-11-232017-11-242017-11-252017-11-262017-11-272017-11-282017-11-292017-11-302017-12-012017-12-022017-12-032017-12-042017-12-052017-12-062017-12-072017-12-082017-12-092017-12-102017-12-112017-12-122017-12-132017-12-142017-12-152017-12-162017-12-172017-12-182017-12-192017-12-202017-12-212017-12-22 1.0 km2017-12-232017-12-242017-12-25 1.5 km2017-12-26 1.0 km2017-12-27 2.0 km2017-12-28 3.0 km2017-12-29 1.1 km2017-12-30 2.0 km2017-12-31 0.5 km2016899.2 kmJanFebMarAprMayJunJulAugSepOctNovDec2015-12-282015-12-292015-12-302015-12-312016-01-01 2.0 km2016-01-022016-01-03 0.8 km2016-01-042016-01-052016-01-062016-01-072016-01-082016-01-092016-01-102016-01-112016-01-122016-01-132016-01-142016-01-152016-01-162016-01-172016-01-18 1.5 km2016-01-192016-01-202016-01-212016-01-222016-01-232016-01-242016-01-252016-01-262016-01-272016-01-282016-01-292016-01-302016-01-312016-02-012016-02-022016-02-032016-02-042016-02-052016-02-062016-02-072016-02-082016-02-092016-02-10 2.0 km2016-02-112016-02-122016-02-132016-02-142016-02-152016-02-162016-02-17 1.1 km2016-02-18 2.1 km2016-02-192016-02-20 2.0 km2016-02-212016-02-222016-02-23 2.1 km2016-02-24 1.1 km2016-02-25 2.8 km2016-02-262016-02-27 0.9 km2016-02-282016-02-29 2.5 km2016-03-012016-03-022016-03-032016-03-042016-03-052016-03-062016-03-072016-03-082016-03-092016-03-102016-03-112016-03-122016-03-132016-03-142016-03-152016-03-16 1.7 km2016-03-17 1.0 km2016-03-182016-03-192016-03-20 2.1 km2016-03-21 3.0 km2016-03-22 1.5 km2016-03-23 3.1 km2016-03-24 2.2 km2016-03-25 1.2 km2016-03-26 5.1 km2016-03-272016-03-28 1.1 km2016-03-29 6.0 km2016-03-30 1.5 km2016-03-312016-04-01 1.0 km2016-04-02 0.7 km2016-04-03 8.0 km2016-04-04 1.0 km2016-04-05 0.8 km2016-04-06 5.0 km2016-04-07 2.1 km2016-04-082016-04-09 10.0 km2016-04-10 2.0 km2016-04-11 4.2 km2016-04-12 4.7 km2016-04-13 14.1 km2016-04-142016-04-15 2.0 km2016-04-16 1.0 km2016-04-17 2.4 km2016-04-18 9.3 km2016-04-19 3.9 km2016-04-20 7.0 km2016-04-21 4.9 km2016-04-22 10.0 km2016-04-23 3.8 km2016-04-24 15.0 km2016-04-25 1.0 km2016-04-26 8.8 km2016-04-27 4.3 km2016-04-28 6.1 km2016-04-29 2.3 km2016-04-30 17.7 km2016-05-01 1.2 km2016-05-02 5.8 km2016-05-03 0.7 km2016-05-04 3.6 km2016-05-05 14.3 km2016-05-06 1.7 km2016-05-072016-05-08 8.5 km2016-05-09 10.4 km2016-05-10 4.3 km2016-05-11 3.6 km2016-05-12 8.0 km2016-05-13 4.1 km2016-05-142016-05-15 20.0 km2016-05-162016-05-17 4.1 km2016-05-182016-05-19 6.1 km2016-05-20 2.0 km2016-05-212016-05-22 21.5 km2016-05-23 1.0 km2016-05-24 7.1 km2016-05-25 2.5 km2016-05-26 2.0 km2016-05-27 1.6 km2016-05-282016-05-292016-05-30 5.0 km2016-05-31 7.0 km2016-06-01 11.6 km2016-06-02 5.1 km2016-06-03 7.2 km2016-06-04 9.0 km2016-06-05 6.4 km2016-06-06 5.3 km2016-06-07 6.7 km2016-06-08 1.6 km2016-06-09 2.2 km2016-06-10 2.4 km2016-06-112016-06-12 1.5 km2016-06-132016-06-14 2.0 km2016-06-152016-06-162016-06-172016-06-182016-06-192016-06-202016-06-212016-06-222016-06-232016-06-242016-06-252016-06-262016-06-272016-06-282016-06-292016-06-302016-07-012016-07-022016-07-032016-07-042016-07-05 2.5 km2016-07-062016-07-072016-07-082016-07-092016-07-102016-07-112016-07-122016-07-132016-07-142016-07-152016-07-162016-07-17 1.0 km2016-07-18 2.1 km2016-07-192016-07-202016-07-212016-07-222016-07-232016-07-242016-07-252016-07-262016-07-272016-07-282016-07-292016-07-302016-07-312016-08-01 3.1 km2016-08-02 1.0 km2016-08-03 1.0 km2016-08-042016-08-052016-08-062016-08-072016-08-082016-08-092016-08-102016-08-112016-08-122016-08-132016-08-142016-08-152016-08-162016-08-172016-08-182016-08-192016-08-202016-08-212016-08-222016-08-232016-08-242016-08-252016-08-262016-08-272016-08-282016-08-292016-08-302016-08-312016-09-012016-09-022016-09-032016-09-042016-09-052016-09-062016-09-072016-09-082016-09-092016-09-102016-09-112016-09-122016-09-132016-09-142016-09-152016-09-162016-09-172016-09-182016-09-192016-09-20 3.0 km2016-09-212016-09-222016-09-232016-09-24 2.0 km2016-09-25 3.1 km2016-09-26 0.6 km2016-09-272016-09-282016-09-292016-09-302016-10-012016-10-022016-10-032016-10-042016-10-052016-10-062016-10-072016-10-082016-10-092016-10-102016-10-112016-10-122016-10-13 2.0 km2016-10-14 3.0 km2016-10-15 2.0 km2016-10-16 3.0 km2016-10-17 4.0 km2016-10-18 1.0 km2016-10-19 0.8 km2016-10-20 4.0 km2016-10-21 2.2 km2016-10-222016-10-23 5.0 km2016-10-24 5.0 km2016-10-25 5.1 km2016-10-26 5.0 km2016-10-27 5.0 km2016-10-28 6.2 km2016-10-29 5.0 km2016-10-30 5.6 km2016-10-31 5.0 km2016-11-01 5.0 km2016-11-02 5.0 km2016-11-03 5.0 km2016-11-04 5.1 km2016-11-05 8.0 km2016-11-06 5.0 km2016-11-07 5.3 km2016-11-08 5.0 km2016-11-09 5.0 km2016-11-10 5.0 km2016-11-11 5.0 km2016-11-12 10.0 km2016-11-13 5.0 km2016-11-14 5.0 km2016-11-15 5.3 km2016-11-16 5.1 km2016-11-17 5.0 km2016-11-18 5.3 km2016-11-19 10.0 km2016-11-20 5.0 km2016-11-21 5.0 km2016-11-22 5.1 km2016-11-23 6.0 km2016-11-24 6.0 km2016-11-25 7.3 km2016-11-26 12.0 km2016-11-27 6.0 km2016-11-28 6.0 km2016-11-29 6.0 km2016-11-30 7.3 km2016-12-01 6.0 km2016-12-02 6.0 km2016-12-03 12.0 km2016-12-04 6.0 km2016-12-05 6.0 km2016-12-06 6.0 km2016-12-07 7.1 km2016-12-08 6.3 km2016-12-09 6.0 km2016-12-10 15.0 km2016-12-11 6.1 km2016-12-12 6.0 km2016-12-13 6.0 km2016-12-14 5.0 km2016-12-15 6.4 km2016-12-16 6.2 km2016-12-17 10.0 km2016-12-18 6.0 km2016-12-19 6.0 km2016-12-20 5.1 km2016-12-21 6.9 km2016-12-22 5.2 km2016-12-23 5.7 km2016-12-24 16.0 km2016-12-25 4.0 km2016-12-26 6.7 km2016-12-27 2.0 km2016-12-28 4.9 km2016-12-29 6.1 km2016-12-30 4.0 km2016-12-31 18.0 km2015435.0 kmJanFebMarAprMayJunJulAugSepOctNovDec2014-12-29 3.0 km2014-12-30 2.4 km2014-12-312015-01-012015-01-02 3.3 km2015-01-032015-01-042015-01-052015-01-062015-01-072015-01-082015-01-092015-01-102015-01-112015-01-122015-01-132015-01-14 2.4 km2015-01-15 5.2 km2015-01-162015-01-172015-01-182015-01-19 1.5 km2015-01-20 3.1 km2015-01-21 12.4 km2015-01-222015-01-232015-01-242015-01-252015-01-26 1.8 km2015-01-272015-01-28 2.6 km2015-01-29 1.8 km2015-01-302015-01-312015-02-01 5.0 km2015-02-02 1.3 km2015-02-03 1.7 km2015-02-042015-02-052015-02-062015-02-072015-02-082015-02-092015-02-102015-02-11 11.1 km2015-02-122015-02-132015-02-142015-02-152015-02-162015-02-172015-02-182015-02-192015-02-202015-02-212015-02-222015-02-232015-02-242015-02-252015-02-262015-02-272015-02-282015-03-012015-03-022015-03-032015-03-042015-03-05 2.0 km2015-03-06 3.1 km2015-03-07 5.0 km2015-03-08 6.3 km2015-03-092015-03-102015-03-112015-03-122015-03-132015-03-142015-03-152015-03-162015-03-172015-03-182015-03-192015-03-202015-03-212015-03-222015-03-232015-03-242015-03-252015-03-262015-03-272015-03-282015-03-292015-03-302015-03-312015-04-012015-04-022015-04-032015-04-042015-04-05 0.5 km2015-04-062015-04-07 1.3 km2015-04-08 2.0 km2015-04-09 2.6 km2015-04-10 1.6 km2015-04-112015-04-122015-04-132015-04-14 2.0 km2015-04-15 3.0 km2015-04-16 1.0 km2015-04-17 3.0 km2015-04-18 0.9 km2015-04-192015-04-20 5.0 km2015-04-21 2.5 km2015-04-222015-04-23 1.5 km2015-04-24 1.9 km2015-04-252015-04-26 6.3 km2015-04-272015-04-28 3.3 km2015-04-29 5.0 km2015-04-30 11.8 km2015-05-012015-05-02 1.1 km2015-05-032015-05-04 5.0 km2015-05-05 2.2 km2015-05-06 5.0 km2015-05-07 5.7 km2015-05-082015-05-09 1.0 km2015-05-102015-05-112015-05-12 1.3 km2015-05-132015-05-14 4.2 km2015-05-152015-05-162015-05-172015-05-182015-05-19 2.0 km2015-05-202015-05-21 2.1 km2015-05-222015-05-23 6.0 km2015-05-242015-05-25 4.3 km2015-05-26 2.0 km2015-05-272015-05-28 1.8 km2015-05-292015-05-302015-05-31 0.9 km2015-06-01 3.0 km2015-06-02 2.5 km2015-06-032015-06-04 3.0 km2015-06-052015-06-062015-06-072015-06-08 2.0 km2015-06-09 3.0 km2015-06-10 5.0 km2015-06-11 5.2 km2015-06-12 2.0 km2015-06-132015-06-14 5.0 km2015-06-15 1.3 km2015-06-16 3.0 km2015-06-17 4.3 km2015-06-18 2.0 km2015-06-19 1.0 km2015-06-202015-06-21 8.0 km2015-06-222015-06-23 2.0 km2015-06-24 4.1 km2015-06-25 1.0 km2015-06-262015-06-272015-06-28 12.0 km2015-06-292015-06-30 1.0 km2015-07-01 4.2 km2015-07-02 1.0 km2015-07-03 5.0 km2015-07-042015-07-05 14.0 km2015-07-062015-07-07 5.0 km2015-07-08 1.3 km2015-07-09 4.3 km2015-07-102015-07-112015-07-12 15.0 km2015-07-132015-07-14 1.4 km2015-07-15 4.2 km2015-07-16 1.2 km2015-07-17 4.2 km2015-07-182015-07-19 17.2 km2015-07-202015-07-21 3.0 km2015-07-22 4.0 km2015-07-23 4.7 km2015-07-242015-07-252015-07-26 15.5 km2015-07-272015-07-28 1.5 km2015-07-292015-07-30 4.4 km2015-07-312015-08-01 2.9 km2015-08-022015-08-03 3.0 km2015-08-04 2.0 km2015-08-05 2.2 km2015-08-062015-08-072015-08-082015-08-092015-08-102015-08-11 1.0 km2015-08-122015-08-13 3.6 km2015-08-142015-08-152015-08-162015-08-172015-08-182015-08-192015-08-202015-08-21 1.5 km2015-08-222015-08-232015-08-242015-08-25 3.0 km2015-08-262015-08-272015-08-282015-08-292015-08-302015-08-312015-09-012015-09-022015-09-032015-09-042015-09-05 1.5 km2015-09-062015-09-072015-09-082015-09-092015-09-102015-09-112015-09-122015-09-132015-09-142015-09-15 1.3 km2015-09-162015-09-172015-09-182015-09-192015-09-202015-09-21 4.3 km2015-09-222015-09-23 1.2 km2015-09-242015-09-252015-09-262015-09-272015-09-28 3.0 km2015-09-292015-09-302015-10-01 4.5 km2015-10-022015-10-032015-10-042015-10-05 1.1 km2015-10-06 3.0 km2015-10-072015-10-08 4.1 km2015-10-09 2.4 km2015-10-10 2.0 km2015-10-11 3.0 km2015-10-12 5.0 km2015-10-13 3.0 km2015-10-14 3.0 km2015-10-15 5.3 km2015-10-162015-10-172015-10-182015-10-19 1.0 km2015-10-20 1.0 km2015-10-212015-10-222015-10-232015-10-242015-10-252015-10-26 1.6 km2015-10-27 2.0 km2015-10-28 1.6 km2015-10-29 3.0 km2015-10-302015-10-312015-11-012015-11-022015-11-032015-11-042015-11-052015-11-062015-11-072015-11-082015-11-092015-11-102015-11-112015-11-12 1.5 km2015-11-132015-11-142015-11-152015-11-162015-11-172015-11-18 1.5 km2015-11-192015-11-202015-11-212015-11-222015-11-232015-11-242015-11-252015-11-262015-11-272015-11-282015-11-292015-11-302015-12-01 0.7 km2015-12-022015-12-032015-12-042015-12-052015-12-062015-12-072015-12-08 0.9 km2015-12-092015-12-102015-12-112015-12-122015-12-132015-12-142015-12-152015-12-162015-12-172015-12-182015-12-192015-12-202015-12-212015-12-222015-12-232015-12-24 2.0 km2015-12-252015-12-262015-12-272015-12-282015-12-292015-12-302015-12-312014822.8 kmJanFebMarAprMayJunJulAugSepOctNovDec2013-12-30 2.5 km2013-12-312014-01-01 5.0 km2014-01-02 5.0 km2014-01-032014-01-04 4.0 km2014-01-05 7.2 km2014-01-06 2.5 km2014-01-072014-01-082014-01-092014-01-102014-01-112014-01-122014-01-132014-01-14 5.0 km2014-01-152014-01-16 3.0 km2014-01-172014-01-182014-01-192014-01-20 5.0 km2014-01-21 7.0 km2014-01-222014-01-232014-01-242014-01-252014-01-262014-01-272014-01-282014-01-292014-01-302014-01-312014-02-012014-02-02 2.0 km2014-02-032014-02-042014-02-052014-02-062014-02-072014-02-082014-02-092014-02-102014-02-11 4.0 km2014-02-12 5.2 km2014-02-13 7.5 km2014-02-14 4.0 km2014-02-15 10.2 km2014-02-16 9.0 km2014-02-17 5.8 km2014-02-18 6.6 km2014-02-19 12.0 km2014-02-20 6.0 km2014-02-21 5.1 km2014-02-22 9.0 km2014-02-23 6.3 km2014-02-24 3.8 km2014-02-25 6.0 km2014-02-26 9.0 km2014-02-27 5.2 km2014-02-28 11.3 km2014-03-01 3.3 km2014-03-02 5.7 km2014-03-03 4.0 km2014-03-04 6.1 km2014-03-05 6.1 km2014-03-06 3.5 km2014-03-07 12.3 km2014-03-08 5.3 km2014-03-09 4.0 km2014-03-10 6.1 km2014-03-11 11.2 km2014-03-12 8.0 km2014-03-13 15.1 km2014-03-142014-03-152014-03-162014-03-17 13.6 km2014-03-18 3.0 km2014-03-19 12.1 km2014-03-20 2.3 km2014-03-21 6.0 km2014-03-22 10.0 km2014-03-23 6.0 km2014-03-24 2.0 km2014-03-25 21.1 km2014-03-262014-03-27 7.1 km2014-03-28 5.4 km2014-03-29 3.0 km2014-03-30 5.0 km2014-03-31 11.1 km2014-04-01 6.0 km2014-04-02 5.3 km2014-04-03 3.1 km2014-04-042014-04-052014-04-062014-04-072014-04-082014-04-09 5.0 km2014-04-10 3.1 km2014-04-11 6.0 km2014-04-122014-04-13 8.4 km2014-04-14 5.0 km2014-04-152014-04-16 3.0 km2014-04-17 2.5 km2014-04-18 5.6 km2014-04-192014-04-202014-04-21 14.0 km2014-04-22 5.0 km2014-04-232014-04-242014-04-25 4.0 km2014-04-26 20.5 km2014-04-272014-04-28 4.3 km2014-04-29 5.0 km2014-04-302014-05-012014-05-022014-05-03 7.8 km2014-05-042014-05-05 12.0 km2014-05-062014-05-072014-05-082014-05-092014-05-10 41.4 km2014-05-112014-05-122014-05-13 0.7 km2014-05-14 5.8 km2014-05-15 1.0 km2014-05-16 5.4 km2014-05-172014-05-182014-05-19 8.1 km2014-05-20 2.2 km2014-05-21 4.0 km2014-05-22 4.0 km2014-05-23 10.0 km2014-05-24 3.0 km2014-05-25 7.6 km2014-05-26 4.0 km2014-05-27 4.0 km2014-05-28 5.0 km2014-05-292014-05-302014-05-312014-06-012014-06-022014-06-03 3.0 km2014-06-04 6.3 km2014-06-05 4.0 km2014-06-062014-06-07 1.4 km2014-06-082014-06-092014-06-10 5.0 km2014-06-112014-06-122014-06-132014-06-142014-06-152014-06-162014-06-172014-06-18 5.0 km2014-06-19 5.9 km2014-06-20 10.0 km2014-06-212014-06-222014-06-23 2.0 km2014-06-24 6.2 km2014-06-252014-06-26 2.2 km2014-06-272014-06-282014-06-292014-06-302014-07-012014-07-02 2.5 km2014-07-032014-07-042014-07-052014-07-06 2.0 km2014-07-07 3.0 km2014-07-082014-07-09 6.4 km2014-07-10 2.3 km2014-07-112014-07-122014-07-132014-07-14 2.2 km2014-07-15 4.1 km2014-07-16 4.0 km2014-07-17 6.0 km2014-07-18 4.0 km2014-07-192014-07-202014-07-21 2.9 km2014-07-222014-07-23 8.0 km2014-07-24 2.0 km2014-07-252014-07-262014-07-27 1.1 km2014-07-28 5.0 km2014-07-29 10.1 km2014-07-30 9.1 km2014-07-31 2.0 km2014-08-01 7.2 km2014-08-022014-08-032014-08-04 2.0 km2014-08-05 7.3 km2014-08-06 14.6 km2014-08-07 2.2 km2014-08-082014-08-092014-08-10 1.4 km2014-08-11 6.0 km2014-08-12 11.1 km2014-08-13 5.0 km2014-08-142014-08-152014-08-162014-08-172014-08-182014-08-192014-08-202014-08-212014-08-222014-08-232014-08-242014-08-252014-08-262014-08-272014-08-282014-08-292014-08-302014-08-312014-09-012014-09-022014-09-032014-09-042014-09-052014-09-062014-09-072014-09-082014-09-092014-09-102014-09-112014-09-122014-09-132014-09-142014-09-152014-09-162014-09-172014-09-182014-09-192014-09-202014-09-212014-09-222014-09-232014-09-242014-09-252014-09-262014-09-272014-09-282014-09-292014-09-302014-10-012014-10-022014-10-032014-10-042014-10-052014-10-062014-10-072014-10-082014-10-092014-10-102014-10-112014-10-122014-10-132014-10-142014-10-152014-10-162014-10-172014-10-182014-10-192014-10-202014-10-212014-10-222014-10-232014-10-242014-10-252014-10-262014-10-272014-10-282014-10-292014-10-302014-10-312014-11-012014-11-022014-11-032014-11-042014-11-052014-11-062014-11-072014-11-082014-11-092014-11-102014-11-112014-11-122014-11-132014-11-142014-11-152014-11-162014-11-172014-11-182014-11-192014-11-202014-11-212014-11-222014-11-232014-11-242014-11-252014-11-262014-11-272014-11-282014-11-292014-11-302014-12-012014-12-02 4.1 km2014-12-03 3.1 km2014-12-042014-12-052014-12-062014-12-072014-12-082014-12-092014-12-102014-12-112014-12-122014-12-132014-12-14 2.1 km2014-12-152014-12-162014-12-172014-12-182014-12-192014-12-20 2.4 km2014-12-21 2.2 km2014-12-222014-12-23 5.0 km2014-12-242014-12-25 2.3 km2014-12-26 3.0 km2014-12-27 4.0 km2014-12-28 2.0 km2014-12-29 3.0 km2014-12-30 2.4 km2014-12-312013789.7 kmJanFebMarAprMayJunJulAugSepOctNovDec2012-12-312013-01-012013-01-022013-01-03 5.0 km2013-01-04 7.4 km2013-01-05 5.0 km2013-01-06 5.0 km2013-01-07 5.0 km2013-01-08 5.0 km2013-01-09 14.8 km2013-01-10 2.6 km2013-01-11 3.3 km2013-01-12 5.2 km2013-01-13 7.2 km2013-01-14 3.0 km2013-01-15 2.0 km2013-01-16 6.0 km2013-01-172013-01-18 8.3 km2013-01-192013-01-202013-01-21 5.2 km2013-01-22 10.2 km2013-01-23 5.0 km2013-01-24 3.3 km2013-01-25 5.2 km2013-01-26 2.1 km2013-01-27 10.0 km2013-01-282013-01-29 5.2 km2013-01-30 5.0 km2013-01-31 4.5 km2013-02-01 3.3 km2013-02-022013-02-03 2.0 km2013-02-04 6.2 km2013-02-05 4.0 km2013-02-06 2.2 km2013-02-072013-02-08 5.2 km2013-02-09 10.0 km2013-02-10 2.9 km2013-02-11 2.0 km2013-02-12 5.0 km2013-02-132013-02-14 8.0 km2013-02-15 5.2 km2013-02-162013-02-17 2.1 km2013-02-18 8.2 km2013-02-19 4.2 km2013-02-202013-02-21 5.2 km2013-02-22 5.0 km2013-02-23 5.2 km2013-02-24 5.0 km2013-02-25 5.0 km2013-02-26 7.5 km2013-02-27 2.5 km2013-02-28 5.0 km2013-03-012013-03-02 7.3 km2013-03-03 3.1 km2013-03-04 5.0 km2013-03-05 5.0 km2013-03-06 12.0 km2013-03-07 2.0 km2013-03-08 4.0 km2013-03-092013-03-10 6.4 km2013-03-11 5.0 km2013-03-12 5.0 km2013-03-132013-03-14 14.5 km2013-03-152013-03-16 3.2 km2013-03-17 7.3 km2013-03-18 4.0 km2013-03-19 7.3 km2013-03-202013-03-21 17.0 km2013-03-22 2.5 km2013-03-232013-03-24 7.2 km2013-03-25 2.3 km2013-03-26 2.2 km2013-03-27 10.0 km2013-03-282013-03-29 2.4 km2013-03-302013-03-31 16.1 km2013-04-012013-04-02 2.6 km2013-04-03 2.3 km2013-04-04 2.4 km2013-04-052013-04-06 2.5 km2013-04-07 10.0 km2013-04-082013-04-09 21.0 km2013-04-102013-04-112013-04-122013-04-132013-04-14 2.0 km2013-04-15 5.0 km2013-04-16 1.3 km2013-04-172013-04-182013-04-192013-04-202013-04-21 21.5 km2013-04-222013-04-232013-04-242013-04-25 2.0 km2013-04-26 3.0 km2013-04-27 5.0 km2013-04-282013-04-292013-04-30 5.3 km2013-05-01 6.1 km2013-05-02 2.0 km2013-05-032013-05-04 17.0 km2013-05-052013-05-062013-05-07 3.3 km2013-05-082013-05-09 5.0 km2013-05-102013-05-11 6.3 km2013-05-122013-05-13 5.2 km2013-05-14 10.0 km2013-05-15 2.1 km2013-05-162013-05-17 0.9 km2013-05-18 8.3 km2013-05-192013-05-20 2.0 km2013-05-21 2.0 km2013-05-22 5.0 km2013-05-23 3.0 km2013-05-24 5.0 km2013-05-252013-05-262013-05-27 7.1 km2013-05-28 2.0 km2013-05-292013-05-30 8.0 km2013-05-312013-06-01 1.2 km2013-06-02 2.1 km2013-06-03 10.0 km2013-06-04 6.0 km2013-06-05 2.1 km2013-06-06 9.4 km2013-06-072013-06-08 2.3 km2013-06-09 4.0 km2013-06-102013-06-11 9.6 km2013-06-122013-06-13 4.0 km2013-06-142013-06-152013-06-162013-06-172013-06-182013-06-192013-06-202013-06-21 4.1 km2013-06-22 14.3 km2013-06-232013-06-24 2.0 km2013-06-252013-06-262013-06-272013-06-282013-06-29 5.0 km2013-06-30 6.6 km2013-07-012013-07-022013-07-03 5.1 km2013-07-042013-07-052013-07-062013-07-07 2.5 km2013-07-08 3.0 km2013-07-092013-07-102013-07-112013-07-122013-07-132013-07-142013-07-152013-07-162013-07-172013-07-182013-07-192013-07-20 4.1 km2013-07-212013-07-222013-07-232013-07-24 6.1 km2013-07-252013-07-262013-07-272013-07-282013-07-292013-07-302013-07-312013-08-012013-08-022013-08-032013-08-042013-08-05 2.5 km2013-08-062013-08-072013-08-082013-08-092013-08-102013-08-112013-08-122013-08-132013-08-14 4.0 km2013-08-152013-08-162013-08-172013-08-182013-08-192013-08-202013-08-212013-08-222013-08-232013-08-242013-08-252013-08-26 4.0 km2013-08-272013-08-282013-08-292013-08-302013-08-312013-09-012013-09-022013-09-032013-09-042013-09-052013-09-062013-09-072013-09-082013-09-092013-09-102013-09-11 3.0 km2013-09-122013-09-132013-09-14 4.0 km2013-09-152013-09-162013-09-172013-09-18 5.0 km2013-09-192013-09-202013-09-212013-09-222013-09-232013-09-242013-09-252013-09-262013-09-272013-09-282013-09-292013-09-302013-10-012013-10-022013-10-032013-10-042013-10-052013-10-062013-10-072013-10-082013-10-092013-10-102013-10-112013-10-122013-10-132013-10-142013-10-152013-10-162013-10-172013-10-182013-10-192013-10-202013-10-212013-10-222013-10-232013-10-242013-10-25 4.0 km2013-10-262013-10-272013-10-282013-10-292013-10-30 8.0 km2013-10-31 5.0 km2013-11-012013-11-022013-11-032013-11-04 10.1 km2013-11-05 1.0 km2013-11-062013-11-072013-11-08 3.0 km2013-11-092013-11-102013-11-11 2.0 km2013-11-122013-11-13 12.3 km2013-11-142013-11-15 1.0 km2013-11-162013-11-172013-11-18 5.0 km2013-11-192013-11-202013-11-212013-11-222013-11-232013-11-242013-11-252013-11-26 5.0 km2013-11-272013-11-282013-11-292013-11-302013-12-012013-12-02 2.5 km2013-12-032013-12-04 5.0 km2013-12-05 5.0 km2013-12-062013-12-072013-12-082013-12-092013-12-102013-12-112013-12-122013-12-132013-12-142013-12-152013-12-162013-12-172013-12-182013-12-192013-12-202013-12-212013-12-222013-12-232013-12-242013-12-252013-12-26 2.7 km2013-12-272013-12-282013-12-292013-12-30 2.5 km2013-12-312012386.5 kmJanFebMarAprMayJunJulAugSepOctNovDec2011-12-262011-12-272011-12-282011-12-292011-12-302011-12-312012-01-012012-01-022012-01-032012-01-042012-01-052012-01-062012-01-072012-01-082012-01-092012-01-102012-01-112012-01-122012-01-132012-01-142012-01-152012-01-162012-01-172012-01-182012-01-192012-01-202012-01-212012-01-222012-01-232012-01-242012-01-252012-01-262012-01-272012-01-282012-01-292012-01-302012-01-312012-02-012012-02-022012-02-032012-02-042012-02-052012-02-062012-02-072012-02-082012-02-092012-02-102012-02-112012-02-122012-02-132012-02-142012-02-152012-02-162012-02-172012-02-182012-02-192012-02-202012-02-212012-02-222012-02-232012-02-242012-02-252012-02-262012-02-272012-02-282012-02-292012-03-012012-03-022012-03-032012-03-042012-03-052012-03-062012-03-072012-03-082012-03-092012-03-102012-03-112012-03-122012-03-132012-03-142012-03-152012-03-162012-03-172012-03-182012-03-192012-03-202012-03-212012-03-222012-03-232012-03-242012-03-252012-03-262012-03-272012-03-282012-03-292012-03-302012-03-312012-04-012012-04-022012-04-032012-04-042012-04-052012-04-062012-04-072012-04-082012-04-092012-04-102012-04-112012-04-122012-04-132012-04-142012-04-152012-04-162012-04-172012-04-182012-04-192012-04-202012-04-212012-04-222012-04-232012-04-242012-04-252012-04-262012-04-272012-04-282012-04-292012-04-302012-05-012012-05-022012-05-032012-05-042012-05-052012-05-062012-05-072012-05-082012-05-092012-05-102012-05-112012-05-122012-05-132012-05-142012-05-152012-05-162012-05-172012-05-182012-05-192012-05-202012-05-212012-05-222012-05-232012-05-242012-05-252012-05-262012-05-272012-05-282012-05-292012-05-302012-05-312012-06-012012-06-022012-06-032012-06-042012-06-052012-06-062012-06-072012-06-082012-06-092012-06-102012-06-112012-06-122012-06-132012-06-142012-06-152012-06-162012-06-172012-06-182012-06-192012-06-202012-06-212012-06-222012-06-232012-06-242012-06-252012-06-262012-06-272012-06-282012-06-292012-06-302012-07-012012-07-022012-07-032012-07-042012-07-052012-07-062012-07-072012-07-082012-07-092012-07-102012-07-112012-07-122012-07-132012-07-142012-07-152012-07-162012-07-172012-07-182012-07-192012-07-202012-07-212012-07-222012-07-232012-07-242012-07-252012-07-262012-07-272012-07-282012-07-292012-07-302012-07-312012-08-012012-08-022012-08-032012-08-042012-08-052012-08-062012-08-072012-08-082012-08-092012-08-102012-08-112012-08-122012-08-132012-08-142012-08-152012-08-162012-08-172012-08-182012-08-192012-08-202012-08-212012-08-222012-08-232012-08-242012-08-252012-08-262012-08-272012-08-282012-08-292012-08-302012-08-312012-09-012012-09-022012-09-032012-09-042012-09-052012-09-062012-09-072012-09-082012-09-092012-09-102012-09-112012-09-122012-09-132012-09-142012-09-152012-09-162012-09-172012-09-182012-09-192012-09-202012-09-212012-09-222012-09-232012-09-242012-09-252012-09-262012-09-272012-09-282012-09-292012-09-302012-10-012012-10-022012-10-032012-10-042012-10-052012-10-062012-10-072012-10-082012-10-092012-10-102012-10-112012-10-122012-10-132012-10-142012-10-152012-10-162012-10-172012-10-182012-10-192012-10-202012-10-212012-10-222012-10-232012-10-242012-10-252012-10-26 4.1 km2012-10-27 6.4 km2012-10-28 6.3 km2012-10-29 6.2 km2012-10-30 4.1 km2012-10-31 4.0 km2012-11-01 6.2 km2012-11-02 5.4 km2012-11-03 7.6 km2012-11-04 5.2 km2012-11-05 10.1 km2012-11-06 2.0 km2012-11-07 6.2 km2012-11-08 10.6 km2012-11-09 6.2 km2012-11-10 3.2 km2012-11-11 8.0 km2012-11-12 6.3 km2012-11-13 8.3 km2012-11-14 2.0 km2012-11-15 10.0 km2012-11-16 6.6 km2012-11-17 2.1 km2012-11-18 10.0 km2012-11-19 6.0 km2012-11-20 4.2 km2012-11-21 5.1 km2012-11-22 5.8 km2012-11-23 12.2 km2012-11-24 6.6 km2012-11-25 4.1 km2012-11-26 4.1 km2012-11-27 5.0 km2012-11-28 6.4 km2012-11-29 15.0 km2012-11-302012-12-012012-12-02 5.4 km2012-12-032012-12-042012-12-05 5.2 km2012-12-062012-12-07 6.1 km2012-12-08 6.7 km2012-12-09 5.4 km2012-12-10 5.0 km2012-12-11 7.1 km2012-12-12 4.8 km2012-12-13 6.1 km2012-12-14 8.1 km2012-12-15 7.0 km2012-12-16 10.1 km2012-12-17 2.0 km2012-12-18 7.3 km2012-12-19 4.9 km2012-12-20 10.1 km2012-12-21 5.0 km2012-12-22 3.0 km2012-12-23 12.0 km2012-12-24 5.0 km2012-12-25 7.4 km2012-12-26 10.0 km2012-12-27 8.2 km2012-12-28 5.0 km2012-12-29 3.5 km2012-12-30 4.0 km2012-12-31 \ No newline at end of file +Yihong0618 RunningATHLETEyihong0618STATISTICSNumber: 2625Weekly: 5.2Total: 10068.5 kmAvg: 3.8 kmMin: 0.5 kmMax: 43.5 km202445.0 kmJanFebMarAprMayJunJulAugSepOctNovDec2024-01-01 2.4 km2024-01-02 3.8 km2024-01-03 3.3 km2024-01-04 4.7 km2024-01-05 2.9 km2024-01-06 3.1 km2024-01-07 2.8 km2024-01-08 3.3 km2024-01-09 3.7 km2024-01-10 3.9 km2024-01-11 2.5 km2024-01-12 3.3 km2024-01-13 5.3 km2024-01-142024-01-152024-01-162024-01-172024-01-182024-01-192024-01-202024-01-212024-01-222024-01-232024-01-242024-01-252024-01-262024-01-272024-01-282024-01-292024-01-302024-01-312024-02-012024-02-022024-02-032024-02-042024-02-052024-02-062024-02-072024-02-082024-02-092024-02-102024-02-112024-02-122024-02-132024-02-142024-02-152024-02-162024-02-172024-02-182024-02-192024-02-202024-02-212024-02-222024-02-232024-02-242024-02-252024-02-262024-02-272024-02-282024-02-292024-03-012024-03-022024-03-032024-03-042024-03-052024-03-062024-03-072024-03-082024-03-092024-03-102024-03-112024-03-122024-03-132024-03-142024-03-152024-03-162024-03-172024-03-182024-03-192024-03-202024-03-212024-03-222024-03-232024-03-242024-03-252024-03-262024-03-272024-03-282024-03-292024-03-302024-03-312024-04-012024-04-022024-04-032024-04-042024-04-052024-04-062024-04-072024-04-082024-04-092024-04-102024-04-112024-04-122024-04-132024-04-142024-04-152024-04-162024-04-172024-04-182024-04-192024-04-202024-04-212024-04-222024-04-232024-04-242024-04-252024-04-262024-04-272024-04-282024-04-292024-04-302024-05-012024-05-022024-05-032024-05-042024-05-052024-05-062024-05-072024-05-082024-05-092024-05-102024-05-112024-05-122024-05-132024-05-142024-05-152024-05-162024-05-172024-05-182024-05-192024-05-202024-05-212024-05-222024-05-232024-05-242024-05-252024-05-262024-05-272024-05-282024-05-292024-05-302024-05-312024-06-012024-06-022024-06-032024-06-042024-06-052024-06-062024-06-072024-06-082024-06-092024-06-102024-06-112024-06-122024-06-132024-06-142024-06-152024-06-162024-06-172024-06-182024-06-192024-06-202024-06-212024-06-222024-06-232024-06-242024-06-252024-06-262024-06-272024-06-282024-06-292024-06-302024-07-012024-07-022024-07-032024-07-042024-07-052024-07-062024-07-072024-07-082024-07-092024-07-102024-07-112024-07-122024-07-132024-07-142024-07-152024-07-162024-07-172024-07-182024-07-192024-07-202024-07-212024-07-222024-07-232024-07-242024-07-252024-07-262024-07-272024-07-282024-07-292024-07-302024-07-312024-08-012024-08-022024-08-032024-08-042024-08-052024-08-062024-08-072024-08-082024-08-092024-08-102024-08-112024-08-122024-08-132024-08-142024-08-152024-08-162024-08-172024-08-182024-08-192024-08-202024-08-212024-08-222024-08-232024-08-242024-08-252024-08-262024-08-272024-08-282024-08-292024-08-302024-08-312024-09-012024-09-022024-09-032024-09-042024-09-052024-09-062024-09-072024-09-082024-09-092024-09-102024-09-112024-09-122024-09-132024-09-142024-09-152024-09-162024-09-172024-09-182024-09-192024-09-202024-09-212024-09-222024-09-232024-09-242024-09-252024-09-262024-09-272024-09-282024-09-292024-09-302024-10-012024-10-022024-10-032024-10-042024-10-052024-10-062024-10-072024-10-082024-10-092024-10-102024-10-112024-10-122024-10-132024-10-142024-10-152024-10-162024-10-172024-10-182024-10-192024-10-202024-10-212024-10-222024-10-232024-10-242024-10-252024-10-262024-10-272024-10-282024-10-292024-10-302024-10-312024-11-012024-11-022024-11-032024-11-042024-11-052024-11-062024-11-072024-11-082024-11-092024-11-102024-11-112024-11-122024-11-132024-11-142024-11-152024-11-162024-11-172024-11-182024-11-192024-11-202024-11-212024-11-222024-11-232024-11-242024-11-252024-11-262024-11-272024-11-282024-11-292024-11-302024-12-012024-12-022024-12-032024-12-042024-12-052024-12-062024-12-072024-12-082024-12-092024-12-102024-12-112024-12-122024-12-132024-12-142024-12-152024-12-162024-12-172024-12-182024-12-192024-12-202024-12-212024-12-222024-12-232024-12-242024-12-252024-12-262024-12-272024-12-282024-12-292024-12-302024-12-312023695.4 kmJanFebMarAprMayJunJulAugSepOctNovDec2022-12-262022-12-272022-12-282022-12-292022-12-302022-12-312023-01-01 1.0 km2023-01-022023-01-03 1.6 km2023-01-04 1.6 km2023-01-05 2.1 km2023-01-06 2.3 km2023-01-07 2.6 km2023-01-08 2.1 km2023-01-09 3.4 km2023-01-10 3.1 km2023-01-11 2.0 km2023-01-12 3.1 km2023-01-13 1.0 km2023-01-142023-01-152023-01-162023-01-172023-01-18 1.3 km2023-01-19 2.1 km2023-01-202023-01-212023-01-222023-01-232023-01-242023-01-252023-01-262023-01-272023-01-282023-01-292023-01-302023-01-312023-02-012023-02-02 1.0 km2023-02-032023-02-042023-02-052023-02-06 1.2 km2023-02-07 1.1 km2023-02-08 2.1 km2023-02-09 2.5 km2023-02-10 2.4 km2023-02-11 2.3 km2023-02-12 1.6 km2023-02-13 2.0 km2023-02-14 3.8 km2023-02-15 3.9 km2023-02-16 2.5 km2023-02-17 3.5 km2023-02-18 5.3 km2023-02-19 3.1 km2023-02-20 3.1 km2023-02-21 4.7 km2023-02-22 5.0 km2023-02-23 3.1 km2023-02-24 1.5 km2023-02-25 5.6 km2023-02-26 5.5 km2023-02-27 2.4 km2023-02-28 3.1 km2023-03-01 3.1 km2023-03-02 2.0 km2023-03-03 2.6 km2023-03-04 5.2 km2023-03-05 3.2 km2023-03-06 3.1 km2023-03-07 3.1 km2023-03-08 4.3 km2023-03-09 2.5 km2023-03-10 4.5 km2023-03-11 6.2 km2023-03-12 5.7 km2023-03-13 2.2 km2023-03-14 2.7 km2023-03-15 7.7 km2023-03-16 2.6 km2023-03-17 5.9 km2023-03-18 5.0 km2023-03-19 6.8 km2023-03-20 2.6 km2023-03-21 3.1 km2023-03-22 3.1 km2023-03-23 2.1 km2023-03-24 3.2 km2023-03-25 6.2 km2023-03-26 4.5 km2023-03-27 3.1 km2023-03-28 2.1 km2023-03-29 3.1 km2023-03-30 2.6 km2023-03-31 2.6 km2023-04-01 10.3 km2023-04-02 3.1 km2023-04-03 4.6 km2023-04-04 2.6 km2023-04-05 3.6 km2023-04-06 2.8 km2023-04-07 3.1 km2023-04-08 4.1 km2023-04-09 6.3 km2023-04-10 4.7 km2023-04-11 1.4 km2023-04-12 1.4 km2023-04-132023-04-14 3.1 km2023-04-152023-04-16 3.0 km2023-04-17 3.1 km2023-04-18 4.6 km2023-04-192023-04-20 2.3 km2023-04-21 2.6 km2023-04-22 2.3 km2023-04-232023-04-24 2.6 km2023-04-25 3.1 km2023-04-262023-04-27 2.1 km2023-04-28 2.4 km2023-04-29 2.0 km2023-04-30 3.1 km2023-05-012023-05-02 1.0 km2023-05-032023-05-042023-05-052023-05-062023-05-072023-05-08 2.3 km2023-05-092023-05-10 2.2 km2023-05-11 0.8 km2023-05-12 2.4 km2023-05-13 3.2 km2023-05-142023-05-15 2.7 km2023-05-16 2.0 km2023-05-17 2.6 km2023-05-18 4.6 km2023-05-19 2.1 km2023-05-202023-05-21 2.6 km2023-05-222023-05-23 1.2 km2023-05-24 2.5 km2023-05-25 2.5 km2023-05-26 2.6 km2023-05-272023-05-282023-05-29 2.2 km2023-05-30 3.2 km2023-05-31 3.6 km2023-06-01 5.3 km2023-06-02 3.1 km2023-06-03 4.1 km2023-06-04 5.1 km2023-06-05 4.1 km2023-06-062023-06-072023-06-082023-06-092023-06-102023-06-112023-06-122023-06-132023-06-14 0.6 km2023-06-15 0.6 km2023-06-16 1.0 km2023-06-172023-06-182023-06-19 1.0 km2023-06-202023-06-21 2.2 km2023-06-222023-06-232023-06-242023-06-252023-06-262023-06-27 1.1 km2023-06-28 1.3 km2023-06-29 1.1 km2023-06-30 2.2 km2023-07-012023-07-022023-07-03 4.9 km2023-07-04 2.9 km2023-07-052023-07-062023-07-07 2.7 km2023-07-08 4.5 km2023-07-092023-07-10 2.7 km2023-07-11 3.7 km2023-07-12 2.6 km2023-07-132023-07-142023-07-152023-07-162023-07-17 1.5 km2023-07-18 2.2 km2023-07-19 2.3 km2023-07-202023-07-212023-07-222023-07-23 1.3 km2023-07-242023-07-25 2.3 km2023-07-262023-07-27 2.8 km2023-07-282023-07-292023-07-302023-07-31 1.4 km2023-08-012023-08-022023-08-03 1.6 km2023-08-04 2.3 km2023-08-05 2.1 km2023-08-06 3.1 km2023-08-07 1.1 km2023-08-08 2.1 km2023-08-092023-08-102023-08-112023-08-12 1.2 km2023-08-132023-08-142023-08-152023-08-16 1.2 km2023-08-17 1.6 km2023-08-182023-08-192023-08-202023-08-21 1.0 km2023-08-22 2.5 km2023-08-232023-08-24 1.6 km2023-08-252023-08-262023-08-27 2.4 km2023-08-282023-08-292023-08-30 0.6 km2023-08-31 2.1 km2023-09-01 1.0 km2023-09-022023-09-03 3.0 km2023-09-04 7.8 km2023-09-05 2.5 km2023-09-06 7.1 km2023-09-07 8.5 km2023-09-08 3.9 km2023-09-09 4.1 km2023-09-10 3.3 km2023-09-11 2.7 km2023-09-12 3.1 km2023-09-13 3.2 km2023-09-14 3.1 km2023-09-15 3.4 km2023-09-16 5.2 km2023-09-17 2.2 km2023-09-18 4.6 km2023-09-19 3.8 km2023-09-20 3.5 km2023-09-21 4.1 km2023-09-22 2.6 km2023-09-23 4.1 km2023-09-24 4.2 km2023-09-25 3.1 km2023-09-26 2.1 km2023-09-27 1.1 km2023-09-28 5.4 km2023-09-29 4.2 km2023-09-30 6.1 km2023-10-01 2.1 km2023-10-02 4.1 km2023-10-03 5.2 km2023-10-04 1.0 km2023-10-05 3.1 km2023-10-06 1.5 km2023-10-072023-10-082023-10-092023-10-10 1.6 km2023-10-11 5.1 km2023-10-12 1.3 km2023-10-13 2.7 km2023-10-14 3.2 km2023-10-15 4.1 km2023-10-162023-10-17 2.1 km2023-10-18 1.0 km2023-10-19 2.3 km2023-10-202023-10-21 2.1 km2023-10-22 3.1 km2023-10-232023-10-24 2.3 km2023-10-25 2.3 km2023-10-262023-10-27 1.2 km2023-10-28 3.1 km2023-10-29 1.4 km2023-10-30 2.5 km2023-10-31 3.1 km2023-11-012023-11-022023-11-03 2.4 km2023-11-04 3.2 km2023-11-052023-11-062023-11-07 2.2 km2023-11-08 1.0 km2023-11-09 2.1 km2023-11-102023-11-112023-11-12 2.1 km2023-11-13 3.2 km2023-11-14 2.3 km2023-11-15 3.1 km2023-11-16 3.7 km2023-11-17 1.2 km2023-11-182023-11-19 2.0 km2023-11-202023-11-21 1.4 km2023-11-22 2.1 km2023-11-23 2.1 km2023-11-242023-11-252023-11-26 1.1 km2023-11-27 2.1 km2023-11-28 2.7 km2023-11-29 1.2 km2023-11-30 2.1 km2023-12-01 1.2 km2023-12-02 2.6 km2023-12-032023-12-04 1.0 km2023-12-05 3.7 km2023-12-06 2.6 km2023-12-07 2.7 km2023-12-08 2.6 km2023-12-09 2.8 km2023-12-102023-12-112023-12-12 1.6 km2023-12-13 2.6 km2023-12-142023-12-152023-12-162023-12-17 1.3 km2023-12-18 1.2 km2023-12-19 2.1 km2023-12-202023-12-212023-12-222023-12-232023-12-242023-12-25 0.7 km2023-12-262023-12-27 1.3 km2023-12-282023-12-29 1.6 km2023-12-302023-12-312022758.2 kmJanFebMarAprMayJunJulAugSepOctNovDec2021-12-272021-12-282021-12-29 1.4 km2021-12-30 5.4 km2021-12-31 2.6 km2022-01-01 3.2 km2022-01-02 4.4 km2022-01-03 3.1 km2022-01-04 3.2 km2022-01-05 3.2 km2022-01-06 3.2 km2022-01-07 5.1 km2022-01-08 3.2 km2022-01-09 4.3 km2022-01-10 3.1 km2022-01-11 1.6 km2022-01-12 4.0 km2022-01-13 6.1 km2022-01-14 1.5 km2022-01-15 4.1 km2022-01-16 3.7 km2022-01-17 3.1 km2022-01-18 7.2 km2022-01-192022-01-20 7.7 km2022-01-21 2.5 km2022-01-22 4.3 km2022-01-23 8.2 km2022-01-24 2.1 km2022-01-25 10.4 km2022-01-26 4.2 km2022-01-27 2.1 km2022-01-28 4.8 km2022-01-29 1.4 km2022-01-30 11.3 km2022-01-31 2.1 km2022-02-01 4.1 km2022-02-02 4.3 km2022-02-03 8.3 km2022-02-04 1.6 km2022-02-052022-02-06 2.0 km2022-02-07 4.7 km2022-02-08 3.3 km2022-02-092022-02-10 3.1 km2022-02-11 1.6 km2022-02-12 6.4 km2022-02-13 8.3 km2022-02-14 5.0 km2022-02-15 5.2 km2022-02-16 5.1 km2022-02-17 6.4 km2022-02-18 6.1 km2022-02-19 5.5 km2022-02-20 5.9 km2022-02-21 7.3 km2022-02-22 4.3 km2022-02-23 15.5 km2022-02-24 2.4 km2022-02-252022-02-26 4.1 km2022-02-27 2.1 km2022-02-28 2.4 km2022-03-01 3.4 km2022-03-02 6.7 km2022-03-03 2.1 km2022-03-04 3.1 km2022-03-05 1.6 km2022-03-06 3.3 km2022-03-072022-03-08 1.5 km2022-03-092022-03-10 2.0 km2022-03-11 3.5 km2022-03-12 1.4 km2022-03-13 5.3 km2022-03-14 1.5 km2022-03-152022-03-16 2.1 km2022-03-17 3.1 km2022-03-18 1.4 km2022-03-192022-03-20 3.7 km2022-03-212022-03-22 1.4 km2022-03-23 3.1 km2022-03-242022-03-252022-03-26 3.1 km2022-03-27 3.2 km2022-03-282022-03-29 3.5 km2022-03-302022-03-31 2.8 km2022-04-01 1.5 km2022-04-02 1.1 km2022-04-03 4.3 km2022-04-042022-04-052022-04-06 2.5 km2022-04-072022-04-082022-04-09 1.5 km2022-04-10 3.1 km2022-04-112022-04-12 2.1 km2022-04-13 3.1 km2022-04-142022-04-15 4.2 km2022-04-16 3.1 km2022-04-17 5.3 km2022-04-182022-04-19 2.1 km2022-04-20 3.1 km2022-04-212022-04-22 2.4 km2022-04-23 2.1 km2022-04-242022-04-25 2.1 km2022-04-262022-04-27 2.1 km2022-04-282022-04-29 2.7 km2022-04-30 2.2 km2022-05-01 3.1 km2022-05-02 3.3 km2022-05-03 3.1 km2022-05-04 4.2 km2022-05-05 3.1 km2022-05-062022-05-07 2.2 km2022-05-082022-05-09 3.8 km2022-05-102022-05-112022-05-122022-05-13 1.8 km2022-05-142022-05-152022-05-162022-05-172022-05-182022-05-192022-05-20 1.6 km2022-05-21 2.0 km2022-05-22 3.1 km2022-05-23 1.0 km2022-05-242022-05-25 1.1 km2022-05-262022-05-27 1.5 km2022-05-282022-05-292022-05-30 2.0 km2022-05-312022-06-01 2.1 km2022-06-022022-06-032022-06-042022-06-05 2.1 km2022-06-062022-06-07 2.4 km2022-06-08 2.1 km2022-06-09 2.1 km2022-06-10 2.4 km2022-06-112022-06-12 2.6 km2022-06-13 2.8 km2022-06-14 2.1 km2022-06-15 3.1 km2022-06-16 3.1 km2022-06-17 1.3 km2022-06-18 1.6 km2022-06-19 4.1 km2022-06-20 1.0 km2022-06-21 2.1 km2022-06-22 2.2 km2022-06-232022-06-24 2.7 km2022-06-252022-06-262022-06-27 1.1 km2022-06-28 1.2 km2022-06-29 1.9 km2022-06-30 2.6 km2022-07-01 3.1 km2022-07-02 3.2 km2022-07-032022-07-04 1.0 km2022-07-05 2.8 km2022-07-062022-07-07 2.0 km2022-07-08 1.1 km2022-07-092022-07-10 4.3 km2022-07-11 3.6 km2022-07-12 4.2 km2022-07-13 2.7 km2022-07-14 3.2 km2022-07-15 2.4 km2022-07-16 2.2 km2022-07-17 5.2 km2022-07-18 5.3 km2022-07-19 4.8 km2022-07-20 3.2 km2022-07-21 7.0 km2022-07-22 2.1 km2022-07-23 5.5 km2022-07-24 4.1 km2022-07-25 4.5 km2022-07-26 4.5 km2022-07-27 3.1 km2022-07-28 2.6 km2022-07-292022-07-302022-07-312022-08-01 2.3 km2022-08-02 2.5 km2022-08-03 2.4 km2022-08-042022-08-052022-08-06 2.3 km2022-08-072022-08-08 3.2 km2022-08-09 2.0 km2022-08-10 2.1 km2022-08-11 2.4 km2022-08-122022-08-132022-08-142022-08-15 1.3 km2022-08-16 1.4 km2022-08-17 2.1 km2022-08-18 2.5 km2022-08-192022-08-202022-08-21 2.1 km2022-08-22 1.6 km2022-08-23 1.2 km2022-08-24 2.8 km2022-08-25 1.4 km2022-08-26 2.7 km2022-08-27 3.1 km2022-08-282022-08-292022-08-302022-08-31 2.1 km2022-09-01 1.1 km2022-09-02 2.1 km2022-09-03 1.0 km2022-09-042022-09-05 2.6 km2022-09-06 3.0 km2022-09-07 2.1 km2022-09-08 3.1 km2022-09-09 1.0 km2022-09-10 2.1 km2022-09-11 2.2 km2022-09-122022-09-132022-09-142022-09-152022-09-16 1.2 km2022-09-17 2.8 km2022-09-18 2.2 km2022-09-19 1.1 km2022-09-20 3.1 km2022-09-21 2.0 km2022-09-22 1.0 km2022-09-23 1.1 km2022-09-24 3.2 km2022-09-252022-09-262022-09-272022-09-28 1.1 km2022-09-29 2.1 km2022-09-30 2.2 km2022-10-012022-10-02 2.1 km2022-10-032022-10-042022-10-05 1.1 km2022-10-06 1.6 km2022-10-07 3.4 km2022-10-082022-10-092022-10-10 3.1 km2022-10-11 4.8 km2022-10-12 3.8 km2022-10-13 4.2 km2022-10-14 4.4 km2022-10-15 4.3 km2022-10-16 3.1 km2022-10-17 6.5 km2022-10-18 5.4 km2022-10-19 4.8 km2022-10-20 3.5 km2022-10-21 7.5 km2022-10-22 1.5 km2022-10-232022-10-24 1.4 km2022-10-25 1.4 km2022-10-262022-10-272022-10-28 1.3 km2022-10-292022-10-302022-10-31 2.1 km2022-11-01 2.1 km2022-11-02 2.0 km2022-11-03 1.7 km2022-11-042022-11-052022-11-06 2.1 km2022-11-07 2.0 km2022-11-08 2.2 km2022-11-092022-11-10 1.6 km2022-11-11 2.1 km2022-11-122022-11-13 0.5 km2022-11-14 2.3 km2022-11-15 2.3 km2022-11-16 2.4 km2022-11-172022-11-182022-11-19 3.8 km2022-11-20 2.1 km2022-11-21 1.8 km2022-11-22 2.1 km2022-11-23 2.5 km2022-11-24 2.6 km2022-11-25 2.3 km2022-11-26 1.2 km2022-11-272022-11-282022-11-292022-11-302022-12-01 2.1 km2022-12-02 2.6 km2022-12-032022-12-042022-12-052022-12-062022-12-07 1.0 km2022-12-08 2.1 km2022-12-092022-12-102022-12-11 2.4 km2022-12-12 2.0 km2022-12-13 2.0 km2022-12-142022-12-152022-12-162022-12-172022-12-182022-12-19 2.1 km2022-12-20 2.0 km2022-12-212022-12-222022-12-232022-12-242022-12-252022-12-262022-12-272022-12-282022-12-292022-12-302022-12-3120211243.5 kmJanFebMarAprMayJunJulAugSepOctNovDec2020-12-282020-12-292020-12-302020-12-31 3.0 km2021-01-01 5.1 km2021-01-02 5.5 km2021-01-03 10.3 km2021-01-04 2.6 km2021-01-05 4.2 km2021-01-06 3.1 km2021-01-07 3.1 km2021-01-08 2.1 km2021-01-09 1.6 km2021-01-10 11.4 km2021-01-11 1.6 km2021-01-12 4.1 km2021-01-13 5.6 km2021-01-14 2.0 km2021-01-15 1.6 km2021-01-16 11.2 km2021-01-17 3.4 km2021-01-18 2.0 km2021-01-19 5.9 km2021-01-20 3.2 km2021-01-21 5.1 km2021-01-22 10.8 km2021-01-23 5.2 km2021-01-24 10.2 km2021-01-25 1.1 km2021-01-26 4.1 km2021-01-27 1.5 km2021-01-28 4.6 km2021-01-29 10.1 km2021-01-30 2.7 km2021-01-31 3.1 km2021-02-01 4.1 km2021-02-02 1.6 km2021-02-03 5.1 km2021-02-04 3.1 km2021-02-05 1.0 km2021-02-06 1.7 km2021-02-07 6.6 km2021-02-08 12.4 km2021-02-09 3.4 km2021-02-10 1.2 km2021-02-11 1.7 km2021-02-12 5.1 km2021-02-13 19.2 km2021-02-14 0.5 km2021-02-15 4.2 km2021-02-16 2.0 km2021-02-17 5.2 km2021-02-18 4.2 km2021-02-19 5.2 km2021-02-20 3.1 km2021-02-21 5.1 km2021-02-22 6.2 km2021-02-23 3.1 km2021-02-24 1.1 km2021-02-25 7.1 km2021-02-26 2.0 km2021-02-27 7.6 km2021-02-28 1.6 km2021-03-01 2.3 km2021-03-02 5.1 km2021-03-03 6.1 km2021-03-04 2.6 km2021-03-05 6.1 km2021-03-06 3.3 km2021-03-07 10.2 km2021-03-08 3.1 km2021-03-09 4.5 km2021-03-10 6.1 km2021-03-11 2.6 km2021-03-12 5.0 km2021-03-13 4.9 km2021-03-14 4.2 km2021-03-15 6.2 km2021-03-16 3.3 km2021-03-17 4.3 km2021-03-18 3.1 km2021-03-19 5.2 km2021-03-20 1.6 km2021-03-21 5.1 km2021-03-22 3.1 km2021-03-23 2.7 km2021-03-24 6.4 km2021-03-25 2.1 km2021-03-26 4.1 km2021-03-27 3.1 km2021-03-28 7.2 km2021-03-29 1.1 km2021-03-30 4.6 km2021-03-31 10.2 km2021-04-01 4.0 km2021-04-02 3.1 km2021-04-03 1.4 km2021-04-04 6.4 km2021-04-05 4.1 km2021-04-06 3.1 km2021-04-07 5.1 km2021-04-08 10.3 km2021-04-09 3.1 km2021-04-10 4.1 km2021-04-11 1.1 km2021-04-12 4.3 km2021-04-13 4.5 km2021-04-14 6.1 km2021-04-15 5.2 km2021-04-16 5.1 km2021-04-17 5.9 km2021-04-18 17.8 km2021-04-19 2.5 km2021-04-20 6.1 km2021-04-21 7.8 km2021-04-22 6.1 km2021-04-23 3.2 km2021-04-24 3.2 km2021-04-25 4.1 km2021-04-26 4.2 km2021-04-27 5.0 km2021-04-28 1.7 km2021-04-29 5.5 km2021-04-30 10.6 km2021-05-01 4.0 km2021-05-02 5.7 km2021-05-03 9.4 km2021-05-04 2.1 km2021-05-05 2.4 km2021-05-06 6.3 km2021-05-07 3.3 km2021-05-08 3.0 km2021-05-09 1.5 km2021-05-102021-05-11 5.1 km2021-05-12 2.2 km2021-05-13 5.5 km2021-05-14 3.1 km2021-05-15 2.6 km2021-05-16 2.2 km2021-05-17 4.2 km2021-05-18 5.7 km2021-05-19 3.7 km2021-05-20 1.0 km2021-05-21 2.3 km2021-05-22 6.4 km2021-05-23 2.5 km2021-05-24 1.5 km2021-05-25 1.2 km2021-05-26 4.3 km2021-05-27 2.1 km2021-05-28 6.9 km2021-05-29 10.3 km2021-05-30 5.2 km2021-05-31 1.1 km2021-06-01 3.2 km2021-06-02 4.3 km2021-06-03 4.1 km2021-06-04 3.3 km2021-06-05 4.3 km2021-06-06 1.5 km2021-06-07 5.4 km2021-06-08 3.4 km2021-06-09 3.2 km2021-06-10 2.2 km2021-06-112021-06-12 1.1 km2021-06-13 5.7 km2021-06-14 2.2 km2021-06-15 4.0 km2021-06-16 4.3 km2021-06-17 4.2 km2021-06-18 1.6 km2021-06-19 0.6 km2021-06-20 6.2 km2021-06-21 1.0 km2021-06-22 1.6 km2021-06-23 4.2 km2021-06-24 5.7 km2021-06-25 1.5 km2021-06-26 2.5 km2021-06-27 10.9 km2021-06-282021-06-29 3.1 km2021-06-30 1.2 km2021-07-01 4.3 km2021-07-02 1.8 km2021-07-03 5.1 km2021-07-04 3.2 km2021-07-05 1.0 km2021-07-06 2.6 km2021-07-072021-07-08 2.5 km2021-07-09 4.4 km2021-07-102021-07-11 5.2 km2021-07-12 1.1 km2021-07-13 2.7 km2021-07-14 3.1 km2021-07-152021-07-16 3.0 km2021-07-172021-07-18 5.2 km2021-07-192021-07-20 2.1 km2021-07-21 1.3 km2021-07-222021-07-232021-07-24 2.6 km2021-07-25 1.1 km2021-07-26 1.7 km2021-07-27 1.5 km2021-07-28 1.8 km2021-07-292021-07-30 3.1 km2021-07-31 2.1 km2021-08-01 6.5 km2021-08-02 1.1 km2021-08-032021-08-04 2.3 km2021-08-052021-08-062021-08-07 2.2 km2021-08-08 3.1 km2021-08-092021-08-10 1.2 km2021-08-112021-08-12 2.1 km2021-08-132021-08-14 3.1 km2021-08-15 3.9 km2021-08-16 3.2 km2021-08-17 3.3 km2021-08-18 3.3 km2021-08-19 2.2 km2021-08-20 3.2 km2021-08-21 5.3 km2021-08-22 2.1 km2021-08-23 3.6 km2021-08-24 1.1 km2021-08-25 3.2 km2021-08-26 5.3 km2021-08-27 4.2 km2021-08-28 6.4 km2021-08-29 3.0 km2021-08-30 5.1 km2021-08-31 3.1 km2021-09-01 4.4 km2021-09-022021-09-03 1.5 km2021-09-04 3.1 km2021-09-05 11.3 km2021-09-062021-09-07 1.9 km2021-09-08 2.0 km2021-09-09 2.6 km2021-09-10 4.3 km2021-09-11 4.2 km2021-09-12 15.2 km2021-09-132021-09-14 2.2 km2021-09-15 2.1 km2021-09-16 3.2 km2021-09-17 5.4 km2021-09-18 2.2 km2021-09-19 2.2 km2021-09-202021-09-21 4.5 km2021-09-22 3.1 km2021-09-232021-09-242021-09-25 8.5 km2021-09-262021-09-272021-09-28 3.2 km2021-09-292021-09-30 10.4 km2021-10-01 4.1 km2021-10-02 1.1 km2021-10-03 2.1 km2021-10-04 3.2 km2021-10-05 5.2 km2021-10-06 5.6 km2021-10-07 3.2 km2021-10-08 3.1 km2021-10-09 4.1 km2021-10-10 6.8 km2021-10-112021-10-12 3.1 km2021-10-13 3.1 km2021-10-14 3.1 km2021-10-15 0.6 km2021-10-162021-10-17 8.1 km2021-10-18 1.1 km2021-10-19 2.0 km2021-10-20 3.1 km2021-10-212021-10-222021-10-23 1.6 km2021-10-24 3.4 km2021-10-252021-10-26 2.8 km2021-10-27 3.2 km2021-10-282021-10-29 3.1 km2021-10-30 4.8 km2021-10-31 3.1 km2021-11-01 5.4 km2021-11-02 3.1 km2021-11-03 4.4 km2021-11-04 1.6 km2021-11-052021-11-062021-11-072021-11-08 1.7 km2021-11-09 3.1 km2021-11-10 5.1 km2021-11-11 1.0 km2021-11-12 2.1 km2021-11-13 1.1 km2021-11-14 5.2 km2021-11-152021-11-16 2.1 km2021-11-17 2.4 km2021-11-18 1.0 km2021-11-19 4.2 km2021-11-20 1.5 km2021-11-21 5.8 km2021-11-22 3.1 km2021-11-23 3.4 km2021-11-242021-11-252021-11-26 2.6 km2021-11-27 5.2 km2021-11-28 5.0 km2021-11-292021-11-30 1.6 km2021-12-01 3.6 km2021-12-02 5.1 km2021-12-032021-12-04 1.2 km2021-12-05 3.1 km2021-12-06 1.8 km2021-12-07 2.4 km2021-12-08 3.2 km2021-12-092021-12-10 1.2 km2021-12-11 5.1 km2021-12-12 2.1 km2021-12-132021-12-14 2.1 km2021-12-15 2.1 km2021-12-16 2.6 km2021-12-172021-12-18 2.4 km2021-12-19 4.1 km2021-12-20 1.5 km2021-12-21 3.1 km2021-12-22 2.1 km2021-12-232021-12-24 1.0 km2021-12-25 2.0 km2021-12-26 3.1 km2021-12-272021-12-282021-12-29 1.4 km2021-12-30 5.4 km2021-12-31 2.6 km20201283.5 kmJanFebMarAprMayJunJulAugSepOctNovDec2019-12-302019-12-31 4.0 km2020-01-01 10.0 km2020-01-02 5.1 km2020-01-03 5.0 km2020-01-04 9.3 km2020-01-05 10.1 km2020-01-06 4.0 km2020-01-072020-01-08 2.0 km2020-01-092020-01-102020-01-11 1.1 km2020-01-12 5.0 km2020-01-13 2.0 km2020-01-14 4.0 km2020-01-15 4.0 km2020-01-16 4.1 km2020-01-17 5.0 km2020-01-18 3.0 km2020-01-19 3.1 km2020-01-20 3.8 km2020-01-212020-01-22 3.0 km2020-01-23 1.5 km2020-01-24 3.0 km2020-01-25 2.0 km2020-01-26 2.0 km2020-01-27 3.0 km2020-01-282020-01-292020-01-302020-01-31 5.0 km2020-02-01 4.0 km2020-02-02 5.0 km2020-02-03 4.0 km2020-02-042020-02-05 3.1 km2020-02-06 3.0 km2020-02-07 4.0 km2020-02-08 3.7 km2020-02-092020-02-10 5.0 km2020-02-11 3.1 km2020-02-12 4.1 km2020-02-132020-02-142020-02-152020-02-162020-02-172020-02-182020-02-192020-02-20 1.2 km2020-02-21 3.0 km2020-02-22 4.3 km2020-02-23 5.0 km2020-02-242020-02-25 1.0 km2020-02-262020-02-27 1.0 km2020-02-282020-02-29 2.0 km2020-03-012020-03-02 1.0 km2020-03-032020-03-04 3.0 km2020-03-052020-03-062020-03-072020-03-082020-03-092020-03-10 2.1 km2020-03-11 1.0 km2020-03-12 1.4 km2020-03-132020-03-142020-03-152020-03-162020-03-17 1.2 km2020-03-18 2.0 km2020-03-192020-03-20 1.2 km2020-03-212020-03-222020-03-23 3.0 km2020-03-242020-03-25 2.3 km2020-03-262020-03-272020-03-282020-03-292020-03-30 1.0 km2020-03-312020-04-01 1.4 km2020-04-022020-04-032020-04-042020-04-052020-04-06 1.4 km2020-04-07 2.0 km2020-04-08 2.0 km2020-04-09 1.3 km2020-04-10 2.1 km2020-04-112020-04-122020-04-13 2.1 km2020-04-14 3.0 km2020-04-15 1.6 km2020-04-162020-04-172020-04-18 2.0 km2020-04-192020-04-202020-04-212020-04-222020-04-232020-04-242020-04-25 1.0 km2020-04-262020-04-27 1.1 km2020-04-28 2.0 km2020-04-29 1.0 km2020-04-30 0.6 km2020-05-012020-05-022020-05-032020-05-042020-05-052020-05-06 1.0 km2020-05-072020-05-082020-05-09 2.7 km2020-05-102020-05-112020-05-12 1.4 km2020-05-132020-05-142020-05-15 1.0 km2020-05-16 6.5 km2020-05-17 4.3 km2020-05-18 1.2 km2020-05-19 5.0 km2020-05-20 4.0 km2020-05-21 3.3 km2020-05-22 7.5 km2020-05-23 3.1 km2020-05-24 7.1 km2020-05-25 4.9 km2020-05-26 4.2 km2020-05-27 6.5 km2020-05-28 5.0 km2020-05-29 4.0 km2020-05-30 9.0 km2020-05-31 3.5 km2020-06-01 4.0 km2020-06-02 5.5 km2020-06-03 4.1 km2020-06-04 8.0 km2020-06-05 3.3 km2020-06-06 11.2 km2020-06-07 6.0 km2020-06-08 4.0 km2020-06-09 4.7 km2020-06-10 6.6 km2020-06-11 5.8 km2020-06-12 13.8 km2020-06-13 1.5 km2020-06-14 10.6 km2020-06-15 5.6 km2020-06-16 4.0 km2020-06-17 5.5 km2020-06-18 11.1 km2020-06-19 6.1 km2020-06-20 8.7 km2020-06-21 2.4 km2020-06-22 12.6 km2020-06-23 4.2 km2020-06-24 7.0 km2020-06-25 7.6 km2020-06-26 5.2 km2020-06-27 13.1 km2020-06-28 4.6 km2020-06-29 6.0 km2020-06-30 8.2 km2020-07-01 3.0 km2020-07-02 6.1 km2020-07-03 6.7 km2020-07-04 15.0 km2020-07-05 4.1 km2020-07-06 6.9 km2020-07-07 5.8 km2020-07-08 3.9 km2020-07-09 6.3 km2020-07-10 8.2 km2020-07-11 9.5 km2020-07-12 5.2 km2020-07-13 10.3 km2020-07-14 8.6 km2020-07-15 6.4 km2020-07-16 5.7 km2020-07-17 11.3 km2020-07-18 7.3 km2020-07-19 14.2 km2020-07-20 5.0 km2020-07-21 6.2 km2020-07-22 6.2 km2020-07-23 4.8 km2020-07-24 2.2 km2020-07-25 10.9 km2020-07-26 4.2 km2020-07-27 10.1 km2020-07-28 4.1 km2020-07-29 2.6 km2020-07-30 5.2 km2020-07-31 7.0 km2020-08-01 10.2 km2020-08-02 5.4 km2020-08-03 1.1 km2020-08-04 5.4 km2020-08-05 5.8 km2020-08-06 2.3 km2020-08-07 5.1 km2020-08-08 9.3 km2020-08-09 5.1 km2020-08-10 8.3 km2020-08-11 5.1 km2020-08-12 1.0 km2020-08-13 6.2 km2020-08-14 2.1 km2020-08-15 5.1 km2020-08-16 20.7 km2020-08-17 3.1 km2020-08-18 2.1 km2020-08-19 3.1 km2020-08-20 2.0 km2020-08-21 4.1 km2020-08-22 2.1 km2020-08-23 5.1 km2020-08-24 2.7 km2020-08-25 2.2 km2020-08-26 4.3 km2020-08-27 5.2 km2020-08-28 5.1 km2020-08-29 6.7 km2020-08-30 3.1 km2020-08-31 5.7 km2020-09-01 10.2 km2020-09-02 6.7 km2020-09-03 5.2 km2020-09-04 5.6 km2020-09-05 5.0 km2020-09-06 4.4 km2020-09-07 6.3 km2020-09-08 10.4 km2020-09-09 4.3 km2020-09-10 4.8 km2020-09-11 2.1 km2020-09-12 1.5 km2020-09-13 10.2 km2020-09-14 5.1 km2020-09-15 1.0 km2020-09-16 10.3 km2020-09-17 10.4 km2020-09-18 6.5 km2020-09-19 7.3 km2020-09-20 20.2 km2020-09-21 1.4 km2020-09-22 2.7 km2020-09-23 3.1 km2020-09-24 2.6 km2020-09-25 2.8 km2020-09-26 5.6 km2020-09-27 1.1 km2020-09-28 1.6 km2020-09-29 4.1 km2020-09-302020-10-01 4.3 km2020-10-02 3.2 km2020-10-03 1.4 km2020-10-04 1.0 km2020-10-05 3.1 km2020-10-06 5.2 km2020-10-07 5.1 km2020-10-08 5.2 km2020-10-09 3.1 km2020-10-10 2.7 km2020-10-11 3.2 km2020-10-12 4.3 km2020-10-13 1.0 km2020-10-14 4.4 km2020-10-15 1.5 km2020-10-162020-10-17 1.4 km2020-10-182020-10-19 2.2 km2020-10-20 3.0 km2020-10-21 5.6 km2020-10-22 3.0 km2020-10-23 3.3 km2020-10-24 5.0 km2020-10-25 3.0 km2020-10-26 3.2 km2020-10-27 4.0 km2020-10-28 2.7 km2020-10-29 1.7 km2020-10-30 5.3 km2020-10-31 11.2 km2020-11-01 1.2 km2020-11-02 3.1 km2020-11-032020-11-04 1.9 km2020-11-05 5.1 km2020-11-06 2.1 km2020-11-07 3.0 km2020-11-08 1.6 km2020-11-09 5.1 km2020-11-10 6.4 km2020-11-11 3.4 km2020-11-12 4.3 km2020-11-132020-11-14 7.0 km2020-11-15 6.2 km2020-11-16 1.5 km2020-11-172020-11-18 1.0 km2020-11-19 2.2 km2020-11-202020-11-21 4.5 km2020-11-22 5.6 km2020-11-23 4.2 km2020-11-24 1.2 km2020-11-25 2.1 km2020-11-26 1.1 km2020-11-27 3.3 km2020-11-28 7.0 km2020-11-292020-11-302020-12-012020-12-02 2.5 km2020-12-03 1.1 km2020-12-04 4.1 km2020-12-05 10.2 km2020-12-06 5.1 km2020-12-07 2.2 km2020-12-08 1.2 km2020-12-09 2.6 km2020-12-10 1.2 km2020-12-112020-12-12 2.6 km2020-12-13 5.1 km2020-12-14 1.0 km2020-12-15 2.2 km2020-12-16 3.1 km2020-12-17 2.5 km2020-12-182020-12-19 3.2 km2020-12-20 10.3 km2020-12-21 2.6 km2020-12-222020-12-23 2.5 km2020-12-24 4.1 km2020-12-252020-12-26 4.6 km2020-12-27 1.6 km2020-12-282020-12-292020-12-302020-12-31 3.0 km20191342.7 kmJanFebMarAprMayJunJulAugSepOctNovDec2018-12-312019-01-01 1.0 km2019-01-02 1.0 km2019-01-03 2.0 km2019-01-04 1.0 km2019-01-05 1.0 km2019-01-06 2.5 km2019-01-07 1.7 km2019-01-08 1.0 km2019-01-09 2.0 km2019-01-10 1.0 km2019-01-112019-01-12 1.2 km2019-01-132019-01-14 0.6 km2019-01-15 1.0 km2019-01-162019-01-17 1.0 km2019-01-18 1.7 km2019-01-192019-01-202019-01-212019-01-222019-01-232019-01-242019-01-252019-01-262019-01-272019-01-282019-01-292019-01-302019-01-312019-02-012019-02-022019-02-032019-02-042019-02-052019-02-062019-02-072019-02-082019-02-092019-02-102019-02-112019-02-122019-02-132019-02-142019-02-152019-02-162019-02-172019-02-182019-02-192019-02-202019-02-21 0.6 km2019-02-222019-02-232019-02-242019-02-252019-02-26 1.0 km2019-02-27 1.0 km2019-02-28 1.0 km2019-03-012019-03-022019-03-032019-03-042019-03-052019-03-062019-03-072019-03-082019-03-092019-03-102019-03-112019-03-122019-03-132019-03-142019-03-152019-03-16 0.6 km2019-03-172019-03-182019-03-192019-03-202019-03-21 1.0 km2019-03-222019-03-232019-03-242019-03-252019-03-262019-03-272019-03-282019-03-292019-03-302019-03-312019-04-012019-04-022019-04-032019-04-042019-04-052019-04-062019-04-072019-04-082019-04-092019-04-102019-04-112019-04-122019-04-132019-04-142019-04-152019-04-162019-04-172019-04-182019-04-192019-04-202019-04-212019-04-222019-04-232019-04-242019-04-252019-04-262019-04-272019-04-282019-04-292019-04-302019-05-012019-05-022019-05-032019-05-042019-05-05 1.0 km2019-05-06 1.0 km2019-05-07 1.6 km2019-05-08 2.0 km2019-05-09 2.0 km2019-05-10 2.2 km2019-05-11 3.0 km2019-05-12 2.0 km2019-05-13 3.0 km2019-05-14 3.5 km2019-05-15 4.2 km2019-05-16 3.0 km2019-05-17 5.0 km2019-05-18 5.3 km2019-05-19 3.5 km2019-05-20 4.2 km2019-05-21 3.0 km2019-05-22 4.2 km2019-05-23 6.0 km2019-05-24 1.6 km2019-05-25 6.0 km2019-05-26 2.7 km2019-05-27 4.0 km2019-05-28 4.0 km2019-05-29 4.0 km2019-05-30 7.0 km2019-05-31 4.2 km2019-06-01 3.0 km2019-06-02 8.0 km2019-06-03 4.0 km2019-06-04 4.0 km2019-06-05 3.0 km2019-06-06 8.0 km2019-06-07 4.0 km2019-06-08 4.0 km2019-06-09 8.4 km2019-06-10 5.0 km2019-06-11 5.0 km2019-06-12 5.0 km2019-06-13 10.0 km2019-06-14 5.0 km2019-06-15 10.0 km2019-06-16 3.0 km2019-06-17 5.0 km2019-06-18 5.3 km2019-06-19 5.0 km2019-06-20 12.0 km2019-06-21 5.0 km2019-06-22 5.0 km2019-06-23 11.5 km2019-06-24 5.0 km2019-06-25 5.0 km2019-06-26 4.3 km2019-06-27 12.0 km2019-06-28 5.0 km2019-06-29 5.0 km2019-06-30 10.0 km2019-07-01 5.0 km2019-07-02 5.0 km2019-07-03 5.0 km2019-07-04 8.0 km2019-07-05 15.1 km2019-07-06 11.0 km2019-07-07 5.0 km2019-07-08 5.0 km2019-07-09 5.0 km2019-07-10 5.0 km2019-07-11 15.0 km2019-07-12 5.0 km2019-07-13 5.0 km2019-07-14 14.0 km2019-07-15 5.0 km2019-07-16 5.0 km2019-07-17 5.0 km2019-07-18 10.0 km2019-07-19 5.0 km2019-07-20 10.0 km2019-07-21 5.0 km2019-07-22 5.0 km2019-07-23 5.0 km2019-07-24 5.0 km2019-07-25 10.0 km2019-07-26 5.0 km2019-07-27 6.4 km2019-07-28 10.0 km2019-07-29 5.0 km2019-07-30 5.0 km2019-07-31 5.0 km2019-08-01 10.0 km2019-08-02 5.0 km2019-08-03 15.4 km2019-08-04 6.3 km2019-08-05 5.0 km2019-08-06 7.2 km2019-08-07 6.0 km2019-08-08 10.2 km2019-08-092019-08-10 5.0 km2019-08-11 3.5 km2019-08-12 5.2 km2019-08-13 5.0 km2019-08-14 2.0 km2019-08-15 9.0 km2019-08-162019-08-172019-08-18 10.0 km2019-08-19 10.0 km2019-08-20 5.0 km2019-08-21 9.7 km2019-08-22 5.0 km2019-08-23 5.0 km2019-08-24 10.3 km2019-08-25 5.0 km2019-08-26 1.1 km2019-08-27 5.0 km2019-08-28 8.0 km2019-08-29 5.1 km2019-08-30 5.0 km2019-08-31 16.0 km2019-09-01 5.0 km2019-09-02 5.0 km2019-09-03 5.1 km2019-09-042019-09-05 2.5 km2019-09-06 5.0 km2019-09-07 12.1 km2019-09-08 6.7 km2019-09-09 5.5 km2019-09-10 5.0 km2019-09-11 6.1 km2019-09-12 5.0 km2019-09-13 12.0 km2019-09-14 5.0 km2019-09-15 15.0 km2019-09-16 5.0 km2019-09-17 4.4 km2019-09-18 5.1 km2019-09-19 6.0 km2019-09-20 4.3 km2019-09-21 5.1 km2019-09-22 14.7 km2019-09-23 4.0 km2019-09-24 8.0 km2019-09-25 5.0 km2019-09-26 8.0 km2019-09-27 3.0 km2019-09-28 20.0 km2019-09-29 3.1 km2019-09-30 9.1 km2019-10-01 5.0 km2019-10-02 2.0 km2019-10-03 10.0 km2019-10-04 6.0 km2019-10-05 2.0 km2019-10-06 18.6 km2019-10-07 1.0 km2019-10-08 3.0 km2019-10-09 5.2 km2019-10-10 2.1 km2019-10-11 5.0 km2019-10-12 2.0 km2019-10-13 21.5 km2019-10-14 1.0 km2019-10-15 5.0 km2019-10-16 8.0 km2019-10-17 3.2 km2019-10-18 5.0 km2019-10-19 24.0 km2019-10-20 2.0 km2019-10-21 5.1 km2019-10-22 3.3 km2019-10-23 6.0 km2019-10-24 3.1 km2019-10-25 4.2 km2019-10-26 20.4 km2019-10-27 3.0 km2019-10-28 2.0 km2019-10-29 5.0 km2019-10-30 2.0 km2019-10-31 3.0 km2019-11-01 4.2 km2019-11-02 1.0 km2019-11-03 43.5 km2019-11-04 5.0 km2019-11-05 1.3 km2019-11-06 5.0 km2019-11-07 2.3 km2019-11-08 3.0 km2019-11-09 1.2 km2019-11-10 4.5 km2019-11-11 5.0 km2019-11-12 10.0 km2019-11-13 1.1 km2019-11-14 2.1 km2019-11-15 4.5 km2019-11-16 5.0 km2019-11-172019-11-18 1.0 km2019-11-19 5.0 km2019-11-20 4.5 km2019-11-21 2.0 km2019-11-22 5.0 km2019-11-23 4.1 km2019-11-24 6.3 km2019-11-25 4.0 km2019-11-262019-11-27 4.0 km2019-11-28 2.4 km2019-11-29 2.1 km2019-11-30 5.0 km2019-12-01 11.6 km2019-12-02 4.0 km2019-12-03 5.0 km2019-12-04 2.5 km2019-12-05 3.0 km2019-12-06 3.5 km2019-12-07 3.2 km2019-12-08 5.0 km2019-12-09 2.0 km2019-12-10 3.0 km2019-12-11 4.0 km2019-12-12 2.4 km2019-12-132019-12-14 5.3 km2019-12-152019-12-16 1.7 km2019-12-172019-12-18 3.0 km2019-12-19 4.0 km2019-12-202019-12-212019-12-22 3.0 km2019-12-23 5.0 km2019-12-242019-12-25 5.0 km2019-12-26 3.0 km2019-12-27 4.0 km2019-12-28 2.0 km2019-12-29 7.0 km2019-12-302019-12-31 4.0 km2018403.9 kmJanFebMarAprMayJunJulAugSepOctNovDec2018-01-01 5.0 km2018-01-02 3.0 km2018-01-03 3.0 km2018-01-04 3.0 km2018-01-05 3.0 km2018-01-06 3.0 km2018-01-07 6.0 km2018-01-08 3.0 km2018-01-09 3.0 km2018-01-10 3.2 km2018-01-11 3.0 km2018-01-12 3.0 km2018-01-13 8.0 km2018-01-14 3.0 km2018-01-15 3.1 km2018-01-16 4.0 km2018-01-17 4.0 km2018-01-18 4.1 km2018-01-19 4.0 km2018-01-20 10.0 km2018-01-21 5.0 km2018-01-22 4.0 km2018-01-23 4.0 km2018-01-24 4.0 km2018-01-25 4.1 km2018-01-26 3.0 km2018-01-27 4.2 km2018-01-28 10.0 km2018-01-29 4.0 km2018-01-30 3.0 km2018-01-31 4.0 km2018-02-01 5.0 km2018-02-02 5.3 km2018-02-03 12.0 km2018-02-04 5.0 km2018-02-05 5.0 km2018-02-06 5.0 km2018-02-07 3.0 km2018-02-08 5.0 km2018-02-09 5.0 km2018-02-10 5.0 km2018-02-11 10.6 km2018-02-12 5.0 km2018-02-13 5.0 km2018-02-14 5.3 km2018-02-15 3.0 km2018-02-16 3.0 km2018-02-17 3.0 km2018-02-18 1.5 km2018-02-19 2.0 km2018-02-20 5.0 km2018-02-21 4.1 km2018-02-22 1.6 km2018-02-23 6.5 km2018-02-24 2.4 km2018-02-25 5.0 km2018-02-26 1.8 km2018-02-27 1.4 km2018-02-282018-03-01 8.4 km2018-03-02 3.8 km2018-03-03 2.3 km2018-03-042018-03-052018-03-062018-03-07 5.0 km2018-03-08 1.0 km2018-03-09 3.1 km2018-03-10 3.5 km2018-03-11 2.0 km2018-03-122018-03-13 2.0 km2018-03-142018-03-152018-03-162018-03-172018-03-18 1.0 km2018-03-19 1.0 km2018-03-202018-03-21 2.0 km2018-03-22 3.0 km2018-03-23 1.0 km2018-03-24 0.8 km2018-03-252018-03-262018-03-272018-03-28 1.0 km2018-03-292018-03-302018-03-312018-04-012018-04-02 1.0 km2018-04-032018-04-042018-04-052018-04-062018-04-072018-04-082018-04-092018-04-102018-04-11 1.0 km2018-04-122018-04-132018-04-142018-04-152018-04-162018-04-172018-04-18 1.0 km2018-04-192018-04-202018-04-212018-04-222018-04-23 1.0 km2018-04-242018-04-252018-04-262018-04-272018-04-282018-04-292018-04-302018-05-01 1.0 km2018-05-02 1.5 km2018-05-03 0.8 km2018-05-04 0.5 km2018-05-052018-05-062018-05-07 1.0 km2018-05-082018-05-092018-05-10 1.0 km2018-05-112018-05-12 1.0 km2018-05-132018-05-142018-05-152018-05-162018-05-172018-05-182018-05-192018-05-202018-05-212018-05-222018-05-23 1.0 km2018-05-242018-05-252018-05-262018-05-272018-05-282018-05-292018-05-302018-05-312018-06-012018-06-022018-06-032018-06-042018-06-052018-06-06 0.6 km2018-06-072018-06-082018-06-092018-06-102018-06-11 1.0 km2018-06-12 1.5 km2018-06-132018-06-14 1.0 km2018-06-15 1.0 km2018-06-162018-06-172018-06-182018-06-19 2.0 km2018-06-20 2.3 km2018-06-21 1.0 km2018-06-22 2.0 km2018-06-23 1.0 km2018-06-242018-06-25 1.1 km2018-06-262018-06-272018-06-282018-06-292018-06-302018-07-012018-07-022018-07-032018-07-042018-07-052018-07-062018-07-072018-07-082018-07-092018-07-102018-07-112018-07-122018-07-132018-07-142018-07-152018-07-162018-07-172018-07-18 0.5 km2018-07-192018-07-202018-07-212018-07-222018-07-232018-07-24 0.5 km2018-07-25 1.0 km2018-07-26 1.0 km2018-07-27 1.5 km2018-07-282018-07-292018-07-30 1.0 km2018-07-312018-08-01 0.5 km2018-08-022018-08-032018-08-042018-08-052018-08-062018-08-072018-08-082018-08-092018-08-102018-08-112018-08-122018-08-132018-08-142018-08-152018-08-162018-08-172018-08-182018-08-192018-08-202018-08-212018-08-22 1.0 km2018-08-23 1.5 km2018-08-24 2.0 km2018-08-252018-08-26 1.0 km2018-08-272018-08-282018-08-29 1.0 km2018-08-302018-08-31 2.0 km2018-09-012018-09-02 3.0 km2018-09-032018-09-042018-09-05 1.0 km2018-09-062018-09-07 1.0 km2018-09-082018-09-092018-09-10 1.0 km2018-09-112018-09-122018-09-132018-09-142018-09-152018-09-162018-09-172018-09-18 1.0 km2018-09-19 0.6 km2018-09-20 1.0 km2018-09-21 1.6 km2018-09-222018-09-232018-09-242018-09-252018-09-262018-09-272018-09-282018-09-292018-09-302018-10-012018-10-02 1.0 km2018-10-032018-10-042018-10-052018-10-062018-10-072018-10-082018-10-092018-10-102018-10-112018-10-122018-10-132018-10-14 1.0 km2018-10-152018-10-16 1.0 km2018-10-172018-10-18 1.1 km2018-10-19 1.5 km2018-10-20 2.0 km2018-10-21 2.0 km2018-10-222018-10-232018-10-24 1.0 km2018-10-252018-10-262018-10-27 1.0 km2018-10-282018-10-292018-10-302018-10-312018-11-012018-11-022018-11-032018-11-042018-11-05 1.0 km2018-11-06 1.0 km2018-11-07 2.0 km2018-11-08 2.0 km2018-11-09 3.0 km2018-11-10 2.2 km2018-11-11 2.1 km2018-11-12 2.0 km2018-11-13 2.0 km2018-11-142018-11-152018-11-16 1.0 km2018-11-172018-11-182018-11-19 1.0 km2018-11-202018-11-212018-11-222018-11-232018-11-242018-11-252018-11-26 1.0 km2018-11-272018-11-282018-11-29 1.0 km2018-11-30 1.0 km2018-12-01 1.0 km2018-12-022018-12-032018-12-042018-12-05 1.0 km2018-12-062018-12-072018-12-08 1.1 km2018-12-09 1.0 km2018-12-10 2.2 km2018-12-11 1.2 km2018-12-122018-12-132018-12-14 1.0 km2018-12-15 2.0 km2018-12-16 1.1 km2018-12-17 2.0 km2018-12-18 2.2 km2018-12-19 2.4 km2018-12-20 1.0 km2018-12-21 1.0 km2018-12-22 3.0 km2018-12-23 0.9 km2018-12-242018-12-25 1.0 km2018-12-262018-12-272018-12-282018-12-292018-12-302018-12-312017962.8 kmJanFebMarAprMayJunJulAugSepOctNovDec2016-12-26 6.7 km2016-12-27 2.0 km2016-12-28 4.9 km2016-12-29 6.1 km2016-12-30 4.0 km2016-12-31 18.0 km2017-01-01 3.5 km2017-01-02 5.0 km2017-01-03 4.0 km2017-01-04 6.4 km2017-01-05 7.0 km2017-01-06 2.4 km2017-01-07 18.1 km2017-01-08 1.0 km2017-01-09 5.0 km2017-01-10 3.4 km2017-01-11 5.3 km2017-01-12 5.1 km2017-01-13 4.0 km2017-01-14 13.8 km2017-01-15 6.6 km2017-01-16 7.6 km2017-01-17 7.3 km2017-01-18 5.8 km2017-01-19 4.5 km2017-01-20 7.7 km2017-01-21 15.8 km2017-01-22 3.5 km2017-01-23 9.6 km2017-01-24 7.2 km2017-01-25 5.3 km2017-01-26 6.0 km2017-01-27 8.0 km2017-01-28 4.5 km2017-01-29 3.0 km2017-01-30 5.0 km2017-01-31 10.0 km2017-02-01 4.0 km2017-02-02 6.0 km2017-02-03 8.1 km2017-02-04 5.5 km2017-02-05 7.4 km2017-02-06 2.6 km2017-02-07 3.0 km2017-02-08 5.0 km2017-02-09 2.4 km2017-02-10 5.1 km2017-02-11 8.0 km2017-02-12 8.9 km2017-02-13 2.1 km2017-02-14 5.9 km2017-02-15 5.3 km2017-02-16 8.0 km2017-02-17 4.9 km2017-02-18 17.1 km2017-02-19 6.2 km2017-02-20 1.0 km2017-02-21 5.4 km2017-02-22 5.0 km2017-02-23 13.1 km2017-02-24 3.9 km2017-02-25 15.2 km2017-02-26 2.0 km2017-02-27 5.0 km2017-02-28 5.2 km2017-03-01 8.6 km2017-03-02 4.0 km2017-03-03 5.2 km2017-03-04 10.0 km2017-03-05 4.0 km2017-03-06 4.0 km2017-03-07 4.0 km2017-03-08 9.2 km2017-03-09 8.0 km2017-03-10 4.0 km2017-03-11 7.3 km2017-03-12 9.1 km2017-03-13 3.0 km2017-03-14 5.2 km2017-03-15 8.8 km2017-03-16 7.0 km2017-03-17 6.0 km2017-03-18 6.7 km2017-03-19 17.8 km2017-03-20 6.8 km2017-03-212017-03-22 3.2 km2017-03-23 2.1 km2017-03-24 5.5 km2017-03-25 1.7 km2017-03-26 13.1 km2017-03-27 9.0 km2017-03-28 10.0 km2017-03-29 10.3 km2017-03-30 10.0 km2017-03-31 4.3 km2017-04-01 6.3 km2017-04-02 20.8 km2017-04-03 10.4 km2017-04-04 10.0 km2017-04-05 5.3 km2017-04-06 5.0 km2017-04-07 2.0 km2017-04-08 9.2 km2017-04-09 3.0 km2017-04-10 4.4 km2017-04-112017-04-12 5.2 km2017-04-13 11.3 km2017-04-14 2.5 km2017-04-15 8.2 km2017-04-16 5.0 km2017-04-17 5.8 km2017-04-18 8.0 km2017-04-19 0.5 km2017-04-20 4.5 km2017-04-21 3.0 km2017-04-22 5.0 km2017-04-23 28.3 km2017-04-242017-04-25 8.0 km2017-04-26 10.3 km2017-04-272017-04-28 3.0 km2017-04-292017-04-30 1.0 km2017-05-01 10.2 km2017-05-022017-05-03 5.3 km2017-05-04 7.6 km2017-05-052017-05-06 3.4 km2017-05-07 22.6 km2017-05-082017-05-092017-05-102017-05-11 5.0 km2017-05-12 2.2 km2017-05-13 42.8 km2017-05-142017-05-15 7.2 km2017-05-16 7.0 km2017-05-17 2.7 km2017-05-18 2.3 km2017-05-192017-05-20 2.1 km2017-05-21 3.0 km2017-05-222017-05-23 5.1 km2017-05-242017-05-25 2.7 km2017-05-26 3.0 km2017-05-272017-05-28 6.0 km2017-05-292017-05-302017-05-31 2.2 km2017-06-01 2.1 km2017-06-022017-06-032017-06-042017-06-052017-06-062017-06-07 3.0 km2017-06-082017-06-09 2.1 km2017-06-102017-06-11 5.6 km2017-06-122017-06-132017-06-14 2.0 km2017-06-152017-06-162017-06-17 2.0 km2017-06-182017-06-19 3.0 km2017-06-202017-06-212017-06-22 2.0 km2017-06-232017-06-242017-06-252017-06-262017-06-27 3.0 km2017-06-282017-06-292017-06-302017-07-012017-07-022017-07-032017-07-04 1.0 km2017-07-052017-07-062017-07-072017-07-082017-07-092017-07-102017-07-112017-07-122017-07-132017-07-142017-07-152017-07-162017-07-172017-07-182017-07-192017-07-202017-07-212017-07-222017-07-232017-07-242017-07-252017-07-26 1.0 km2017-07-272017-07-28 2.0 km2017-07-292017-07-302017-07-312017-08-012017-08-022017-08-032017-08-042017-08-052017-08-062017-08-072017-08-082017-08-092017-08-102017-08-112017-08-122017-08-132017-08-142017-08-152017-08-162017-08-172017-08-182017-08-192017-08-20 2.0 km2017-08-21 3.0 km2017-08-222017-08-232017-08-242017-08-252017-08-262017-08-272017-08-282017-08-292017-08-302017-08-312017-09-012017-09-022017-09-032017-09-042017-09-052017-09-062017-09-072017-09-082017-09-092017-09-102017-09-112017-09-122017-09-132017-09-142017-09-152017-09-162017-09-172017-09-182017-09-192017-09-202017-09-212017-09-222017-09-232017-09-242017-09-252017-09-262017-09-272017-09-282017-09-292017-09-302017-10-012017-10-022017-10-032017-10-042017-10-052017-10-062017-10-072017-10-082017-10-092017-10-102017-10-112017-10-122017-10-13 1.0 km2017-10-142017-10-15 1.4 km2017-10-16 2.0 km2017-10-172017-10-18 1.1 km2017-10-192017-10-202017-10-212017-10-222017-10-232017-10-242017-10-252017-10-262017-10-272017-10-282017-10-292017-10-302017-10-312017-11-012017-11-022017-11-032017-11-04 1.2 km2017-11-052017-11-062017-11-072017-11-082017-11-092017-11-102017-11-112017-11-122017-11-132017-11-142017-11-152017-11-162017-11-172017-11-182017-11-192017-11-202017-11-212017-11-222017-11-232017-11-242017-11-252017-11-262017-11-272017-11-282017-11-292017-11-302017-12-012017-12-022017-12-032017-12-042017-12-052017-12-062017-12-072017-12-082017-12-092017-12-102017-12-112017-12-122017-12-132017-12-142017-12-152017-12-162017-12-172017-12-182017-12-192017-12-202017-12-212017-12-22 1.0 km2017-12-232017-12-242017-12-25 1.5 km2017-12-26 1.0 km2017-12-27 2.0 km2017-12-28 3.0 km2017-12-29 1.1 km2017-12-30 2.0 km2017-12-31 0.5 km2016899.2 kmJanFebMarAprMayJunJulAugSepOctNovDec2015-12-282015-12-292015-12-302015-12-312016-01-01 2.0 km2016-01-022016-01-03 0.8 km2016-01-042016-01-052016-01-062016-01-072016-01-082016-01-092016-01-102016-01-112016-01-122016-01-132016-01-142016-01-152016-01-162016-01-172016-01-18 1.5 km2016-01-192016-01-202016-01-212016-01-222016-01-232016-01-242016-01-252016-01-262016-01-272016-01-282016-01-292016-01-302016-01-312016-02-012016-02-022016-02-032016-02-042016-02-052016-02-062016-02-072016-02-082016-02-092016-02-10 2.0 km2016-02-112016-02-122016-02-132016-02-142016-02-152016-02-162016-02-17 1.1 km2016-02-18 2.1 km2016-02-192016-02-20 2.0 km2016-02-212016-02-222016-02-23 2.1 km2016-02-24 1.1 km2016-02-25 2.8 km2016-02-262016-02-27 0.9 km2016-02-282016-02-29 2.5 km2016-03-012016-03-022016-03-032016-03-042016-03-052016-03-062016-03-072016-03-082016-03-092016-03-102016-03-112016-03-122016-03-132016-03-142016-03-152016-03-16 1.7 km2016-03-17 1.0 km2016-03-182016-03-192016-03-20 2.1 km2016-03-21 3.0 km2016-03-22 1.5 km2016-03-23 3.1 km2016-03-24 2.2 km2016-03-25 1.2 km2016-03-26 5.1 km2016-03-272016-03-28 1.1 km2016-03-29 6.0 km2016-03-30 1.5 km2016-03-312016-04-01 1.0 km2016-04-02 0.7 km2016-04-03 8.0 km2016-04-04 1.0 km2016-04-05 0.8 km2016-04-06 5.0 km2016-04-07 2.1 km2016-04-082016-04-09 10.0 km2016-04-10 2.0 km2016-04-11 4.2 km2016-04-12 4.7 km2016-04-13 14.1 km2016-04-142016-04-15 2.0 km2016-04-16 1.0 km2016-04-17 2.4 km2016-04-18 9.3 km2016-04-19 3.9 km2016-04-20 7.0 km2016-04-21 4.9 km2016-04-22 10.0 km2016-04-23 3.8 km2016-04-24 15.0 km2016-04-25 1.0 km2016-04-26 8.8 km2016-04-27 4.3 km2016-04-28 6.1 km2016-04-29 2.3 km2016-04-30 17.7 km2016-05-01 1.2 km2016-05-02 5.8 km2016-05-03 0.7 km2016-05-04 3.6 km2016-05-05 14.3 km2016-05-06 1.7 km2016-05-072016-05-08 8.5 km2016-05-09 10.4 km2016-05-10 4.3 km2016-05-11 3.6 km2016-05-12 8.0 km2016-05-13 4.1 km2016-05-142016-05-15 20.0 km2016-05-162016-05-17 4.1 km2016-05-182016-05-19 6.1 km2016-05-20 2.0 km2016-05-212016-05-22 21.5 km2016-05-23 1.0 km2016-05-24 7.1 km2016-05-25 2.5 km2016-05-26 2.0 km2016-05-27 1.6 km2016-05-282016-05-292016-05-30 5.0 km2016-05-31 7.0 km2016-06-01 11.6 km2016-06-02 5.1 km2016-06-03 7.2 km2016-06-04 9.0 km2016-06-05 6.4 km2016-06-06 5.3 km2016-06-07 6.7 km2016-06-08 1.6 km2016-06-09 2.2 km2016-06-10 2.4 km2016-06-112016-06-12 1.5 km2016-06-132016-06-14 2.0 km2016-06-152016-06-162016-06-172016-06-182016-06-192016-06-202016-06-212016-06-222016-06-232016-06-242016-06-252016-06-262016-06-272016-06-282016-06-292016-06-302016-07-012016-07-022016-07-032016-07-042016-07-05 2.5 km2016-07-062016-07-072016-07-082016-07-092016-07-102016-07-112016-07-122016-07-132016-07-142016-07-152016-07-162016-07-17 1.0 km2016-07-18 2.1 km2016-07-192016-07-202016-07-212016-07-222016-07-232016-07-242016-07-252016-07-262016-07-272016-07-282016-07-292016-07-302016-07-312016-08-01 3.1 km2016-08-02 1.0 km2016-08-03 1.0 km2016-08-042016-08-052016-08-062016-08-072016-08-082016-08-092016-08-102016-08-112016-08-122016-08-132016-08-142016-08-152016-08-162016-08-172016-08-182016-08-192016-08-202016-08-212016-08-222016-08-232016-08-242016-08-252016-08-262016-08-272016-08-282016-08-292016-08-302016-08-312016-09-012016-09-022016-09-032016-09-042016-09-052016-09-062016-09-072016-09-082016-09-092016-09-102016-09-112016-09-122016-09-132016-09-142016-09-152016-09-162016-09-172016-09-182016-09-192016-09-20 3.0 km2016-09-212016-09-222016-09-232016-09-24 2.0 km2016-09-25 3.1 km2016-09-26 0.6 km2016-09-272016-09-282016-09-292016-09-302016-10-012016-10-022016-10-032016-10-042016-10-052016-10-062016-10-072016-10-082016-10-092016-10-102016-10-112016-10-122016-10-13 2.0 km2016-10-14 3.0 km2016-10-15 2.0 km2016-10-16 3.0 km2016-10-17 4.0 km2016-10-18 1.0 km2016-10-19 0.8 km2016-10-20 4.0 km2016-10-21 2.2 km2016-10-222016-10-23 5.0 km2016-10-24 5.0 km2016-10-25 5.1 km2016-10-26 5.0 km2016-10-27 5.0 km2016-10-28 6.2 km2016-10-29 5.0 km2016-10-30 5.6 km2016-10-31 5.0 km2016-11-01 5.0 km2016-11-02 5.0 km2016-11-03 5.0 km2016-11-04 5.1 km2016-11-05 8.0 km2016-11-06 5.0 km2016-11-07 5.3 km2016-11-08 5.0 km2016-11-09 5.0 km2016-11-10 5.0 km2016-11-11 5.0 km2016-11-12 10.0 km2016-11-13 5.0 km2016-11-14 5.0 km2016-11-15 5.3 km2016-11-16 5.1 km2016-11-17 5.0 km2016-11-18 5.3 km2016-11-19 10.0 km2016-11-20 5.0 km2016-11-21 5.0 km2016-11-22 5.1 km2016-11-23 6.0 km2016-11-24 6.0 km2016-11-25 7.3 km2016-11-26 12.0 km2016-11-27 6.0 km2016-11-28 6.0 km2016-11-29 6.0 km2016-11-30 7.3 km2016-12-01 6.0 km2016-12-02 6.0 km2016-12-03 12.0 km2016-12-04 6.0 km2016-12-05 6.0 km2016-12-06 6.0 km2016-12-07 7.1 km2016-12-08 6.3 km2016-12-09 6.0 km2016-12-10 15.0 km2016-12-11 6.1 km2016-12-12 6.0 km2016-12-13 6.0 km2016-12-14 5.0 km2016-12-15 6.4 km2016-12-16 6.2 km2016-12-17 10.0 km2016-12-18 6.0 km2016-12-19 6.0 km2016-12-20 5.1 km2016-12-21 6.9 km2016-12-22 5.2 km2016-12-23 5.7 km2016-12-24 16.0 km2016-12-25 4.0 km2016-12-26 6.7 km2016-12-27 2.0 km2016-12-28 4.9 km2016-12-29 6.1 km2016-12-30 4.0 km2016-12-31 18.0 km2015435.0 kmJanFebMarAprMayJunJulAugSepOctNovDec2014-12-29 3.0 km2014-12-30 2.4 km2014-12-312015-01-012015-01-02 3.3 km2015-01-032015-01-042015-01-052015-01-062015-01-072015-01-082015-01-092015-01-102015-01-112015-01-122015-01-132015-01-14 2.4 km2015-01-15 5.2 km2015-01-162015-01-172015-01-182015-01-19 1.5 km2015-01-20 3.1 km2015-01-21 12.4 km2015-01-222015-01-232015-01-242015-01-252015-01-26 1.8 km2015-01-272015-01-28 2.6 km2015-01-29 1.8 km2015-01-302015-01-312015-02-01 5.0 km2015-02-02 1.3 km2015-02-03 1.7 km2015-02-042015-02-052015-02-062015-02-072015-02-082015-02-092015-02-102015-02-11 11.1 km2015-02-122015-02-132015-02-142015-02-152015-02-162015-02-172015-02-182015-02-192015-02-202015-02-212015-02-222015-02-232015-02-242015-02-252015-02-262015-02-272015-02-282015-03-012015-03-022015-03-032015-03-042015-03-05 2.0 km2015-03-06 3.1 km2015-03-07 5.0 km2015-03-08 6.3 km2015-03-092015-03-102015-03-112015-03-122015-03-132015-03-142015-03-152015-03-162015-03-172015-03-182015-03-192015-03-202015-03-212015-03-222015-03-232015-03-242015-03-252015-03-262015-03-272015-03-282015-03-292015-03-302015-03-312015-04-012015-04-022015-04-032015-04-042015-04-05 0.5 km2015-04-062015-04-07 1.3 km2015-04-08 2.0 km2015-04-09 2.6 km2015-04-10 1.6 km2015-04-112015-04-122015-04-132015-04-14 2.0 km2015-04-15 3.0 km2015-04-16 1.0 km2015-04-17 3.0 km2015-04-18 0.9 km2015-04-192015-04-20 5.0 km2015-04-21 2.5 km2015-04-222015-04-23 1.5 km2015-04-24 1.9 km2015-04-252015-04-26 6.3 km2015-04-272015-04-28 3.3 km2015-04-29 5.0 km2015-04-30 11.8 km2015-05-012015-05-02 1.1 km2015-05-032015-05-04 5.0 km2015-05-05 2.2 km2015-05-06 5.0 km2015-05-07 5.7 km2015-05-082015-05-09 1.0 km2015-05-102015-05-112015-05-12 1.3 km2015-05-132015-05-14 4.2 km2015-05-152015-05-162015-05-172015-05-182015-05-19 2.0 km2015-05-202015-05-21 2.1 km2015-05-222015-05-23 6.0 km2015-05-242015-05-25 4.3 km2015-05-26 2.0 km2015-05-272015-05-28 1.8 km2015-05-292015-05-302015-05-31 0.9 km2015-06-01 3.0 km2015-06-02 2.5 km2015-06-032015-06-04 3.0 km2015-06-052015-06-062015-06-072015-06-08 2.0 km2015-06-09 3.0 km2015-06-10 5.0 km2015-06-11 5.2 km2015-06-12 2.0 km2015-06-132015-06-14 5.0 km2015-06-15 1.3 km2015-06-16 3.0 km2015-06-17 4.3 km2015-06-18 2.0 km2015-06-19 1.0 km2015-06-202015-06-21 8.0 km2015-06-222015-06-23 2.0 km2015-06-24 4.1 km2015-06-25 1.0 km2015-06-262015-06-272015-06-28 12.0 km2015-06-292015-06-30 1.0 km2015-07-01 4.2 km2015-07-02 1.0 km2015-07-03 5.0 km2015-07-042015-07-05 14.0 km2015-07-062015-07-07 5.0 km2015-07-08 1.3 km2015-07-09 4.3 km2015-07-102015-07-112015-07-12 15.0 km2015-07-132015-07-14 1.4 km2015-07-15 4.2 km2015-07-16 1.2 km2015-07-17 4.2 km2015-07-182015-07-19 17.2 km2015-07-202015-07-21 3.0 km2015-07-22 4.0 km2015-07-23 4.7 km2015-07-242015-07-252015-07-26 15.5 km2015-07-272015-07-28 1.5 km2015-07-292015-07-30 4.4 km2015-07-312015-08-01 2.9 km2015-08-022015-08-03 3.0 km2015-08-04 2.0 km2015-08-05 2.2 km2015-08-062015-08-072015-08-082015-08-092015-08-102015-08-11 1.0 km2015-08-122015-08-13 3.6 km2015-08-142015-08-152015-08-162015-08-172015-08-182015-08-192015-08-202015-08-21 1.5 km2015-08-222015-08-232015-08-242015-08-25 3.0 km2015-08-262015-08-272015-08-282015-08-292015-08-302015-08-312015-09-012015-09-022015-09-032015-09-042015-09-05 1.5 km2015-09-062015-09-072015-09-082015-09-092015-09-102015-09-112015-09-122015-09-132015-09-142015-09-15 1.3 km2015-09-162015-09-172015-09-182015-09-192015-09-202015-09-21 4.3 km2015-09-222015-09-23 1.2 km2015-09-242015-09-252015-09-262015-09-272015-09-28 3.0 km2015-09-292015-09-302015-10-01 4.5 km2015-10-022015-10-032015-10-042015-10-05 1.1 km2015-10-06 3.0 km2015-10-072015-10-08 4.1 km2015-10-09 2.4 km2015-10-10 2.0 km2015-10-11 3.0 km2015-10-12 5.0 km2015-10-13 3.0 km2015-10-14 3.0 km2015-10-15 5.3 km2015-10-162015-10-172015-10-182015-10-19 1.0 km2015-10-20 1.0 km2015-10-212015-10-222015-10-232015-10-242015-10-252015-10-26 1.6 km2015-10-27 2.0 km2015-10-28 1.6 km2015-10-29 3.0 km2015-10-302015-10-312015-11-012015-11-022015-11-032015-11-042015-11-052015-11-062015-11-072015-11-082015-11-092015-11-102015-11-112015-11-12 1.5 km2015-11-132015-11-142015-11-152015-11-162015-11-172015-11-18 1.5 km2015-11-192015-11-202015-11-212015-11-222015-11-232015-11-242015-11-252015-11-262015-11-272015-11-282015-11-292015-11-302015-12-01 0.7 km2015-12-022015-12-032015-12-042015-12-052015-12-062015-12-072015-12-08 0.9 km2015-12-092015-12-102015-12-112015-12-122015-12-132015-12-142015-12-152015-12-162015-12-172015-12-182015-12-192015-12-202015-12-212015-12-222015-12-232015-12-24 2.0 km2015-12-252015-12-262015-12-272015-12-282015-12-292015-12-302015-12-312014822.8 kmJanFebMarAprMayJunJulAugSepOctNovDec2013-12-30 2.5 km2013-12-312014-01-01 5.0 km2014-01-02 5.0 km2014-01-032014-01-04 4.0 km2014-01-05 7.2 km2014-01-06 2.5 km2014-01-072014-01-082014-01-092014-01-102014-01-112014-01-122014-01-132014-01-14 5.0 km2014-01-152014-01-16 3.0 km2014-01-172014-01-182014-01-192014-01-20 5.0 km2014-01-21 7.0 km2014-01-222014-01-232014-01-242014-01-252014-01-262014-01-272014-01-282014-01-292014-01-302014-01-312014-02-012014-02-02 2.0 km2014-02-032014-02-042014-02-052014-02-062014-02-072014-02-082014-02-092014-02-102014-02-11 4.0 km2014-02-12 5.2 km2014-02-13 7.5 km2014-02-14 4.0 km2014-02-15 10.2 km2014-02-16 9.0 km2014-02-17 5.8 km2014-02-18 6.6 km2014-02-19 12.0 km2014-02-20 6.0 km2014-02-21 5.1 km2014-02-22 9.0 km2014-02-23 6.3 km2014-02-24 3.8 km2014-02-25 6.0 km2014-02-26 9.0 km2014-02-27 5.2 km2014-02-28 11.3 km2014-03-01 3.3 km2014-03-02 5.7 km2014-03-03 4.0 km2014-03-04 6.1 km2014-03-05 6.1 km2014-03-06 3.5 km2014-03-07 12.3 km2014-03-08 5.3 km2014-03-09 4.0 km2014-03-10 6.1 km2014-03-11 11.2 km2014-03-12 8.0 km2014-03-13 15.1 km2014-03-142014-03-152014-03-162014-03-17 13.6 km2014-03-18 3.0 km2014-03-19 12.1 km2014-03-20 2.3 km2014-03-21 6.0 km2014-03-22 10.0 km2014-03-23 6.0 km2014-03-24 2.0 km2014-03-25 21.1 km2014-03-262014-03-27 7.1 km2014-03-28 5.4 km2014-03-29 3.0 km2014-03-30 5.0 km2014-03-31 11.1 km2014-04-01 6.0 km2014-04-02 5.3 km2014-04-03 3.1 km2014-04-042014-04-052014-04-062014-04-072014-04-082014-04-09 5.0 km2014-04-10 3.1 km2014-04-11 6.0 km2014-04-122014-04-13 8.4 km2014-04-14 5.0 km2014-04-152014-04-16 3.0 km2014-04-17 2.5 km2014-04-18 5.6 km2014-04-192014-04-202014-04-21 14.0 km2014-04-22 5.0 km2014-04-232014-04-242014-04-25 4.0 km2014-04-26 20.5 km2014-04-272014-04-28 4.3 km2014-04-29 5.0 km2014-04-302014-05-012014-05-022014-05-03 7.8 km2014-05-042014-05-05 12.0 km2014-05-062014-05-072014-05-082014-05-092014-05-10 41.4 km2014-05-112014-05-122014-05-13 0.7 km2014-05-14 5.8 km2014-05-15 1.0 km2014-05-16 5.4 km2014-05-172014-05-182014-05-19 8.1 km2014-05-20 2.2 km2014-05-21 4.0 km2014-05-22 4.0 km2014-05-23 10.0 km2014-05-24 3.0 km2014-05-25 7.6 km2014-05-26 4.0 km2014-05-27 4.0 km2014-05-28 5.0 km2014-05-292014-05-302014-05-312014-06-012014-06-022014-06-03 3.0 km2014-06-04 6.3 km2014-06-05 4.0 km2014-06-062014-06-07 1.4 km2014-06-082014-06-092014-06-10 5.0 km2014-06-112014-06-122014-06-132014-06-142014-06-152014-06-162014-06-172014-06-18 5.0 km2014-06-19 5.9 km2014-06-20 10.0 km2014-06-212014-06-222014-06-23 2.0 km2014-06-24 6.2 km2014-06-252014-06-26 2.2 km2014-06-272014-06-282014-06-292014-06-302014-07-012014-07-02 2.5 km2014-07-032014-07-042014-07-052014-07-06 2.0 km2014-07-07 3.0 km2014-07-082014-07-09 6.4 km2014-07-10 2.3 km2014-07-112014-07-122014-07-132014-07-14 2.2 km2014-07-15 4.1 km2014-07-16 4.0 km2014-07-17 6.0 km2014-07-18 4.0 km2014-07-192014-07-202014-07-21 2.9 km2014-07-222014-07-23 8.0 km2014-07-24 2.0 km2014-07-252014-07-262014-07-27 1.1 km2014-07-28 5.0 km2014-07-29 10.1 km2014-07-30 9.1 km2014-07-31 2.0 km2014-08-01 7.2 km2014-08-022014-08-032014-08-04 2.0 km2014-08-05 7.3 km2014-08-06 14.6 km2014-08-07 2.2 km2014-08-082014-08-092014-08-10 1.4 km2014-08-11 6.0 km2014-08-12 11.1 km2014-08-13 5.0 km2014-08-142014-08-152014-08-162014-08-172014-08-182014-08-192014-08-202014-08-212014-08-222014-08-232014-08-242014-08-252014-08-262014-08-272014-08-282014-08-292014-08-302014-08-312014-09-012014-09-022014-09-032014-09-042014-09-052014-09-062014-09-072014-09-082014-09-092014-09-102014-09-112014-09-122014-09-132014-09-142014-09-152014-09-162014-09-172014-09-182014-09-192014-09-202014-09-212014-09-222014-09-232014-09-242014-09-252014-09-262014-09-272014-09-282014-09-292014-09-302014-10-012014-10-022014-10-032014-10-042014-10-052014-10-062014-10-072014-10-082014-10-092014-10-102014-10-112014-10-122014-10-132014-10-142014-10-152014-10-162014-10-172014-10-182014-10-192014-10-202014-10-212014-10-222014-10-232014-10-242014-10-252014-10-262014-10-272014-10-282014-10-292014-10-302014-10-312014-11-012014-11-022014-11-032014-11-042014-11-052014-11-062014-11-072014-11-082014-11-092014-11-102014-11-112014-11-122014-11-132014-11-142014-11-152014-11-162014-11-172014-11-182014-11-192014-11-202014-11-212014-11-222014-11-232014-11-242014-11-252014-11-262014-11-272014-11-282014-11-292014-11-302014-12-012014-12-02 4.1 km2014-12-03 3.1 km2014-12-042014-12-052014-12-062014-12-072014-12-082014-12-092014-12-102014-12-112014-12-122014-12-132014-12-14 2.1 km2014-12-152014-12-162014-12-172014-12-182014-12-192014-12-20 2.4 km2014-12-21 2.2 km2014-12-222014-12-23 5.0 km2014-12-242014-12-25 2.3 km2014-12-26 3.0 km2014-12-27 4.0 km2014-12-28 2.0 km2014-12-29 3.0 km2014-12-30 2.4 km2014-12-312013789.7 kmJanFebMarAprMayJunJulAugSepOctNovDec2012-12-312013-01-012013-01-022013-01-03 5.0 km2013-01-04 7.4 km2013-01-05 5.0 km2013-01-06 5.0 km2013-01-07 5.0 km2013-01-08 5.0 km2013-01-09 14.8 km2013-01-10 2.6 km2013-01-11 3.3 km2013-01-12 5.2 km2013-01-13 7.2 km2013-01-14 3.0 km2013-01-15 2.0 km2013-01-16 6.0 km2013-01-172013-01-18 8.3 km2013-01-192013-01-202013-01-21 5.2 km2013-01-22 10.2 km2013-01-23 5.0 km2013-01-24 3.3 km2013-01-25 5.2 km2013-01-26 2.1 km2013-01-27 10.0 km2013-01-282013-01-29 5.2 km2013-01-30 5.0 km2013-01-31 4.5 km2013-02-01 3.3 km2013-02-022013-02-03 2.0 km2013-02-04 6.2 km2013-02-05 4.0 km2013-02-06 2.2 km2013-02-072013-02-08 5.2 km2013-02-09 10.0 km2013-02-10 2.9 km2013-02-11 2.0 km2013-02-12 5.0 km2013-02-132013-02-14 8.0 km2013-02-15 5.2 km2013-02-162013-02-17 2.1 km2013-02-18 8.2 km2013-02-19 4.2 km2013-02-202013-02-21 5.2 km2013-02-22 5.0 km2013-02-23 5.2 km2013-02-24 5.0 km2013-02-25 5.0 km2013-02-26 7.5 km2013-02-27 2.5 km2013-02-28 5.0 km2013-03-012013-03-02 7.3 km2013-03-03 3.1 km2013-03-04 5.0 km2013-03-05 5.0 km2013-03-06 12.0 km2013-03-07 2.0 km2013-03-08 4.0 km2013-03-092013-03-10 6.4 km2013-03-11 5.0 km2013-03-12 5.0 km2013-03-132013-03-14 14.5 km2013-03-152013-03-16 3.2 km2013-03-17 7.3 km2013-03-18 4.0 km2013-03-19 7.3 km2013-03-202013-03-21 17.0 km2013-03-22 2.5 km2013-03-232013-03-24 7.2 km2013-03-25 2.3 km2013-03-26 2.2 km2013-03-27 10.0 km2013-03-282013-03-29 2.4 km2013-03-302013-03-31 16.1 km2013-04-012013-04-02 2.6 km2013-04-03 2.3 km2013-04-04 2.4 km2013-04-052013-04-06 2.5 km2013-04-07 10.0 km2013-04-082013-04-09 21.0 km2013-04-102013-04-112013-04-122013-04-132013-04-14 2.0 km2013-04-15 5.0 km2013-04-16 1.3 km2013-04-172013-04-182013-04-192013-04-202013-04-21 21.5 km2013-04-222013-04-232013-04-242013-04-25 2.0 km2013-04-26 3.0 km2013-04-27 5.0 km2013-04-282013-04-292013-04-30 5.3 km2013-05-01 6.1 km2013-05-02 2.0 km2013-05-032013-05-04 17.0 km2013-05-052013-05-062013-05-07 3.3 km2013-05-082013-05-09 5.0 km2013-05-102013-05-11 6.3 km2013-05-122013-05-13 5.2 km2013-05-14 10.0 km2013-05-15 2.1 km2013-05-162013-05-17 0.9 km2013-05-18 8.3 km2013-05-192013-05-20 2.0 km2013-05-21 2.0 km2013-05-22 5.0 km2013-05-23 3.0 km2013-05-24 5.0 km2013-05-252013-05-262013-05-27 7.1 km2013-05-28 2.0 km2013-05-292013-05-30 8.0 km2013-05-312013-06-01 1.2 km2013-06-02 2.1 km2013-06-03 10.0 km2013-06-04 6.0 km2013-06-05 2.1 km2013-06-06 9.4 km2013-06-072013-06-08 2.3 km2013-06-09 4.0 km2013-06-102013-06-11 9.6 km2013-06-122013-06-13 4.0 km2013-06-142013-06-152013-06-162013-06-172013-06-182013-06-192013-06-202013-06-21 4.1 km2013-06-22 14.3 km2013-06-232013-06-24 2.0 km2013-06-252013-06-262013-06-272013-06-282013-06-29 5.0 km2013-06-30 6.6 km2013-07-012013-07-022013-07-03 5.1 km2013-07-042013-07-052013-07-062013-07-07 2.5 km2013-07-08 3.0 km2013-07-092013-07-102013-07-112013-07-122013-07-132013-07-142013-07-152013-07-162013-07-172013-07-182013-07-192013-07-20 4.1 km2013-07-212013-07-222013-07-232013-07-24 6.1 km2013-07-252013-07-262013-07-272013-07-282013-07-292013-07-302013-07-312013-08-012013-08-022013-08-032013-08-042013-08-05 2.5 km2013-08-062013-08-072013-08-082013-08-092013-08-102013-08-112013-08-122013-08-132013-08-14 4.0 km2013-08-152013-08-162013-08-172013-08-182013-08-192013-08-202013-08-212013-08-222013-08-232013-08-242013-08-252013-08-26 4.0 km2013-08-272013-08-282013-08-292013-08-302013-08-312013-09-012013-09-022013-09-032013-09-042013-09-052013-09-062013-09-072013-09-082013-09-092013-09-102013-09-11 3.0 km2013-09-122013-09-132013-09-14 4.0 km2013-09-152013-09-162013-09-172013-09-18 5.0 km2013-09-192013-09-202013-09-212013-09-222013-09-232013-09-242013-09-252013-09-262013-09-272013-09-282013-09-292013-09-302013-10-012013-10-022013-10-032013-10-042013-10-052013-10-062013-10-072013-10-082013-10-092013-10-102013-10-112013-10-122013-10-132013-10-142013-10-152013-10-162013-10-172013-10-182013-10-192013-10-202013-10-212013-10-222013-10-232013-10-242013-10-25 4.0 km2013-10-262013-10-272013-10-282013-10-292013-10-30 8.0 km2013-10-31 5.0 km2013-11-012013-11-022013-11-032013-11-04 10.1 km2013-11-05 1.0 km2013-11-062013-11-072013-11-08 3.0 km2013-11-092013-11-102013-11-11 2.0 km2013-11-122013-11-13 12.3 km2013-11-142013-11-15 1.0 km2013-11-162013-11-172013-11-18 5.0 km2013-11-192013-11-202013-11-212013-11-222013-11-232013-11-242013-11-252013-11-26 5.0 km2013-11-272013-11-282013-11-292013-11-302013-12-012013-12-02 2.5 km2013-12-032013-12-04 5.0 km2013-12-05 5.0 km2013-12-062013-12-072013-12-082013-12-092013-12-102013-12-112013-12-122013-12-132013-12-142013-12-152013-12-162013-12-172013-12-182013-12-192013-12-202013-12-212013-12-222013-12-232013-12-242013-12-252013-12-26 2.7 km2013-12-272013-12-282013-12-292013-12-30 2.5 km2013-12-312012386.5 kmJanFebMarAprMayJunJulAugSepOctNovDec2011-12-262011-12-272011-12-282011-12-292011-12-302011-12-312012-01-012012-01-022012-01-032012-01-042012-01-052012-01-062012-01-072012-01-082012-01-092012-01-102012-01-112012-01-122012-01-132012-01-142012-01-152012-01-162012-01-172012-01-182012-01-192012-01-202012-01-212012-01-222012-01-232012-01-242012-01-252012-01-262012-01-272012-01-282012-01-292012-01-302012-01-312012-02-012012-02-022012-02-032012-02-042012-02-052012-02-062012-02-072012-02-082012-02-092012-02-102012-02-112012-02-122012-02-132012-02-142012-02-152012-02-162012-02-172012-02-182012-02-192012-02-202012-02-212012-02-222012-02-232012-02-242012-02-252012-02-262012-02-272012-02-282012-02-292012-03-012012-03-022012-03-032012-03-042012-03-052012-03-062012-03-072012-03-082012-03-092012-03-102012-03-112012-03-122012-03-132012-03-142012-03-152012-03-162012-03-172012-03-182012-03-192012-03-202012-03-212012-03-222012-03-232012-03-242012-03-252012-03-262012-03-272012-03-282012-03-292012-03-302012-03-312012-04-012012-04-022012-04-032012-04-042012-04-052012-04-062012-04-072012-04-082012-04-092012-04-102012-04-112012-04-122012-04-132012-04-142012-04-152012-04-162012-04-172012-04-182012-04-192012-04-202012-04-212012-04-222012-04-232012-04-242012-04-252012-04-262012-04-272012-04-282012-04-292012-04-302012-05-012012-05-022012-05-032012-05-042012-05-052012-05-062012-05-072012-05-082012-05-092012-05-102012-05-112012-05-122012-05-132012-05-142012-05-152012-05-162012-05-172012-05-182012-05-192012-05-202012-05-212012-05-222012-05-232012-05-242012-05-252012-05-262012-05-272012-05-282012-05-292012-05-302012-05-312012-06-012012-06-022012-06-032012-06-042012-06-052012-06-062012-06-072012-06-082012-06-092012-06-102012-06-112012-06-122012-06-132012-06-142012-06-152012-06-162012-06-172012-06-182012-06-192012-06-202012-06-212012-06-222012-06-232012-06-242012-06-252012-06-262012-06-272012-06-282012-06-292012-06-302012-07-012012-07-022012-07-032012-07-042012-07-052012-07-062012-07-072012-07-082012-07-092012-07-102012-07-112012-07-122012-07-132012-07-142012-07-152012-07-162012-07-172012-07-182012-07-192012-07-202012-07-212012-07-222012-07-232012-07-242012-07-252012-07-262012-07-272012-07-282012-07-292012-07-302012-07-312012-08-012012-08-022012-08-032012-08-042012-08-052012-08-062012-08-072012-08-082012-08-092012-08-102012-08-112012-08-122012-08-132012-08-142012-08-152012-08-162012-08-172012-08-182012-08-192012-08-202012-08-212012-08-222012-08-232012-08-242012-08-252012-08-262012-08-272012-08-282012-08-292012-08-302012-08-312012-09-012012-09-022012-09-032012-09-042012-09-052012-09-062012-09-072012-09-082012-09-092012-09-102012-09-112012-09-122012-09-132012-09-142012-09-152012-09-162012-09-172012-09-182012-09-192012-09-202012-09-212012-09-222012-09-232012-09-242012-09-252012-09-262012-09-272012-09-282012-09-292012-09-302012-10-012012-10-022012-10-032012-10-042012-10-052012-10-062012-10-072012-10-082012-10-092012-10-102012-10-112012-10-122012-10-132012-10-142012-10-152012-10-162012-10-172012-10-182012-10-192012-10-202012-10-212012-10-222012-10-232012-10-242012-10-252012-10-26 4.1 km2012-10-27 6.4 km2012-10-28 6.3 km2012-10-29 6.2 km2012-10-30 4.1 km2012-10-31 4.0 km2012-11-01 6.2 km2012-11-02 5.4 km2012-11-03 7.6 km2012-11-04 5.2 km2012-11-05 10.1 km2012-11-06 2.0 km2012-11-07 6.2 km2012-11-08 10.6 km2012-11-09 6.2 km2012-11-10 3.2 km2012-11-11 8.0 km2012-11-12 6.3 km2012-11-13 8.3 km2012-11-14 2.0 km2012-11-15 10.0 km2012-11-16 6.6 km2012-11-17 2.1 km2012-11-18 10.0 km2012-11-19 6.0 km2012-11-20 4.2 km2012-11-21 5.1 km2012-11-22 5.8 km2012-11-23 12.2 km2012-11-24 6.6 km2012-11-25 4.1 km2012-11-26 4.1 km2012-11-27 5.0 km2012-11-28 6.4 km2012-11-29 15.0 km2012-11-302012-12-012012-12-02 5.4 km2012-12-032012-12-042012-12-05 5.2 km2012-12-062012-12-07 6.1 km2012-12-08 6.7 km2012-12-09 5.4 km2012-12-10 5.0 km2012-12-11 7.1 km2012-12-12 4.8 km2012-12-13 6.1 km2012-12-14 8.1 km2012-12-15 7.0 km2012-12-16 10.1 km2012-12-17 2.0 km2012-12-18 7.3 km2012-12-19 4.9 km2012-12-20 10.1 km2012-12-21 5.0 km2012-12-22 3.0 km2012-12-23 12.0 km2012-12-24 5.0 km2012-12-25 7.4 km2012-12-26 10.0 km2012-12-27 8.2 km2012-12-28 5.0 km2012-12-29 3.5 km2012-12-30 4.0 km2012-12-31 \ No newline at end of file diff --git a/assets/github_2023.svg b/assets/github_2023.svg index c62e65f6363..1c9c0955eb0 100644 --- a/assets/github_2023.svg +++ b/assets/github_2023.svg @@ -1,2 +1,2 @@ -2023 RunningATHLETEyihong0618STATISTICSNumber: 206Weekly: 5.4Total: 521.9 kmAvg: 2.5 kmMin: 0.6 kmMax: 10.3 km2023521.9 kmJanFebMarAprMayJunJulAugSepOctNovDec2022-12-262022-12-272022-12-282022-12-292022-12-302022-12-312023-01-01 1.0 km2023-01-022023-01-03 1.6 km2023-01-04 1.6 km2023-01-05 2.1 km2023-01-06 2.3 km2023-01-07 2.6 km2023-01-08 2.1 km2023-01-09 3.4 km2023-01-10 3.1 km2023-01-11 2.0 km2023-01-12 3.1 km2023-01-13 1.0 km2023-01-142023-01-152023-01-162023-01-172023-01-18 1.3 km2023-01-19 2.1 km2023-01-202023-01-212023-01-222023-01-232023-01-242023-01-252023-01-262023-01-272023-01-282023-01-292023-01-302023-01-312023-02-012023-02-02 1.0 km2023-02-032023-02-042023-02-052023-02-06 1.2 km2023-02-07 1.1 km2023-02-08 2.1 km2023-02-09 2.5 km2023-02-10 2.4 km2023-02-11 2.3 km2023-02-12 1.6 km2023-02-13 2.0 km2023-02-14 3.8 km2023-02-15 3.9 km2023-02-16 2.5 km2023-02-17 3.5 km2023-02-18 5.3 km2023-02-19 3.1 km2023-02-20 3.1 km2023-02-21 4.7 km2023-02-22 5.0 km2023-02-23 3.1 km2023-02-24 1.5 km2023-02-25 5.6 km2023-02-26 5.5 km2023-02-27 2.4 km2023-02-28 3.1 km2023-03-01 3.1 km2023-03-02 2.0 km2023-03-03 2.6 km2023-03-04 5.2 km2023-03-05 3.2 km2023-03-06 3.1 km2023-03-07 3.1 km2023-03-08 4.3 km2023-03-09 2.5 km2023-03-10 4.5 km2023-03-11 6.2 km2023-03-12 5.7 km2023-03-13 2.2 km2023-03-14 2.7 km2023-03-15 7.7 km2023-03-16 2.6 km2023-03-17 5.9 km2023-03-18 5.0 km2023-03-19 6.8 km2023-03-20 2.6 km2023-03-21 3.1 km2023-03-22 3.1 km2023-03-23 2.1 km2023-03-24 3.2 km2023-03-25 6.2 km2023-03-26 4.5 km2023-03-27 3.1 km2023-03-28 2.1 km2023-03-29 3.1 km2023-03-30 2.6 km2023-03-31 2.6 km2023-04-01 10.3 km2023-04-02 3.1 km2023-04-03 4.6 km2023-04-04 2.6 km2023-04-05 3.6 km2023-04-06 2.8 km2023-04-07 3.1 km2023-04-08 4.1 km2023-04-09 6.3 km2023-04-10 4.7 km2023-04-11 1.4 km2023-04-12 1.4 km2023-04-132023-04-14 3.1 km2023-04-152023-04-16 3.0 km2023-04-17 3.1 km2023-04-18 4.6 km2023-04-192023-04-20 2.3 km2023-04-21 2.6 km2023-04-22 2.3 km2023-04-232023-04-24 2.6 km2023-04-25 3.1 km2023-04-262023-04-27 2.1 km2023-04-28 2.4 km2023-04-29 2.0 km2023-04-30 3.1 km2023-05-012023-05-02 1.0 km2023-05-032023-05-042023-05-052023-05-062023-05-072023-05-08 2.3 km2023-05-092023-05-10 2.2 km2023-05-11 0.8 km2023-05-12 2.4 km2023-05-13 3.2 km2023-05-142023-05-15 2.7 km2023-05-16 2.0 km2023-05-17 2.6 km2023-05-18 4.6 km2023-05-19 2.1 km2023-05-202023-05-21 2.6 km2023-05-222023-05-23 1.2 km2023-05-24 2.5 km2023-05-25 2.5 km2023-05-26 2.6 km2023-05-272023-05-282023-05-29 2.2 km2023-05-30 3.2 km2023-05-31 3.6 km2023-06-01 5.3 km2023-06-02 3.1 km2023-06-03 4.1 km2023-06-04 5.1 km2023-06-05 4.1 km2023-06-062023-06-072023-06-082023-06-092023-06-102023-06-112023-06-122023-06-132023-06-14 0.6 km2023-06-15 0.6 km2023-06-16 1.0 km2023-06-172023-06-182023-06-19 1.0 km2023-06-202023-06-21 2.2 km2023-06-222023-06-232023-06-242023-06-252023-06-262023-06-27 1.1 km2023-06-28 1.3 km2023-06-29 1.1 km2023-06-30 2.2 km2023-07-012023-07-022023-07-03 4.9 km2023-07-04 2.9 km2023-07-052023-07-062023-07-07 2.7 km2023-07-08 4.5 km2023-07-092023-07-10 2.7 km2023-07-11 3.7 km2023-07-12 2.6 km2023-07-132023-07-142023-07-152023-07-162023-07-17 1.5 km2023-07-18 2.2 km2023-07-19 2.3 km2023-07-202023-07-212023-07-222023-07-23 1.3 km2023-07-242023-07-25 2.3 km2023-07-262023-07-27 2.8 km2023-07-282023-07-292023-07-302023-07-31 1.4 km2023-08-012023-08-022023-08-03 1.6 km2023-08-04 2.3 km2023-08-05 2.1 km2023-08-06 3.1 km2023-08-07 1.1 km2023-08-08 2.1 km2023-08-092023-08-102023-08-112023-08-12 1.2 km2023-08-132023-08-142023-08-152023-08-16 1.2 km2023-08-17 1.6 km2023-08-182023-08-192023-08-202023-08-21 1.0 km2023-08-22 2.5 km2023-08-232023-08-24 1.6 km2023-08-252023-08-262023-08-27 2.4 km2023-08-282023-08-292023-08-30 0.6 km2023-08-31 2.1 km2023-09-01 1.0 km2023-09-022023-09-03 3.0 km2023-09-04 7.8 km2023-09-05 2.5 km2023-09-06 7.1 km2023-09-07 8.5 km2023-09-08 3.9 km2023-09-09 4.1 km2023-09-10 3.3 km2023-09-11 2.7 km2023-09-12 3.1 km2023-09-13 3.2 km2023-09-14 3.1 km2023-09-15 3.4 km2023-09-16 5.2 km2023-09-17 2.2 km2023-09-18 4.6 km2023-09-19 3.8 km2023-09-20 3.5 km2023-09-212023-09-222023-09-232023-09-242023-09-252023-09-262023-09-272023-09-282023-09-292023-09-302023-10-012023-10-022023-10-032023-10-042023-10-052023-10-062023-10-072023-10-082023-10-092023-10-102023-10-112023-10-122023-10-132023-10-142023-10-152023-10-162023-10-172023-10-182023-10-192023-10-202023-10-212023-10-222023-10-232023-10-242023-10-252023-10-262023-10-272023-10-282023-10-292023-10-302023-10-312023-11-012023-11-022023-11-032023-11-042023-11-052023-11-062023-11-072023-11-082023-11-092023-11-102023-11-112023-11-122023-11-132023-11-142023-11-152023-11-162023-11-172023-11-182023-11-192023-11-202023-11-212023-11-222023-11-232023-11-242023-11-252023-11-262023-11-272023-11-282023-11-292023-11-302023-12-012023-12-022023-12-032023-12-042023-12-052023-12-062023-12-072023-12-082023-12-092023-12-102023-12-112023-12-122023-12-132023-12-142023-12-152023-12-162023-12-172023-12-182023-12-192023-12-202023-12-212023-12-222023-12-232023-12-242023-12-252023-12-262023-12-272023-12-282023-12-292023-12-302023-12-31 \ No newline at end of file +2023 RunningATHLETEyihong0618STATISTICSNumber: 280Weekly: 5.5Total: 695.4 kmAvg: 2.5 kmMin: 0.6 kmMax: 10.3 km2023695.4 kmJanFebMarAprMayJunJulAugSepOctNovDec2022-12-262022-12-272022-12-282022-12-292022-12-302022-12-312023-01-01 1.0 km2023-01-022023-01-03 1.6 km2023-01-04 1.6 km2023-01-05 2.1 km2023-01-06 2.3 km2023-01-07 2.6 km2023-01-08 2.1 km2023-01-09 3.4 km2023-01-10 3.1 km2023-01-11 2.0 km2023-01-12 3.1 km2023-01-13 1.0 km2023-01-142023-01-152023-01-162023-01-172023-01-18 1.3 km2023-01-19 2.1 km2023-01-202023-01-212023-01-222023-01-232023-01-242023-01-252023-01-262023-01-272023-01-282023-01-292023-01-302023-01-312023-02-012023-02-02 1.0 km2023-02-032023-02-042023-02-052023-02-06 1.2 km2023-02-07 1.1 km2023-02-08 2.1 km2023-02-09 2.5 km2023-02-10 2.4 km2023-02-11 2.3 km2023-02-12 1.6 km2023-02-13 2.0 km2023-02-14 3.8 km2023-02-15 3.9 km2023-02-16 2.5 km2023-02-17 3.5 km2023-02-18 5.3 km2023-02-19 3.1 km2023-02-20 3.1 km2023-02-21 4.7 km2023-02-22 5.0 km2023-02-23 3.1 km2023-02-24 1.5 km2023-02-25 5.6 km2023-02-26 5.5 km2023-02-27 2.4 km2023-02-28 3.1 km2023-03-01 3.1 km2023-03-02 2.0 km2023-03-03 2.6 km2023-03-04 5.2 km2023-03-05 3.2 km2023-03-06 3.1 km2023-03-07 3.1 km2023-03-08 4.3 km2023-03-09 2.5 km2023-03-10 4.5 km2023-03-11 6.2 km2023-03-12 5.7 km2023-03-13 2.2 km2023-03-14 2.7 km2023-03-15 7.7 km2023-03-16 2.6 km2023-03-17 5.9 km2023-03-18 5.0 km2023-03-19 6.8 km2023-03-20 2.6 km2023-03-21 3.1 km2023-03-22 3.1 km2023-03-23 2.1 km2023-03-24 3.2 km2023-03-25 6.2 km2023-03-26 4.5 km2023-03-27 3.1 km2023-03-28 2.1 km2023-03-29 3.1 km2023-03-30 2.6 km2023-03-31 2.6 km2023-04-01 10.3 km2023-04-02 3.1 km2023-04-03 4.6 km2023-04-04 2.6 km2023-04-05 3.6 km2023-04-06 2.8 km2023-04-07 3.1 km2023-04-08 4.1 km2023-04-09 6.3 km2023-04-10 4.7 km2023-04-11 1.4 km2023-04-12 1.4 km2023-04-132023-04-14 3.1 km2023-04-152023-04-16 3.0 km2023-04-17 3.1 km2023-04-18 4.6 km2023-04-192023-04-20 2.3 km2023-04-21 2.6 km2023-04-22 2.3 km2023-04-232023-04-24 2.6 km2023-04-25 3.1 km2023-04-262023-04-27 2.1 km2023-04-28 2.4 km2023-04-29 2.0 km2023-04-30 3.1 km2023-05-012023-05-02 1.0 km2023-05-032023-05-042023-05-052023-05-062023-05-072023-05-08 2.3 km2023-05-092023-05-10 2.2 km2023-05-11 0.8 km2023-05-12 2.4 km2023-05-13 3.2 km2023-05-142023-05-15 2.7 km2023-05-16 2.0 km2023-05-17 2.6 km2023-05-18 4.6 km2023-05-19 2.1 km2023-05-202023-05-21 2.6 km2023-05-222023-05-23 1.2 km2023-05-24 2.5 km2023-05-25 2.5 km2023-05-26 2.6 km2023-05-272023-05-282023-05-29 2.2 km2023-05-30 3.2 km2023-05-31 3.6 km2023-06-01 5.3 km2023-06-02 3.1 km2023-06-03 4.1 km2023-06-04 5.1 km2023-06-05 4.1 km2023-06-062023-06-072023-06-082023-06-092023-06-102023-06-112023-06-122023-06-132023-06-14 0.6 km2023-06-15 0.6 km2023-06-16 1.0 km2023-06-172023-06-182023-06-19 1.0 km2023-06-202023-06-21 2.2 km2023-06-222023-06-232023-06-242023-06-252023-06-262023-06-27 1.1 km2023-06-28 1.3 km2023-06-29 1.1 km2023-06-30 2.2 km2023-07-012023-07-022023-07-03 4.9 km2023-07-04 2.9 km2023-07-052023-07-062023-07-07 2.7 km2023-07-08 4.5 km2023-07-092023-07-10 2.7 km2023-07-11 3.7 km2023-07-12 2.6 km2023-07-132023-07-142023-07-152023-07-162023-07-17 1.5 km2023-07-18 2.2 km2023-07-19 2.3 km2023-07-202023-07-212023-07-222023-07-23 1.3 km2023-07-242023-07-25 2.3 km2023-07-262023-07-27 2.8 km2023-07-282023-07-292023-07-302023-07-31 1.4 km2023-08-012023-08-022023-08-03 1.6 km2023-08-04 2.3 km2023-08-05 2.1 km2023-08-06 3.1 km2023-08-07 1.1 km2023-08-08 2.1 km2023-08-092023-08-102023-08-112023-08-12 1.2 km2023-08-132023-08-142023-08-152023-08-16 1.2 km2023-08-17 1.6 km2023-08-182023-08-192023-08-202023-08-21 1.0 km2023-08-22 2.5 km2023-08-232023-08-24 1.6 km2023-08-252023-08-262023-08-27 2.4 km2023-08-282023-08-292023-08-30 0.6 km2023-08-31 2.1 km2023-09-01 1.0 km2023-09-022023-09-03 3.0 km2023-09-04 7.8 km2023-09-05 2.5 km2023-09-06 7.1 km2023-09-07 8.5 km2023-09-08 3.9 km2023-09-09 4.1 km2023-09-10 3.3 km2023-09-11 2.7 km2023-09-12 3.1 km2023-09-13 3.2 km2023-09-14 3.1 km2023-09-15 3.4 km2023-09-16 5.2 km2023-09-17 2.2 km2023-09-18 4.6 km2023-09-19 3.8 km2023-09-20 3.5 km2023-09-21 4.1 km2023-09-22 2.6 km2023-09-23 4.1 km2023-09-24 4.2 km2023-09-25 3.1 km2023-09-26 2.1 km2023-09-27 1.1 km2023-09-28 5.4 km2023-09-29 4.2 km2023-09-30 6.1 km2023-10-01 2.1 km2023-10-02 4.1 km2023-10-03 5.2 km2023-10-04 1.0 km2023-10-05 3.1 km2023-10-06 1.5 km2023-10-072023-10-082023-10-092023-10-10 1.6 km2023-10-11 5.1 km2023-10-12 1.3 km2023-10-13 2.7 km2023-10-14 3.2 km2023-10-15 4.1 km2023-10-162023-10-17 2.1 km2023-10-18 1.0 km2023-10-19 2.3 km2023-10-202023-10-21 2.1 km2023-10-22 3.1 km2023-10-232023-10-24 2.3 km2023-10-25 2.3 km2023-10-262023-10-27 1.2 km2023-10-28 3.1 km2023-10-29 1.4 km2023-10-30 2.5 km2023-10-31 3.1 km2023-11-012023-11-022023-11-03 2.4 km2023-11-04 3.2 km2023-11-052023-11-062023-11-07 2.2 km2023-11-08 1.0 km2023-11-09 2.1 km2023-11-102023-11-112023-11-12 2.1 km2023-11-13 3.2 km2023-11-14 2.3 km2023-11-15 3.1 km2023-11-16 3.7 km2023-11-17 1.2 km2023-11-182023-11-19 2.0 km2023-11-202023-11-21 1.4 km2023-11-22 2.1 km2023-11-23 2.1 km2023-11-242023-11-252023-11-26 1.1 km2023-11-27 2.1 km2023-11-28 2.7 km2023-11-29 1.2 km2023-11-30 2.1 km2023-12-01 1.2 km2023-12-02 2.6 km2023-12-032023-12-04 1.0 km2023-12-05 3.7 km2023-12-06 2.6 km2023-12-07 2.7 km2023-12-08 2.6 km2023-12-09 2.8 km2023-12-102023-12-112023-12-12 1.6 km2023-12-13 2.6 km2023-12-142023-12-152023-12-162023-12-17 1.3 km2023-12-18 1.2 km2023-12-19 2.1 km2023-12-202023-12-212023-12-222023-12-232023-12-242023-12-25 0.7 km2023-12-262023-12-27 1.3 km2023-12-282023-12-29 1.6 km2023-12-302023-12-31 \ No newline at end of file diff --git a/assets/github_2024.svg b/assets/github_2024.svg new file mode 100644 index 00000000000..441af32ea60 --- /dev/null +++ b/assets/github_2024.svg @@ -0,0 +1,2 @@ + +2024 RunningATHLETEyihong0618STATISTICSNumber: 18Weekly: 9.0Total: 45.0 kmAvg: 2.5 kmMin: 1.2 kmMax: 5.3 km202445.0 kmJanFebMarAprMayJunJulAugSepOctNovDec2024-01-01 2.4 km2024-01-02 3.8 km2024-01-03 3.3 km2024-01-04 4.7 km2024-01-05 2.9 km2024-01-06 3.1 km2024-01-07 2.8 km2024-01-08 3.3 km2024-01-09 3.7 km2024-01-10 3.9 km2024-01-11 2.5 km2024-01-12 3.3 km2024-01-13 5.3 km2024-01-142024-01-152024-01-162024-01-172024-01-182024-01-192024-01-202024-01-212024-01-222024-01-232024-01-242024-01-252024-01-262024-01-272024-01-282024-01-292024-01-302024-01-312024-02-012024-02-022024-02-032024-02-042024-02-052024-02-062024-02-072024-02-082024-02-092024-02-102024-02-112024-02-122024-02-132024-02-142024-02-152024-02-162024-02-172024-02-182024-02-192024-02-202024-02-212024-02-222024-02-232024-02-242024-02-252024-02-262024-02-272024-02-282024-02-292024-03-012024-03-022024-03-032024-03-042024-03-052024-03-062024-03-072024-03-082024-03-092024-03-102024-03-112024-03-122024-03-132024-03-142024-03-152024-03-162024-03-172024-03-182024-03-192024-03-202024-03-212024-03-222024-03-232024-03-242024-03-252024-03-262024-03-272024-03-282024-03-292024-03-302024-03-312024-04-012024-04-022024-04-032024-04-042024-04-052024-04-062024-04-072024-04-082024-04-092024-04-102024-04-112024-04-122024-04-132024-04-142024-04-152024-04-162024-04-172024-04-182024-04-192024-04-202024-04-212024-04-222024-04-232024-04-242024-04-252024-04-262024-04-272024-04-282024-04-292024-04-302024-05-012024-05-022024-05-032024-05-042024-05-052024-05-062024-05-072024-05-082024-05-092024-05-102024-05-112024-05-122024-05-132024-05-142024-05-152024-05-162024-05-172024-05-182024-05-192024-05-202024-05-212024-05-222024-05-232024-05-242024-05-252024-05-262024-05-272024-05-282024-05-292024-05-302024-05-312024-06-012024-06-022024-06-032024-06-042024-06-052024-06-062024-06-072024-06-082024-06-092024-06-102024-06-112024-06-122024-06-132024-06-142024-06-152024-06-162024-06-172024-06-182024-06-192024-06-202024-06-212024-06-222024-06-232024-06-242024-06-252024-06-262024-06-272024-06-282024-06-292024-06-302024-07-012024-07-022024-07-032024-07-042024-07-052024-07-062024-07-072024-07-082024-07-092024-07-102024-07-112024-07-122024-07-132024-07-142024-07-152024-07-162024-07-172024-07-182024-07-192024-07-202024-07-212024-07-222024-07-232024-07-242024-07-252024-07-262024-07-272024-07-282024-07-292024-07-302024-07-312024-08-012024-08-022024-08-032024-08-042024-08-052024-08-062024-08-072024-08-082024-08-092024-08-102024-08-112024-08-122024-08-132024-08-142024-08-152024-08-162024-08-172024-08-182024-08-192024-08-202024-08-212024-08-222024-08-232024-08-242024-08-252024-08-262024-08-272024-08-282024-08-292024-08-302024-08-312024-09-012024-09-022024-09-032024-09-042024-09-052024-09-062024-09-072024-09-082024-09-092024-09-102024-09-112024-09-122024-09-132024-09-142024-09-152024-09-162024-09-172024-09-182024-09-192024-09-202024-09-212024-09-222024-09-232024-09-242024-09-252024-09-262024-09-272024-09-282024-09-292024-09-302024-10-012024-10-022024-10-032024-10-042024-10-052024-10-062024-10-072024-10-082024-10-092024-10-102024-10-112024-10-122024-10-132024-10-142024-10-152024-10-162024-10-172024-10-182024-10-192024-10-202024-10-212024-10-222024-10-232024-10-242024-10-252024-10-262024-10-272024-10-282024-10-292024-10-302024-10-312024-11-012024-11-022024-11-032024-11-042024-11-052024-11-062024-11-072024-11-082024-11-092024-11-102024-11-112024-11-122024-11-132024-11-142024-11-152024-11-162024-11-172024-11-182024-11-192024-11-202024-11-212024-11-222024-11-232024-11-242024-11-252024-11-262024-11-272024-11-282024-11-292024-11-302024-12-012024-12-022024-12-032024-12-042024-12-052024-12-062024-12-072024-12-082024-12-092024-12-102024-12-112024-12-122024-12-132024-12-142024-12-152024-12-162024-12-172024-12-182024-12-192024-12-202024-12-212024-12-222024-12-232024-12-242024-12-252024-12-262024-12-272024-12-282024-12-292024-12-302024-12-31 \ No newline at end of file diff --git a/assets/grid.svg b/assets/grid.svg index b174126fd94..00bffe9777f 100644 --- a/assets/grid.svg +++ b/assets/grid.svg @@ -1,2 +1,2 @@ -Over 10km RunsATHLETEyihong0618STATISTICSNumber: 167Weekly: 1.2Total: 2212.4 kmAvg: 13.2 kmMin: 10.0 kmMax: 43.5 km2023-04-01 10.3km2022-02-23 15.5km2022-01-30 11.3km2022-01-25 10.4km2021-09-30 10.4km2021-09-12 15.2km2021-09-05 11.3km2021-06-27 10.9km2021-05-29 10.3km2021-04-18 17.8km2021-04-08 10.3km2021-03-31 10.2km2021-03-07 10.2km2021-02-13 19.2km2021-02-08 12.4km2021-01-29 10.1km2021-01-24 10.2km2021-01-22 10.8km2021-01-16 11.2km2021-01-10 11.4km2021-01-03 10.3km2020-12-20 10.3km2020-12-05 10.2km2020-10-31 11.2km2020-09-20 20.2km2020-09-17 10.4km2020-09-16 10.3km2020-09-13 10.2km2020-09-01 10.2km2020-08-16 20.7km2020-08-01 10.2km2020-07-27 10.1km2020-07-25 10.9km2020-07-19 14.2km2020-07-17 10.1km2020-07-04 15.0km2020-06-27 13.1km2020-06-18 10.0km2020-06-12 12.0km2020-06-06 10.0km2020-01-05 10.1km2020-01-01 10.0km2019-12-01 10.0km2019-11-12 10.0km2019-11-03 43.5km2019-10-26 20.4km2019-10-19 24.0km2019-10-13 21.5km2019-10-06 18.6km2019-10-03 10.0km2019-09-28 20.0km2019-09-22 12.8km2019-09-15 15.0km2019-09-13 12.0km2019-09-07 12.1km2019-08-31 16.0km2019-08-24 10.3km2019-08-19 10.0km2019-08-18 10.0km2019-08-08 10.2km2019-08-03 15.4km2019-08-01 10.0km2019-07-25 10.0km2019-07-20 10.0km2019-07-18 10.0km2019-07-14 10.0km2019-07-11 15.0km2019-07-06 11.0km2019-07-05 10.0km2019-06-30 10.0km2019-06-27 12.0km2019-06-23 11.5km2019-06-20 12.0km2019-06-15 10.0km2019-06-13 10.0km2018-02-11 10.6km2018-02-03 12.0km2018-01-28 10.0km2018-01-20 10.0km2017-05-13 42.8km2017-05-07 20.1km2017-05-01 10.2km2017-04-26 10.3km2017-04-23 28.3km2017-04-04 10.0km2017-04-02 20.8km2017-03-30 10.0km2017-03-28 10.0km2017-03-26 10.0km2017-03-19 17.8km2017-03-04 10.0km2017-02-25 10.0km2017-02-18 17.1km2017-01-31 10.0km2017-01-21 15.8km2017-01-14 12.1km2017-01-07 12.3km2016-12-31 18.0km2016-12-24 16.0km2016-12-17 10.0km2016-12-10 15.0km2016-12-03 12.0km2016-11-26 12.0km2016-11-19 10.0km2016-11-12 10.0km2016-06-01 11.6km2016-05-22 21.5km2016-05-15 20.0km2016-05-09 10.4km2016-05-05 14.3km2016-04-30 16.1km2016-04-24 15.0km2016-04-22 10.0km2016-04-13 12.0km2016-04-09 10.0km2015-07-26 15.5km2015-07-19 17.2km2015-07-12 15.0km2015-07-05 14.0km2015-06-28 12.0km2015-04-30 11.8km2015-02-11 11.1km2015-01-21 12.4km2014-08-12 10.3km2014-08-06 14.6km2014-06-20 10.0km2014-05-23 10.0km2014-05-10 41.4km2014-05-05 12.0km2014-04-26 20.5km2014-04-21 10.0km2014-03-31 11.1km2014-03-25 20.1km2014-03-22 10.0km2014-03-17 13.6km2014-03-13 15.1km2014-03-11 11.2km2014-03-07 12.3km2014-02-28 11.3km2014-02-19 12.0km2014-02-15 10.2km2013-11-13 12.3km2013-11-04 10.1km2013-06-22 14.3km2013-05-14 10.0km2013-05-04 17.0km2013-04-21 21.5km2013-04-09 21.0km2013-04-07 10.0km2013-03-31 16.1km2013-03-27 10.0km2013-03-21 17.0km2013-03-14 14.5km2013-03-06 12.0km2013-02-09 10.0km2013-01-27 10.0km2013-01-22 10.2km2013-01-09 14.8km2012-12-26 10.0km2012-12-23 12.0km2012-12-20 10.1km2012-12-16 10.1km2012-11-29 15.0km2012-11-23 12.2km2012-11-18 10.0km2012-11-15 10.0km2012-11-05 10.1km \ No newline at end of file +Over 10km RunsATHLETEyihong0618STATISTICSNumber: 167Weekly: 1.2Total: 2212.4 kmAvg: 13.2 kmMin: 10.0 kmMax: 43.5 km2023-04-01 10.3km88160222392022-02-23 15.5km67247814222022-01-30 11.3km66042892792022-01-25 10.4km65777250522021-09-30 10.4km60426206722021-09-12 15.2km59483874312021-09-05 11.3km59100133292021-06-27 10.9km55377556922021-05-29 10.3km53762023052021-04-18 17.8km51452341382021-04-08 10.3km50873950122021-03-31 10.2km50406458612021-03-07 10.2km49029196472021-02-13 19.2km47797018652021-02-08 12.4km47548476522021-01-29 10.1km46998036952021-01-24 10.2km46728002092021-01-22 10.8km46621047542021-01-16 11.2km46285861232021-01-10 11.4km45976149262021-01-03 10.3km45602462452020-12-20 10.3km44949458012020-12-05 10.2km44322006092020-10-31 11.2km42656378892020-09-20 20.2km40867035832020-09-17 10.4km40731574142020-09-16 10.3km40681671582020-09-13 10.2km40513862912020-09-01 10.2km39957815362020-08-16 20.7km39214379702020-08-01 10.2km38476885012020-07-27 10.1km38201317282020-07-25 10.9km38112443422020-07-19 14.2km37847770902020-07-17 10.1km37755971082020-07-04 15.0km37111597832020-06-27 13.1km36780700082020-06-18 10.0km36332762152020-06-12 12.0km36027260442020-06-06 10.0km35714504532020-01-05 10.1km35078793842020-01-01 10.0km35078795542019-12-01 10.0km35078805472019-11-12 10.0km35078813022019-11-03 43.5km35078822082019-10-26 20.4km35078822642019-10-19 24.0km35078827242019-10-13 21.5km35078829702019-10-06 18.6km35078832332019-10-03 10.0km35078832202019-09-28 20.0km35078835662019-09-22 12.8km35078838752019-09-15 15.0km35078841812019-09-13 12.0km35078842862019-09-07 12.1km35078845522019-08-31 16.0km35078847592019-08-24 10.3km35078850792019-08-19 10.0km35078852992019-08-18 10.0km35078853312019-08-08 10.2km35078927612019-08-03 15.4km35078933402019-08-01 10.0km35078933542019-07-25 10.0km35078997322019-07-20 10.0km35079001192019-07-18 10.0km35079003342019-07-14 10.0km35079005092019-07-11 15.0km35079008742019-07-06 11.0km35079011492019-07-05 10.0km37223362232019-06-30 10.0km35079050922019-06-27 12.0km35079057942019-06-23 11.5km35079060342019-06-20 12.0km35079062022019-06-15 10.0km35079065322019-06-13 10.0km35079064762018-02-11 10.6km35079328852018-02-03 12.0km35079333412018-01-28 10.0km35079335642018-01-20 10.0km35079338722017-05-13 42.8km35079368702017-05-07 20.1km35079367792017-05-01 10.2km35079369352017-04-26 10.3km35079371372017-04-23 28.3km35079372832017-04-04 10.0km35079380552017-04-02 20.8km35079383492017-03-30 10.0km35079383912017-03-28 10.0km35079385612017-03-26 10.0km35079386132017-03-19 17.8km35079389932017-03-04 10.0km35079395622017-02-25 10.0km35079399632017-02-18 17.1km35079402262017-01-31 10.0km35079410862017-01-21 15.8km35079415602017-01-14 12.1km35079419702017-01-07 12.3km35079422542016-12-31 18.0km35079425592016-12-24 16.0km35079431192016-12-17 10.0km35079438232016-12-10 15.0km35079445682016-12-03 12.0km36909247152016-11-26 12.0km36909248762016-11-19 10.0km36909249612016-11-12 10.0km36909250872016-06-01 11.6km36909263302016-05-22 21.5km36909265472016-05-15 20.0km36909265752016-05-09 10.4km36909266672016-05-05 14.3km36909267622016-04-30 16.1km36909269132016-04-24 15.0km36909271312016-04-22 10.0km36909271572016-04-13 12.0km36909274622016-04-09 10.0km36909276172015-07-26 15.5km36909289762015-07-19 17.2km36909290582015-07-12 15.0km36909291612015-07-05 14.0km36909292182015-06-28 12.0km36909292722015-04-30 11.8km36909299962015-02-11 11.1km36909304532015-01-21 12.4km36909305612014-08-12 10.3km36909309342014-08-06 14.6km36909310222014-06-20 10.0km36909315342014-05-23 10.0km36909318312014-05-10 41.4km36909321482014-05-05 12.0km36909320582014-04-26 20.5km36909322142014-04-21 10.0km36909322042014-03-31 11.1km36909324142014-03-25 20.1km36909325532014-03-22 10.0km36909325612014-03-17 13.6km36909326812014-03-13 15.1km36909327012014-03-11 11.2km36909327732014-03-07 12.3km36909329422014-02-28 11.3km36909330492014-02-19 12.0km36909332472014-02-15 10.2km36909334072013-11-13 12.3km36909338852013-11-04 10.1km36909339222013-06-22 14.3km36909342432013-05-14 10.0km36909346542013-05-04 17.0km36909348322013-04-21 21.5km36909349842013-04-09 21.0km36909350322013-04-07 10.0km36909350402013-03-31 16.1km36909351762013-03-27 10.0km36909352552013-03-21 17.0km36909354232013-03-14 14.5km36909355262013-03-06 12.0km36909356762013-02-09 10.0km36909360302013-01-27 10.0km36909361862013-01-22 10.2km36909362872013-01-09 14.8km36909364432012-12-26 10.0km36909365772012-12-23 12.0km36909366482012-12-20 10.1km36909367032012-12-16 10.1km36909367602012-11-29 15.0km36909370752012-11-23 12.2km36909371392012-11-18 10.0km36909372262012-11-15 10.0km36909372892012-11-05 10.1km3690937458 \ No newline at end of file diff --git a/assets/index.tsx b/assets/index.tsx index 03ce70dcee2..8efea083b54 100644 --- a/assets/index.tsx +++ b/assets/index.tsx @@ -1 +1,2 @@ export const yearStats = import.meta.glob('./year_*.svg', { import: 'ReactComponent' }) +export const totalStat = import.meta.glob(['./github.svg', './grid.svg'], { import: 'ReactComponent' }) diff --git a/assets/year_2023.svg b/assets/year_2023.svg index 61ab3912fcb..2b2fbbd367b 100644 --- a/assets/year_2023.svg +++ b/assets/year_2023.svg @@ -1,2 +1,2 @@ -2023JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember \ No newline at end of file +2023JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember \ No newline at end of file diff --git a/assets/year_2024.svg b/assets/year_2024.svg new file mode 100644 index 00000000000..18ca7218da7 --- /dev/null +++ b/assets/year_2024.svg @@ -0,0 +1,2 @@ + +2024JanuaryFebruaryMarchAprilMayJuneJulyAugustSeptemberOctoberNovemberDecember \ No newline at end of file diff --git a/package.json b/package.json index d32ec67a5c9..f49ea588920 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "dependencies": { "@mapbox/mapbox-gl-language": "^1.0.0", "@mapbox/polyline": "^1.1.1", + "@surbowl/world-geo-json-zh": "^2.1.3", + "@svgr/plugin-svgo": "^8.1.0", "@vercel/analytics": "^0.1.6", "@vitejs/plugin-react": "^4.0.0", "gcoord": "^0.3.2", @@ -14,13 +16,10 @@ "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-ga4": "^2.1.0", "react-helmet-async": "^1.3.0", "react-map-gl": "^7.1.6", "react-router-dom": "^6.15.0", - "sass": "^1.52.3", - "sass-mq": "^6.0.0", - "tachyons": "^4.12.0", - "tachyons-sass": "git+https://github.com/tachyons-css/tachyons-sass.git", "viewport-mercator-project": "^7.0.4", "vite": "^4.3.9", "vite-plugin-svgr": "^3.2.0", @@ -28,6 +27,7 @@ }, "license": "MIT", "private": true, + "type": "module", "scripts": { "data:clean": "rm run_page/data.db {GPX,TCX,FIT}_OUT/* activities/* src/static/activities.json", "data:download:garmin": "python3 run_page/garmin_sync.py", @@ -36,7 +36,7 @@ "develop": "vite dev", "serve": "vite serve", "lint": "eslint --ext .ts,.tsx src --fix", - "check": "prettier --write src/main.tsx src/**/*.{scss,json,ts,tsx} *.{md,yaml,json,ts,js}", + "check": "prettier --write src/main.tsx src/**/*.{css,json,ts,tsx} *.{md,yaml,json,ts,js}", "ci": "pnpm run check && pnpm run lint && pnpm run build" }, "engineStrict": true, @@ -49,17 +49,22 @@ "url": "https://github.com/yihong0618/running_page" }, "devDependencies": { + "@types/geojson": "^7946.0.14", "@types/mapbox__polyline": "^1.0.2", "@types/node": "^20.3.3", + "@types/prop-types": "^15.7.11", "@types/react": "^18.2.14", "@types/react-dom": "^18.2.6", "@typescript-eslint/parser": "^5.59.2", + "autoprefixer": "^10.4.19", "eslint": "^8.17.0", "eslint-config-prettier": "^8.8.0", "eslint-plugin-prettier": "^4.2.1", "eslint-plugin-react": "^7.32.2", - "prettier": "2.8.8", + "postcss": "^8.4.38", + "prettier": "^3.2.5", + "prettier-plugin-tailwindcss": "^0.5.13", + "tailwindcss": "^3.4.3", "typescript": "^5.1.6" - }, - "packageManager": "pnpm@8.7.0" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f85f54cedd8..597f6795685 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,12 @@ dependencies: '@mapbox/polyline': specifier: ^1.1.1 version: 1.2.0 + '@surbowl/world-geo-json-zh': + specifier: ^2.1.3 + version: 2.1.3 + '@svgr/plugin-svgo': + specifier: ^8.1.0 + version: 8.1.0(@svgr/core@8.1.0) '@vercel/analytics': specifier: ^0.1.6 version: 0.1.11(react@18.2.0) @@ -35,6 +41,9 @@ dependencies: react-dom: specifier: ^18.2.0 version: 18.2.0(react@18.2.0) + react-ga4: + specifier: ^2.1.0 + version: 2.1.0 react-helmet-async: specifier: ^1.3.0 version: 1.3.0(react-dom@18.2.0)(react@18.2.0) @@ -44,24 +53,12 @@ dependencies: react-router-dom: specifier: ^6.15.0 version: 6.15.0(react-dom@18.2.0)(react@18.2.0) - sass: - specifier: ^1.52.3 - version: 1.66.1 - sass-mq: - specifier: ^6.0.0 - version: 6.0.0 - tachyons: - specifier: ^4.12.0 - version: 4.12.0 - tachyons-sass: - specifier: git+https://github.com/tachyons-css/tachyons-sass.git - version: github.com/tachyons-css/tachyons-sass/2dce89b83729bddb9f4498c7d6f82b73d60d6538 viewport-mercator-project: specifier: ^7.0.4 version: 7.0.4 vite: specifier: ^4.3.9 - version: 4.4.9(@types/node@20.5.7)(sass@1.66.1) + version: 4.4.9(@types/node@20.5.7) vite-plugin-svgr: specifier: ^3.2.0 version: 3.2.0(vite@4.4.9) @@ -70,12 +67,18 @@ dependencies: version: 4.2.0(typescript@5.2.2)(vite@4.4.9) devDependencies: + '@types/geojson': + specifier: ^7946.0.14 + version: 7946.0.14 '@types/mapbox__polyline': specifier: ^1.0.2 version: 1.0.2 '@types/node': specifier: ^20.3.3 version: 20.5.7 + '@types/prop-types': + specifier: ^15.7.11 + version: 15.7.11 '@types/react': specifier: ^18.2.14 version: 18.2.21 @@ -85,6 +88,9 @@ devDependencies: '@typescript-eslint/parser': specifier: ^5.59.2 version: 5.62.0(eslint@8.48.0)(typescript@5.2.2) + autoprefixer: + specifier: ^10.4.19 + version: 10.4.19(postcss@8.4.38) eslint: specifier: ^8.17.0 version: 8.48.0 @@ -93,13 +99,22 @@ devDependencies: version: 8.10.0(eslint@8.48.0) eslint-plugin-prettier: specifier: ^4.2.1 - version: 4.2.1(eslint-config-prettier@8.10.0)(eslint@8.48.0)(prettier@2.8.8) + version: 4.2.1(eslint-config-prettier@8.10.0)(eslint@8.48.0)(prettier@3.2.5) eslint-plugin-react: specifier: ^7.32.2 version: 7.33.2(eslint@8.48.0) + postcss: + specifier: ^8.4.38 + version: 8.4.38 prettier: - specifier: 2.8.8 - version: 2.8.8 + specifier: ^3.2.5 + version: 3.2.5 + prettier-plugin-tailwindcss: + specifier: ^0.5.13 + version: 0.5.13(prettier@3.2.5) + tailwindcss: + specifier: ^3.4.3 + version: 3.4.3 typescript: specifier: ^5.1.6 version: 5.2.2 @@ -111,6 +126,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /@alloc/quick-lru@5.2.0: + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + dev: true + /@ampproject/remapping@2.2.1: resolution: {integrity: sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==} engines: {node: '>=6.0.0'} @@ -119,6 +139,14 @@ packages: '@jridgewell/trace-mapping': 0.3.19 dev: false + /@ampproject/remapping@2.3.0: + resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + dev: false + /@babel/code-frame@7.22.13: resolution: {integrity: sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==} engines: {node: '>=6.9.0'} @@ -127,11 +155,24 @@ packages: chalk: 2.4.2 dev: false + /@babel/code-frame@7.24.2: + resolution: {integrity: sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.24.2 + picocolors: 1.0.0 + dev: false + /@babel/compat-data@7.22.9: resolution: {integrity: sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==} engines: {node: '>=6.9.0'} dev: false + /@babel/compat-data@7.24.4: + resolution: {integrity: sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/core@7.22.11: resolution: {integrity: sha512-lh7RJrtPdhibbxndr6/xx0w8+CVlY5FJZiaSz908Fpy+G0xkBFTvwLcKJFF4PJxVfGhVWNebikpWGnOoC71juQ==} engines: {node: '>=6.9.0'} @@ -155,6 +196,29 @@ packages: - supports-color dev: false + /@babel/core@7.24.4: + resolution: {integrity: sha512-MBVlMXP+kkl5394RBLSxxk/iLTeVGuXTV3cIDXavPpMMqnSnt6apKgan/U8O3USWZCWZT/TbgfEpKa4uMgN4Dg==} + engines: {node: '>=6.9.0'} + dependencies: + '@ampproject/remapping': 2.3.0 + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.4 + '@babel/helper-compilation-targets': 7.23.6 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.24.4) + '@babel/helpers': 7.24.4 + '@babel/parser': 7.24.4 + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.1 + '@babel/types': 7.24.0 + convert-source-map: 2.0.0 + debug: 4.3.4 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + dev: false + /@babel/generator@7.22.10: resolution: {integrity: sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==} engines: {node: '>=6.9.0'} @@ -165,6 +229,16 @@ packages: jsesc: 2.5.2 dev: false + /@babel/generator@7.24.4: + resolution: {integrity: sha512-Xd6+v6SnjWVx/nus+y0l1sxMOTOMBkyL4+BIdbALyatQnAe/SRVjANeDPSCYaX+i1iJmuGSKf3Z+E+V/va1Hvw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.0 + '@jridgewell/gen-mapping': 0.3.5 + '@jridgewell/trace-mapping': 0.3.25 + jsesc: 2.5.2 + dev: false + /@babel/helper-compilation-targets@7.22.10: resolution: {integrity: sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==} engines: {node: '>=6.9.0'} @@ -176,6 +250,22 @@ packages: semver: 6.3.1 dev: false + /@babel/helper-compilation-targets@7.23.6: + resolution: {integrity: sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/compat-data': 7.24.4 + '@babel/helper-validator-option': 7.23.5 + browserslist: 4.23.0 + lru-cache: 5.1.1 + semver: 6.3.1 + dev: false + + /@babel/helper-environment-visitor@7.22.20: + resolution: {integrity: sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/helper-environment-visitor@7.22.5: resolution: {integrity: sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==} engines: {node: '>=6.9.0'} @@ -189,6 +279,14 @@ packages: '@babel/types': 7.22.11 dev: false + /@babel/helper-function-name@7.23.0: + resolution: {integrity: sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.24.0 + '@babel/types': 7.24.0 + dev: false + /@babel/helper-hoist-variables@7.22.5: resolution: {integrity: sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==} engines: {node: '>=6.9.0'} @@ -203,6 +301,13 @@ packages: '@babel/types': 7.22.11 dev: false + /@babel/helper-module-imports@7.24.3: + resolution: {integrity: sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.24.0 + dev: false + /@babel/helper-module-transforms@7.22.9(@babel/core@7.22.11): resolution: {integrity: sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==} engines: {node: '>=6.9.0'} @@ -217,6 +322,20 @@ packages: '@babel/helper-validator-identifier': 7.22.5 dev: false + /@babel/helper-module-transforms@7.23.3(@babel/core@7.24.4): + resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + dependencies: + '@babel/core': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-module-imports': 7.24.3 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/helper-validator-identifier': 7.22.20 + dev: false + /@babel/helper-plugin-utils@7.22.5: resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} engines: {node: '>=6.9.0'} @@ -241,6 +360,16 @@ packages: engines: {node: '>=6.9.0'} dev: false + /@babel/helper-string-parser@7.24.1: + resolution: {integrity: sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-identifier@7.22.20: + resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/helper-validator-identifier@7.22.5: resolution: {integrity: sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==} engines: {node: '>=6.9.0'} @@ -251,6 +380,11 @@ packages: engines: {node: '>=6.9.0'} dev: false + /@babel/helper-validator-option@7.23.5: + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + dev: false + /@babel/helpers@7.22.11: resolution: {integrity: sha512-vyOXC8PBWaGc5h7GMsNx68OH33cypkEDJCHvYVVgVbbxJDROYVtexSk0gK5iCF1xNjRIN2s8ai7hwkWDq5szWg==} engines: {node: '>=6.9.0'} @@ -262,6 +396,17 @@ packages: - supports-color dev: false + /@babel/helpers@7.24.4: + resolution: {integrity: sha512-FewdlZbSiwaVGlgT1DPANDuCHaDMiOo+D/IDYRFYjHOuv66xMSJ7fQwwODwRNAPkADIO/z1EoF/l2BCWlWABDw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/template': 7.24.0 + '@babel/traverse': 7.24.1 + '@babel/types': 7.24.0 + transitivePeerDependencies: + - supports-color + dev: false + /@babel/highlight@7.22.13: resolution: {integrity: sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==} engines: {node: '>=6.9.0'} @@ -271,6 +416,16 @@ packages: js-tokens: 4.0.0 dev: false + /@babel/highlight@7.24.2: + resolution: {integrity: sha512-Yac1ao4flkTxTteCDZLEvdxg2fZfz1v8M4QpaGypq/WPDqg3ijHYbDfs+LG5hvzSoqaSZ9/Z9lKSP3CjZjv+pA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.22.20 + chalk: 2.4.2 + js-tokens: 4.0.0 + picocolors: 1.0.0 + dev: false + /@babel/parser@7.22.13: resolution: {integrity: sha512-3l6+4YOvc9wx7VlCSw4yQfcBo01ECA8TicQfbnCPuCEpRQrf+gTUyGdxNw+pyTUyywp6JRD1w0YQs9TpBXYlkw==} engines: {node: '>=6.0.0'} @@ -279,23 +434,31 @@ packages: '@babel/types': 7.22.11 dev: false - /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.22.11): + /@babel/parser@7.24.4: + resolution: {integrity: sha512-zTvEBcghmeBma9QIGunWevvBAp4/Qu9Bdq+2k0Ot4fVMD6v3dsC9WOcRSKk7tRRyBM/53yKMJko9xOatGQAwSg==} + engines: {node: '>=6.0.0'} + hasBin: true + dependencies: + '@babel/types': 7.24.0 + dev: false + + /@babel/plugin-transform-react-jsx-self@7.22.5(@babel/core@7.24.4): resolution: {integrity: sha512-nTh2ogNUtxbiSbxaT4Ds6aXnXEipHweN9YRgOX/oNXdf0cCrGn/+2LozFa3lnPV5D90MkjhgckCPBrsoSc1a7g==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.11 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.22.5 dev: false - /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.22.11): + /@babel/plugin-transform-react-jsx-source@7.22.5(@babel/core@7.24.4): resolution: {integrity: sha512-yIiRO6yobeEIaI0RTbIr8iAK9FcBHLtZq0S89ZPjDLQXBA4xvghaKqI0etp/tF3htTM0sazJKKLz9oEiGRtu7w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 dependencies: - '@babel/core': 7.22.11 + '@babel/core': 7.24.4 '@babel/helper-plugin-utils': 7.22.5 dev: false @@ -315,6 +478,15 @@ packages: '@babel/types': 7.22.11 dev: false + /@babel/template@7.24.0: + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/parser': 7.24.4 + '@babel/types': 7.24.0 + dev: false + /@babel/traverse@7.22.11: resolution: {integrity: sha512-mzAenteTfomcB7mfPtyi+4oe5BZ6MXxWcn4CX+h4IRJ+OOGXBrWU6jDQavkQI9Vuc5P+donFabBfFCcmWka9lQ==} engines: {node: '>=6.9.0'} @@ -333,6 +505,24 @@ packages: - supports-color dev: false + /@babel/traverse@7.24.1: + resolution: {integrity: sha512-xuU6o9m68KeqZbQuDt2TcKSxUw/mrsvavlEqQ1leZ/B+C9tk6E4sRWy97WaXgvq5E+nU3cXMxv3WKOCanVMCmQ==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/code-frame': 7.24.2 + '@babel/generator': 7.24.4 + '@babel/helper-environment-visitor': 7.22.20 + '@babel/helper-function-name': 7.23.0 + '@babel/helper-hoist-variables': 7.22.5 + '@babel/helper-split-export-declaration': 7.22.6 + '@babel/parser': 7.24.4 + '@babel/types': 7.24.0 + debug: 4.3.4 + globals: 11.12.0 + transitivePeerDependencies: + - supports-color + dev: false + /@babel/types@7.22.11: resolution: {integrity: sha512-siazHiGuZRz9aB9NpHy9GOs9xiQPKnMzgdr493iI1M67vRXpnEq8ZOOKzezC5q7zwuQ6sDhdSp4SD9ixKSqKZg==} engines: {node: '>=6.9.0'} @@ -342,6 +532,15 @@ packages: to-fast-properties: 2.0.0 dev: false + /@babel/types@7.24.0: + resolution: {integrity: sha512-+j7a5c253RfKh8iABBhywc8NSfP5LURe7Uh4qpsh6jc+aLJguvmIUBdjSdEMQv2bENrCR5MfRdjGo7vzS/ob7w==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.24.1 + '@babel/helper-validator-identifier': 7.22.20 + to-fast-properties: 2.0.0 + dev: false + /@esbuild/android-arm64@0.18.20: resolution: {integrity: sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==} engines: {node: '>=12'} @@ -597,6 +796,18 @@ packages: resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} dev: true + /@isaacs/cliui@8.0.2: + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + dependencies: + string-width: 5.1.2 + string-width-cjs: /string-width@4.2.3 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: true + /@jridgewell/gen-mapping@0.3.3: resolution: {integrity: sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==} engines: {node: '>=6.0.0'} @@ -606,19 +817,34 @@ packages: '@jridgewell/trace-mapping': 0.3.19 dev: false + /@jridgewell/gen-mapping@0.3.5: + resolution: {integrity: sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==} + engines: {node: '>=6.0.0'} + dependencies: + '@jridgewell/set-array': 1.2.1 + '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/trace-mapping': 0.3.25 + /@jridgewell/resolve-uri@3.1.1: resolution: {integrity: sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==} engines: {node: '>=6.0.0'} dev: false + /@jridgewell/resolve-uri@3.1.2: + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + /@jridgewell/set-array@1.1.2: resolution: {integrity: sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==} engines: {node: '>=6.0.0'} dev: false + /@jridgewell/set-array@1.2.1: + resolution: {integrity: sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==} + engines: {node: '>=6.0.0'} + /@jridgewell/sourcemap-codec@1.4.15: resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} - dev: false /@jridgewell/trace-mapping@0.3.19: resolution: {integrity: sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==} @@ -627,6 +853,12 @@ packages: '@jridgewell/sourcemap-codec': 1.4.15 dev: false + /@jridgewell/trace-mapping@0.3.25: + resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.4.15 + /@mapbox/geojson-rewind@0.5.2: resolution: {integrity: sha512-tJaT+RbYGJYStt7wI3cq4Nl4SXxG8W7JDG5DMJu97V25RnbNg3QtQtf+KD+VLjNpWKYsRvXDNmNrBgEETr1ifA==} hasBin: true @@ -722,6 +954,13 @@ packages: fastq: 1.15.0 dev: true + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: true + optional: true + /@remix-run/router@1.8.0: resolution: {integrity: sha512-mrfKqIHnSZRyIzBcanNJmVQELTnX+qagEDlcKO90RgRBVOZGSGvZKeDihTRfWcqoDn5N/NkUcwWTccnpN18Tfg==} engines: {node: '>=14.0.0'} @@ -741,6 +980,10 @@ packages: picomatch: 2.3.1 dev: false + /@surbowl/world-geo-json-zh@2.1.3: + resolution: {integrity: sha512-6m/eVcSsWvFXYkaEQLHCxJqFSkTMVaDgfTg9weQr2lozyrxxH+SsRTq24DFfCqFX7L9zKtDQcP6+5VKemY1Rcg==} + dev: false + /@svgr/babel-plugin-add-jsx-attribute@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-khWbXesWIP9v8HuKCl2NU2HNAyqpSQ/vkIl36Nbn4HIwEYSRWL0H7Gs6idJdha2DkpFDWlsqMELvoCE8lfFY6Q==} engines: {node: '>=14'} @@ -750,6 +993,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-add-jsx-attribute@8.0.0(@babel/core@7.24.4): + resolution: {integrity: sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-plugin-remove-jsx-attribute@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-iiZaIvb3H/c7d3TH2HBeK91uI2rMhZNwnsIrvd7ZwGLkFw6mmunOCoVnjdYua662MqGFxlN9xTq4fv9hgR4VXQ==} engines: {node: '>=14'} @@ -759,6 +1011,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-remove-jsx-attribute@8.0.0(@babel/core@7.24.4): + resolution: {integrity: sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-plugin-remove-jsx-empty-expression@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-sQQmyo+qegBx8DfFc04PFmIO1FP1MHI1/QEpzcIcclo5OAISsOJPW76ZIs0bDyO/DBSJEa/tDa1W26pVtt0FRw==} engines: {node: '>=14'} @@ -768,6 +1029,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-remove-jsx-empty-expression@8.0.0(@babel/core@7.24.4): + resolution: {integrity: sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-plugin-replace-jsx-attribute-value@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-i6MaAqIZXDOJeikJuzocByBf8zO+meLwfQ/qMHIjCcvpnfvWf82PFvredEZElErB5glQFJa2KVKk8N2xV6tRRA==} engines: {node: '>=14'} @@ -777,6 +1047,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-replace-jsx-attribute-value@8.0.0(@babel/core@7.24.4): + resolution: {integrity: sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-plugin-svg-dynamic-title@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-BoVSh6ge3SLLpKC0pmmN9DFlqgFy4NxNgdZNLPNJWBUU7TQpDWeBuyVuDW88iXydb5Cv0ReC+ffa5h3VrKfk1w==} engines: {node: '>=14'} @@ -786,6 +1065,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-svg-dynamic-title@8.0.0(@babel/core@7.24.4): + resolution: {integrity: sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-plugin-svg-em-dimensions@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-tNDcBa+hYn0gO+GkP/AuNKdVtMufVhU9fdzu+vUQsR18RIJ9RWe7h/pSBY338RO08wArntwbDk5WhQBmhf2PaA==} engines: {node: '>=14'} @@ -795,6 +1083,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-svg-em-dimensions@8.0.0(@babel/core@7.24.4): + resolution: {integrity: sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-plugin-transform-react-native-svg@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-qw54u8ljCJYL2KtBOjI5z7Nzg8LnSvQOP5hPKj77H4VQL4+HdKbAT5pnkkZLmHKYwzsIHSYKXxHouD8zZamCFQ==} engines: {node: '>=14'} @@ -804,6 +1101,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-transform-react-native-svg@8.1.0(@babel/core@7.24.4): + resolution: {integrity: sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-plugin-transform-svg-component@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-CcFECkDj98daOg9jE3Bh3uyD9kzevCAnZ+UtzG6+BQG/jOQ2OA3jHnX6iG4G1MCJkUQFnUvEv33NvQfqrb/F3A==} engines: {node: '>=12'} @@ -813,6 +1119,15 @@ packages: '@babel/core': 7.22.11 dev: false + /@svgr/babel-plugin-transform-svg-component@8.0.0(@babel/core@7.24.4): + resolution: {integrity: sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==} + engines: {node: '>=12'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + dev: false + /@svgr/babel-preset@7.0.0(@babel/core@7.22.11): resolution: {integrity: sha512-EX/NHeFa30j5UjldQGVQikuuQNHUdGmbh9kEpBKofGUtF0GUPJ4T4rhoYiqDAOmBOxojyot36JIFiDUHUK1ilQ==} engines: {node: '>=14'} @@ -830,6 +1145,23 @@ packages: '@svgr/babel-plugin-transform-svg-component': 7.0.0(@babel/core@7.22.11) dev: false + /@svgr/babel-preset@8.1.0(@babel/core@7.24.4): + resolution: {integrity: sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==} + engines: {node: '>=14'} + peerDependencies: + '@babel/core': ^7.0.0-0 + dependencies: + '@babel/core': 7.24.4 + '@svgr/babel-plugin-add-jsx-attribute': 8.0.0(@babel/core@7.24.4) + '@svgr/babel-plugin-remove-jsx-attribute': 8.0.0(@babel/core@7.24.4) + '@svgr/babel-plugin-remove-jsx-empty-expression': 8.0.0(@babel/core@7.24.4) + '@svgr/babel-plugin-replace-jsx-attribute-value': 8.0.0(@babel/core@7.24.4) + '@svgr/babel-plugin-svg-dynamic-title': 8.0.0(@babel/core@7.24.4) + '@svgr/babel-plugin-svg-em-dimensions': 8.0.0(@babel/core@7.24.4) + '@svgr/babel-plugin-transform-react-native-svg': 8.1.0(@babel/core@7.24.4) + '@svgr/babel-plugin-transform-svg-component': 8.0.0(@babel/core@7.24.4) + dev: false + /@svgr/core@7.0.0: resolution: {integrity: sha512-ztAoxkaKhRVloa3XydohgQQCb0/8x9T63yXovpmHzKMkHO6pkjdsIAWKOS4bE95P/2quVh1NtjSKlMRNzSBffw==} engines: {node: '>=14'} @@ -842,6 +1174,20 @@ packages: - supports-color dev: false + /@svgr/core@8.1.0(typescript@5.2.2): + resolution: {integrity: sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==} + engines: {node: '>=14'} + dependencies: + '@babel/core': 7.24.4 + '@svgr/babel-preset': 8.1.0(@babel/core@7.24.4) + camelcase: 6.3.0 + cosmiconfig: 8.3.6(typescript@5.2.2) + snake-case: 3.0.4 + transitivePeerDependencies: + - supports-color + - typescript + dev: false + /@svgr/hast-util-to-babel-ast@7.0.0: resolution: {integrity: sha512-42Ej9sDDEmsJKjrfQ1PHmiDiHagh/u9AHO9QWbeNx4KmD9yS5d1XHmXUNINfUcykAU+4431Cn+k6Vn5mWBYimQ==} engines: {node: '>=14'} @@ -862,23 +1208,40 @@ packages: - supports-color dev: false + /@svgr/plugin-svgo@8.1.0(@svgr/core@8.1.0): + resolution: {integrity: sha512-Ywtl837OGO9pTLIN/onoWLmDQ4zFUycI1g76vuKGEz6evR/ZTJlJuz3G/fIkb6OVBJ2g0o6CGJzaEjfmEo3AHA==} + engines: {node: '>=14'} + peerDependencies: + '@svgr/core': '*' + dependencies: + '@svgr/core': 8.1.0(typescript@5.2.2) + cosmiconfig: 8.2.0 + deepmerge: 4.3.1 + svgo: 3.1.0 + dev: false + + /@trysound/sax@0.2.0: + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + dev: false + /@types/estree@1.0.1: resolution: {integrity: sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==} dev: false - /@types/geojson@7946.0.10: - resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==} + /@types/geojson@7946.0.14: + resolution: {integrity: sha512-WCfD5Ht3ZesJUsONdhvm84dmzWOiOzOAqOncN0++w0lBw1o8OuDNJF2McvvCef/yBqb/HYRahp1BYtODFQ8bRg==} /@types/mapbox-gl@2.7.13: resolution: {integrity: sha512-qNffhTdYkeFl8QG9Q1zPPJmcs8PvHgmLa1PcwP1rxvcfMsIgcFr/FnrCttG0ZnH7Kzdd7xfECSRNTWSr4jC3PQ==} dependencies: - '@types/geojson': 7946.0.10 + '@types/geojson': 7946.0.14 dev: false /@types/mapbox__polyline@1.0.2: resolution: {integrity: sha512-Kr/oznVL3e8uvWM0+VPlVu2rVP+jKyVW/994HE9YJFBfcAcKqB4wMvqCdgXLG0SSrxxlKc5Nqaj7zt+KRl9oCA==} dependencies: - '@types/geojson': 7946.0.10 + '@types/geojson': 7946.0.14 dev: true /@types/minimist@1.2.2: @@ -892,8 +1255,8 @@ packages: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: false - /@types/prop-types@15.7.5: - resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + /@types/prop-types@15.7.11: + resolution: {integrity: sha512-ga8y9v9uyeiLdpKddhxYQkxNDrfvuPrlFb0N1qnZZByvcElJaXthF1UhvCh9TLWJBEHeNtdnbysW7Y6Uq8CVng==} dev: true /@types/react-dom@18.2.7: @@ -905,7 +1268,7 @@ packages: /@types/react@18.2.21: resolution: {integrity: sha512-neFKG/sBAwGxHgXiIxnbm3/AAVQ/cMRS93hvBpg8xYRbeQSPVABp9U2bRnPf0iI4+Ucdv3plSxKK+3CW2ENJxA==} dependencies: - '@types/prop-types': 15.7.5 + '@types/prop-types': 15.7.11 '@types/scheduler': 0.16.3 csstype: 3.1.2 dev: true @@ -990,11 +1353,11 @@ packages: peerDependencies: vite: ^4.2.0 dependencies: - '@babel/core': 7.22.11 - '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.22.11) - '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.22.11) + '@babel/core': 7.24.4 + '@babel/plugin-transform-react-jsx-self': 7.22.5(@babel/core@7.24.4) + '@babel/plugin-transform-react-jsx-source': 7.22.5(@babel/core@7.24.4) react-refresh: 0.14.0 - vite: 4.4.9(@types/node@20.5.7)(sass@1.66.1) + vite: 4.4.9(@types/node@20.5.7) transitivePeerDependencies: - supports-color dev: false @@ -1027,6 +1390,11 @@ packages: engines: {node: '>=8'} dev: true + /ansi-regex@6.0.1: + resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} + engines: {node: '>=12'} + dev: true + /ansi-styles@3.2.1: resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} engines: {node: '>=4'} @@ -1041,13 +1409,26 @@ packages: color-convert: 2.0.1 dev: true + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + dev: true + + /any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + dev: true + /anymatch@3.1.3: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} dependencies: normalize-path: 3.0.0 picomatch: 2.3.1 - dev: false + dev: true + + /arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true /argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -1138,6 +1519,22 @@ packages: has-symbols: 1.0.3 dev: true + /autoprefixer@10.4.19(postcss@8.4.38): + resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + dependencies: + browserslist: 4.23.0 + caniuse-lite: 1.0.30001605 + fraction.js: 4.3.7 + normalize-range: 0.1.2 + picocolors: 1.0.0 + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + dev: true + /available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} @@ -1150,6 +1547,10 @@ packages: /binary-extensions@2.2.0: resolution: {integrity: sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==} engines: {node: '>=8'} + dev: true + + /boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} dev: false /brace-expansion@1.1.11: @@ -1159,23 +1560,40 @@ packages: concat-map: 0.0.1 dev: true + /brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + dependencies: + balanced-match: 1.0.2 + dev: true + /braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} dependencies: fill-range: 7.0.1 + dev: true /browserslist@4.21.10: resolution: {integrity: sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true dependencies: - caniuse-lite: 1.0.30001524 + caniuse-lite: 1.0.30001605 electron-to-chromium: 1.4.505 node-releases: 2.0.13 update-browserslist-db: 1.0.11(browserslist@4.21.10) dev: false + /browserslist@4.23.0: + resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + dependencies: + caniuse-lite: 1.0.30001605 + electron-to-chromium: 1.4.726 + node-releases: 2.0.14 + update-browserslist-db: 1.0.13(browserslist@4.23.0) + /bytewise-core@1.2.3: resolution: {integrity: sha512-nZD//kc78OOxeYtRlVk8/zXqTB4gf/nlguL1ggWA8FuchMyOxcyHR4QPQZMUmA7czC+YnaBrPUCubqAWe50DaA==} dependencies: @@ -1200,6 +1618,11 @@ packages: resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} engines: {node: '>=6'} + /camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + dev: true + /camelcase-keys@6.2.2: resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} engines: {node: '>=8'} @@ -1219,9 +1642,8 @@ packages: engines: {node: '>=10'} dev: false - /caniuse-lite@1.0.30001524: - resolution: {integrity: sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA==} - dev: false + /caniuse-lite@1.0.30001605: + resolution: {integrity: sha512-nXwGlFWo34uliI9z3n6Qc0wZaf7zaZWA1CPZ169La5mV3I/gem7bst0vr5XQH5TJXZIMfDeZyOrZnSlVzKxxHQ==} /chalk@2.4.2: resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} @@ -1253,7 +1675,7 @@ packages: readdirp: 3.6.0 optionalDependencies: fsevents: 2.3.3 - dev: false + dev: true /color-convert@1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} @@ -1276,6 +1698,16 @@ packages: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + dev: true + + /commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + dev: false + /concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true @@ -1284,6 +1716,10 @@ packages: resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} dev: false + /convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + dev: false + /cosmiconfig@8.2.0: resolution: {integrity: sha512-3rTMnFJA1tCOPwRxtgF4wd7Ab2qvDbL8jX+3smjIbS4HlZBagTlpERbdN7iAbWlrfxE3M8c27kTwTawQ7st+OQ==} engines: {node: '>=14'} @@ -1294,6 +1730,22 @@ packages: path-type: 4.0.0 dev: false + /cosmiconfig@8.3.6(typescript@5.2.2): + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + import-fresh: 3.3.0 + js-yaml: 4.1.0 + parse-json: 5.2.0 + path-type: 4.0.0 + typescript: 5.2.2 + dev: false + /cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -1303,10 +1755,54 @@ packages: which: 2.0.2 dev: true + /css-select@5.1.0: + resolution: {integrity: sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==} + dependencies: + boolbase: 1.0.0 + css-what: 6.1.0 + domhandler: 5.0.3 + domutils: 3.1.0 + nth-check: 2.1.1 + dev: false + + /css-tree@2.2.1: + resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.0.2 + dev: false + + /css-tree@2.3.1: + resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + dependencies: + mdn-data: 2.0.30 + source-map-js: 1.0.2 + dev: false + + /css-what@6.1.0: + resolution: {integrity: sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==} + engines: {node: '>= 6'} + dev: false + /csscolorparser@1.0.3: resolution: {integrity: sha512-umPSgYwZkdFoUrH5hIq5kf0wPSXiro51nPw0j2K/c83KflkPSTBGMz6NJvMB+07VlL0y7VPo6QJcDjcgKTTm3w==} dev: false + /cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + + /csso@5.0.5: + resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} + dependencies: + css-tree: 2.2.1 + dev: false + /csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} dev: true @@ -1339,6 +1835,11 @@ packages: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} dev: true + /deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + dev: false + /define-properties@1.2.0: resolution: {integrity: sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==} engines: {node: '>= 0.4'} @@ -1347,6 +1848,10 @@ packages: object-keys: 1.1.1 dev: true + /didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + dev: true + /dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -1354,6 +1859,10 @@ packages: path-type: 4.0.0 dev: true + /dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + dev: true + /doctrine@2.1.0: resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} engines: {node: '>=0.10.0'} @@ -1368,14 +1877,63 @@ packages: esutils: 2.0.3 dev: true + /dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + dev: false + + /domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + dev: false + + /domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + dependencies: + domelementtype: 2.3.0 + dev: false + + /domutils@3.1.0: + resolution: {integrity: sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==} + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + dev: false + + /dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + dependencies: + no-case: 3.0.4 + tslib: 2.6.2 + dev: false + /earcut@2.2.4: resolution: {integrity: sha512-/pjZsA1b4RPHbeWZQn66SWS8nZZWLQQ23oE3Eam7aroEFGEvwKAsJfZ9ytiEMycfzXWpca4FA9QIOehf7PocBQ==} dev: false + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + dev: true + /electron-to-chromium@1.4.505: resolution: {integrity: sha512-0A50eL5BCCKdxig2SsCXhpuztnB9PfUgRMojj5tMvt8O54lbwz3t6wNgnpiTRosw5QjlJB7ixhVyeg8daLQwSQ==} dev: false + /electron-to-chromium@1.4.726: + resolution: {integrity: sha512-xtjfBXn53RORwkbyKvDfTajtnTp0OJoPOIBzXvkNbb7+YYvCHJflba3L7Txyx/6Fov3ov2bGPr/n5MTixmPhdQ==} + + /emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + + /emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + dev: true + /entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1510,6 +2068,10 @@ packages: engines: {node: '>=6'} dev: false + /escalade@3.1.2: + resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} + engines: {node: '>=6'} + /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} @@ -1529,7 +2091,7 @@ packages: eslint: 8.48.0 dev: true - /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0)(eslint@8.48.0)(prettier@2.8.8): + /eslint-plugin-prettier@4.2.1(eslint-config-prettier@8.10.0)(eslint@8.48.0)(prettier@3.2.5): resolution: {integrity: sha512-f/0rXLXUt0oFYs8ra4w49wYZBG5GKZpAYsJSm6rnYL5uVDjd+zowwMwVZHnAjf4edNrKpCDYfXDgmRE/Ak7QyQ==} engines: {node: '>=12.0.0'} peerDependencies: @@ -1542,7 +2104,7 @@ packages: dependencies: eslint: 8.48.0 eslint-config-prettier: 8.10.0(eslint@8.48.0) - prettier: 2.8.8 + prettier: 3.2.5 prettier-linter-helpers: 1.0.0 dev: true @@ -1727,6 +2289,7 @@ packages: engines: {node: '>=8'} dependencies: to-regex-range: 5.0.1 + dev: true /find-up@4.1.0: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} @@ -1763,6 +2326,18 @@ packages: is-callable: 1.2.7 dev: true + /foreground-child@3.1.1: + resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} + engines: {node: '>=14'} + dependencies: + cross-spawn: 7.0.3 + signal-exit: 4.1.0 + dev: true + + /fraction.js@4.3.7: + resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} + dev: true + /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true @@ -1772,7 +2347,6 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] requiresBuild: true - dev: false optional: true /function-bind@1.1.1: @@ -1846,6 +2420,7 @@ packages: engines: {node: '>= 6'} dependencies: is-glob: 4.0.3 + dev: true /glob-parent@6.0.2: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} @@ -1854,6 +2429,18 @@ packages: is-glob: 4.0.3 dev: true + /glob@10.3.12: + resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + foreground-child: 3.1.1 + jackspeak: 2.3.6 + minimatch: 9.0.4 + minipass: 7.0.4 + path-scurry: 1.10.2 + dev: true + /glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} dependencies: @@ -1975,10 +2562,6 @@ packages: engines: {node: '>= 4'} dev: true - /immutable@4.3.4: - resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==} - dev: false - /import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -2052,7 +2635,7 @@ packages: engines: {node: '>=8'} dependencies: binary-extensions: 2.2.0 - dev: false + dev: true /is-boolean-object@1.1.2: resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} @@ -2094,6 +2677,7 @@ packages: /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} + dev: true /is-finalizationregistry@1.0.2: resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} @@ -2101,6 +2685,11 @@ packages: call-bind: 1.0.2 dev: true + /is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + /is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -2113,6 +2702,7 @@ packages: engines: {node: '>=0.10.0'} dependencies: is-extglob: 2.1.1 + dev: true /is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} @@ -2133,6 +2723,7 @@ packages: /is-number@7.0.0: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + dev: true /is-path-inside@3.0.3: resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} @@ -2230,6 +2821,20 @@ packages: reflect.getprototypeof: 1.0.3 dev: true + /jackspeak@2.3.6: + resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} + engines: {node: '>=14'} + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + dev: true + + /jiti@1.21.0: + resolution: {integrity: sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==} + hasBin: true + dev: true + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2304,9 +2909,18 @@ packages: type-check: 0.4.0 dev: true + /lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + dev: true + + /lilconfig@3.1.1: + resolution: {integrity: sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==} + engines: {node: '>=14'} + dev: true + /lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - dev: false /locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} @@ -2332,6 +2946,17 @@ packages: dependencies: js-tokens: 4.0.0 + /lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + dependencies: + tslib: 2.6.2 + dev: false + + /lru-cache@10.2.0: + resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} + engines: {node: 14 || >=16.14} + dev: true + /lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} dependencies: @@ -2382,6 +3007,14 @@ packages: vt-pbf: 3.1.3 dev: false + /mdn-data@2.0.28: + resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} + dev: false + + /mdn-data@2.0.30: + resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==} + dev: false + /meow@6.1.1: resolution: {integrity: sha512-3YffViIt2QWgTy6Pale5QpopX/IvU3LPL03jOTqp6pGj3VjesdO/U8CuHMKpnQr4shCNCM5fd5XFFvIIl6JBHg==} engines: {node: '>=8'} @@ -2423,6 +3056,13 @@ packages: brace-expansion: 1.1.11 dev: true + /minimatch@9.0.4: + resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + brace-expansion: 2.0.1 + dev: true + /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -2436,6 +3076,11 @@ packages: resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} dev: false + /minipass@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + dev: true + /ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -2443,20 +3088,37 @@ packages: resolution: {integrity: sha512-TvmkNhkv8yct0SVBSy+o8wYzXjE4Zz3PCesbfs8HiCXXdcTuocApFv11UWlNFWKYsP2okqrhb7JNlSm9InBhIw==} dev: false - /nanoid@3.3.6: - resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==} + /mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + dev: true + + /nanoid@3.3.7: + resolution: {integrity: sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - dev: false /natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} dev: true + /no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + dependencies: + lower-case: 2.0.2 + tslib: 2.6.2 + dev: false + /node-releases@2.0.13: resolution: {integrity: sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==} dev: false + /node-releases@2.0.14: + resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -2469,12 +3131,28 @@ packages: /normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} + dev: true + + /normalize-range@0.1.2: + resolution: {integrity: sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==} + engines: {node: '>=0.10.0'} + dev: true + + /nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + dependencies: + boolbase: 1.0.0 dev: false /object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} + /object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + dev: true + /object-inspect@1.12.3: resolution: {integrity: sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==} dev: true @@ -2612,6 +3290,14 @@ packages: /path-parse@1.0.7: resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + /path-scurry@1.10.2: + resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} + engines: {node: '>=16 || 14 >=14.17'} + dependencies: + lru-cache: 10.2.0 + minipass: 7.0.4 + dev: true + /path-type@4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -2626,20 +3312,89 @@ packages: /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} - dev: false /picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - /postcss@8.4.29: - resolution: {integrity: sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==} + /pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + dev: true + + /pirates@4.0.6: + resolution: {integrity: sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==} + engines: {node: '>= 6'} + dev: true + + /postcss-import@15.1.0(postcss@8.4.38): + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + dependencies: + postcss: 8.4.38 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.4 + dev: true + + /postcss-js@4.0.1(postcss@8.4.38): + resolution: {integrity: sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + dependencies: + camelcase-css: 2.0.1 + postcss: 8.4.38 + dev: true + + /postcss-load-config@4.0.2(postcss@8.4.38): + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + dependencies: + lilconfig: 3.1.1 + postcss: 8.4.38 + yaml: 2.4.1 + dev: true + + /postcss-nested@6.0.1(postcss@8.4.38): + resolution: {integrity: sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + dependencies: + postcss: 8.4.38 + postcss-selector-parser: 6.0.16 + dev: true + + /postcss-selector-parser@6.0.16: + resolution: {integrity: sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: true + + /postcss@8.4.38: + resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} engines: {node: ^10 || ^12 || >=14} dependencies: - nanoid: 3.3.6 + nanoid: 3.3.7 picocolors: 1.0.0 - source-map-js: 1.0.2 - dev: false + source-map-js: 1.2.0 /potpack@2.0.0: resolution: {integrity: sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw==} @@ -2657,9 +3412,64 @@ packages: fast-diff: 1.3.0 dev: true - /prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} + /prettier-plugin-tailwindcss@0.5.13(prettier@3.2.5): + resolution: {integrity: sha512-2tPWHCFNC+WRjAC4SIWQNSOdcL1NNkydXim8w7TDqlZi+/ulZYz2OouAI6qMtkggnPt7lGamboj6LcTMwcCvoQ==} + engines: {node: '>=14.21.3'} + peerDependencies: + '@ianvs/prettier-plugin-sort-imports': '*' + '@prettier/plugin-pug': '*' + '@shopify/prettier-plugin-liquid': '*' + '@trivago/prettier-plugin-sort-imports': '*' + '@zackad/prettier-plugin-twig-melody': '*' + prettier: ^3.0 + prettier-plugin-astro: '*' + prettier-plugin-css-order: '*' + prettier-plugin-import-sort: '*' + prettier-plugin-jsdoc: '*' + prettier-plugin-marko: '*' + prettier-plugin-organize-attributes: '*' + prettier-plugin-organize-imports: '*' + prettier-plugin-sort-imports: '*' + prettier-plugin-style-order: '*' + prettier-plugin-svelte: '*' + peerDependenciesMeta: + '@ianvs/prettier-plugin-sort-imports': + optional: true + '@prettier/plugin-pug': + optional: true + '@shopify/prettier-plugin-liquid': + optional: true + '@trivago/prettier-plugin-sort-imports': + optional: true + '@zackad/prettier-plugin-twig-melody': + optional: true + prettier-plugin-astro: + optional: true + prettier-plugin-css-order: + optional: true + prettier-plugin-import-sort: + optional: true + prettier-plugin-jsdoc: + optional: true + prettier-plugin-marko: + optional: true + prettier-plugin-organize-attributes: + optional: true + prettier-plugin-organize-imports: + optional: true + prettier-plugin-sort-imports: + optional: true + prettier-plugin-style-order: + optional: true + prettier-plugin-svelte: + optional: true + dependencies: + prettier: 3.2.5 + dev: true + + /prettier@3.2.5: + resolution: {integrity: sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==} + engines: {node: '>=14'} hasBin: true dev: true @@ -2706,6 +3516,10 @@ packages: resolution: {integrity: sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ==} dev: false + /react-ga4@2.1.0: + resolution: {integrity: sha512-ZKS7PGNFqqMd3PJ6+C2Jtz/o1iU9ggiy8Y8nUeksgVuvNISbmrQtJiZNvC/TjDsqD0QlU5Wkgs7i+w9+OjHhhQ==} + dev: false + /react-helmet-async@1.3.0(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-9jZ57/dAn9t3q6hneQS0wukqC2ENOBgMNVEhb/ZG9ZSxUetzVIw4iAmEU38IaVg3QGYauQPhSeUTuIUtFglWpg==} peerDependencies: @@ -2779,6 +3593,12 @@ packages: loose-envify: 1.4.0 dev: false + /read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + dependencies: + pify: 2.3.0 + dev: true + /read-pkg-up@7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -2803,7 +3623,7 @@ packages: engines: {node: '>=8.10.0'} dependencies: picomatch: 2.3.1 - dev: false + dev: true /redent@3.0.0: resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} @@ -2855,7 +3675,6 @@ packages: is-core-module: 2.13.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - dev: false /resolve@2.0.0-next.4: resolution: {integrity: sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==} @@ -2914,20 +3733,6 @@ packages: is-regex: 1.1.4 dev: true - /sass-mq@6.0.0: - resolution: {integrity: sha512-h4VicIy8lszFlqqggqLIFGt/9wS5fHLPoTXHRjC8Vw6UsA4s4JtDvEeypXbbECfgY336mXyc/cdpbRacH0UzGA==} - dev: false - - /sass@1.66.1: - resolution: {integrity: sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==} - engines: {node: '>=14.0.0'} - hasBin: true - dependencies: - chokidar: 3.5.3 - immutable: 4.3.4 - source-map-js: 1.0.2 - dev: false - /scheduler@0.23.0: resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} dependencies: @@ -2985,11 +3790,23 @@ packages: object-inspect: 1.12.3 dev: true + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + dev: true + /slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} dev: true + /snake-case@3.0.4: + resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} + dependencies: + dot-case: 3.0.4 + tslib: 2.6.2 + dev: false + /sort-asc@0.2.0: resolution: {integrity: sha512-umMGhjPeHAI6YjABoSTrFp2zaBtXBej1a0yKkuMUyjjqu6FJsTF+JYwCswWDg+zJfk/5npWUUbd33HH/WLzpaA==} engines: {node: '>=0.10.0'} @@ -3017,6 +3834,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /source-map-js@1.2.0: + resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} + engines: {node: '>=0.10.0'} + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: @@ -3046,6 +3867,24 @@ packages: extend-shallow: 3.0.2 dev: false + /string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + + /string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.1.0 + dev: true + /string.prototype.matchall@4.0.9: resolution: {integrity: sha512-6i5hL3MqG/K2G43mWXWgP+qizFW/QH/7kCNN13JrJS5q48FN5IKksLDscexKP3dnmB6cdm9jlNgAsWNLpSykmA==} dependencies: @@ -3091,6 +3930,13 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-ansi@7.1.0: + resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} + engines: {node: '>=12'} + dependencies: + ansi-regex: 6.0.1 + dev: true + /strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -3103,6 +3949,20 @@ packages: engines: {node: '>=8'} dev: true + /sucrase@3.35.0: + resolution: {integrity: sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + dependencies: + '@jridgewell/gen-mapping': 0.3.5 + commander: 4.1.1 + glob: 10.3.12 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.6 + ts-interface-checker: 0.1.13 + dev: true + /supercluster@8.0.1: resolution: {integrity: sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==} dependencies: @@ -3131,18 +3991,68 @@ packages: resolution: {integrity: sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==} dev: false - /tachyons-custom@4.9.8: - resolution: {integrity: sha512-MVnslsN5dFswmh2+Sw+OOA4/6pQfnJrA8/wrtaf+Px347wze3MIk3lp3Q3QmbUPqrJda79s0dZtY3dgTwVHdfg==} + /svgo@3.1.0: + resolution: {integrity: sha512-R5SnNA89w1dYgNv570591F66v34b3eQShpIBcQtZtM5trJwm1VvxbIoMpRYY3ybTAutcKTLEmTsdnaknOHbiQA==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 5.1.0 + css-tree: 2.3.1 + css-what: 6.1.0 + csso: 5.0.5 + picocolors: 1.0.0 dev: false - /tachyons@4.12.0: - resolution: {integrity: sha512-2nA2IrYFy3raCM9fxJ2KODRGHVSZNTW3BR0YnlGsLUf1DA3pk3YfWZ/DdfbnZK6zLZS+jUenlUGJsKcA5fUiZg==} - dev: false + /tailwindcss@3.4.3: + resolution: {integrity: sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==} + engines: {node: '>=14.0.0'} + hasBin: true + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.5.3 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.1 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.0 + lilconfig: 2.1.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.38 + postcss-import: 15.1.0(postcss@8.4.38) + postcss-js: 4.0.1(postcss@8.4.38) + postcss-load-config: 4.0.2(postcss@8.4.38) + postcss-nested: 6.0.1(postcss@8.4.38) + postcss-selector-parser: 6.0.16 + resolve: 1.22.4 + sucrase: 3.35.0 + transitivePeerDependencies: + - ts-node + dev: true /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true + /thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + dependencies: + thenify: 3.3.1 + dev: true + + /thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + dependencies: + any-promise: 1.3.0 + dev: true + /tinyqueue@2.0.3: resolution: {integrity: sha512-ppJZNDuKGgxzkHihX8v9v9G5f+18gzaTfrukGrq6ueg0lmH4nqVnA2IPG0AEH3jKEk2GRJCUhDoqpoiw3PHLBA==} dev: false @@ -3157,12 +4067,17 @@ packages: engines: {node: '>=8.0'} dependencies: is-number: 7.0.0 + dev: true /trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} dev: false + /ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + dev: true + /tsconfck@2.1.2(typescript@5.2.2): resolution: {integrity: sha512-ghqN1b0puy3MhhviwO2kGF8SeMDNhEbnKxjK7h6+fvY9JAxqvXi8y5NAHSQv687OVboS2uZIByzGd45/YxrRHg==} engines: {node: ^14.13.1 || ^16 || >=18} @@ -3180,6 +4095,10 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true + /tslib@2.6.2: + resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} + dev: false + /tsutils@3.21.0(typescript@5.2.2): resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} @@ -3300,12 +4219,26 @@ packages: picocolors: 1.0.0 dev: false + /update-browserslist-db@1.0.13(browserslist@4.23.0): + resolution: {integrity: sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + dependencies: + browserslist: 4.23.0 + escalade: 3.1.2 + picocolors: 1.0.0 + /uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} dependencies: punycode: 2.3.0 dev: true + /util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + dev: true + /validate-npm-package-license@3.0.4: resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} dependencies: @@ -3327,7 +4260,7 @@ packages: '@rollup/pluginutils': 5.0.4 '@svgr/core': 7.0.0 '@svgr/plugin-jsx': 7.0.0 - vite: 4.4.9(@types/node@20.5.7)(sass@1.66.1) + vite: 4.4.9(@types/node@20.5.7) transitivePeerDependencies: - rollup - supports-color @@ -3344,13 +4277,13 @@ packages: debug: 4.3.4 globrex: 0.1.2 tsconfck: 2.1.2(typescript@5.2.2) - vite: 4.4.9(@types/node@20.5.7)(sass@1.66.1) + vite: 4.4.9(@types/node@20.5.7) transitivePeerDependencies: - supports-color - typescript dev: false - /vite@4.4.9(@types/node@20.5.7)(sass@1.66.1): + /vite@4.4.9(@types/node@20.5.7): resolution: {integrity: sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true @@ -3380,9 +4313,8 @@ packages: dependencies: '@types/node': 20.5.7 esbuild: 0.18.20 - postcss: 8.4.29 + postcss: 8.4.38 rollup: 3.28.1 - sass: 1.66.1 optionalDependencies: fsevents: 2.3.3 dev: false @@ -3451,6 +4383,24 @@ packages: isexe: 2.0.0 dev: true + /wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + + /wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + dependencies: + ansi-styles: 6.2.1 + string-width: 5.1.2 + strip-ansi: 7.1.0 + dev: true + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true @@ -3463,6 +4413,12 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml@2.4.1: + resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} + engines: {node: '>= 14'} + hasBin: true + dev: true + /yargs-parser@18.1.3: resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==} engines: {node: '>=6'} @@ -3475,11 +4431,3 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true - - github.com/tachyons-css/tachyons-sass/2dce89b83729bddb9f4498c7d6f82b73d60d6538: - resolution: {tarball: https://codeload.github.com/tachyons-css/tachyons-sass/tar.gz/2dce89b83729bddb9f4498c7d6f82b73d60d6538} - name: tachyons-sass - version: 4.9.5 - dependencies: - tachyons-custom: 4.9.8 - dev: false diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 00000000000..2aa7205d4b4 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/requirements.txt b/requirements.txt index da5d2237234..124aa41936d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,6 @@ httpx>=0.15.0,<=0.15.5 gpxpy==1.4.2 -stravalib +stravalib==0.10.4 appdirs>=1.4.0 svgwrite>=1.1.9 colour>=0.1.5 @@ -14,16 +14,16 @@ timezonefinder; platform_system == "Windows" pyyaml aiofiles cloudscraper==1.2.58 -git+https://github.com/alenrajsp/tcxreader.git +tcxreader rich -lxml +lxml==4.9.4 eviltransform stravaweblib tenacity numpy tzlocal fit-tool +garmin-fit-sdk haversine==2.8.0 garth pycryptodome - diff --git a/run_page/codoon_sync.py b/run_page/codoon_sync.py index aa0d198975e..c91a69e6169 100755 --- a/run_page/codoon_sync.py +++ b/run_page/codoon_sync.py @@ -9,6 +9,7 @@ import xml.etree.ElementTree as ET from collections import namedtuple from datetime import datetime, timedelta +from xml.dom import minidom import eviltransform import gpxpy @@ -45,6 +46,8 @@ # device info user_agent = "CodoonSport(8.9.0 1170;Android 7;Sony XZ1)" did = "24-00000000-03e1-7dd7-0033-c5870033c588" +# May be Forerunner 945? +CONNECT_API_PART_NUMBER = "006-D2449-00" # fixed params base_url = "https://api.codoon.com" @@ -61,9 +64,9 @@ # for tcx type TCX_TYPE_DICT = { - 0: "Hike", + 0: "Hiking", 1: "Running", - 2: "Ride", + 2: "Biking", } # only for running sports, if you want others, please change the True to False @@ -127,8 +130,17 @@ def formated_input( def tcx_output(fit_array, run_data): + """ + If you want to make a more detailed tcx file, please refer to oppo_sync.py + """ # route ID fit_id = str(run_data["id"]) + # local time + fit_start_time_local = run_data["start_time"] + # zulu time + utc = adjust_time_to_utc(to_date(fit_start_time_local), str(get_localzone())) + fit_start_time = utc.strftime("%Y-%m-%dT%H:%M:%SZ") + # Root node training_center_database = ET.Element( "TrainingCenterDatabase", @@ -143,7 +155,7 @@ def tcx_output(fit_array, run_data): }, ) # xml tree - tree = ET.ElementTree(training_center_database) + ET.ElementTree(training_center_database) # Activities activities = ET.Element("Activities") training_center_database.append(activities) @@ -154,23 +166,19 @@ def tcx_output(fit_array, run_data): activities.append(activity) # Id activity_id = ET.Element("Id") - activity_id.text = fit_id + activity_id.text = fit_start_time # Codoon use start_time as ID activity.append(activity_id) # Creator - activity_creator = ET.Element("Creator") + activity_creator = ET.Element("Creator", {"xsi:type": "Device_t"}) activity.append(activity_creator) # Name activity_creator_name = ET.Element("Name") - activity_creator_name.text = "咕咚" + activity_creator_name.text = "Codoon" activity_creator.append(activity_creator_name) + activity_creator_product = ET.Element("ProductID") + activity_creator_product.text = "3441" + activity_creator.append(activity_creator_product) # Lap - - # local time - fit_start_time_local = run_data["start_time"] - # zulu time - utc = adjust_time_to_utc(to_date(fit_start_time_local), str(get_localzone())) - fit_start_time = utc.strftime("%Y-%m-%dT%H:%M:%SZ") - activity_lap = ET.Element("Lap", {"StartTime": fit_start_time}) activity.append(activity_lap) # TotalTimeSeconds @@ -216,11 +224,22 @@ def tcx_output(fit_array, run_data): altitude_meters = ET.Element("AltitudeMeters") altitude_meters.text = bytes.decode(i["elevation"]) tp.append(altitude_meters) - + # Author + author = ET.Element("Author", {"xsi:type": "Application_t"}) + training_center_database.append(author) + author_name = ET.Element("Name") + author_name.text = "Connect Api" + author.append(author_name) + author_lang = ET.Element("LangID") + author_lang.text = "en" + author.append(author_lang) + author_part = ET.Element("PartNumber") + author_part.text = CONNECT_API_PART_NUMBER + author.append(author_part) # write to TCX file - tree.write( - TCX_FOLDER + "/" + fit_id + ".tcx", encoding="utf-8", xml_declaration=True - ) + xml_str = minidom.parseString(ET.tostring(training_center_database)).toprettyxml() + with open(TCX_FOLDER + "/" + fit_id + ".tcx", "w") as f: + f.write(str(xml_str)) # TODO time complexity is too heigh, need to be reduced @@ -347,9 +366,9 @@ def __call__(self, r): r.headers["timestamp"] = timestamp if "refresh_token" in params: r.headers["authorization"] = "Basic " + basic_auth - r.headers[ - "content-type" - ] = "application/x-www-form-urlencode; charset=utf-8" + r.headers["content-type"] = ( + "application/x-www-form-urlencode; charset=utf-8" + ) else: r.headers["authorization"] = "Bearer " + self.token r.headers["content-type"] = "application/json; charset=utf-8" diff --git a/run_page/config.py b/run_page/config.py index cd40767a31a..1548d5ea80c 100644 --- a/run_page/config.py +++ b/run_page/config.py @@ -19,13 +19,13 @@ SQL_FILE = os.path.join(parent, "run_page", "data.db") JSON_FILE = os.path.join(parent, "src", "static", "activities.json") SYNCED_FILE = os.path.join(parent, "imported.json") +SYNCED_ACTIVITY_FILE = os.path.join(parent, "synced_activity.json") + +# TODO: Move into nike_sync NRC THINGS -# TODO: Move into nike_sync -BASE_URL = "https://api.nike.com/sport/v3/me" -TOKEN_REFRESH_URL = "https://unite.nike.com/tokenRefresh" -NIKE_CLIENT_ID = "HlHa2Cje3ctlaOqnxvgZXNaAs7T9nAuH" -BASE_TIMEZONE = "Asia/Shanghai" +BASE_TIMEZONE = "Asia/Shanghai" +UTC_TIMEZONE = "UTC" start_point = namedtuple("start_point", "lat lon") run_map = namedtuple("polyline", "summary_polyline") diff --git a/run_page/coros_sync.py b/run_page/coros_sync.py new file mode 100644 index 00000000000..48193cd94cd --- /dev/null +++ b/run_page/coros_sync.py @@ -0,0 +1,166 @@ +import argparse +import asyncio +import hashlib +import os +import time + +import aiofiles +import httpx + +from config import JSON_FILE, SQL_FILE, FIT_FOLDER +from utils import make_activities_file + +COROS_URL_DICT = { + "LOGIN_URL": "https://teamcnapi.coros.com/account/login", + "DOWNLOAD_URL": "https://teamcnapi.coros.com/activity/detail/download", + "ACTIVITY_LIST": "https://teamcnapi.coros.com/activity/query?&modeList=100,102,103", +} + +TIME_OUT = httpx.Timeout(240.0, connect=360.0) + + +class Coros: + def __init__(self, account, password): + self.account = account + self.password = password + self.headers = None + self.req = None + + async def login(self): + url = COROS_URL_DICT.get("LOGIN_URL") + headers = { + "authority": "teamcnapi.coros.com", + "accept": "application/json, text/plain, */*", + "accept-language": "zh-CN,zh;q=0.9", + "content-type": "application/json;charset=UTF-8", + "dnt": "1", + "origin": "https://t.coros.com", + "referer": "https://t.coros.com/", + "sec-ch-ua": '"Chromium";v="122", "Not(A:Brand";v="24", "Google Chrome";v="122"', + "sec-ch-ua-mobile": "?0", + "sec-ch-ua-platform": '"macOS"', + "sec-fetch-dest": "empty", + "sec-fetch-mode": "cors", + "sec-fetch-site": "same-site", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", + } + data = {"account": self.account, "accountType": 2, "pwd": self.password} + async with httpx.AsyncClient(timeout=TIME_OUT) as client: + response = await client.post(url, json=data, headers=headers) + resp_json = response.json() + access_token = resp_json.get("data", {}).get("accessToken") + if not access_token: + raise Exception( + "============Login failed! please check your account and password===========" + ) + self.headers = { + "accesstoken": access_token, + "cookie": f"CPL-coros-region=2; CPL-coros-token={access_token}", + } + self.req = httpx.AsyncClient(timeout=TIME_OUT, headers=self.headers) + await client.aclose() + + async def init(self): + await self.login() + + async def fetch_activity_ids(self): + page_number = 1 + all_activities_ids = [] + + while True: + url = f"{COROS_URL_DICT.get('ACTIVITY_LIST')}&pageNumber={page_number}&size=20" + response = await self.req.get(url) + data = response.json() + activities = data.get("data", {}).get("dataList", None) + if not activities: + break + for activity in activities: + label_id = activity["labelId"] + if label_id is None: + continue + all_activities_ids.append(label_id) + + page_number += 1 + + return all_activities_ids + + async def download_activity(self, label_id): + download_folder = FIT_FOLDER + download_url = f"{COROS_URL_DICT.get('DOWNLOAD_URL')}?labelId={label_id}&sportType=100&fileType=4" + file_url = None + try: + response = await self.req.post(download_url) + resp_json = response.json() + file_url = resp_json.get("data", {}).get("fileUrl") + if not file_url: + print(f"No file URL found for label_id {label_id}") + return None, None + + fname = os.path.basename(file_url) + file_path = os.path.join(download_folder, fname) + + async with self.req.stream("GET", file_url) as response: + response.raise_for_status() + async with aiofiles.open(file_path, "wb") as f: + async for chunk in response.aiter_bytes(): + await f.write(chunk) + except httpx.HTTPStatusError as exc: + print( + f"Failed to download {file_url} with status code {response.status_code}: {exc}" + ) + return None, None + except Exception as exc: + print(f"Error occurred while downloading {file_url}: {exc}") + return None, None + + return label_id, fname + + +def get_downloaded_ids(folder): + return [i.split(".")[0] for i in os.listdir(folder) if not i.startswith(".")] + + +async def download_and_generate(account, password): + folder = FIT_FOLDER + downloaded_ids = get_downloaded_ids(folder) + coros = Coros(account, password) + await coros.init() + + activity_ids = await coros.fetch_activity_ids() + print("activity_ids: ", len(activity_ids)) + print("downloaded_ids: ", len(downloaded_ids)) + to_generate_coros_ids = list(set(activity_ids) - set(downloaded_ids)) + print("to_generate_activity_ids: ", len(to_generate_coros_ids)) + + start_time = time.time() + await gather_with_concurrency( + 10, + [coros.download_activity(label_d) for label_d in to_generate_coros_ids], + ) + print(f"Download finished. Elapsed {time.time()-start_time} seconds") + await coros.req.aclose() + make_activities_file(SQL_FILE, FIT_FOLDER, JSON_FILE, "fit") + + +async def gather_with_concurrency(n, tasks): + semaphore = asyncio.Semaphore(n) + + async def sem_task(task): + async with semaphore: + return await task + + return await asyncio.gather(*(sem_task(task) for task in tasks)) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("account", nargs="?", help="input coros account") + + parser.add_argument("password", nargs="?", help="input coros password") + options = parser.parse_args() + + account = options.account + password = options.password + encrypted_pwd = hashlib.md5(password.encode()).hexdigest() + + asyncio.run(download_and_generate(account, encrypted_pwd)) diff --git a/run_page/endomondo_sync.py b/run_page/endomondo_sync.py index 1b330f5fa1f..bdecf438d68 100644 --- a/run_page/endomondo_sync.py +++ b/run_page/endomondo_sync.py @@ -2,6 +2,7 @@ need to download the files from endomondo and store it in Workous dir in running_page """ + import json import os from collections import namedtuple diff --git a/run_page/garmin_sync.py b/run_page/garmin_sync.py index 1c06cd21fe1..1defb34a4fa 100755 --- a/run_page/garmin_sync.py +++ b/run_page/garmin_sync.py @@ -117,7 +117,9 @@ async def download_activity(self, activity_id, file_type="gpx"): response.raise_for_status() return response.read() - async def upload_activities_original(self, datas, use_fake_garmin_device=False): + async def upload_activities_original_from_strava( + self, datas, use_fake_garmin_device=False + ): print( "start upload activities to garmin!, use_fake_garmin_device:", use_fake_garmin_device, @@ -152,6 +154,38 @@ async def upload_activities_original(self, datas, use_fake_garmin_device=False): print("garmin upload failed: ", e) await self.req.aclose() + async def upload_activity_from_file(self, file): + print("Uploading " + str(file)) + f = open(file, "rb") + + file_body = BytesIO(f.read()) + files = {"file": (file, file_body)} + + try: + res = await self.req.post( + self.upload_url, files=files, headers=self.headers + ) + f.close() + except Exception as e: + print(str(e)) + # just pass for now + return + try: + resp = res.json()["detailedImportResult"] + print("garmin upload success: ", resp) + except Exception as e: + print("garmin upload failed: ", e) + + async def upload_activities_files(self, files): + print("start upload activities to garmin!") + + await gather_with_concurrency( + 10, + [self.upload_activity_from_file(file=f) for f in files], + ) + + await self.req.aclose() + class GarminConnectHttpError(Exception): def __init__(self, status): @@ -201,10 +235,18 @@ async def download_garmin_data(client, activity_id, file_type="gpx"): zip_file = zipfile.ZipFile(file_path, "r") for file_info in zip_file.infolist(): zip_file.extract(file_info, folder) - os.rename( - os.path.join(folder, f"{activity_id}_ACTIVITY.fit"), - os.path.join(folder, f"{activity_id}.fit"), - ) + if file_info.filename.endswith(".fit"): + os.rename( + os.path.join(folder, f"{activity_id}_ACTIVITY.fit"), + os.path.join(folder, f"{activity_id}.fit"), + ) + elif file_info.filename.endswith(".gpx"): + os.rename( + os.path.join(folder, f"{activity_id}_ACTIVITY.gpx"), + os.path.join(FOLDER_DICT["gpx"], f"{activity_id}.gpx"), + ) + else: + os.remove(os.path.join(folder, file_info.filename)) os.remove(file_path) except Exception as e: print(f"Failed to download activity {activity_id}: {str(e)}") @@ -320,4 +362,7 @@ async def download_new_activities( ) ) loop.run_until_complete(future) + # fit may contain gpx(maybe upload by user) + if file_type == "fit": + make_activities_file(SQL_FILE, FOLDER_DICT["gpx"], JSON_FILE, file_suffix="gpx") make_activities_file(SQL_FILE, folder, JSON_FILE, file_suffix=file_type) diff --git a/run_page/garmin_sync_cn_global.py b/run_page/garmin_sync_cn_global.py new file mode 100644 index 00000000000..8d4ab0cd58b --- /dev/null +++ b/run_page/garmin_sync_cn_global.py @@ -0,0 +1,105 @@ +""" +Python 3 API wrapper for Garmin Connect to get your statistics. +Copy most code from https://github.com/cyberjunky/python-garminconnect +""" + +import argparse +import asyncio +import logging +import os +import sys +import time +import traceback +import zipfile +from io import BytesIO + +import aiofiles +import cloudscraper +import garth +import httpx +from config import FIT_FOLDER, GPX_FOLDER, JSON_FILE, SQL_FILE, config +from garmin_device_adaptor import wrap_device_info +from garmin_sync import Garmin, get_downloaded_ids +from garmin_sync import download_new_activities, gather_with_concurrency +from synced_data_file_logger import load_synced_activity_list, save_synced_activity_list +from utils import make_activities_file + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument( + "cn_secret_string", nargs="?", help="secret_string fro get_garmin_secret.py" + ) + parser.add_argument( + "global_secret_string", nargs="?", help="secret_string fro get_garmin_secret.py" + ) + parser.add_argument( + "--only-run", + dest="only_run", + action="store_true", + help="if is only for running", + ) + + options = parser.parse_args() + secret_string_cn = options.cn_secret_string + secret_string_global = options.global_secret_string + auth_domain = "CN" + is_only_running = options.only_run + if secret_string_cn is None or secret_string_global is None: + print("Missing argument nor valid configuration file") + sys.exit(1) + + # Step 1: + # Sync all activities from Garmin CN to Garmin Global in FIT format + # If the activity is manually imported with a GPX, the GPX file will be synced + + # load synced activity list + synced_activity = load_synced_activity_list() + + folder = FIT_FOLDER + # make gpx or tcx dir + if not os.path.exists(folder): + os.mkdir(folder) + + loop = asyncio.get_event_loop() + future = asyncio.ensure_future( + download_new_activities( + secret_string_cn, + auth_domain, + synced_activity, + is_only_running, + folder, + "fit", + ) + ) + loop.run_until_complete(future) + new_ids = future.result() + + to_upload_files = [] + for i in new_ids: + if os.path.exists(os.path.join(FIT_FOLDER, f"{i}.fit")): + # upload fit files + to_upload_files.append(os.path.join(FIT_FOLDER, f"{i}.fit")) + elif os.path.exists(os.path.join(GPX_FOLDER, f"{i}.gpx")): + # upload gpx files which are manually uploaded to garmin connect + to_upload_files.append(os.path.join(GPX_FOLDER, f"{i}.gpx")) + + print("Files to sync:" + " ".join(to_upload_files)) + garmin_global_client = Garmin( + secret_string_global, + config("sync", "garmin", "authentication_domain"), + is_only_running, + ) + loop = asyncio.get_event_loop() + future = asyncio.ensure_future( + garmin_global_client.upload_activities_files(to_upload_files) + ) + loop.run_until_complete(future) + + # Save synced activity list for speeding up + synced_activity.extend(new_ids) + save_synced_activity_list(synced_activity) + + # Step 2: + # Generate track from fit/gpx file + make_activities_file(SQL_FILE, GPX_FOLDER, JSON_FILE, file_suffix="gpx") + make_activities_file(SQL_FILE, FIT_FOLDER, JSON_FILE, file_suffix="fit") diff --git a/run_page/garmin_to_strava_sync.py b/run_page/garmin_to_strava_sync.py index c484ebee7c5..d7fe8a0cf70 100644 --- a/run_page/garmin_to_strava_sync.py +++ b/run_page/garmin_to_strava_sync.py @@ -2,6 +2,7 @@ new garmin ids to strava; not the same logic as nike_to_strava_sync """ + import argparse import asyncio import os diff --git a/run_page/generator/__init__.py b/run_page/generator/__init__.py index 9d91210de70..9c798718a6f 100644 --- a/run_page/generator/__init__.py +++ b/run_page/generator/__init__.py @@ -156,3 +156,16 @@ def get_old_tracks_ids(self): # pass the error print(f"something wrong with {str(e)}") return [] + + def get_old_tracks_dates(self): + try: + activities = ( + self.session.query(Activity) + .order_by(Activity.start_date_local.desc()) + .all() + ) + return [str(a.start_date_local) for a in activities] + except Exception as e: + # pass the error + print(f"something wrong with {str(e)}") + return [] diff --git a/run_page/generator/db.py b/run_page/generator/db.py index 4f1ce9d6615..7ee681d7491 100644 --- a/run_page/generator/db.py +++ b/run_page/generator/db.py @@ -85,13 +85,18 @@ def update_or_create_activity(session, run_activity): if not location_country and start_point or location_country == "China": try: location_country = str( - g.reverse(f"{start_point.lat}, {start_point.lon}") + g.reverse( + f"{start_point.lat}, {start_point.lon}", language="zh-CN" + ) ) # limit (only for the first time) except Exception as e: try: location_country = str( - g.reverse(f"{start_point.lat}, {start_point.lon}") + g.reverse( + f"{start_point.lat}, {start_point.lon}", + language="zh-CN", + ) ) except Exception as e: pass diff --git a/run_page/gpxtrackposter/circular_drawer.py b/run_page/gpxtrackposter/circular_drawer.py index 6deecf1ff38..cfd0a945223 100644 --- a/run_page/gpxtrackposter/circular_drawer.py +++ b/run_page/gpxtrackposter/circular_drawer.py @@ -1,4 +1,5 @@ """Draw a circular Poster.""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/run_page/gpxtrackposter/grid_drawer.py b/run_page/gpxtrackposter/grid_drawer.py index b53784aebd3..e4ae66f9eac 100644 --- a/run_page/gpxtrackposter/grid_drawer.py +++ b/run_page/gpxtrackposter/grid_drawer.py @@ -1,4 +1,5 @@ """Draw a grid poster.""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/run_page/gpxtrackposter/poster.py b/run_page/gpxtrackposter/poster.py index 9d42667a34c..6f9a45cd692 100644 --- a/run_page/gpxtrackposter/poster.py +++ b/run_page/gpxtrackposter/poster.py @@ -1,4 +1,5 @@ """Create a poster from track data.""" + import gettext import locale from collections import defaultdict diff --git a/run_page/gpxtrackposter/track.py b/run_page/gpxtrackposter/track.py index c37f75a00df..88daacd1e8d 100644 --- a/run_page/gpxtrackposter/track.py +++ b/run_page/gpxtrackposter/track.py @@ -1,4 +1,5 @@ """Create and maintain info about a given activity track (corresponding to one GPX file).""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # 2019-now yihong0618 Florian Pigorsch & Contributors. All rights reserved. # Use of this source code is governed by a MIT-style @@ -12,14 +13,8 @@ import lxml import polyline import s2sphere as s2 -from fit_tool.fit_file import FitFile -from fit_tool.profile.messages.activity_message import ActivityMessage -from fit_tool.profile.messages.device_info_message import DeviceInfoMessage -from fit_tool.profile.messages.file_id_message import FileIdMessage -from fit_tool.profile.messages.record_message import RecordMessage -from fit_tool.profile.messages.session_message import SessionMessage -from fit_tool.profile.messages.software_message import SoftwareMessage -from fit_tool.profile.profile_type import Sport +from garmin_fit_sdk import Decoder, Stream +from garmin_fit_sdk.util import FIT_EPOCH_S from polyline_processor import filter_out from rich import print from tcxreader.tcxreader import TCXReader @@ -32,6 +27,13 @@ IGNORE_BEFORE_SAVING = os.getenv("IGNORE_BEFORE_SAVING", False) +# Garmin stores all latitude and longitude values as 32-bit integer values. +# This unit is called semicircle. +# So that gives 2^32 possible values. +# And to represent values up to 360° (or -180° to 180°), each 'degree' represents 2^32 / 360 = 11930465. +# So dividing latitude and longitude (int32) value by 11930465 will give the decimal value. +SEMICIRCLE = 11930465 + class Track: def __init__(self): @@ -92,9 +94,12 @@ def load_fit(self, file_name): # (for example, treadmill runs pulled via garmin-connect-export) if os.path.getsize(file_name) == 0: raise TrackLoadError("Empty FIT file") - - fit = FitFile.from_file(file_name) - self._load_fit_data(fit) + stream = Stream.from_file(file_name) + decoder = Decoder(stream) + messages, errors = decoder.read(convert_datetimes_to_dates=False) + if errors: + print(f"FIT file read fail: {errors}") + self._load_fit_data(messages) except Exception as e: print( f"Something went wrong when loading FIT. for file {self.file_names[0]}, we just ignore this file and continue" @@ -110,8 +115,10 @@ def load_from_db(self, activity): self.start_time_local = start_time self.end_time = start_time + activity.elapsed_time self.length = float(activity.distance) - if not IGNORE_BEFORE_SAVING: + if IGNORE_BEFORE_SAVING: summary_polyline = filter_out(activity.summary_polyline) + else: + summary_polyline = activity.summary_polyline polyline_data = polyline.decode(summary_polyline) if summary_polyline else [] self.polylines = [[s2.LatLng.from_degrees(p[0], p[1]) for p in polyline_data]] self.run_id = activity.run_id @@ -226,62 +233,60 @@ def _load_gpx_data(self, gpx): gpx.get_uphill_downhill().uphill if gpx.has_elevations() else None ) - def _load_fit_data(self, fit: FitFile): + def _load_fit_data(self, fit: dict): _polylines = [] self.polyline_container = [] + message = fit["session_mesgs"][0] + self.start_time = datetime.datetime.utcfromtimestamp( + (message["start_time"] + FIT_EPOCH_S) + ) + self.run_id = self.__make_run_id(self.start_time) + self.end_time = datetime.datetime.utcfromtimestamp( + (message["start_time"] + FIT_EPOCH_S + message["total_elapsed_time"]) + ) + self.length = message["total_distance"] + self.average_heartrate = ( + message["avg_heart_rate"] if "avg_heart_rate" in message else None + ) + self.total_elevation_gain = ( + message["total_ascent"] if "total_ascent" in message else None + ) + self.type = message["sport"].lower() - for record in fit.records: - message = record.message - - if isinstance(message, RecordMessage): - if message.position_lat and message.position_long: - _polylines.append( - s2.LatLng.from_degrees( - message.position_lat, message.position_long - ) - ) - self.polyline_container.append( - [message.position_lat, message.position_long] - ) - elif isinstance(message, SessionMessage): - self.start_time = datetime.datetime.utcfromtimestamp( - message.start_time / 1000 - ) - self.run_id = message.start_time - self.end_time = datetime.datetime.utcfromtimestamp( - (message.start_time + message.total_elapsed_time * 1000) / 1000 - ) - self.length = message.total_distance - self.average_heartrate = ( - message.avg_heart_rate if message.avg_heart_rate != 0 else None - ) - self.total_elevation_gain = ( - message.total_ascent if message.total_ascent != 0 else None - ) - self.type = Sport(message.sport).name.lower() - - # moving_dict - self.moving_dict["distance"] = message.total_distance - self.moving_dict["moving_time"] = datetime.timedelta( - seconds=message.total_moving_time - if message.total_moving_time - else message.total_timer_time - ) - self.moving_dict["elapsed_time"] = datetime.timedelta( - seconds=message.total_elapsed_time - ) - self.moving_dict["average_speed"] = ( - message.enhanced_avg_speed - if message.enhanced_avg_speed - else message.avg_speed - ) - - self.start_time_local, self.end_time_local = parse_datetime_to_local( - self.start_time, self.end_time, self.polyline_container[0] + # moving_dict + self.moving_dict["distance"] = message["total_distance"] + self.moving_dict["moving_time"] = datetime.timedelta( + seconds=( + message["total_moving_time"] + if "total_moving_time" in message + else message["total_timer_time"] + ) + ) + self.moving_dict["elapsed_time"] = datetime.timedelta( + seconds=message["total_elapsed_time"] ) - self.start_latlng = start_point(*self.polyline_container[0]) - self.polylines.append(_polylines) - self.polyline_str = polyline.encode(self.polyline_container) + self.moving_dict["average_speed"] = ( + message["enhanced_avg_speed"] + if message["enhanced_avg_speed"] + else message["avg_speed"] + ) + for record in fit["record_mesgs"]: + if "position_lat" in record and "position_long" in record: + lat = record["position_lat"] / SEMICIRCLE + lng = record["position_long"] / SEMICIRCLE + _polylines.append(s2.LatLng.from_degrees(lat, lng)) + self.polyline_container.append([lat, lng]) + if self.polyline_container: + self.start_time_local, self.end_time_local = parse_datetime_to_local( + self.start_time, self.end_time, self.polyline_container[0] + ) + self.start_latlng = start_point(*self.polyline_container[0]) + self.polylines.append(_polylines) + self.polyline_str = polyline.encode(self.polyline_container) + else: + self.start_time_local, self.end_time_local = parse_datetime_to_local( + self.start_time, self.end_time, None + ) def append(self, other): """Append other track to self.""" @@ -319,9 +324,11 @@ def _get_moving_data(gpx): "elapsed_time": datetime.timedelta( seconds=(moving_data.moving_time + moving_data.stopped_time) ), - "average_speed": moving_data.moving_distance / moving_data.moving_time - if moving_data.moving_time - else 0, + "average_speed": ( + moving_data.moving_distance / moving_data.moving_time + if moving_data.moving_time + else 0 + ), } def to_namedtuple(self): @@ -334,12 +341,12 @@ def to_namedtuple(self): "start_date_local": self.start_time_local.strftime("%Y-%m-%d %H:%M:%S"), "end_local": self.end_time_local.strftime("%Y-%m-%d %H:%M:%S"), "length": self.length, - "average_heartrate": int(self.average_heartrate) - if self.average_heartrate - else None, - "total_elevation_gain": int(self.total_elevation_gain) - if self.total_elevation_gain - else None, + "average_heartrate": ( + int(self.average_heartrate) if self.average_heartrate else None + ), + "total_elevation_gain": ( + int(self.total_elevation_gain) if self.total_elevation_gain else None + ), "map": run_map(self.polyline_str), "start_latlng": self.start_latlng, } diff --git a/run_page/gpxtrackposter/track_loader.py b/run_page/gpxtrackposter/track_loader.py index b63cb9ba969..260b92af03b 100644 --- a/run_page/gpxtrackposter/track_loader.py +++ b/run_page/gpxtrackposter/track_loader.py @@ -1,6 +1,5 @@ """Handle parsing of GPX files""" - # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # 2019-now Yihong0618 # @@ -67,7 +66,7 @@ def __init__(self): "fit": load_fit_file, } - def load_tracks(self, data_dir, file_suffix): + def load_tracks(self, data_dir, file_suffix="gpx"): """Load tracks data_dir and return as a List of tracks""" file_names = [x for x in self._list_data_files(data_dir, file_suffix)] print(f"{file_suffix.upper()} files: {len(file_names)}") diff --git a/run_page/gpxtrackposter/tracks_drawer.py b/run_page/gpxtrackposter/tracks_drawer.py index fa42e0f332f..912de8ce952 100644 --- a/run_page/gpxtrackposter/tracks_drawer.py +++ b/run_page/gpxtrackposter/tracks_drawer.py @@ -1,4 +1,5 @@ """Contains the base class TracksDrawer, which other Drawers inherit from.""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/run_page/gpxtrackposter/utils.py b/run_page/gpxtrackposter/utils.py index b2234c97484..45acfadd85f 100644 --- a/run_page/gpxtrackposter/utils.py +++ b/run_page/gpxtrackposter/utils.py @@ -1,4 +1,5 @@ """Assorted utility methods for use in creating posters.""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style @@ -129,16 +130,19 @@ def format_float(f): def parse_datetime_to_local(start_time, end_time, point): - # just parse the start time, because start/end maybe different - offset = start_time.utcoffset() - if offset: - return start_time + offset, end_time + offset - lat, lng = point - try: - timezone = get_tz(lng=lng, lat=lat) - except: - # just a little trick when tzfpy support windows will delete this + if not point: + timezone = "Asia/Shanghai" + else: + # just parse the start time, because start/end maybe different + offset = start_time.utcoffset() + if offset: + return start_time + offset, end_time + offset lat, lng = point - timezone = tf.timezone_at(lng=lng, lat=lat) + try: + timezone = get_tz(lng=lng, lat=lat) + except: + # just a little trick when tzfpy support windows will delete this + lat, lng = point + timezone = tf.timezone_at(lng=lng, lat=lat) tc_offset = datetime.now(pytz.timezone(timezone)).utcoffset() return start_time + tc_offset, end_time + tc_offset diff --git a/run_page/gpxtrackposter/value_range.py b/run_page/gpxtrackposter/value_range.py index 740c0dc03c5..907e55e0df1 100644 --- a/run_page/gpxtrackposter/value_range.py +++ b/run_page/gpxtrackposter/value_range.py @@ -1,4 +1,5 @@ """Represent a range of numerical values""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/run_page/gpxtrackposter/xy.py b/run_page/gpxtrackposter/xy.py index 507006c2bb6..c8ecd59e956 100644 --- a/run_page/gpxtrackposter/xy.py +++ b/run_page/gpxtrackposter/xy.py @@ -1,4 +1,5 @@ """Represent x,y coords with properly overloaded operations.""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/run_page/gpxtrackposter/year_range.py b/run_page/gpxtrackposter/year_range.py index d38da273cda..dacb977b9d9 100644 --- a/run_page/gpxtrackposter/year_range.py +++ b/run_page/gpxtrackposter/year_range.py @@ -1,4 +1,5 @@ """Represent a range of years, with ability to update based on a track""" + # Copyright 2016-2019 Florian Pigorsch & Contributors. All rights reserved. # # Use of this source code is governed by a MIT-style diff --git a/run_page/joyrun_sync.py b/run_page/joyrun_sync.py index 3f738cc7637..6441f198f62 100755 --- a/run_page/joyrun_sync.py +++ b/run_page/joyrun_sync.py @@ -130,7 +130,9 @@ def __update_loginInfo(self): self.session.headers.update({"ypcookie": loginCookie}) self.session.cookies.clear() self.session.cookies.set("ypcookie", quote(loginCookie).lower()) - self.session.headers.update(self.device_info_headers) # 更新设备信息中的 uid 字段 + self.session.headers.update( + self.device_info_headers + ) # 更新设备信息中的 uid 字段 def login_by_phone(self): params = { @@ -184,18 +186,38 @@ def parse_content_to_ponits(content): return points @staticmethod - def parse_points_to_gpx(run_points_data, start_time, end_time, interval=5): - # TODO for now kind of same as `keep` maybe refactor later + def parse_points_to_gpx( + run_points_data, start_time, end_time, pause_list, interval=5 + ): + """ + parse run_data content to gpx object + TODO for now kind of same as `keep` maybe refactor later + + :param run_points_data: [[latitude, longitude],...] + :param pause_list: [[interval_index, pause_seconds],...] + :param interval: time interval between each point, in seconds + """ + + # format data + segment_list = [] points_dict_list = [] - i = 0 - for point in run_points_data[:-1]: + current_time = start_time + + for index, point in enumerate(run_points_data[:-1]): points_dict = { "latitude": point[0], "longitude": point[1], - "time": datetime.utcfromtimestamp(start_time + interval * i), + "time": datetime.utcfromtimestamp(current_time), } - i += 1 points_dict_list.append(points_dict) + + current_time += interval + if pause_list and int(pause_list[0][0]) - 1 == index: + segment_list.append(points_dict_list[:]) + points_dict_list.clear() + current_time += int(pause_list[0][1]) + pause_list.pop(0) + points_dict_list.append( { "latitude": run_points_data[-1][0], @@ -203,18 +225,22 @@ def parse_points_to_gpx(run_points_data, start_time, end_time, interval=5): "time": datetime.utcfromtimestamp(end_time), } ) + segment_list.append(points_dict_list) + + # gpx part gpx = gpxpy.gpx.GPX() gpx.nsmap["gpxtpx"] = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1" gpx_track = gpxpy.gpx.GPXTrack() gpx_track.name = "gpx from joyrun" gpx.tracks.append(gpx_track) - # Create first segment in our GPX track: - gpx_segment = gpxpy.gpx.GPXTrackSegment() - gpx_track.segments.append(gpx_segment) - for p in points_dict_list: - point = gpxpy.gpx.GPXTrackPoint(**p) - gpx_segment.points.append(point) + # add segment list to our GPX track: + for point_list in segment_list: + gpx_segment = gpxpy.gpx.GPXTrackSegment() + gpx_track.segments.append(gpx_segment) + for p in point_list: + point = gpxpy.gpx.GPXTrackPoint(**p) + gpx_segment.points.append(point) return gpx.to_xml() @@ -237,12 +263,13 @@ def parse_raw_data_to_nametuple(self, run_data, old_gpx_ids, with_gpx=False): start_time = run_data["starttime"] end_time = run_data["endtime"] + pause_list = run_data["pause"] run_points_data = self.parse_content_to_ponits(run_data["content"]) if with_gpx: # pass the track no points if run_points_data: gpx_data = self.parse_points_to_gpx( - run_points_data, start_time, end_time + run_points_data, start_time, end_time, pause_list ) download_joyrun_gpx(gpx_data, str(joyrun_id)) try: diff --git a/run_page/keep_sync.py b/run_page/keep_sync.py index 7f45f47fcc8..c4c85ced5b8 100755 --- a/run_page/keep_sync.py +++ b/run_page/keep_sync.py @@ -17,10 +17,17 @@ from utils import adjust_time import xml.etree.ElementTree as ET +KEEP_SPORT_TYPES = ["running", "hiking", "cycling"] +KEEP2STRAVA = { + "outdoorWalking": "Walk", + "outdoorRunning": "Run", + "outdoorCycling": "Ride", + "indoorRunning": "VirtualRun", +} # need to test LOGIN_API = "https://api.gotokeep.com/v1.1/users/login" -RUN_DATA_API = "https://api.gotokeep.com/pd/v3/stats/detail?dateUnit=all&type=running&lastDate={last_date}" -RUN_LOG_API = "https://api.gotokeep.com/pd/v3/runninglog/{run_id}" +RUN_DATA_API = "https://api.gotokeep.com/pd/v3/stats/detail?dateUnit=all&type={sport_type}&lastDate={last_date}" +RUN_LOG_API = "https://api.gotokeep.com/pd/v3/{sport_type}log/{run_id}" HR_FRAME_THRESHOLD_IN_DECISECOND = 100 # Maximum time difference to consider a data point as the nearest, the unit is decisecond(分秒) @@ -43,11 +50,15 @@ def login(session, mobile, password): return session, headers -def get_to_download_runs_ids(session, headers): +def get_to_download_runs_ids(session, headers, sport_type): last_date = 0 result = [] + while 1: - r = session.get(RUN_DATA_API.format(last_date=last_date), headers=headers) + r = session.get( + RUN_DATA_API.format(sport_type=sport_type, last_date=last_date), + headers=headers, + ) if r.ok: run_logs = r.json()["data"]["records"] @@ -63,8 +74,10 @@ def get_to_download_runs_ids(session, headers): return result -def get_single_run_data(session, headers, run_id): - r = session.get(RUN_LOG_API.format(run_id=run_id), headers=headers) +def get_single_run_data(session, headers, run_id, sport_type): + r = session.get( + RUN_LOG_API.format(sport_type=sport_type, run_id=run_id), headers=headers + ) if r.ok: return r.json() @@ -82,7 +95,10 @@ def decode_runmap_data(text, is_geo=False): def parse_raw_data_to_nametuple( - run_data, old_gpx_ids, session, with_download_gpx=False + run_data, + old_gpx_ids, + session, + with_download_gpx=False, ): run_data = run_data["data"] run_points_data = [] @@ -119,11 +135,12 @@ def parse_raw_data_to_nametuple( if p_hr: p["hr"] = p_hr if with_download_gpx: - if ( - str(keep_id) not in old_gpx_ids - and run_data["dataType"] == "outdoorRunning" + if str(keep_id) not in old_gpx_ids and run_data["dataType"].startswith( + "outdoor" ): - gpx_data = parse_points_to_gpx(run_points_data_gpx, start_time) + gpx_data = parse_points_to_gpx( + run_points_data_gpx, start_time, KEEP2STRAVA[run_data["dataType"]] + ) download_keep_gpx(gpx_data, str(keep_id)) else: print(f"ID {keep_id} no gps data") @@ -139,9 +156,9 @@ def parse_raw_data_to_nametuple( return d = { "id": int(keep_id), - "name": "run from keep", + "name": f"{KEEP2STRAVA[run_data['dataType']]} from keep", # future to support others workout now only for run - "type": "Run", + "type": f"{KEEP2STRAVA[(run_data['dataType'])]}", "start_date": datetime.strftime(start_date, "%Y-%m-%d %H:%M:%S"), "end": datetime.strftime(end, "%Y-%m-%d %H:%M:%S"), "start_date_local": datetime.strftime(start_date_local, "%Y-%m-%d %H:%M:%S"), @@ -162,31 +179,34 @@ def parse_raw_data_to_nametuple( return namedtuple("x", d.keys())(*d.values()) -def get_all_keep_tracks(email, password, old_tracks_ids, with_download_gpx=False): +def get_all_keep_tracks( + email, password, old_tracks_ids, keep_sports_data_api, with_download_gpx=False +): if with_download_gpx and not os.path.exists(GPX_FOLDER): os.mkdir(GPX_FOLDER) s = requests.Session() s, headers = login(s, email, password) - runs = get_to_download_runs_ids(s, headers) - runs = [run for run in runs if run.split("_")[1] not in old_tracks_ids] - print(f"{len(runs)} new keep runs to generate") tracks = [] - old_gpx_ids = os.listdir(GPX_FOLDER) - old_gpx_ids = [i.split(".")[0] for i in old_gpx_ids if not i.startswith(".")] - for run in runs: - print(f"parsing keep id {run}") - try: - run_data = get_single_run_data(s, headers, run) - track = parse_raw_data_to_nametuple( - run_data, old_gpx_ids, s, with_download_gpx - ) - tracks.append(track) - except Exception as e: - print(f"Something wrong paring keep id {run}" + str(e)) + for api in keep_sports_data_api: + runs = get_to_download_runs_ids(s, headers, api) + runs = [run for run in runs if run.split("_")[1] not in old_tracks_ids] + print(f"{len(runs)} new keep {api} data to generate") + old_gpx_ids = os.listdir(GPX_FOLDER) + old_gpx_ids = [i.split(".")[0] for i in old_gpx_ids if not i.startswith(".")] + for run in runs: + print(f"parsing keep id {run}") + try: + run_data = get_single_run_data(s, headers, run, api) + track = parse_raw_data_to_nametuple( + run_data, old_gpx_ids, s, with_download_gpx + ) + tracks.append(track) + except Exception as e: + print(f"Something wrong paring keep id {run}" + str(e)) return tracks -def parse_points_to_gpx(run_points_data, start_time): +def parse_points_to_gpx(run_points_data, start_time, sport_type): """ Convert run points data to GPX format. @@ -220,7 +240,7 @@ def parse_points_to_gpx(run_points_data, start_time): gpx.nsmap["gpxtpx"] = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1" gpx_track = gpxpy.gpx.GPXTrack() gpx_track.name = "gpx from keep" - gpx_track.type = "running" + gpx_track.type = sport_type gpx.tracks.append(gpx_track) # Create first segment in our GPX track: @@ -294,15 +314,18 @@ def download_keep_gpx(gpx_data, keep_id): file_path = os.path.join(GPX_FOLDER, str(keep_id) + ".gpx") with open(file_path, "w") as fb: fb.write(gpx_data) + return file_path except: print(f"wrong id {keep_id}") pass -def run_keep_sync(email, password, with_download_gpx=False): +def run_keep_sync(email, password, keep_sports_data_api, with_download_gpx=False): generator = Generator(SQL_FILE) old_tracks_ids = generator.get_old_tracks_ids() - new_tracks = get_all_keep_tracks(email, password, old_tracks_ids, with_download_gpx) + new_tracks = get_all_keep_tracks( + email, password, old_tracks_ids, keep_sports_data_api, with_download_gpx + ) generator.sync_from_app(new_tracks) activities_list = generator.load() @@ -314,6 +337,13 @@ def run_keep_sync(email, password, with_download_gpx=False): parser = argparse.ArgumentParser() parser.add_argument("phone_number", help="keep login phone number") parser.add_argument("password", help="keep login password") + parser.add_argument( + "--sync-types", + dest="sync_types", + nargs="+", + default=["running"], + help="sync sport types from keep, default is running, you can choose from running, hiking, cycling", + ) parser.add_argument( "--with-gpx", dest="with_gpx", @@ -321,4 +351,10 @@ def run_keep_sync(email, password, with_download_gpx=False): help="get all keep data to gpx and download", ) options = parser.parse_args() - run_keep_sync(options.phone_number, options.password, options.with_gpx) + for _tpye in options.sync_types: + assert ( + _tpye in KEEP_SPORT_TYPES + ), f"{_tpye} are not supported type, please make sure that the type entered in the {KEEP_SPORT_TYPES}" + run_keep_sync( + options.phone_number, options.password, options.sync_types, options.with_gpx + ) diff --git a/run_page/keep_to_strava_sync.py b/run_page/keep_to_strava_sync.py new file mode 100644 index 00000000000..38647b1871b --- /dev/null +++ b/run_page/keep_to_strava_sync.py @@ -0,0 +1,146 @@ +import argparse +import json +import os +from sre_constants import SUCCESS +import time +from collections import namedtuple +import requests +from config import GPX_FOLDER +from Crypto.Cipher import AES +from config import OUTPUT_DIR +from stravalib.exc import ActivityUploadFailed, RateLimitTimeout +from utils import make_strava_client, upload_file_to_strava +from keep_sync import KEEP_DATA_TYPE_API, get_all_keep_tracks +from strava_sync import run_strava_sync + +""" +Only provide the ability to sync data from Keep's multiple sport types to Strava's corresponding sport types to help those who use multiple devices like me, the web page presentation still uses Strava (or refer to nike_to_strava_sync.py to modify it to suit you). +My own best practices: +1. running/hiking/Cycling (Huawei/OPPO) -> Keep +2. Keep -> Strava (add this scripts to run_data_sync.yml) +3. Road Cycling(Garmin) -> Strava. +4. running_page(Strava) + +""" +KEEP2STRAVA_BK_PATH = os.path.join(OUTPUT_DIR, "keep2strava.json") + + +def run_keep_sync(email, password, keep_sports_data_api, with_download_gpx=False): + + if not os.path.exists(KEEP2STRAVA_BK_PATH): + file = open(KEEP2STRAVA_BK_PATH, "w") + file.close() + content = [] + else: + with open(KEEP2STRAVA_BK_PATH) as f: + try: + content = json.loads(f.read()) + except: + content = [] + old_tracks_ids = [str(a["run_id"]) for a in content] + _new_tracks = get_all_keep_tracks( + email, password, old_tracks_ids, keep_sports_data_api, True + ) + new_tracks = [] + for track in _new_tracks: + # By default only outdoor sports have latlng as well as GPX. + if track.start_latlng is not None: + file_path = namedtuple("x", "gpx_file_path")( + os.path.join(GPX_FOLDER, str(track.id) + ".gpx") + ) + else: + file_path = namedtuple("x", "gpx_file_path")(None) + track = namedtuple("y", track._fields + file_path._fields)(*(track + file_path)) + new_tracks.append(track) + + return new_tracks + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("phone_number", help="keep login phone number") + parser.add_argument("password", help="keep login password") + parser.add_argument("client_id", help="strava client id") + parser.add_argument("client_secret", help="strava client secret") + parser.add_argument("strava_refresh_token", help="strava refresh token") + parser.add_argument( + "--sync-types", + dest="sync_types", + nargs="+", + default=["running"], + help="sync sport types from keep, default is running, you can choose from running, hiking, cycling", + ) + + options = parser.parse_args() + for api in options.sync_types: + assert ( + api in KEEP_DATA_TYPE_API + ), f"{api} are not supported type, please make sure that the type entered in the {KEEP_DATA_TYPE_API}" + new_tracks = run_keep_sync( + options.phone_number, options.password, options.sync_types, True + ) + + # to strava. + print("Need to load all gpx files maybe take some time") + last_time = 0 + client = make_strava_client( + options.client_id, options.client_secret, options.strava_refresh_token + ) + + index = 1 + print(f"Up to {len(new_tracks)} files are waiting to be uploaded") + uploaded_file_paths = [] + for track in new_tracks: + if track.gpx_file_path is not None: + try: + upload_file_to_strava(client, track.gpx_file_path, "gpx", False) + uploaded_file_paths.append(track) + except RateLimitTimeout as e: + timeout = e.timeout + print(f"Strava API Rate Limit Timeout. Retry in {timeout} seconds\n") + time.sleep(timeout) + # try previous again + upload_file_to_strava(client, track.gpx_file_path, "gpx", False) + uploaded_file_paths.append(track) + except ActivityUploadFailed as e: + print(f"Upload faild error {str(e)}") + # spider rule + time.sleep(1) + else: + # for no gps data, like indoorRunning. + uploaded_file_paths.append(track) + time.sleep(10) + + # This file is used to record which logs have been uploaded to strava + # to avoid intrusion into the data.db resulting in double counting of data. + with open(KEEP2STRAVA_BK_PATH, "r") as f: + try: + content = json.loads(f.read()) + except: + content = [] + + # Extend and Save the successfully uploaded log to the backup file. + content.extend( + [ + dict( + run_id=track.id, + name=track.name, + type=track.type, + gpx_file_path=track.gpx_file_path, + ) + for track in uploaded_file_paths + ] + ) + with open(KEEP2STRAVA_BK_PATH, "w") as f: + json.dump(content, f, indent=0) + + # del the uploaded GPX file. + for track in uploaded_file_paths: + if track.gpx_file_path is not None: + os.remove(track.gpx_file_path) + else: + continue + + run_strava_sync( + options.client_id, options.client_secret, options.strava_refresh_token + ) diff --git a/run_page/nike_sync.py b/run_page/nike_sync.py index d2adfbd34ab..04c72f55e3d 100644 --- a/run_page/nike_sync.py +++ b/run_page/nike_sync.py @@ -1,4 +1,5 @@ import argparse +from base64 import b64decode import json import logging import os.path @@ -11,13 +12,10 @@ import httpx from config import ( BASE_TIMEZONE, - BASE_URL, GPX_FOLDER, JSON_FILE, - NIKE_CLIENT_ID, OUTPUT_DIR, SQL_FILE, - TOKEN_REFRESH_URL, run_map, ) from generator import Generator @@ -26,6 +24,16 @@ # logging.basicConfig(level=logging.INFO) logger = logging.getLogger("nike_sync") +BASE_URL = "https://api.nike.com/sport/v3/me" +TOKEN_REFRESH_URL = "https://api.nike.com/idn/shim/oauth/2.0/token" +NIKE_CLIENT_ID = "VmhBZWFmRUdKNkc4ZTlEeFJVejhpRTUwQ1o5TWlKTUc=" +NIKE_UX_ID = "Y29tLm5pa2Uuc3BvcnQucnVubmluZy5pb3MuNS4xNQ==" +NIKE_HEADERS = { + "Host": "api.nike.com", + "Accept": "application/json", + "Content-Type": "application/json", +} + class Nike: def __init__(self, refresh_token): @@ -33,10 +41,12 @@ def __init__(self, refresh_token): response = self.client.post( TOKEN_REFRESH_URL, + headers=NIKE_HEADERS, json={ "refresh_token": refresh_token, - "client_id": NIKE_CLIENT_ID, + "client_id": b64decode(NIKE_CLIENT_ID).decode(), "grant_type": "refresh_token", + "ux_id": b64decode(NIKE_UX_ID).decode(), }, timeout=60, ) diff --git a/run_page/oppo_sync.py b/run_page/oppo_sync.py new file mode 100644 index 00000000000..cf5bf803f6b --- /dev/null +++ b/run_page/oppo_sync.py @@ -0,0 +1,727 @@ +import argparse +import hashlib +import json +import os +import time +import xml.etree.ElementTree as ET +from collections import namedtuple +from datetime import datetime, timedelta +from xml.dom import minidom + +import gpxpy +import polyline +import requests +from tzlocal import get_localzone + +from config import ( + GPX_FOLDER, + JSON_FILE, + SQL_FILE, + run_map, + start_point, + TCX_FOLDER, + UTC_TIMEZONE, +) +from generator import Generator +from utils import adjust_time + +TOKEN_REFRESH_URL = "https://sport.health.heytapmobi.com/open/v1/oauth/token" +OPPO_HEADERS = { + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", + "Content-Type": "application/json", + "Accept": "application/json", +} + +# Query brief version of sports records +# The query range cannot exceed one month! +""" +Return value is like: +[ + { + "dataType": 2,//运动数类型 1=健身类 2=其他运动类 + "startTime": 1630323565000, //开始时间 单位毫秒 + "endTime": 1630337130000,//结束时间 单位毫秒 + "sportMode": 10,//运动模式 室内跑 详情见文档附录 + "otherSportData": { + "avgHeartRate": 153,//平均心率 单位:count/min + "avgPace": 585,//平均配速 单位s/km + "avgStepRate": 115,//平均步频 单位step/min + "bestStepRate": 135,//最佳步频 单位step/min + "bestPace": 572,//最佳配速 单位s/km + "totalCalories": 2176000,//总消耗 单位卡 + "totalDistance": 23175,//总距离 单位米 + "totalSteps": 26062,//总步数 + "totalTime": 13562000,//总时长,单位:毫秒 + "totalClimb": 100//累计爬升高度,单位:米 + }, + }, + { + "dataType": 1,//运动数类型 1=健身类 2=其他运动类 + "startTime": 1630293981497 //开始时间 单位毫秒 + "endTime": 1630294218127,//结束时间 单位毫秒 + "sportMode": 9,//运动模式 健身 详情见文档附录 + "fitnessData": { + "avgHeartRate": 90,//平均心率 单位:count/min + "courseName": "零基础减脂碎片练习",//课程名称 + "finishNumber": 1,//课程完成次数 + "trainedCalorie": 13554,//训练消耗的卡路里,单位:卡 + "trainedDuration": 176000//实际训练时间,单位:ms + }, + } +] +""" +BRIEF_SPORT_DATA_API = "https://sport.health.heytapmobi.com/open/v1/data/sport/record?startTimeMillis={start_time}&endTimeMillis={end_time}" + +# Query detailed sports records +# The query range cannot exceed one day! +DETAILED_SPORT_DATA_API = "https://sport.health.heytapmobi.com/open/v2/data/sport/record?startTimeMillis={start_time}&endTimeMillis={end_time}" + +TIMESTAMP_THRESHOLD_IN_MILLISECOND = 5000 + +# If your points need trans from gcj02 to wgs84 coordinate which use by Mapbox +TRANS_GCJ02_TO_WGS84 = True + +# May be Forerunner 945? +CONNECT_API_PART_NUMBER = "006-D2449-00" + +AVAILABLE_OUTDOOR_SPORT_MODE = [ + 1, # WALK + 2, # RUN + 3, # RIDE + 13, # OUTDOOR_PHYSICAL_RUN + 15, # OUTDOOR_5KM_RELAX_RUN + 17, # OUTDOOR_FAT_REDUCE_RUN + 22, # MARATHON + 36, # MOUNTAIN_CLIMBING + 37, # CROSS_COUNTRY +] + +AVAILABLE_INDOOR_SPORT_MODE = [ + 10, # INDOOR_RUN + 14, # INDOOR_PHYSICAL_RUN + 16, # INDOOR_5KM_RELAX_RUN + 18, # INDOOR_FAT_REDUCE_RUN + 19, # INDOOR_FITNESS_WALK + 21, # TREADMILL_RUN +] + + +def get_access_token(session, client_id, client_secret, refresh_token): + headers = { + "User-Agent": "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0", + "Content-Type": "application/json", + "Accept": "application/json", + } + data = { + "clientId": client_id, + "clientSecret": client_secret, + "refreshToken": refresh_token, + "grantType": "refreshToken", + } + r = session.post(TOKEN_REFRESH_URL, headers=headers, json=data) + if r.ok: + token = r.json()["body"]["accessToken"] + headers["access-token"] = token + return session, headers + + +def get_to_download_runs_ranges(session, sync_months, headers, start_timestamp): + result = [] + current_time = datetime.now() + start_datatime = datetime.fromtimestamp(start_timestamp / 1000) + + if start_datatime < current_time + timedelta(days=-30 * sync_months): + """retrieve the data of last 6 months.""" + while sync_months >= 0: + temp_end = int(current_time.timestamp() * 1000) + current_time = current_time + timedelta(days=-30) + temp_start = int(current_time.timestamp() * 1000) + sync_months = sync_months - 1 + result.extend( + parse_brief_sport_data(session, headers, temp_start, temp_end) + ) + else: + while start_datatime < current_time: + temp_start = int(start_datatime.timestamp() * 1000) + start_datatime = start_datatime + timedelta(days=30) + temp_end = int(start_datatime.timestamp() * 1000) + result.extend( + parse_brief_sport_data(session, headers, temp_start, temp_end) + ) + return result + + +def parse_brief_sport_data(session, headers, temp_start, temp_end): + result = [] + r = session.get( + BRIEF_SPORT_DATA_API.format(end_time=temp_end, start_time=temp_start), + headers=headers, + ) + if r.ok: + sport_logs = r.json()["body"] + for i in sport_logs: + if ( + i["sportMode"] in AVAILABLE_INDOOR_SPORT_MODE + or i["sportMode"] in AVAILABLE_OUTDOOR_SPORT_MODE + ): + result.append((i["startTime"], i["endTime"])) + print(f"sync record: start_time: " + str(i["startTime"])) + time.sleep(1) # spider rule + return result + + +def get_single_run_data(session, headers, start, end): + r = session.get( + DETAILED_SPORT_DATA_API.format(end_time=end, start_time=start), headers=headers + ) + if r.ok: + return r.json() + + +def parse_raw_data_to_name_tuple(sport_data, with_gpx, with_tcx): + sport_data = sport_data["body"][0] + m = hashlib.md5() + m.update(str.encode(str(sport_data))) + oppo_id_str = str(int(m.hexdigest(), 16))[0:16] + oppo_id = int(oppo_id_str) + + sport_data["id"] = oppo_id + start_time = sport_data["startTime"] + other_data = sport_data["otherSportData"] + avg_heart_rate = None + if other_data: + avg_heart_rate = other_data.get("avgHeartRate", None) + # fix #66 + if avg_heart_rate and avg_heart_rate < 0: + avg_heart_rate = None + + # if TRANS_GCJ02_TO_WGS84: + # run_points_data = [ + # list(eviltransform.gcj2wgs(p["latitude"], p["longitude"])) + # for p in run_points_data + # ] + # for i, p in enumerate(run_points_data_gpx): + # p["latitude"] = run_points_data[i][0] + # p["longitude"] = run_points_data[i][1] + + point_dict = prepare_track_points(sport_data, with_gpx) + + if with_gpx is True: + gpx_data = parse_points_to_gpx(sport_data, point_dict) + download_keep_gpx(gpx_data, str(oppo_id)) + if with_tcx is True: + parse_points_to_tcx(sport_data, point_dict) + + else: + print(f"ID {oppo_id} no gps data") + + gps_data = [ + (item["latitude"], item["longitude"]) for item in other_data["gpsPoint"] + ] + polyline_str = polyline.encode(gps_data) if gps_data else "" + start_latlng = start_point(*gps_data[0]) if gps_data else None + start_date = datetime.utcfromtimestamp(start_time / 1000) + start_date_local = adjust_time(start_date, str(get_localzone())) + end = datetime.utcfromtimestamp(sport_data["endTime"] / 1000) + end_local = adjust_time(end, str(get_localzone())) + location_country = None + if not other_data["totalTime"]: + print(f"ID {oppo_id} has no total time just ignore please check") + return + d = { + "id": int(oppo_id), + "name": "activity from oppo", + # future to support others workout now only for run + "type": map_oppo_fit_type_to_strava_activity_type(sport_data["sportMode"]), + "start_date": datetime.strftime(start_date, "%Y-%m-%d %H:%M:%S"), + "end": datetime.strftime(end, "%Y-%m-%d %H:%M:%S"), + "start_date_local": datetime.strftime(start_date_local, "%Y-%m-%d %H:%M:%S"), + "end_local": datetime.strftime(end_local, "%Y-%m-%d %H:%M:%S"), + "length": other_data["totalDistance"], + "average_heartrate": int(avg_heart_rate) if avg_heart_rate else None, + "map": run_map(polyline_str), + "start_latlng": start_latlng, + "distance": other_data["totalDistance"], + "moving_time": timedelta(seconds=other_data["totalTime"]), + "elapsed_time": timedelta( + seconds=int((sport_data["endTime"] - sport_data["startTime"]) / 1000) + ), + "average_speed": other_data["totalDistance"] / other_data["totalTime"] * 1000, + "location_country": location_country, + "source": sport_data["deviceName"], + } + return namedtuple("x", d.keys())(*d.values()) + + +def get_all_oppo_tracks( + client_id, + client_secret, + refresh_token, + sync_months, + last_track_date, + with_download_gpx, + with_download_tcx, +): + if with_download_gpx and not os.path.exists(GPX_FOLDER): + os.mkdir(GPX_FOLDER) + s = requests.Session() + s, headers = get_access_token(s, client_id, client_secret, refresh_token) + + last_timestamp = ( + 0 + if (last_track_date == 0) + else int( + datetime.timestamp(datetime.strptime(last_track_date, "%Y-%m-%d %H:%M:%S")) + * 1000 + ) + ) + + runs = get_to_download_runs_ranges(s, sync_months, headers, last_timestamp + 1000) + print(f"{len(runs)} new oppo runs to generate") + tracks = [] + for start, end in runs: + print(f"parsing oppo id {str(start)}-{str(end)}") + try: + run_data = get_single_run_data(s, headers, start, end) + track = parse_raw_data_to_name_tuple( + run_data, with_download_gpx, with_download_tcx + ) + tracks.append(track) + except Exception as e: + print(f"Something wrong paring keep id {str(start)}-{str(end)}" + str(e)) + return tracks + + +def switch(v): + yield lambda *c: v in c + + +def map_oppo_fit_type_to_gpx_type(oppo_type): + for case in switch(oppo_type): + if case(1): # WALK + return "Walking" + if case(2, 13, 15, 17, 22, 10, 14, 16, 18, 21, 37): + # RUN | + # OUTDOOR_PHYSICAL_RUN | + # OUTDOOR_5KM_RELAX_RUN | + # OUTDOOR_FAT_REDUCE_RUN | + # MARATHON + # INDOOR_RUN, etc. + # CROSS_COUNTRY + return "Running" + if case(19): # MOUNTAIN_CLIMBING + return "Hiking" + if case(3): # Ride + return "Biking" + + +def map_oppo_fit_type_to_strava_activity_type(oppo_type): + """ + Note: should consider the supported strava activity type: + Link: https://developers.strava.com/docs/reference/#api-models-ActivityType + """ + for case in switch(oppo_type): + if case(1): # WALK + return "Walk" + if case(2, 13, 15, 17, 22, 10, 14, 16, 18, 21, 37): + # RUN | + # OUTDOOR_PHYSICAL_RUN | + # OUTDOOR_5KM_RELAX_RUN | + # OUTDOOR_FAT_REDUCE_RUN | + # MARATHON + # INDOOR_RUN, etc. + # CROSS_COUNTRY + return "Run" + if case(19): # MOUNTAIN_CLIMBING + return "Hike" + if case(3): # Ride + return "Ride" + + +def parse_points_to_gpx(sport_data, points_dict_list): + gpx = gpxpy.gpx.GPX() + gpx.nsmap["gpxtpx"] = "http://www.garmin.com/xmlschemas/TrackPointExtension/v1" + gpx_track = gpxpy.gpx.GPXTrack() + gpx_track.name = f"""gpx from {sport_data["deviceName"]}""" + gpx_track.type = map_oppo_fit_type_to_gpx_type(sport_data["sportMode"]) + gpx.tracks.append(gpx_track) + + # Create first segment in our GPX track: + gpx_segment = gpxpy.gpx.GPXTrackSegment() + gpx_track.segments.append(gpx_segment) + for p in points_dict_list: + point = gpxpy.gpx.GPXTrackPoint( + latitude=p["latitude"], + longitude=p["longitude"], + time=p["time"], + elevation=p.get("elevation"), + ) + hr = p.get("hr") + cad = p.get("cad") + if hr is not None or cad is not None: + hr_str = f"""{hr}""" if hr is not None else "" + cad_str = ( + f"""{p["cad"]}""" if cad is not None else "" + ) + gpx_extension = ET.fromstring( + f""" + {hr_str} + {cad_str} + + """ + ) + point.extensions.append(gpx_extension) + gpx_segment.points.append(point) + return gpx.to_xml() + + +def download_keep_gpx(gpx_data, keep_id): + try: + print(f"downloading keep_id {str(keep_id)} gpx") + file_path = os.path.join(GPX_FOLDER, str(keep_id) + ".gpx") + with open(file_path, "w") as fb: + fb.write(gpx_data) + except: + print(f"wrong id {keep_id}") + pass + + +def prepare_track_points(sport_data, with_gpx): + """ + Convert run points data to GPX format. + + Args: + sport_data (map of dict): A map of run data points. + with_gpx (boolean): export to gpx file or not. + + Returns: + points_dict_list (list): data with need to parse. + """ + other_data = sport_data["otherSportData"] + decoded_hr_data = other_data.get("heartRate", None) + points_dict_list = [] + + if other_data.get("gpsPoint"): + timestamp_list = [item["timestamp"] for item in decoded_hr_data] + other_data = sport_data["otherSportData"] + value_size = len(other_data.get("gpsPoint", None)) + + for i in range(value_size): + temp_timestamp = other_data.get("gpsPoint")[i]["timestamp"] + j = timestamp_list.index(temp_timestamp) + + points_dict = { + "latitude": other_data.get("gpsPoint")[i]["latitude"], + "longitude": other_data.get("gpsPoint")[i]["longitude"], + "time": datetime.utcfromtimestamp(temp_timestamp / 1000), + "hr": other_data.get("heartRate")[j]["value"], + } + points_dict_list.append(get_value(j, points_dict, other_data)) + elif with_gpx is False: + value_size = len(other_data.get("heartRate", None)) + + for i in range(value_size): + temp_timestamp = other_data.get("heartRate")[i]["timestamp"] + temp_date = datetime.utcfromtimestamp(temp_timestamp / 1000) + points_dict = { + "time": temp_date, + "hr": other_data.get("heartRate")[i]["value"], + } + points_dict_list.append(get_value(i, points_dict, other_data)) + + return points_dict_list + + +def get_value(index, points_dict, other_data): + if other_data.get("pace"): + pace = other_data.get("pace")[index]["value"] + points_dict["speed"] = 0 if pace == 0 else 1000 / pace + if other_data.get("frequency"): + points_dict["cad"] = other_data.get("frequency")[index]["value"] + if other_data.get("distance"): + points_dict["distance"] = other_data.get("distance")[index]["value"] + if other_data.get("elevation"): + points_dict["elevation"] = other_data.get("elevation")[index]["value"] + return points_dict + + +def parse_points_to_tcx(sport_data, points_dict_list): + # route ID + fit_id = str(sport_data["id"]) + # local time + start_time = sport_data["startTime"] + start_date = datetime.utcfromtimestamp(start_time / 1000) + fit_start_time = datetime.strftime( + adjust_time(start_date, UTC_TIMEZONE), "%Y-%m-%dT%H:%M:%SZ" + ) + + # Root node + training_center_database = ET.Element( + "TrainingCenterDatabase", + { + "xmlns": "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2", + "xmlns:ns5": "http://www.garmin.com/xmlschemas/ActivityGoals/v1", + "xmlns:ns3": "http://www.garmin.com/xmlschemas/ActivityExtension/v2", + "xmlns:ns2": "http://www.garmin.com/xmlschemas/UserProfile/v2", + "xmlns:xsi": "http://www.w3.org/2001/XMLSchema-instance", + "xmlns:ns4": "http://www.garmin.com/xmlschemas/ProfileExtension/v1", + "xsi:schemaLocation": "http://www.garmin.com/xmlschemas/TrainingCenterDatabase/v2 http://www.garmin.com/xmlschemas/TrainingCenterDatabasev2.xsd", + }, + ) + # xml tree + ET.ElementTree(training_center_database) + # Activities + activities = ET.Element("Activities") + training_center_database.append(activities) + # sport type + sports_type = map_oppo_fit_type_to_gpx_type(sport_data["sportMode"]) + # activity + activity = ET.Element("Activity", {"Sport": sports_type}) + activities.append(activity) + # Id + activity_id = ET.Element("Id") + activity_id.text = fit_start_time # Codoon use start_time as ID + activity.append(activity_id) + # Creator + activity_creator = ET.Element("Creator", {"xsi:type": "Device_t"}) + activity.append(activity_creator) + # Name + activity_creator_name = ET.Element("Name") + activity_creator_name.text = sport_data["deviceName"] + activity_creator.append(activity_creator_name) + activity_creator_product = ET.Element("ProductID") + activity_creator_product.text = "3441" + activity_creator.append(activity_creator_product) + + """ + first, find distance split index + """ + lap_split_indexes = [0] + points_dict_list_chunks = [] + + for idx, item in enumerate(points_dict_list): + size = len(lap_split_indexes) + if sports_type == "Running": + target_distance = 1000 * size + elif sports_type == "Biking": + target_distance = 5000 * size + else: + break + + if idx + 1 != len(points_dict_list): + if ( + item["distance"] + < target_distance + <= points_dict_list[idx + 1]["distance"] + ): + lap_split_indexes.append(idx) + + if len(lap_split_indexes) == 1: + points_dict_list_chunks = [points_dict_list] + else: + for idx, item in enumerate(lap_split_indexes): + if idx + 1 == len(lap_split_indexes): + points_dict_list_chunks.append( + points_dict_list[item : len(points_dict_list) - 1] + ) + else: + points_dict_list_chunks.append( + points_dict_list[item : lap_split_indexes[idx + 1]] + ) + + current_distance = 0 + current_time = start_date + + for item in points_dict_list_chunks: + # Lap + lap_start_time = datetime.strftime( + adjust_time(item[0]["time"], UTC_TIMEZONE), "%Y-%m-%dT%H:%M:%SZ" + ) + activity_lap = ET.Element("Lap", {"StartTime": lap_start_time}) + activity.append(activity_lap) + + # DistanceMeters + total_distance_node = ET.Element("DistanceMeters") + total_distance_node.text = str(item[-1]["distance"] - current_distance) + current_distance = item[-1]["distance"] + activity_lap.append(total_distance_node) + # TotalTimeSeconds + chile_node = ET.Element("TotalTimeSeconds") + chile_node.text = str((item[-1]["time"] - current_time).total_seconds()) + current_time = item[-1]["time"] + activity_lap.append(chile_node) + # MaximumSpeed + chile_node = ET.Element("MaximumSpeed") + chile_node.text = str(max(node["speed"] for node in item)) + activity_lap.append(chile_node) + # # Calories + # chile_node = ET.Element("Calories") + # chile_node.text = str(int(other_data["totalCalories"] / 1000)) + # activity_lap.append(chile_node) + # AverageHeartRateBpm + # bpm = ET.Element("AverageHeartRateBpm") + # bpm_value = ET.Element("Value") + # bpm.append(bpm_value) + # bpm_value.text = str(other_data["avgHeartRate"]) + # heartrate_list = [item["value"] for item in other_data["heartRate"]] + # bpm_value.text = str(round(statistics.mean(heartrate_list))) + # activity_lap.append(bpm) + # # MaximumHeartRateBpm + # bpm = ET.Element("MaximumHeartRateBpm") + # bpm_value = ET.Element("Value") + # bpm.append(bpm_value) + # bpm_value.text = str(max(node["hr"] for node in item)) + # activity_lap.append(bpm) + + # Track + track = ET.Element("Track") + activity_lap.append(track) + + for p in item: + tp = ET.Element("Trackpoint") + track.append(tp) + # Time + time_stamp = datetime.strftime( + adjust_time(p["time"], UTC_TIMEZONE), "%Y-%m-%dT%H:%M:%SZ" + ) + time_label = ET.Element("Time") + time_label.text = time_stamp + + tp.append(time_label) + if sports_type == "Biking" and p.get("cad"): + cadence_label = ET.Element("Cadence") + cadence_label.text = str(p["cad"]) + tp.append(cadence_label) + if p.get("distance"): + distance_label = ET.Element("DistanceMeters") + distance_label.text = str(p["distance"]) + tp.append(distance_label) + # HeartRateBpm + # None was converted to bytes by np.dtype, becoming a string "None" after decode...-_- + # as well as LatitudeDegrees and LongitudeDegrees below + if p.get("hr"): + bpm = ET.Element("HeartRateBpm") + bpm_value = ET.Element("Value") + bpm.append(bpm_value) + bpm_value.text = str(p["hr"]) + tp.append(bpm) + # AltitudeMeters + if p.get("elevation"): + altitude_meters = ET.Element("AltitudeMeters") + altitude_meters.text = str(p["elevation"] / 10) + tp.append(altitude_meters) + if p.get("latitude"): + position = ET.Element("Position") + tp.append(position) + # LatitudeDegrees + lati = ET.Element("LatitudeDegrees") + lati.text = str(p["latitude"]) + position.append(lati) + # LongitudeDegrees + longi = ET.Element("LongitudeDegrees") + longi.text = str(p["longitude"]) + position.append(longi) + # Extensions + if p.get("speed") is not None or ( + p.get("cad") is not None and sports_type == "Running" + ): + extensions = ET.Element("Extensions") + tp.append(extensions) + tpx = ET.Element("ns3:TPX") + extensions.append(tpx) + # LatitudeDegrees + # LatitudeDegrees + if p.get("speed") is not None: + speed = ET.Element("ns3:Speed") + speed.text = str(p["speed"]) + tpx.append(speed) + if p.get("cad") is not None and sports_type == "Running": + cad = ET.Element("ns3:RunCadence") + cad.text = str(round(p["cad"] / 2)) + tpx.append(cad) + # Author + author = ET.Element("Author", {"xsi:type": "Application_t"}) + training_center_database.append(author) + author_name = ET.Element("Name") + author_name.text = "Connect Api" + author.append(author_name) + author_lang = ET.Element("LangID") + author_lang.text = "en" + author.append(author_lang) + author_part = ET.Element("PartNumber") + author_part.text = CONNECT_API_PART_NUMBER + author.append(author_part) + # write to TCX file + xml_str = minidom.parseString(ET.tostring(training_center_database)).toprettyxml() + with open(TCX_FOLDER + "/" + fit_id + ".tcx", "w") as f: + f.write(str(xml_str)) + + +def formated_input( + run_data, run_data_label, tcx_label +): # load run_data from run_data_label, parse to tcx_label, return xml node + fit_data = str(run_data[run_data_label]) + chile_node = ET.Element(tcx_label) + chile_node.text = fit_data + return chile_node + + +def run_oppo_sync( + client_id, + client_secret, + refresh_token, + sync_months=6, + with_download_gpx=False, + with_download_tcx=True, +): + generator = Generator(SQL_FILE) + old_tracks_dates = generator.get_old_tracks_dates() + new_tracks = get_all_oppo_tracks( + client_id, + client_secret, + refresh_token, + sync_months, + old_tracks_dates[0] if old_tracks_dates else 0, + with_download_gpx, + with_download_tcx, + ) + generator.sync_from_app(new_tracks) + + activities_list = generator.load() + with open(JSON_FILE, "w") as f: + json.dump(activities_list, f, indent=0) + + +if __name__ == "__main__": + parser = argparse.ArgumentParser() + parser.add_argument("client_id", help="oppo heytap fit client id") + parser.add_argument("client_secret", help="oppo heytap fit client secret") + parser.add_argument("refresh_token", help="oppo heytap fit refresh token") + parser.add_argument( + "--with-gpx", + dest="with_gpx", + action="store_true", + help="get all oppo fit data to gpx and download", + ) + parser.add_argument( + "--with-tcx", + dest="with_tcx", + action="store_true", + help="get all oppo fit data to tcx and download", + ) + parser.add_argument( + "-m" "--months", + type=int, + default=6, + dest="sync_months", + help="oppo has limited the data retrieve, so the default months we can sync is 6.", + ) + options = parser.parse_args() + run_oppo_sync( + options.client_id, + options.client_secret, + options.refresh_token, + options.sync_months, + options.with_gpx, + options.with_tcx, + ) diff --git a/run_page/strava_to_garmin_sync.py b/run_page/strava_to_garmin_sync.py index 41a786136b3..ca8890c2f23 100644 --- a/run_page/strava_to_garmin_sync.py +++ b/run_page/strava_to_garmin_sync.py @@ -92,13 +92,15 @@ async def upload_to_activities( return files_list # strava rate limit - for i in strava_activities[: len(strava_activities)]: + for i in sorted(strava_activities, key=lambda i: int(i.id)): try: data = strava_web_client.get_activity_data(i.id, fmt=format) files_list.append(data) except Exception as ex: print("get strava data error: ", ex) - await garmin_client.upload_activities_original(files_list, use_fake_garmin_device) + await garmin_client.upload_activities_original_from_strava( + files_list, use_fake_garmin_device + ) return files_list diff --git a/run_page/synced_data_file_logger.py b/run_page/synced_data_file_logger.py index de9583b59d5..5a7fb98efce 100644 --- a/run_page/synced_data_file_logger.py +++ b/run_page/synced_data_file_logger.py @@ -1,5 +1,5 @@ import os -from config import SYNCED_FILE +from config import SYNCED_FILE, SYNCED_ACTIVITY_FILE import json @@ -12,6 +12,11 @@ def save_synced_data_file_list(file_list: list): json.dump(file_list, f) +def save_synced_activity_list(activity_list: list): + with open(SYNCED_ACTIVITY_FILE, "w") as f: + json.dump(activity_list, f) + + def load_synced_file_list(): if os.path.exists(SYNCED_FILE): with open(SYNCED_FILE, "r") as f: @@ -22,3 +27,15 @@ def load_synced_file_list(): pass return [] + + +def load_synced_activity_list(): + if os.path.exists(SYNCED_ACTIVITY_FILE): + with open(SYNCED_ACTIVITY_FILE, "r") as f: + try: + return json.load(f) + except Exception as e: + print(f"json load {SYNCED_ACTIVITY_FILE} \nerror {e}") + pass + + return [] diff --git a/run_page/tcx_to_garmin_sync.py b/run_page/tcx_to_garmin_sync.py new file mode 100644 index 00000000000..6a3a42ba64a --- /dev/null +++ b/run_page/tcx_to_garmin_sync.py @@ -0,0 +1,81 @@ +import argparse +import asyncio +import os +from datetime import datetime + +from tcxreader.tcxreader import TCXReader + +from config import TCX_FOLDER +from garmin_sync import Garmin + + +def get_to_generate_files(last_time): + """ + return to one sorted list for next time upload + """ + file_names = os.listdir(TCX_FOLDER) + tcx = TCXReader() + tcx_files = [ + ( + tcx.read(os.path.join(TCX_FOLDER, i), only_gps=False), + os.path.join(TCX_FOLDER, i), + ) + for i in file_names + if i.endswith(".tcx") + ] + tcx_files_dict = { + int(i[0].trackpoints[0].time.timestamp()): i[1] + for i in tcx_files + if len(i[0].trackpoints) > 0 + and int(i[0].trackpoints[0].time.timestamp()) > last_time + } + + dict(sorted(tcx_files_dict.items())) + + return tcx_files_dict.values() + + +async def upload_tcx_files_to_garmin(options): + print("Need to load all tcx files maybe take some time") + garmin_auth_domain = "CN" if options.is_cn else "" + garmin_client = Garmin(options.secret_string, garmin_auth_domain) + + last_time = 0 + if not options.all: + print("upload new tcx to Garmin") + last_activity = await garmin_client.get_activities(0, 1) + if not last_activity: + print("no garmin activity") + else: + after_datetime_str = last_activity[0]["startTimeGMT"] + after_datetime = datetime.strptime(after_datetime_str, "%Y-%m-%d %H:%M:%S") + last_time = datetime.timestamp(after_datetime) + else: + print("Need to load all tcx files maybe take some time") + to_upload_dict = get_to_generate_files(last_time) + + await garmin_client.upload_activities_files(to_upload_dict) + + +if __name__ == "__main__": + if not os.path.exists(TCX_FOLDER): + os.mkdir(TCX_FOLDER) + parser = argparse.ArgumentParser() + parser.add_argument( + "secret_string", nargs="?", help="secret_string fro get_garmin_secret.py" + ) + parser.add_argument( + "--all", + dest="all", + action="store_true", + help="if upload to strava all without check last time", + ) + parser.add_argument( + "--is-cn", + dest="is_cn", + action="store_true", + help="if garmin account is cn", + ) + loop = asyncio.get_event_loop() + future = asyncio.ensure_future(upload_tcx_files_to_garmin(parser.parse_args())) + loop.run_until_complete(future) diff --git a/run_page/utils.py b/run_page/utils.py index b003707284a..de6945ee48a 100644 --- a/run_page/utils.py +++ b/run_page/utils.py @@ -40,7 +40,9 @@ def to_date(ts): # shouldn't be an issue since it's an offline cmdline tool return datetime.strptime(ts, ts_fmt) except ValueError: - print("Error: Can not execute strptime") + print( + f"Warning: Can not execute strptime {ts} with ts_fmt {ts_fmt}, try next one..." + ) pass raise ValueError(f"cannot parse timestamp {ts} into date with fmts: {ts_fmts}") diff --git a/src/components/Header/index.tsx b/src/components/Header/index.tsx index 2d431b69ac3..1807e625b82 100644 --- a/src/components/Header/index.tsx +++ b/src/components/Header/index.tsx @@ -1,5 +1,4 @@ import { Link } from 'react-router-dom'; -import React from 'react'; import useSiteMetadata from '@/hooks/useSiteMetadata'; const Header = () => { @@ -7,23 +6,20 @@ const Header = () => { return ( <> -