Skip to content

Commit

Permalink
feat: v1.0.0
Browse files Browse the repository at this point in the history
Change-Id: I1772457a486c0a9fb1277d22c68886f5439c8716
  • Loading branch information
shenlvmeng committed Sep 1, 2019
1 parent 3e97439 commit db0d3ab
Show file tree
Hide file tree
Showing 11 changed files with 179 additions and 21 deletions.
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,36 @@

## 网页功能

[x] 展示所有轨迹的叠加
[x] 显示总里程、总时间
[x] 点击路线高亮整条路径,并显示对应运动详情
[x] 显示路径所在的所有城市和省份,点击跳转到城市所在位置
[x] 支持展示有gps定位的图片
- 展示所有轨迹的叠加
- 显示总里程、总时间
- 点击路线高亮整条路径,并显示对应运动详情
- 显示路径所在的所有城市和省份,点击跳转到城市所在位置
- 支持展示有gps定位的图片

## 开发

### Electron部分

```bash
# test
yarn dev
# start = dev
yarn start
# build
yarn build
yarn dist
# release = build + dist
yarn release
```

### web网页部分

```bash
yarn dev-portal
yarn build-portal
```

更多介绍见`doc/intro.md`

## License

Expand Down
85 changes: 85 additions & 0 deletions doc/intro.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# 工程化改造

1.0.0项目由两部分组成:**Electron程序****web网页**。两者不直接关联,通过配置文件`roadmap.config.json`解耦。

另外,Electron程序和web网页属于两套开发流程,互不干扰,在使用配置文件作为接口的基础上,可以独立开发和升级。

## Electron程序

提供给用户程序,通过输入地图配置、gpx文件、定位图片物料,产出`roadmap.config.json`。整体使用Electron + webpack + TypeScript的脚手架。

Electron工作重点在gpx文件转换和图片压缩:

- gpx文件使用`parse-gpx`库解析,产出JSON字符串
- 图片压缩,保留EXIF信息的图片压缩,产出压缩后的图片

最后加上用户的输入,综合产出`roadmap.config.json`

### gpx解析

> 见src/main/gpx2json.ts
使用`parse-gpx`库解析,将经纬度坐标换算到百度地图坐标,产出保留经纬度、海拔信息的结构体,储存为JSON文件,便于网页读取。

### 图片压缩

> 见src/renderer/image.ts
带有EXIF信息的图片通常体积很大,不适合直接放在网页,会严重拖慢网页加载速度。而经过调研,常见的图片压缩工具都不会保留图片EXIF信息,即使保留也不会保留我们需要的经纬度、海拔信息。

另外,满足要求的图片压缩工具(如Adobe PhotoShop)没法整合在整个流程中。因此需要自己实现。

思路是:

- 读取原始图片中的EXIF信息
- 借助canvas压缩图片体积、同时调整图片尺寸
- 再度组合EXIF信息和压缩后的图片,得到保留完整EXIF信息的压缩图片

### 网页模板

使用`html-loader`加载已经产出好的`output.html`,读取为字符串,直接输出到指定目录即可。

网页模板的开发流程见portal一节。

### 产物

> 样例见src/test/portal
产物生成在桌面的roadmap-output文件夹,**新生成的文件夹会覆盖老的**。内容如下:

- `index.html` 目标网页
- `roadmap.config.json` 配置信息
- `data`
- `xx.json` gpx内容
- `images` 图片信息

## roadmap.config.json

用于解耦。包含了基础的配置信息

- city 默认定位的中心城市
- title 网页标题
- gpxCount gpx路径数
- imgTitles 图片标题,不需要和图片一一对应

## web网页(portal)

web网页为了便于迭代,使用了和Electron程序独立的webpack工程。在`config`中有独立的webpack配置,有独立的webpack调试、打包命令。

工程位于`src/portal`,使用TypeScript。产物位于`portal`目录下,由Electron程序引用。

在portal工程的webpack配置中:

- 使用`MiniCssExtractPlugin`抽出css为css资源文件,加载时的避免样式闪动
- 使用`HtmlWebpackInlineSourcePlugin`将引用的css和js文件inline,使得Electron程序只需引用一个HTML文件即可
- 需要配置html-loader的`attr`,避免web网页在加载时,里面的`<img>`标签的`src`属性被解析

### gps轨迹

使用百度地图API绘制polyline实现,Electron程序生成的JSON中,已经提前转成百度地图坐标地址。

### 图床

图片存储于免费的[路过图床](https://imgchr.com),因为不支持自定义访问路径,因此需要将上传图床后的路径保存为图片的title,在网页加载时,通过压缩图的title找到图片在图床上的对应地址(这个地方的设计待优化,所以暂时未开放)。

图片的位置使用EXIF.js读取压缩图片的EXIF信息拿到,转换坐标后绘制在地图上。
Empty file modified icon_creator.sh
100644 → 100755
Empty file.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"publish": {
"owner": "shenlvmeng",
"provider": "github",
"repo": "calendone"
"repo": "roadmap"
}
},
"scripts": {
Expand Down Expand Up @@ -79,6 +79,7 @@
"@types/jquery": "^3.3.30",
"@types/rimraf": "^2.0.2",
"babel-loader": "^8.0.4",
"copy-webpack-plugin": "^5.0.4",
"cross-env": "^5.2.0",
"css-loader": "^2.1.1",
"electron-builder": "^20.38.2",
Expand Down
7 changes: 3 additions & 4 deletions portal/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -173,12 +173,11 @@ body,
position: absolute;
right: 20px;
bottom: 20px;
color: #c7c7c7;
color: #a6a6a6;
font-weight: 400;
font-size: 12px;
}
.toggle-images {
display: none;
position: absolute;
top: 40px;
right: 10px;
Expand All @@ -188,8 +187,8 @@ body,
color: #fff;
cursor: pointer;
}
.toggle-images.visible {
display: block;
.toggle-images.hidden {
display: none;
}
.toggle-images.enable {
background: #16be6b;
Expand Down
11 changes: 5 additions & 6 deletions portal/output.html
Original file line number Diff line number Diff line change
Expand Up @@ -182,12 +182,11 @@
position: absolute;
right: 20px;
bottom: 20px;
color: #c7c7c7;
color: #a6a6a6;
font-weight: 400;
font-size: 12px;
}
.toggle-images {
display: none;
position: absolute;
top: 40px;
right: 10px;
Expand All @@ -197,8 +196,8 @@
color: #fff;
cursor: pointer;
}
.toggle-images.visible {
display: block;
.toggle-images.hidden {
display: none;
}
.toggle-images.enable {
background: #16be6b;
Expand Down Expand Up @@ -238,8 +237,8 @@
<div class="copyright">
Made by <a href="//github.com/shenlvmeng/roadmap" target="_blank" rel="noopener noreferrer">roadmap</a>
</div>
<div id="toggle" class="toggle-images error">关闭图片展示</div>
<script type="text/javascript">!function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});const r=n(1),o=n(2);n(3);let a,i,c=0,u=0;const l=[],s=[],f=[];let d=!0;const p=t=>document.querySelector(t);function h(t){return r.__awaiter(this,void 0,void 0,function*(){const e=new XMLHttpRequest;return new Promise((n,r)=>{e.open("GET",t),e.onload=()=>n(JSON.parse(e.responseText)),e.onerror=()=>r(e.statusText),e.send()})})}function y(t){return t>31536e3?`${~~(t/315636e3)}${y(t%31536e3)}`:t>2592e3?`${~~(t/2592e3)}${y(t%2592e3)}`:t>86400?`${~~(t/86400)}${y(t%86400)}`:t>3600?`${~~(t/3600)}h ${y(t%3600)}`:t>60?`${~~(t/60)}m ${y(t%60)}`:`${t.toFixed(0)}s`}function m(t){const e=t.city,n=t.province;n&&-1===l.indexOf(n)&&l.push(n),e&&-1===s.indexOf(e)&&s.push(e),p("#provinces").innerHTML=l.map(t=>t.slice(0,-1)).join(", "),p("#cities").innerHTML=s.map(t=>`<span class="city">${t.slice(0,-1)}</span>`).join(", ")}function M(){a&&(a.setStrokeColor("#3a6bdb"),p("#panel").className+=" hide",a=null)}function g(t){return r.__awaiter(this,void 0,void 0,function*(){const e=yield h(`./data/${t}.json`);c+=e.distance,u+=e.duration;const n=e.points.map(t=>new BMap.Point(t.lon,t.lat)),r=new BMap.Polyline(n,{enableMassClear:!1});r.metadata={distance:e.distance,duration:e.duration,startTime:e.startTime},r.addEventListener("click",t=>{M(),t.target.setStrokeColor("#ed4040");const e=t.target.metadata;p("#curr_distance").innerHTML=e.distance.toFixed(3),p("#curr_time").innerHTML=y(e.duration/1e3),p("#start_time").innerHTML=new Date(e.startTime).toLocaleString(),p("#panel").className="panel",a=t.target,t.domEvent.stopPropagation()}),i.addOverlay(r),(new BMap.Geocoder).getLocation(n[0],t=>{m(t.addressComponents)}),(new BMap.Geocoder).getLocation(n[~~(n.length/2)],t=>{m(t.addressComponents)}),(new BMap.Geocoder).getLocation(n[n.length-1],t=>{m(t.addressComponents)}),p("#distance").innerHTML=c.toFixed(3),p("#time").innerHTML=y(u/1e3)})}function v(t){return t[0]+t[1]/60+t[2]/3600}function _(){f.forEach(t=>{const e=o.wgs2bd({lat:t.latitude,lon:t.longitude}),n=new BMap.Point(e.lon,e.lat),r=`<div class="shortcut" title="摄于${t.time}" data-key="${t.title}">\n <img src='./data/images/${t.title}.jpg' />\n <p>海拔 ${t.altitude.toFixed(1)}m</p>\n </div>`,a=new BMap.Label(r,{position:n,offset:new BMap.Size(-38,t.vertical?-134:-90)});i.addOverlay(a)})}!function(){r.__awaiter(this,void 0,void 0,function*(){const t=yield h("./roadmap.config.json"),{city:e,title:n,gpxCount:r,imgTitles:o=[]}=t;if(!e||!n||!r)throw Error("roadmap.config.json格式错误");document.title=n||"我的路书",(i=new BMap.Map("map")).centerAndZoom(e||"北京"),i.addControl(new BMap.MapTypeControl({mapTypes:[BMAP_NORMAL_MAP,BMAP_HYBRID_MAP]})),i.enableScrollWheelZoom(!0),i.addEventListener("click",()=>{M()}),p("#cities").addEventListener("click",t=>{(t.target.className="city")&&i.centerAndZoom(t.target.innerText)}),p("#close").addEventListener("click",t=>{t.target.parentNode.className+=" hide"});for(let t=0;t<r;t+=1)g(t);o.length&&(yield Promise.all(o.map(t=>new Promise(e=>{const n=new Image;n.src=`./data/images/${t}.jpg`,n.onload=()=>{EXIF.getData(n,function(){f.push({title:t,vertical:this.height>=150,time:EXIF.getTag(this,"DateTime"),altitude:EXIF.getTag(this,"GPSAltitude").valueOf(),latitude:v(EXIF.getTag(this,"GPSLatitude")),longitude:v(EXIF.getTag(this,"GPSLongitude"))}),e()})}}))),_(),p("#map").addEventListener("click",t=>{let e=t.target;if(e.dataset.key||(e=e.parentNode),e.dataset.key){let t;if(f.forEach(n=>{n.title===e.dataset.key&&(t=n)}),!t)return;p("#modal").classList.remove("invisible"),p("#altitude").innerHTML=t.altitude.toFixed(3),p("#longitude").innerHTML=t.longitude.toFixed(5),p("#latitude").innerHTML=t.latitude.toFixed(5),p("#photo").src=`https://s2.ax1x.com/${e.dataset.key.replace(/-/g,"/")}.md.jpg`,p("#create-time").innerHTML=`${t.time.slice(0,11).replace(/:/g,"-")}${t.time.slice(11)}`}}),p("#hide").addEventListener("click",()=>{p("#modal").classList.add("invisible")}),p("#toggle").addEventListener("click",()=>{d=!d,p("#toggle").innerHTML=d?"关闭图片展示":"开启图片展示",p("#toggle").className=d?"toggle-images disable":"toggle-images enable",d?_():i.clearOverlays()}))})}()},function(t,e,n){"use strict";n.r(e),n.d(e,"__extends",function(){return o}),n.d(e,"__assign",function(){return a}),n.d(e,"__rest",function(){return i}),n.d(e,"__decorate",function(){return c}),n.d(e,"__param",function(){return u}),n.d(e,"__metadata",function(){return l}),n.d(e,"__awaiter",function(){return s}),n.d(e,"__generator",function(){return f}),n.d(e,"__exportStar",function(){return d}),n.d(e,"__values",function(){return p}),n.d(e,"__read",function(){return h}),n.d(e,"__spread",function(){return y}),n.d(e,"__spreadArrays",function(){return m}),n.d(e,"__await",function(){return M}),n.d(e,"__asyncGenerator",function(){return g}),n.d(e,"__asyncDelegator",function(){return v}),n.d(e,"__asyncValues",function(){return _}),n.d(e,"__makeTemplateObject",function(){return b}),n.d(e,"__importStar",function(){return w}),n.d(e,"__importDefault",function(){return O});
<div id="toggle" class="toggle-images disable hidden">关闭图片展示</div>
<script type="text/javascript">!function(t){var e={};function n(r){if(e[r])return e[r].exports;var o=e[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{enumerable:!0,get:r})},n.r=function(t){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},n.t=function(t,e){if(1&e&&(t=n(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var r=Object.create(null);if(n.r(r),Object.defineProperty(r,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var o in t)n.d(r,o,function(e){return t[e]}.bind(null,o));return r},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=0)}([function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0});const r=n(1),o=n(2);n(3);let a,i,c=0,u=0;const l=[],s=[];let f=!0,d=[];const p=[],h=t=>document.querySelector(t);function y(t){return r.__awaiter(this,void 0,void 0,function*(){const e=new XMLHttpRequest;return new Promise((n,r)=>{e.open("GET",t),e.onload=()=>n(JSON.parse(e.responseText)),e.onerror=()=>r(e.statusText),e.send()})})}function m(t){return t>31536e3?`${~~(t/315636e3)}${m(t%31536e3)}`:t>2592e3?`${~~(t/2592e3)}${m(t%2592e3)}`:t>86400?`${~~(t/86400)}${m(t%86400)}`:t>3600?`${~~(t/3600)}h ${m(t%3600)}`:t>60?`${~~(t/60)}m ${m(t%60)}`:`${t.toFixed(0)}s`}function M(t){const e=t.city,n=t.province;n&&-1===l.indexOf(n)&&l.push(n),e&&-1===s.indexOf(e)&&s.push(e),h("#provinces").innerHTML=l.map(t=>t.slice(0,-1)).join(", "),h("#cities").innerHTML=s.map(t=>`<span class="city">${t.slice(0,-1)}</span>`).join(", ")}function g(){a&&(a.setStrokeColor("#3a6bdb"),h("#panel").className+=" hide",a=null)}function v(t){return r.__awaiter(this,void 0,void 0,function*(){const e=yield y(`./data/${t}.json`);c+=e.distance,u+=e.duration;const n=e.points.map(t=>new BMap.Point(t.lon,t.lat)),r=new BMap.Polyline(n,{enableMassClear:!1});r.metadata={distance:e.distance,duration:e.duration,startTime:e.startTime},r.addEventListener("click",t=>{g(),t.target.setStrokeColor("#ed4040");const e=t.target.metadata;h("#curr_distance").innerHTML=e.distance.toFixed(3),h("#curr_time").innerHTML=m(e.duration/1e3),h("#start_time").innerHTML=new Date(e.startTime).toLocaleString(),h("#panel").className="panel",a=t.target,t.domEvent.stopPropagation()}),i.addOverlay(r),(new BMap.Geocoder).getLocation(n[0],t=>{M(t.addressComponents)}),(new BMap.Geocoder).getLocation(n[~~(n.length/2)],t=>{M(t.addressComponents)}),(new BMap.Geocoder).getLocation(n[n.length-1],t=>{M(t.addressComponents)}),h("#distance").innerHTML=c.toFixed(3),h("#time").innerHTML=m(u/1e3)})}function _(t){return t[0]+t[1]/60+t[2]/3600}function b(){p.forEach(t=>{const e=o.wgs2bd({lat:t.latitude,lon:t.longitude}),n=new BMap.Point(e.lon,e.lat),r=`<div class="shortcut" title="摄于${t.time}" data-key="${t.title}">\n <img src='./data/images/${t.title}.jpg' />\n <p>海拔 ${t.altitude.toFixed(1)}m</p>\n </div>`,a=new BMap.Label(r,{position:n,offset:new BMap.Size(-38,t.vertical?-134:-90)});i.addOverlay(a)})}function w(t){d.push(t.key),"shenlvmeng"===d.join("")?(h("#toggle").classList.remove("hidden"),document.removeEventListener("keyup",w)):"shenlvmeng".includes(d.join(""))||(d=[])}!function(){r.__awaiter(this,void 0,void 0,function*(){const t=yield y("./roadmap.config.json"),{city:e,title:n,gpxCount:r,imgTitles:o=[]}=t;if(!e||!n||!r)throw Error("roadmap.config.json格式错误");document.title=n||"我的路书",(i=new BMap.Map("map")).centerAndZoom(e||"北京"),i.addControl(new BMap.MapTypeControl({mapTypes:[BMAP_NORMAL_MAP,BMAP_HYBRID_MAP]})),i.enableScrollWheelZoom(!0),i.addEventListener("click",()=>{g()}),h("#cities").addEventListener("click",t=>{(t.target.className="city")&&i.centerAndZoom(t.target.innerText)}),h("#close").addEventListener("click",t=>{t.target.parentNode.className+=" hide"});for(let t=0;t<r;t+=1)v(t);o.length&&(yield Promise.all(o.map(t=>new Promise(e=>{const n=new Image;n.src=`./data/images/${t}.jpg`,n.onload=()=>{EXIF.getData(n,function(){p.push({title:t,vertical:this.height>=150,time:EXIF.getTag(this,"DateTime"),altitude:EXIF.getTag(this,"GPSAltitude").valueOf(),latitude:_(EXIF.getTag(this,"GPSLatitude")),longitude:_(EXIF.getTag(this,"GPSLongitude"))}),e()})}}))),b(),h("#map").addEventListener("click",t=>{let e=t.target;if(e.dataset.key||(e=e.parentNode),e.dataset.key){let t;if(p.forEach(n=>{n.title===e.dataset.key&&(t=n)}),!t)return;h("#modal").classList.remove("invisible"),h("#altitude").innerHTML=t.altitude.toFixed(3),h("#longitude").innerHTML=t.longitude.toFixed(5),h("#latitude").innerHTML=t.latitude.toFixed(5),h("#photo").src=`https://s2.ax1x.com/${e.dataset.key.replace(/-/g,"/")}.md.jpg`,h("#create-time").innerHTML=`${t.time.slice(0,11).replace(/:/g,"-")}${t.time.slice(11)}`}}),h("#hide").addEventListener("click",()=>{h("#modal").classList.add("invisible")}),document.addEventListener("keyup",w),h("#toggle").addEventListener("click",()=>{f=!f,h("#toggle").innerHTML=f?"关闭图片展示":"开启图片展示",h("#toggle").className=f?"toggle-images disable":"toggle-images enable",f?b():i.clearOverlays()}))})}()},function(t,e,n){"use strict";n.r(e),n.d(e,"__extends",function(){return o}),n.d(e,"__assign",function(){return a}),n.d(e,"__rest",function(){return i}),n.d(e,"__decorate",function(){return c}),n.d(e,"__param",function(){return u}),n.d(e,"__metadata",function(){return l}),n.d(e,"__awaiter",function(){return s}),n.d(e,"__generator",function(){return f}),n.d(e,"__exportStar",function(){return d}),n.d(e,"__values",function(){return p}),n.d(e,"__read",function(){return h}),n.d(e,"__spread",function(){return y}),n.d(e,"__spreadArrays",function(){return m}),n.d(e,"__await",function(){return M}),n.d(e,"__asyncGenerator",function(){return g}),n.d(e,"__asyncDelegator",function(){return v}),n.d(e,"__asyncValues",function(){return _}),n.d(e,"__makeTemplateObject",function(){return b}),n.d(e,"__importStar",function(){return w}),n.d(e,"__importDefault",function(){return O});
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
Expand Down
Loading

0 comments on commit db0d3ab

Please sign in to comment.