Repository: RyanEdo/mini-metro-web
Branch: master
Commit: fdbd501c7a9f
Files: 104
Total size: 572.7 KB
Directory structure:
gitextract_0n7p8h2y/
├── .gitignore
├── .vscode/
│ └── settings.json
├── README-cn.md
├── README.md
├── package.json
├── public/
│ ├── beijing.json
│ ├── changsha.json
│ ├── guangzhou.json
│ ├── hongkong.json
│ ├── index.html
│ ├── manifest.json
│ ├── robots.txt
│ ├── shanghai.json
│ ├── shenzhen.json
│ └── tianjing.json
├── src/
│ ├── Common/
│ │ ├── AutoGrowthInput.scss
│ │ ├── AutoGrowthInput.tsx
│ │ ├── api.ts
│ │ ├── color.ts
│ │ ├── const.ts
│ │ ├── teyvat.ts
│ │ └── util.ts
│ ├── Data/
│ │ ├── Shape.ts
│ │ └── UserData.ts
│ ├── DataStructure/
│ │ ├── Bend.ts
│ │ ├── ConnectType.ts
│ │ ├── Direction.ts
│ │ ├── Display.ts
│ │ ├── Line.ts
│ │ ├── LineRecord.ts
│ │ ├── Mode.ts
│ │ ├── Point.ts
│ │ ├── Rail.ts
│ │ ├── RailPair.ts
│ │ ├── Station.ts
│ │ ├── Straight.ts
│ │ ├── Track.ts
│ │ └── Vector.ts
│ ├── Entrance/
│ │ ├── App.scss
│ │ ├── App.test.tsx
│ │ ├── App.tsx
│ │ ├── reportWebVitals.js
│ │ └── setupTests.js
│ ├── Grid/
│ │ └── Scale.ts
│ ├── Line/
│ │ ├── Handle.ts
│ │ └── LinePoints.ts
│ ├── Render/
│ │ ├── Card/
│ │ │ ├── Cards.scss
│ │ │ ├── Cards.tsx
│ │ │ ├── LineCard.scss
│ │ │ ├── LineCard.tsx
│ │ │ ├── StationCard.scss
│ │ │ └── StationCard.tsx
│ │ ├── Component/
│ │ │ └── LineRender.tsx
│ │ ├── Delete/
│ │ │ ├── DeleteConfirmation.scss
│ │ │ └── DeleteConfirmation.tsx
│ │ ├── ErrorFallback/
│ │ │ ├── ErrorFallback.scss
│ │ │ └── ErrorFallback.tsx
│ │ ├── Header/
│ │ │ ├── Component/
│ │ │ │ ├── OpacityControl.scss
│ │ │ │ ├── OpacityControl.tsx
│ │ │ │ ├── ShapeSelector.scss
│ │ │ │ └── ShapeSelector.tsx
│ │ │ ├── Menu.scss
│ │ │ └── Menu.tsx
│ │ ├── Layer/
│ │ │ ├── DevelopLayer.scss
│ │ │ ├── DevelopLayer.tsx
│ │ │ ├── RenderLayer.scss
│ │ │ ├── RenderLayer.tsx
│ │ │ ├── ScaleLayer.scss
│ │ │ └── ScaleLayer.tsx
│ │ └── Recovery/
│ │ ├── Recovery.scss
│ │ └── Recovery.tsx
│ ├── Resource/
│ │ ├── Icon/
│ │ │ ├── airwave.tsx
│ │ │ ├── arrow.tsx
│ │ │ ├── auto.tsx
│ │ │ ├── bolt.tsx
│ │ │ ├── edit.tsx
│ │ │ ├── expand.tsx
│ │ │ ├── export.tsx
│ │ │ ├── finished.tsx
│ │ │ ├── goto.tsx
│ │ │ ├── infinity.tsx
│ │ │ ├── pageview.tsx
│ │ │ ├── plus.tsx
│ │ │ ├── shrink.tsx
│ │ │ └── touch.tsx
│ │ └── Shape/
│ │ └── shape.tsx
│ ├── Style/
│ │ └── Cursor.ts
│ ├── WelcomeTour/
│ │ ├── Driver.ts
│ │ ├── HightLights.tsx
│ │ ├── Steps/
│ │ │ ├── common-operation.ts
│ │ │ ├── export.ts
│ │ │ ├── line-card.ts
│ │ │ ├── quick-edit.ts
│ │ │ ├── skip.ts
│ │ │ ├── station-card.ts
│ │ │ └── tag-setting.ts
│ │ ├── WelcomeTour.scss
│ │ └── WelcomeTour.tsx
│ ├── i18n/
│ │ ├── config.ts
│ │ └── locales/
│ │ ├── en.json
│ │ └── zh.json
│ ├── index.js
│ └── types/
│ └── svg.d.ts
└── tsconfig.json
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
================================================
FILE: .vscode/settings.json
================================================
{
"i18n-ally.localesPaths": ["src/i18n/locales"],
"i18n-ally.extract.autoDetect": true,
"i18n-ally.keystyle": "nested",
"i18n-ally.sourceLanguage": "zh",
"i18n-ally.displayLanguage": "zh",
"i18n-ally.extract.ignored": [
"“\n ",
"”\n ",
"”\n ",
"\n 重做\n ",
".title .click-panel",
".title .click-panel"
],
"i18n-ally.extract.ignoredByFiles": {
"src/WelcomeTour/WelcomeTour.tsx": [
".welcome-tour .body"
]
}
}
================================================
FILE: README-cn.md
================================================
# Mini Metro Web
迷你地铁地图构建工具: `创建迷你地铁风格的地铁线路图`。
支持**无限**站点与线路,支持**多次穿过**同一站点,设置**背景图**,设置**支线**,支持**导出图片**。
[https://mini-metro-web.gitlab.io/](https://mini-metro-web.gitlab.io/)
[English Version](https://github.com/RyanEdo/mini-metro-web/blob/master/README.md)
## 更新日志
#### 1.2.1 `修复添加站点时,连续双击站点导致的站点重复添加报错`
#### 1.2.0 `支持英文`
#### 1.1.1 `增加更多站点形状,新增站点时支持修改默认形状`
#### 1.1.0 `支持修改背景颜色与背景图`
#### 1.0.2 `加入刷新后快速恢复通知`
#### 1.0.1 `加入报错指引`
## 基本用法
### 菜单
点击左上角标题进入菜单,点击任意空白处退出菜单
### 创建站点
1. 菜单 => 站点 => 添加站点
2. 点击空白处添加
### 创建线路
1. 点击任意站点
2. 选择 操作 => 以此为起点新建线路
3. 按照提示依次点击站点添加到线路
### 删除站点/线路
1. 点击站点/线路
2. 选择 操作 => 删除
### 设置背景图
1. 点击线路 => 设定背景
2. 选择 `纯白` / `浅黄` / `取色器` 设定背景颜色
3. 选择 `导入背景图` 导入图片
4. 导入图片后,进入修改图片页面,可以拖动修改图片位置,也可以修改图片透明度
### 导出图片/文件
菜单 => 作为图片/文件导出
## 进阶用法
请参考应用内教程或[视频教程](https://space.bilibili.com/8217854)
## 构建
和大部分react项目一样,先运行`npm i`,然后:
### `npm start`
本地运行
打开 [http://localhost:3000](http://localhost:3000) 浏览器中查看.
### `npm run build`
打包成静态文件
### 注意
##### 建议安装 `i18n-ally`[VS Code 插件地址](https://marketplace.visualstudio.com/items?itemName=Lokalise.i18n-ally) 。项目有部分字符串由该插件生成,安装后能自动在字符串位置显示原文。
================================================
FILE: README.md
================================================
# Mini Metro Web
Mini Metro Map Building Tool: `Create mini metro-style subway maps`.
Supports **unlimited** stations and lines, supports **multiple crossings** of the same station, set **background images**, set **sub lines**, and supports **exporting images**.
https://mini-metro-web.gitlab.io/
[中文文档](https://github.com/RyanEdo/mini-metro-web/blob/master/README-cn.md)
## Changelog
#### 1.2.1 `fix adding duplicate stations when double clicking at adding station mode`
#### 1.2.0 `Support for English`
#### 1.1.1 `Added more station shapes, support for modifying default shapes when adding new stations`
#### 1.1.0 `Support for changing background color and background image`
#### 1.0.2 `Added quick recovery notification after refresh`
#### 1.0.1 `Added error guidance`
## Basic Usage
### Menu
Click the title in the top left corner to enter the menu, click any blank area to exit the menu.
### Create Station
1. Menu => Station => Add Station
2. Click on a blank area to add
### Create Line
1. Click any station
2. Select Action => Create new line from this point
3. Follow the prompts to click stations in sequence to add them to the line
### Delete Station/Line
1. Click the station/line
2. Select Action => Delete
### Set Background Image
1. Click the line => Set background
2. Choose `Pure White` / `Light Yellow` / `Color Picker` to set the background color
3. Choose `Import Background Image` to import an image
4. After importing the image, enter the image editing page, where you can drag to adjust the image position and modify the image transparency
### Export Image/File
Menu => Export as image/file
## Advanced Usage
Please refer to the in-app tutorial or video tutorial
## Build
Like most React projects, first run `npm i`, then:
### `npm start`
Run locally
Open http://localhost:3000 to view in the browser.
### `npm run build`
Build into static files
### Note
##### It is recommended to install the `i18n-ally` VS Code plugin. Some strings in the project are generated by this plugin, and after installation, the original text will be automatically displayed at the string location.
================================================
FILE: package.json
================================================
{
"name": "mini-metro-web",
"version": "1.2.1",
"private": true,
"dependencies": {
"@svgr/webpack": "^8.1.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/downloadjs": "^1.4.6",
"classnames": "^2.5.1",
"downloadjs": "^1.4.7",
"driver.js": "^1.3.1",
"html-to-image": "^1.11.11",
"i18next": "^23.16.3",
"i18next-browser-languagedetector": "^8.0.0",
"moment": "^2.30.1",
"qrcode": "^1.5.3",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.13",
"react-i18next": "^15.1.0",
"react-scripts": "5.0.1",
"sass": "^1.62.0",
"ua-parser-js": "^1.0.37",
"web-vitals": "^2.1.4"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"@types/qrcode": "^1.5.5",
"@types/ua-parser-js": "^0.7.39"
}
}
================================================
FILE: public/beijing.json
================================================
{"title":"北京","stations":[{"stationId":1,"stationName":"苹果园","position":[2056,3189],"shape":"cicle","lineIds":[1,6]},{"stationId":2,"stationName":"金安桥","position":[1892,3213],"shape":"cicle","lineIds":[1,6,11]},{"stationId":3,"stationName":"四道桥","position":[1606,3286],"shape":"cicle","lineIds":[1]},{"stationId":4,"stationName":"桥户营","position":[1524,3322],"shape":"cicle","lineIds":[1]},{"stationId":5,"stationName":"上岸","position":[1488,3395],"shape":"cicle","lineIds":[1]},{"stationId":6,"stationName":"栗园庄","position":[1499,3488],"shape":"cicle","lineIds":[1]},{"stationId":7,"stationName":"小园","position":[1410,3542],"shape":"cicle","lineIds":[1],"tagDirection":3},{"stationId":8,"stationName":"石厂","position":[1269,3552],"shape":"cicle","lineIds":[1]},{"stationId":9,"stationName":"古城","position":[2169,3372],"shape":"cicle","lineIds":[2]},{"stationId":10,"stationName":"八角游乐园","position":[2393,3372],"shape":"cicle","lineIds":[2]},{"stationId":11,"stationName":"八宝山","position":[2626,3372],"shape":"cicle","lineIds":[2]},{"stationId":12,"stationName":"玉泉路","position":[2795,3372],"shape":"cicle","lineIds":[2]},{"stationId":13,"stationName":"五棵松","position":[3006,3372],"shape":"cicle","lineIds":[2]},{"stationId":14,"stationName":"万寿路","position":[3214,3372],"shape":"cicle","lineIds":[2]},{"stationId":15,"stationName":"公主坟","position":[3365,3372],"shape":"cicle","lineIds":[2,10]},{"stationId":16,"stationName":"军事博物馆","position":[3481,3372],"shape":"cicle","lineIds":[2,9]},{"stationId":17,"stationName":"木樨地","position":[3642,3372],"shape":"cicle","lineIds":[2,15],"tagDirection":3},{"stationId":18,"stationName":"南礼士路","position":[3792,3374],"shape":"cicle","lineIds":[2],"tagDirection":7},{"stationId":19,"stationName":"复兴门","position":[3835,3374],"shape":"cicle","lineIds":[2,3],"tagDirection":5},{"stationId":20,"stationName":"西单","position":[4007,3372],"shape":"cicle","lineIds":[2,4]},{"stationId":21,"stationName":"天安门西","position":[4179,3372],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":22,"stationName":"天安门东","position":[4278,3368],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":23,"stationName":"王府井","position":[4382,3366],"shape":"cicle","lineIds":[2,8],"tagDirection":4},{"stationId":24,"stationName":"东单","position":[4451,3363],"shape":"cicle","lineIds":[2,5],"tagDirection":1},{"stationId":25,"stationName":"建国门","position":[4624,3361],"shape":"cicle","lineIds":[2,3],"tagDirection":1},{"stationId":26,"stationName":"永安里","position":[4771,3362],"shape":"cicle","lineIds":[2]},{"stationId":27,"stationName":"国贸","position":[4884,3355],"shape":"cicle","lineIds":[2,10],"tagDirection":1},{"stationId":28,"stationName":"大望路","position":[5024,3363],"shape":"cicle","lineIds":[2,13],"tagDirection":1},{"stationId":29,"stationName":"四惠","position":[5221,3359],"shape":"cicle","lineIds":[2]},{"stationId":30,"stationName":"四惠东","position":[5423,3361],"shape":"cicle","lineIds":[2]},{"stationId":31,"stationName":"高碑店","position":[5580,3352],"shape":"cicle","lineIds":[2]},{"stationId":32,"stationName":"传媒大学","position":[5812,3354],"shape":"cicle","lineIds":[2]},{"stationId":33,"stationName":"双桥","position":[6034,3345],"shape":"cicle","lineIds":[2]},{"stationId":34,"stationName":"管庄","position":[6256,3355],"shape":"cicle","lineIds":[2]},{"stationId":35,"stationName":"八里桥","position":[6453,3385],"shape":"cicle","lineIds":[2]},{"stationId":36,"stationName":"通州北苑","position":[6639,3408],"shape":"cicle","lineIds":[2]},{"stationId":37,"stationName":"果园","position":[6732,3513],"shape":"cicle","lineIds":[2]},{"stationId":38,"stationName":"九棵树","position":[6841,3543],"shape":"cicle","lineIds":[2]},{"stationId":39,"stationName":"梨园","position":[6953,3609],"shape":"cicle","lineIds":[2]},{"stationId":40,"stationName":"临河里","position":[7054,3691],"shape":"cicle","lineIds":[2]},{"stationId":41,"stationName":"土桥","position":[7130,3727],"shape":"cicle","lineIds":[2]},{"stationId":42,"stationName":"花庄","position":[7221,3879],"shape":"cicle","lineIds":[2,7]},{"stationId":43,"stationName":"环球度假区","position":[7057,3955],"shape":"cicle","lineIds":[2,7]},{"stationId":44,"stationName":"积水潭","position":[3997,2960],"shape":"cicle","lineIds":[3,18],"tagDirection":1},{"stationId":45,"stationName":"鼓楼大街","position":[4204,2957],"shape":"cicle","lineIds":[3,8]},{"stationId":46,"stationName":"安定门","position":[4348,2954],"shape":"cicle","lineIds":[3]},{"stationId":47,"stationName":"雍和宫","position":[4437,2953],"shape":"cicle","lineIds":[3,5]},{"stationId":48,"stationName":"东直门","position":[4624,3030],"shape":"cicle","lineIds":[3,12,22],"tagDirection":7},{"stationId":49,"stationName":"东四十条","position":[4607,3108],"shape":"cicle","lineIds":[3],"tagDirection":3},{"stationId":50,"stationName":"朝阳门","position":[4612,3201],"shape":"cicle","lineIds":[3,6],"tagDirection":7},{"stationId":51,"stationName":"北京站","position":[4539,3396],"shape":"cicle","lineIds":[3]},{"stationId":52,"stationName":"崇文门","position":[4437,3436],"shape":"cicle","lineIds":[3,5],"tagDirection":5},{"stationId":53,"stationName":"前门","position":[4245,3444],"shape":"cicle","lineIds":[3,8],"tagDirection":5},{"stationId":54,"stationName":"和平门","position":[4108,3445],"shape":"cicle","lineIds":[3],"tagDirection":4},{"stationId":55,"stationName":"宣武门","position":[4009,3449],"shape":"cicle","lineIds":[3,4],"tagDirection":5},{"stationId":56,"stationName":"长椿街","position":[3900,3452],"shape":"cicle","lineIds":[3]},{"stationId":57,"stationName":"阜成门","position":[3826,3209],"shape":"cicle","lineIds":[3]},{"stationId":58,"stationName":"车公庄","position":[3810,3122],"shape":"cicle","lineIds":[3,6]},{"stationId":59,"stationName":"西直门","position":[3820,3041],"shape":"cicle","lineIds":[3,4,12]},{"stationId":60,"stationName":"天宫院","position":[3465,5743],"shape":"cicle","lineIds":[4]},{"stationId":61,"stationName":"生物医药基地","position":[3483,5581],"shape":"cicle","lineIds":[4]},{"stationId":62,"stationName":"义和庄","position":[3457,5322],"shape":"cicle","lineIds":[4]},{"stationId":63,"stationName":"黄村火车站","position":[3592,5217],"shape":"cicle","lineIds":[4]},{"stationId":64,"stationName":"黄村西大街","position":[3592,5129],"shape":"cicle","lineIds":[4]},{"stationId":65,"stationName":"清源路","position":[3591,5019],"shape":"cicle","lineIds":[4]},{"stationId":66,"stationName":"枣园","position":[3588,4912],"shape":"cicle","lineIds":[4]},{"stationId":67,"stationName":"高米店南","position":[3582,4811],"shape":"cicle","lineIds":[4]},{"stationId":68,"stationName":"高米店北","position":[3574,4711],"shape":"cicle","lineIds":[4]},{"stationId":69,"stationName":"西红门","position":[3553,4548],"shape":"cicle","lineIds":[4]},{"stationId":70,"stationName":"新宫","position":[3922,4320],"shape":"cicle","lineIds":[4,18]},{"stationId":71,"stationName":"公益西桥","position":[3974,4085],"shape":"cicle","lineIds":[4],"tagDirection":2},{"stationId":72,"stationName":"角门西","position":[3978,3988],"shape":"cicle","lineIds":[4,10]},{"stationId":73,"stationName":"马家堡","position":[3980,3915],"shape":"cicle","lineIds":[4]},{"stationId":74,"stationName":"北京南站","position":[4056,3796],"shape":"cicle","lineIds":[4,13],"tagDirection":1},{"stationId":75,"stationName":"陶然亭","position":[4010,3662],"shape":"cicle","lineIds":[4]},{"stationId":76,"stationName":"菜市口","position":[4010,3553],"shape":"cicle","lineIds":[4,7],"tagDirection":5},{"stationId":77,"stationName":"灵境胡同","position":[4004,3287],"shape":"cicle","lineIds":[4]},{"stationId":78,"stationName":"西四","position":[3999,3204],"shape":"cicle","lineIds":[4]},{"stationId":79,"stationName":"平安里","position":[3995,3107],"shape":"cicle","lineIds":[4,6,18]},{"stationId":80,"stationName":"新街口","position":[3943,3040],"shape":"cicle","lineIds":[4],"tagDirection":7},{"stationId":81,"stationName":"动物园","position":[3656,3064],"shape":"cicle","lineIds":[4],"tagDirection":1},{"stationId":82,"stationName":"国家图书馆","position":[3518,3015],"shape":"cicle","lineIds":[4,9,15]},{"stationId":83,"stationName":"魏公村","position":[3498,2867],"shape":"cicle","lineIds":[4]},{"stationId":84,"stationName":"人民大学","position":[3480,2777],"shape":"cicle","lineIds":[4]},{"stationId":85,"stationName":"海淀黄庄","position":[3442,2686],"shape":"cicle","lineIds":[4,10],"tagDirection":5},{"stationId":86,"stationName":"中关村","position":[3431,2606],"shape":"cicle","lineIds":[4]},{"stationId":87,"stationName":"北京大学东门","position":[3424,2524],"shape":"cicle","lineIds":[4]},{"stationId":88,"stationName":"圆明园","position":[3368,2450],"shape":"cicle","lineIds":[4]},{"stationId":89,"stationName":"西苑","position":[3175,2464],"shape":"cicle","lineIds":[4,15]},{"stationId":90,"stationName":"北宫门","position":[3043,2423],"shape":"cicle","lineIds":[4]},{"stationId":91,"stationName":"安河桥北","position":[2966,2324],"shape":"cicle","lineIds":[4]},{"stationId":92,"stationName":"宋家庄","position":[4550,3988],"shape":"cicle","lineIds":[5,10,25],"tagDirection":5},{"stationId":93,"stationName":"刘家窑","position":[4487,3871],"shape":"cicle","lineIds":[5]},{"stationId":94,"stationName":"蒲黄榆","position":[4497,3790],"shape":"cicle","lineIds":[5,13],"tagDirection":1},{"stationId":95,"stationName":"天坛东门","position":[4474,3621],"shape":"cicle","lineIds":[5]},{"stationId":96,"stationName":"磁器口","position":[4465,3515],"shape":"cicle","lineIds":[5,7],"tagDirection":5},{"stationId":97,"stationName":"灯市口","position":[4444,3275],"shape":"cicle","lineIds":[5]},{"stationId":98,"stationName":"东四","position":[4441,3203],"shape":"cicle","lineIds":[5,6],"tagDirection":3},{"stationId":99,"stationName":"张自忠路","position":[4438,3110],"shape":"cicle","lineIds":[5]},{"stationId":100,"stationName":"北新桥","position":[4435,3038],"shape":"cicle","lineIds":[5,22]},{"stationId":101,"stationName":"和平里北街","position":[4451,2859],"shape":"cicle","lineIds":[5]},{"stationId":102,"stationName":"和平西桥","position":[4446,2762],"shape":"cicle","lineIds":[5]},{"stationId":103,"stationName":"惠新西街南口","position":[4441,2675],"shape":"cicle","lineIds":[5,10],"tagDirection":1},{"stationId":104,"stationName":"惠新西街北口","position":[4436,2568],"shape":"cicle","lineIds":[5]},{"stationId":105,"stationName":"大屯路东","position":[4440,2408],"shape":"cicle","lineIds":[5,14],"tagDirection":7},{"stationId":106,"stationName":"北苑路北","position":[4447,2142],"shape":"cicle","lineIds":[5]},{"stationId":107,"stationName":"立水桥南","position":[4411,2027],"shape":"cicle","lineIds":[5]},{"stationId":108,"stationName":"立水桥","position":[4390,1916],"shape":"cicle","lineIds":[5,12]},{"stationId":109,"stationName":"天通苑南","position":[4393,1782],"shape":"cicle","lineIds":[5]},{"stationId":110,"stationName":"天通苑","position":[4394,1694],"shape":"cicle","lineIds":[5]},{"stationId":111,"stationName":"天通苑北","position":[4395,1610],"shape":"cicle","lineIds":[5]},{"stationId":112,"stationName":"潞城","position":[7740,3420],"shape":"cicle","lineIds":[6]},{"stationId":113,"stationName":"东夏园","position":[7605,3415],"shape":"cicle","lineIds":[6],"tagDirection":0},{"stationId":114,"stationName":"郝家府","position":[7444,3414],"shape":"cicle","lineIds":[6]},{"stationId":115,"stationName":"北运河东","position":[7337,3414],"shape":"cicle","lineIds":[6],"tagDirection":0},{"stationId":116,"stationName":"北运河西","position":[7150,3416],"shape":"cicle","lineIds":[6]},{"stationId":117,"stationName":"通州北关","position":[6877,3267],"shape":"cicle","lineIds":[6]},{"stationId":118,"stationName":"物资学院路","position":[6659,3178],"shape":"cicle","lineIds":[6]},{"stationId":119,"stationName":"草房","position":[6422,3201],"shape":"cicle","lineIds":[6]},{"stationId":120,"stationName":"常营","position":[6263,3189],"shape":"cicle","lineIds":[6],"tagDirection":0},{"stationId":121,"stationName":"黄渠","position":[6049,3204],"shape":"cicle","lineIds":[6],"tagDirection":0},{"stationId":122,"stationName":"褡裢坡","position":[5906,3206],"shape":"cicle","lineIds":[6]},{"stationId":123,"stationName":"青年路","position":[5440,3215],"shape":"cicle","lineIds":[6]},{"stationId":124,"stationName":"十里堡","position":[5286,3215],"shape":"cicle","lineIds":[6]},{"stationId":125,"stationName":"金台路","position":[5047,3211],"shape":"cicle","lineIds":[6,13],"tagDirection":7},{"stationId":126,"stationName":"呼家楼","position":[4882,3213],"shape":"cicle","lineIds":[6,10],"tagDirection":7},{"stationId":127,"stationName":"东大桥","position":[4783,3216],"shape":"cicle","lineIds":[6]},{"stationId":128,"stationName":"南锣鼓巷","position":[4308,3108],"shape":"cicle","lineIds":[6,8],"tagDirection":1},{"stationId":129,"stationName":"北海北","position":[4134,3114],"shape":"cicle","lineIds":[6]},{"stationId":130,"stationName":"车公庄西","position":[3707,3122],"shape":"cicle","lineIds":[6],"tagDirection":4},{"stationId":131,"stationName":"二里沟","position":[3603,3122],"shape":"cicle","lineIds":[6,15]},{"stationId":132,"stationName":"白石桥南","position":[3523,3116],"shape":"cicle","lineIds":[6,9],"tagDirection":7},{"stationId":133,"stationName":"花园桥","position":[3373,3123],"shape":"cicle","lineIds":[6]},{"stationId":134,"stationName":"慈寿寺","position":[3221,3114],"shape":"cicle","lineIds":[6,10],"tagDirection":5},{"stationId":135,"stationName":"海淀五路居","position":[3031,3120],"shape":"cicle","lineIds":[6]},{"stationId":136,"stationName":"田村","position":[2795,3151],"shape":"cicle","lineIds":[6]},{"stationId":137,"stationName":"廖公庄","position":[2539,3122],"shape":"cicle","lineIds":[6]},{"stationId":138,"stationName":"西黄村","position":[2335,3110],"shape":"cicle","lineIds":[6]},{"stationId":139,"stationName":"杨庄","position":[2136,3168],"shape":"cicle","lineIds":[6],"tagDirection":3},{"stationId":140,"stationName":"高楼金","position":[7109,3815],"shape":"cicle","lineIds":[7]},{"stationId":141,"stationName":"群芳","position":[6974,3811],"shape":"cicle","lineIds":[7]},{"stationId":142,"stationName":"万盛东","position":[6840,3811],"shape":"cicle","lineIds":[7],"tagDirection":0},{"stationId":143,"stationName":"万盛西","position":[6588,3813],"shape":"cicle","lineIds":[7]},{"stationId":144,"stationName":"黑庄户","position":[6257,3859],"shape":"cicle","lineIds":[7]},{"stationId":145,"stationName":"郎辛庄","position":[5964,3881],"shape":"cicle","lineIds":[7]},{"stationId":146,"stationName":"黄厂","position":[5804,3956],"shape":"cicle","lineIds":[7]},{"stationId":147,"stationName":"焦化厂","position":[5639,3891],"shape":"cicle","lineIds":[7]},{"stationId":148,"stationName":"双合","position":[5534,3849],"shape":"cicle","lineIds":[7]},{"stationId":149,"stationName":"垡头","position":[5384,3839],"shape":"cicle","lineIds":[7]},{"stationId":150,"stationName":"欢乐谷景区","position":[5267,3781],"shape":"cicle","lineIds":[7]},{"stationId":151,"stationName":"南楼梓庄","position":[5277,3700],"shape":"cicle","lineIds":[7]},{"stationId":152,"stationName":"化工","position":[5300,3573],"shape":"cicle","lineIds":[7]},{"stationId":153,"stationName":"百子湾","position":[5244,3521],"shape":"cicle","lineIds":[7]},{"stationId":154,"stationName":"大郊亭","position":[5145,3514],"shape":"cicle","lineIds":[7]},{"stationId":155,"stationName":"九龙山","position":[5055,3514],"shape":"cicle","lineIds":[7,13],"tagDirection":7},{"stationId":156,"stationName":"双井","position":[4884,3511],"shape":"cicle","lineIds":[7,10],"tagDirection":7},{"stationId":157,"stationName":"广渠门外","position":[4756,3510],"shape":"cicle","lineIds":[7]},{"stationId":158,"stationName":"广渠门内","position":[4605,3510],"shape":"cicle","lineIds":[7],"tagDirection":0},{"stationId":159,"stationName":"桥湾","position":[4351,3519],"shape":"cicle","lineIds":[7]},{"stationId":160,"stationName":"珠市口","position":[4250,3533],"shape":"cicle","lineIds":[7,8]},{"stationId":161,"stationName":"虎坊桥","position":[4112,3551],"shape":"cicle","lineIds":[7],"tagDirection":3},{"stationId":162,"stationName":"广安门内","position":[3848,3552],"shape":"cicle","lineIds":[7]},{"stationId":163,"stationName":"达官营","position":[3622,3547],"shape":"cicle","lineIds":[7,15]},{"stationId":164,"stationName":"湾子","position":[3544,3547],"shape":"cicle","lineIds":[7]},{"stationId":165,"stationName":"北京西站","position":[3478,3499],"shape":"cicle","lineIds":[7,9],"tagDirection":1},{"stationId":166,"stationName":"瀛海","position":[4756,4839],"shape":"cicle","lineIds":[8]},{"stationId":167,"stationName":"德茂","position":[4681,4720],"shape":"cicle","lineIds":[8]},{"stationId":168,"stationName":"五福堂","position":[4505,4596],"shape":"cicle","lineIds":[8]},{"stationId":169,"stationName":"火箭万源","position":[4452,4453],"shape":"cicle","lineIds":[8]},{"stationId":170,"stationName":"东高地","position":[4360,4396],"shape":"cicle","lineIds":[8]},{"stationId":171,"stationName":"和义","position":[4287,4298],"shape":"cicle","lineIds":[8]},{"stationId":172,"stationName":"大红门南","position":[4276,4079],"shape":"cicle","lineIds":[8]},{"stationId":173,"stationName":"海户屯","position":[4269,3928],"shape":"cicle","lineIds":[8]},{"stationId":174,"stationName":"木樨园","position":[4264,3854],"shape":"cicle","lineIds":[8]},{"stationId":175,"stationName":"永定门外","position":[4260,3772],"shape":"cicle","lineIds":[8,13]},{"stationId":176,"stationName":"天桥","position":[4253,3627],"shape":"cicle","lineIds":[8]},{"stationId":177,"stationName":"金鱼胡同","position":[4378,3292],"shape":"cicle","lineIds":[8],"tagDirection":6},{"stationId":178,"stationName":"中国美术馆","position":[4374,3209],"shape":"cicle","lineIds":[8],"tagDirection":6},{"stationId":179,"stationName":"什刹海","position":[4228,3070],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":180,"stationName":"安德里北街","position":[4218,2874],"shape":"cicle","lineIds":[8]},{"stationId":181,"stationName":"安华桥","position":[4213,2761],"shape":"cicle","lineIds":[8]},{"stationId":182,"stationName":"北土城","position":[4208,2677],"shape":"cicle","lineIds":[8,10],"tagDirection":1},{"stationId":183,"stationName":"奥体中心","position":[4204,2588],"shape":"cicle","lineIds":[8]},{"stationId":184,"stationName":"奥林匹克公园","position":[4184,2424],"shape":"cicle","lineIds":[8,14],"tagDirection":5},{"stationId":185,"stationName":"森林公园南门","position":[4192,2345],"shape":"cicle","lineIds":[8]},{"stationId":186,"stationName":"林萃桥","position":[3996,2227],"shape":"cicle","lineIds":[8]},{"stationId":187,"stationName":"永泰庄","position":[3812,2069],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":188,"stationName":"西小口","position":[3782,1978],"shape":"cicle","lineIds":[8]},{"stationId":189,"stationName":"育新","position":[3739,1846],"shape":"cicle","lineIds":[8]},{"stationId":190,"stationName":"霍营","position":[3869,1728],"shape":"cicle","lineIds":[8,12]},{"stationId":191,"stationName":"回龙观东大街","position":[3896,1634],"shape":"cicle","lineIds":[8]},{"stationId":192,"stationName":"平西府","position":[3770,1540],"shape":"cicle","lineIds":[8]},{"stationId":193,"stationName":"育知路","position":[3536,1568],"shape":"cicle","lineIds":[8]},{"stationId":194,"stationName":"朱辛庄","position":[3403,1403],"shape":"cicle","lineIds":[8,19]},{"stationId":195,"stationName":"郭公庄","position":[3285,4303],"shape":"cicle","lineIds":[9,21]},{"stationId":196,"stationName":"丰台科技园","position":[3238,4194],"shape":"cicle","lineIds":[9]},{"stationId":197,"stationName":"科怡路","position":[3240,4121],"shape":"cicle","lineIds":[9]},{"stationId":198,"stationName":"丰台南路","position":[3232,4035],"shape":"cicle","lineIds":[9,15]},{"stationId":199,"stationName":"丰台东大街","position":[3205,3895],"shape":"cicle","lineIds":[9]},{"stationId":200,"stationName":"七里庄","position":[3209,3776],"shape":"cicle","lineIds":[9,13]},{"stationId":201,"stationName":"六里桥","position":[3294,3644],"shape":"cicle","lineIds":[9,10]},{"stationId":202,"stationName":"六里桥东","position":[3417,3577],"shape":"cicle","lineIds":[9]},{"stationId":203,"stationName":"白堆子","position":[3524,3208],"shape":"cicle","lineIds":[9],"tagDirection":6},{"stationId":204,"stationName":"巴沟","position":[3200,2702],"shape":"cicle","lineIds":[10,23],"tagDirection":6},{"stationId":205,"stationName":"苏州街","position":[3329,2690],"shape":"cicle","lineIds":[10,15],"tagDirection":5},{"stationId":206,"stationName":"知春里","position":[3553,2683],"shape":"cicle","lineIds":[10]},{"stationId":207,"stationName":"知春路","position":[3666,2681],"shape":"cicle","lineIds":[10,12]},{"stationId":208,"stationName":"西土城","position":[3809,2683],"shape":"cicle","lineIds":[10,19],"tagDirection":1},{"stationId":209,"stationName":"牡丹园","position":[3964,2680],"shape":"cicle","lineIds":[10,18],"tagDirection":1},{"stationId":210,"stationName":"健德门","position":[4080,2679],"shape":"cicle","lineIds":[10],"tagDirection":1},{"stationId":211,"stationName":"安贞门","position":[4326,2676],"shape":"cicle","lineIds":[10],"tagDirection":1},{"stationId":212,"stationName":"芍药居","position":[4625,2670],"shape":"cicle","lineIds":[10,12]},{"stationId":213,"stationName":"太阳宫","position":[4749,2709],"shape":"cicle","lineIds":[10,17]},{"stationId":214,"stationName":"三元桥","position":[4836,2831],"shape":"cicle","lineIds":[10,22]},{"stationId":215,"stationName":"亮马桥","position":[4884,2952],"shape":"cicle","lineIds":[10]},{"stationId":216,"stationName":"农业展览馆","position":[4883,3033],"shape":"cicle","lineIds":[10],"tagDirection":2},{"stationId":217,"stationName":"团结湖","position":[4884,3109],"shape":"cicle","lineIds":[10],"tagDirection":2},{"stationId":218,"stationName":"金台夕照","position":[4883,3278],"shape":"cicle","lineIds":[10]},{"stationId":219,"stationName":"劲松","position":[4879,3602],"shape":"cicle","lineIds":[10]},{"stationId":220,"stationName":"潘家园","position":[4875,3692],"shape":"cicle","lineIds":[10]},{"stationId":221,"stationName":"十里河","position":[4847,3785],"shape":"cicle","lineIds":[10,13,16],"tagDirection":1},{"stationId":222,"stationName":"分钟寺","position":[4806,3924],"shape":"cicle","lineIds":[10]},{"stationId":223,"stationName":"成寿寺","position":[4741,3987],"shape":"cicle","lineIds":[10],"tagDirection":7},{"stationId":224,"stationName":"石榴庄","position":[4407,3987],"shape":"cicle","lineIds":[10],"tagDirection":4},{"stationId":225,"stationName":"大红门","position":[4258,3992],"shape":"cicle","lineIds":[10],"tagDirection":5},{"stationId":226,"stationName":"角门东","position":[4123,3995],"shape":"cicle","lineIds":[10],"tagDirection":4},{"stationId":227,"stationName":"草桥","position":[3780,3988],"shape":"cicle","lineIds":[10,18,20]},{"stationId":228,"stationName":"纪家庙","position":[3600,4002],"shape":"cicle","lineIds":[10]},{"stationId":229,"stationName":"首经贸","position":[3468,4002],"shape":"cicle","lineIds":[10,21]},{"stationId":230,"stationName":"丰台站","position":[3311,3944],"shape":"cicle","lineIds":[10,15]},{"stationId":231,"stationName":"泥洼","position":[3308,3860],"shape":"cicle","lineIds":[10]},{"stationId":232,"stationName":"西局","position":[3307,3779],"shape":"cicle","lineIds":[10,13],"tagDirection":1},{"stationId":233,"stationName":"莲花桥","position":[3370,3468],"shape":"cicle","lineIds":[10]},{"stationId":234,"stationName":"西钓鱼台","position":[3247,3211],"shape":"cicle","lineIds":[10]},{"stationId":235,"stationName":"车道沟","position":[3204,2967],"shape":"cicle","lineIds":[10]},{"stationId":236,"stationName":"长春桥","position":[3209,2861],"shape":"cicle","lineIds":[10]},{"stationId":237,"stationName":"火器营","position":[3157,2787],"shape":"cicle","lineIds":[10]},{"stationId":238,"stationName":"模式口","position":[1828,3113],"shape":"cicle","lineIds":[11]},{"stationId":239,"stationName":"北辛安","position":[1914,3292],"shape":"cicle","lineIds":[11]},{"stationId":240,"stationName":"新首钢","position":[1914,3350],"shape":"cicle","lineIds":[11]},{"stationId":241,"stationName":"大钟寺","position":[3717,2780],"shape":"cicle","lineIds":[12]},{"stationId":242,"stationName":"五道口","position":[3643,2517],"shape":"cicle","lineIds":[12]},{"stationId":243,"stationName":"上地","position":[3468,2116],"shape":"cicle","lineIds":[12]},{"stationId":244,"stationName":"清河站","position":[3416,2035],"shape":"cicle","lineIds":[12,19]},{"stationId":245,"stationName":"西二旗","position":[3329,1916],"shape":"cicle","lineIds":[12,19]},{"stationId":246,"stationName":"龙泽","position":[3460,1737],"shape":"cicle","lineIds":[12]},{"stationId":247,"stationName":"回龙观","position":[3627,1738],"shape":"cicle","lineIds":[12]},{"stationId":248,"stationName":"北苑","position":[4611,2016],"shape":"cicle","lineIds":[12]},{"stationId":249,"stationName":"望京西","position":[4765,2489],"shape":"cicle","lineIds":[12,14]},{"stationId":250,"stationName":"光熙门","position":[4584,2763],"shape":"cicle","lineIds":[12],"tagDirection":2},{"stationId":251,"stationName":"柳芳","position":[4593,2865],"shape":"cicle","lineIds":[12],"tagDirection":2},{"stationId":252,"stationName":"善各庄","position":[5048,2175],"shape":"cicle","lineIds":[13]},{"stationId":253,"stationName":"来广营","position":[4936,2242],"shape":"cicle","lineIds":[13]},{"stationId":254,"stationName":"东湖渠","position":[4940,2341],"shape":"cicle","lineIds":[13]},{"stationId":255,"stationName":"望京","position":[4960,2461],"shape":"cicle","lineIds":[13,14]},{"stationId":256,"stationName":"阜通","position":[4981,2528],"shape":"cicle","lineIds":[13]},{"stationId":257,"stationName":"望京南","position":[5085,2600],"shape":"cicle","lineIds":[13]},{"stationId":258,"stationName":"将台","position":[5168,2736],"shape":"cicle","lineIds":[13]},{"stationId":259,"stationName":"东风北桥","position":[5125,2862],"shape":"cicle","lineIds":[13]},{"stationId":260,"stationName":"枣营","position":[5016,3005],"shape":"cicle","lineIds":[13],"tagDirection":2},{"stationId":261,"stationName":"朝阳公园","position":[5049,3111],"shape":"cicle","lineIds":[13]},{"stationId":262,"stationName":"平乐园","position":[5039,3593],"shape":"cicle","lineIds":[13]},{"stationId":263,"stationName":"北工大西门","position":[5039,3693],"shape":"cicle","lineIds":[13]},{"stationId":264,"stationName":"方庄","position":[4668,3788],"shape":"cicle","lineIds":[13],"tagDirection":0},{"stationId":265,"stationName":"景泰","position":[4376,3794],"shape":"cicle","lineIds":[13]},{"stationId":266,"stationName":"景风门","position":[3926,3820],"shape":"cicle","lineIds":[13,18],"tagDirection":1},{"stationId":267,"stationName":"西铁营","position":[3826,3833],"shape":"cicle","lineIds":[13]},{"stationId":268,"stationName":"菜户营","position":[3690,3770],"shape":"cicle","lineIds":[13],"tagDirection":4},{"stationId":269,"stationName":"丽泽商务区","position":[3579,3769],"shape":"cicle","lineIds":[13],"tagDirection":4},{"stationId":270,"stationName":"东管头","position":[3478,3771],"shape":"cicle","lineIds":[13],"tagDirection":0},{"stationId":271,"stationName":"大井","position":[3027,3794],"shape":"cicle","lineIds":[13],"tagDirection":7},{"stationId":272,"stationName":"郭庄子","position":[2797,3798],"shape":"cicle","lineIds":[13]},{"stationId":273,"stationName":"大瓦窑","position":[2671,3852],"shape":"cicle","lineIds":[13]},{"stationId":274,"stationName":"园博园","position":[2282,3833],"shape":"cicle","lineIds":[13]},{"stationId":275,"stationName":"张郭庄","position":[2138,3865],"shape":"cicle","lineIds":[13]},{"stationId":276,"stationName":"俸伯","position":[7113,1121],"shape":"cicle","lineIds":[14]},{"stationId":277,"stationName":"顺义","position":[6836,1146],"shape":"cicle","lineIds":[14],"tagDirection":0},{"stationId":278,"stationName":"石门","position":[6677,1148],"shape":"cicle","lineIds":[14],"tagDirection":7},{"stationId":279,"stationName":"南法信","position":[6361,1161],"shape":"cicle","lineIds":[14]},{"stationId":280,"stationName":"后沙峪","position":[5908,1305],"shape":"cicle","lineIds":[14]},{"stationId":281,"stationName":"花梨坎","position":[5842,1602],"shape":"cicle","lineIds":[14]},{"stationId":282,"stationName":"国展","position":[5817,1746],"shape":"cicle","lineIds":[14]},{"stationId":283,"stationName":"孙河","position":[5613,1995],"shape":"cicle","lineIds":[14]},{"stationId":284,"stationName":"马泉营","position":[5301,2109],"shape":"cicle","lineIds":[14]},{"stationId":285,"stationName":"崔各庄","position":[5196,2224],"shape":"cicle","lineIds":[14]},{"stationId":286,"stationName":"望京东","position":[5137,2414],"shape":"cicle","lineIds":[14]},{"stationId":287,"stationName":"关庄","position":[4576,2435],"shape":"cicle","lineIds":[14]},{"stationId":288,"stationName":"安立路","position":[4344,2420],"shape":"cicle","lineIds":[14],"tagDirection":4},{"stationId":289,"stationName":"北沙滩","position":[3947,2431],"shape":"cicle","lineIds":[14],"tagDirection":7},{"stationId":290,"stationName":"六道口","position":[3794,2446],"shape":"cicle","lineIds":[14,19]},{"stationId":291,"stationName":"清华东路西口","position":[3661,2440],"shape":"cicle","lineIds":[14]},{"stationId":292,"stationName":"北安河","position":[1570,1765],"shape":"cicle","lineIds":[15]},{"stationId":293,"stationName":"温阳路","position":[1880,1761],"shape":"cicle","lineIds":[15]},{"stationId":294,"stationName":"稻香湖路","position":[2148,1757],"shape":"cicle","lineIds":[15]},{"stationId":295,"stationName":"屯佃","position":[2425,1762],"shape":"cicle","lineIds":[15]},{"stationId":296,"stationName":"永丰","position":[2651,1728],"shape":"cicle","lineIds":[15]},{"stationId":297,"stationName":"永丰南","position":[2748,1790],"shape":"cicle","lineIds":[15]},{"stationId":298,"stationName":"西北旺","position":[2845,1959],"shape":"cicle","lineIds":[15]},{"stationId":299,"stationName":"马连洼","position":[2991,2120],"shape":"cicle","lineIds":[15]},{"stationId":300,"stationName":"农大南路","position":[3089,2232],"shape":"cicle","lineIds":[15]},{"stationId":301,"stationName":"万泉河桥","position":[3268,2596],"shape":"cicle","lineIds":[15]},{"stationId":302,"stationName":"苏州桥","position":[3347,2826],"shape":"cicle","lineIds":[15]},{"stationId":303,"stationName":"万寿寺","position":[3365,2969],"shape":"cicle","lineIds":[15]},{"stationId":304,"stationName":"甘家口","position":[3611,3212],"shape":"cicle","lineIds":[15],"tagDirection":2},{"stationId":305,"stationName":"玉渊潭东门","position":[3612,3306],"shape":"cicle","lineIds":[15],"tagDirection":2},{"stationId":306,"stationName":"红莲南路","position":[3633,3671],"shape":"cicle","lineIds":[15]},{"stationId":307,"stationName":"东管头南","position":[3497,3890],"shape":"cicle","lineIds":[15,21]},{"stationId":308,"stationName":"富丰桥","position":[3136,4084],"shape":"cicle","lineIds":[15],"tagDirection":7},{"stationId":309,"stationName":"看丹","position":[2991,4097],"shape":"cicle","lineIds":[15]},{"stationId":310,"stationName":"榆树庄","position":[2856,4059],"shape":"cicle","lineIds":[15]},{"stationId":311,"stationName":"洪泰庄","position":[2759,3961],"shape":"cicle","lineIds":[15]},{"stationId":312,"stationName":"宛平城","position":[2562,3956],"shape":"cicle","lineIds":[15]},{"stationId":313,"stationName":"周家庄","position":[5030,3879],"shape":"cicle","lineIds":[16]},{"stationId":314,"stationName":"十八里店","position":[5225,4003],"shape":"cicle","lineIds":[16]},{"stationId":315,"stationName":"北神树","position":[5794,4260],"shape":"cicle","lineIds":[16]},{"stationId":316,"stationName":"次渠北","position":[6033,4352],"shape":"cicle","lineIds":[16]},{"stationId":317,"stationName":"次渠","position":[6182,4411],"shape":"cicle","lineIds":[16,25]},{"stationId":318,"stationName":"嘉会湖","position":[6369,4521],"shape":"cicle","lineIds":[16]},{"stationId":319,"stationName":"未来科学城北","position":[4957,1143],"shape":"cicle","lineIds":[17]},{"stationId":320,"stationName":"未来科学城","position":[4934,1290],"shape":"cicle","lineIds":[17]},{"stationId":321,"stationName":"天通苑东","position":[4689,1758],"shape":"cicle","lineIds":[17]},{"stationId":322,"stationName":"清河营","position":[4687,1938],"shape":"cicle","lineIds":[17]},{"stationId":323,"stationName":"红军营","position":[4684,2109],"shape":"cicle","lineIds":[17]},{"stationId":324,"stationName":"西坝河","position":[4659,2798],"shape":"cicle","lineIds":[17],"tagDirection":2},{"stationId":325,"stationName":"左家庄","position":[4693,2947],"shape":"cicle","lineIds":[17]},{"stationId":326,"stationName":"工人体育场","position":[4771,3104],"shape":"cicle","lineIds":[17],"tagDirection":6},{"stationId":327,"stationName":"新发地","position":[3729,4212],"shape":"cicle","lineIds":[18]},{"stationId":328,"stationName":"牛街","position":[3901,3550],"shape":"cicle","lineIds":[18],"tagDirection":1},{"stationId":329,"stationName":"太平桥","position":[3899,3349],"shape":"cicle","lineIds":[18],"tagDirection":7},{"stationId":330,"stationName":"北太平庄","position":[3967,2758],"shape":"cicle","lineIds":[18]},{"stationId":331,"stationName":"昌平西山口","position":[2220,0],"shape":"cicle","lineIds":[19]},{"stationId":332,"stationName":"十三陵景区","position":[2343,44],"shape":"cicle","lineIds":[19]},{"stationId":333,"stationName":"昌平","position":[2602,241],"shape":"cicle","lineIds":[19]},{"stationId":334,"stationName":"昌平东关","position":[2887,229],"shape":"cicle","lineIds":[19]},{"stationId":335,"stationName":"北邵洼","position":[3086,226],"shape":"cicle","lineIds":[19]},{"stationId":336,"stationName":"南邵","position":[3141,371],"shape":"cicle","lineIds":[19]},{"stationId":337,"stationName":"沙河高教园","position":[3071,800],"shape":"cicle","lineIds":[19]},{"stationId":338,"stationName":"沙河","position":[3155,963],"shape":"cicle","lineIds":[19]},{"stationId":339,"stationName":"巩华城","position":[3206,1137],"shape":"cicle","lineIds":[19]},{"stationId":340,"stationName":"生命科学园","position":[3208,1498],"shape":"cicle","lineIds":[19]},{"stationId":341,"stationName":"朱房北","position":[3599,2085],"shape":"cicle","lineIds":[19]},{"stationId":342,"stationName":"清河小营桥","position":[3703,2099],"shape":"cicle","lineIds":[19],"tagDirection":5},{"stationId":343,"stationName":"学知园","position":[3788,2296],"shape":"cicle","lineIds":[19]},{"stationId":344,"stationName":"学院桥","position":[3800,2564],"shape":"cicle","lineIds":[19]},{"stationId":345,"stationName":"大兴新城","position":[3921,5090],"shape":"cicle","lineIds":[20]},{"stationId":346,"stationName":"大兴机场","position":[4433,7332],"shape":"cicle","lineIds":[20]},{"stationId":347,"stationName":"阎村东","position":[1274,5156],"shape":"cicle","lineIds":[21,24]},{"stationId":348,"stationName":"苏庄","position":[1519,5214],"shape":"cicle","lineIds":[21]},{"stationId":349,"stationName":"良乡南关","position":[1674,5214],"shape":"cicle","lineIds":[21]},{"stationId":350,"stationName":"良乡大学城西","position":[1829,5215],"shape":"cicle","lineIds":[21]},{"stationId":351,"stationName":"良乡大学城","position":[2031,5215],"shape":"cicle","lineIds":[21]},{"stationId":352,"stationName":"良乡大学城北","position":[2101,5147],"shape":"cicle","lineIds":[21]},{"stationId":353,"stationName":"广阳城","position":[2116,4967],"shape":"cicle","lineIds":[21]},{"stationId":354,"stationName":"篱笆房","position":[2161,4840],"shape":"cicle","lineIds":[21]},{"stationId":355,"stationName":"长阳","position":[2393,4808],"shape":"cicle","lineIds":[21]},{"stationId":356,"stationName":"稻田","position":[2454,4497],"shape":"cicle","lineIds":[21]},{"stationId":357,"stationName":"大葆台","position":[3183,4368],"shape":"cicle","lineIds":[21]},{"stationId":358,"stationName":"白盆窑","position":[3400,4250],"shape":"cicle","lineIds":[21]},{"stationId":359,"stationName":"花乡东桥","position":[3467,4154],"shape":"cicle","lineIds":[21]},{"stationId":360,"stationName":"3号航站楼","position":[6422,1920],"shape":"cicle","lineIds":[22]},{"stationId":361,"stationName":"2号航站楼","position":[6194,1653],"shape":"cicle","lineIds":[22]},{"stationId":362,"stationName":"颐和园西门","position":[2899,2589],"shape":"cicle","lineIds":[23]},{"stationId":363,"stationName":"茶棚","position":[2747,2625],"shape":"cicle","lineIds":[23]},{"stationId":364,"stationName":"万安","position":[2586,2606],"shape":"cicle","lineIds":[23]},{"stationId":365,"stationName":"国家植物园","position":[2423,2511],"shape":"cicle","lineIds":[23]},{"stationId":366,"stationName":"香山","position":[2311,2506],"shape":"cicle","lineIds":[23]},{"stationId":367,"stationName":"紫草坞","position":[1133,5200],"shape":"cicle","lineIds":[24]},{"stationId":368,"stationName":"阎村","position":[1068,5279],"shape":"cicle","lineIds":[24]},{"stationId":369,"stationName":"星城","position":[880,5309],"shape":"cicle","lineIds":[24]},{"stationId":370,"stationName":"大石河东","position":[665,5347],"shape":"cicle","lineIds":[24]},{"stationId":371,"stationName":"马各庄","position":[432,5395],"shape":"cicle","lineIds":[24]},{"stationId":372,"stationName":"饶乐府","position":[327,5432],"shape":"cicle","lineIds":[24]},{"stationId":373,"stationName":"房山城关","position":[162,5386],"shape":"cicle","lineIds":[24]},{"stationId":374,"stationName":"燕山","position":[0,5250],"shape":"cicle","lineIds":[24]},{"stationId":375,"stationName":"肖村","position":[4750,4104],"shape":"cicle","lineIds":[25]},{"stationId":376,"stationName":"小红门","position":[4858,4167],"shape":"cicle","lineIds":[25]},{"stationId":377,"stationName":"旧宫","position":[4874,4377],"shape":"cicle","lineIds":[25]},{"stationId":378,"stationName":"亦庄桥","position":[5069,4416],"shape":"cicle","lineIds":[25]},{"stationId":379,"stationName":"亦庄文化园","position":[5172,4377],"shape":"cicle","lineIds":[25]},{"stationId":380,"stationName":"万源街","position":[5320,4417],"shape":"cicle","lineIds":[25]},{"stationId":381,"stationName":"荣京东街","position":[5399,4514],"shape":"cicle","lineIds":[25]},{"stationId":382,"stationName":"荣昌东街","position":[5483,4618],"shape":"cicle","lineIds":[25]},{"stationId":383,"stationName":"同济南路","position":[5665,4717],"shape":"cicle","lineIds":[25]},{"stationId":384,"stationName":"经海路","position":[5889,4610],"shape":"cicle","lineIds":[25]},{"stationId":385,"stationName":"次渠南","position":[6080,4495],"shape":"cicle","lineIds":[25]},{"stationId":386,"stationName":"亦庄火车站","position":[6285,4320],"shape":"cicle","lineIds":[25]}],"lines":[{"lineId":1,"lineName":"S1线","color":"#B35A1F","stationIds":[1,2,3,4,5,6,7,8],"sign":"S1线","order":1,"bendFirst":[true,true,true,true,true,true,true,true]},{"lineId":2,"lineName":"1号线八通线","color":"#CC0000","stationIds":[9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43],"sign":"1","order":2,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":3,"lineName":"2号线","color":"#0065B3","stationIds":[44,45,46,47,48,49,50,25,51,52,53,54,55,56,19,57,58,59,44],"sign":"2","order":3,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":4,"lineName":"4号线大兴线","color":"#008187","stationIds":[60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,55,20,77,78,79,80,59,81,82,83,84,85,86,87,88,89,90,91],"sign":"4","order":4,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":5,"lineName":"5号线","color":"#A61D7F","stationIds":[92,93,94,95,96,52,24,97,98,99,100,47,101,102,103,104,105,106,107,108,109,110,111],"sign":"5","order":5,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":6,"lineName":"6号线","color":"#D0970A","stationIds":[112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,50,98,128,129,79,58,130,131,132,133,134,135,136,137,138,139,1,2],"sign":"6","order":6,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":7,"lineName":"7号线","color":"#F9BE58","stationIds":[43,42,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,96,159,160,161,76,162,163,164,165],"sign":"7","order":7,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":8,"lineName":"8号线","color":"#018237","stationIds":[166,167,168,169,170,171,172,173,174,175,176,160,53,23,177,178,128,179,45,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194],"sign":"8","order":8,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":9,"lineName":"9号线","color":"#86B81C","stationIds":[195,196,197,198,199,200,201,202,165,16,203,132,82],"sign":"9","order":9,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":10,"lineName":"10号线","color":"#019AC3","stationIds":[204,205,85,206,207,208,209,210,182,211,103,212,213,214,215,216,217,126,218,27,156,219,220,221,222,223,92,224,225,226,72,227,228,229,230,231,232,201,233,15,234,134,235,236,237,204],"sign":"10","order":10,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":11,"lineName":"11号线","color":"#EE3F42","stationIds":[238,2,239,240],"sign":"11","order":11,"bendFirst":[true,true,true,true]},{"lineId":12,"lineName":"13号线","color":"#FCD600","stationIds":[59,241,207,242,243,244,245,246,247,190,108,248,249,212,250,251,48],"sign":"13","order":12,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":13,"lineName":"14号线","color":"#E4A8A3","stationIds":[252,253,254,255,256,257,258,259,260,261,125,28,155,262,263,221,264,94,265,175,74,266,267,268,269,270,232,200,271,272,273,274,275],"sign":"14","order":13,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":14,"lineName":"15号线","color":"#793E8C","stationIds":[276,277,278,279,280,281,282,283,284,285,286,255,249,287,105,288,184,289,290,291],"sign":"15","order":14,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":15,"lineName":"16号线","color":"#6CB46B","stationIds":[292,293,294,295,296,297,298,299,300,89,301,205,302,303,82,131,304,305,17,163,306,307,230,198,308,309,310,311,312],"sign":"16","order":15,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":16,"lineName":"17号线","color":"#00AD8E","stationIds":[221,313,314,315,316,317,318],"sign":"17","order":16,"bendFirst":[true,true,true,true,true,true,true]},{"lineId":17,"lineName":"17号线北段","color":"#00AD8E","stationIds":[319,320,321,322,323,213,324,325,326],"sign":"17","order":17,"bendFirst":[true,true,true,true,true,true,true,true,true]},{"lineId":18,"lineName":"19号线","color":"#EB81B9","stationIds":[70,327,227,266,328,329,79,44,330,209],"sign":"19","order":18,"bendFirst":[true,true,true,true,true,true,true,true,true,true]},{"lineId":19,"lineName":"昌平线","color":"#EB81B9","stationIds":[331,332,333,334,335,336,337,338,339,194,340,245,244,341,342,343,290,344,208],"sign":"昌平线","order":19,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":20,"lineName":"大兴国际机场线","color":"#2249A3","stationIds":[227,345,346],"sign":"大兴国际机场线","order":20,"bendFirst":[true,true,true]},{"lineId":21,"lineName":"房山线","color":"#EE782E","stationIds":[347,348,349,350,351,352,353,354,355,356,357,195,358,359,229,307],"sign":"房山线","order":21,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":22,"lineName":"首都机场线","color":"#A49ABD","stationIds":[360,361,214,48,100],"sign":"首都机场线","order":22,"bendFirst":[true,true,true,true,true]},{"lineId":23,"lineName":"西郊线","color":"#FC0601","stationIds":[204,362,363,364,365,366],"sign":"西郊线","order":23,"bendFirst":[true,true,true,true,true,true]},{"lineId":24,"lineName":"燕房线","color":"#EE782E","stationIds":[347,367,368,369,370,371,372,373,374],"sign":"燕房线","order":24,"bendFirst":[true,true,true,true,true,true,true,true,true]},{"lineId":25,"lineName":"亦庄线","color":"#F0087D","stationIds":[92,375,376,377,378,379,380,381,382,383,384,385,317,386],"sign":"亦庄线","order":25,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true]}]}
================================================
FILE: public/changsha.json
================================================
{"stations":[{"stationId":1,"stationName":"开福区政府","position":[1496,328],"shape":"cicle","lineIds":[1]},{"stationId":2,"stationName":"马厂","position":[1529,436],"shape":"cicle","lineIds":[1]},{"stationId":3,"stationName":"北辰三角洲","position":[1477,586],"shape":"cicle","lineIds":[1]},{"stationId":4,"stationName":"开福寺","position":[1437,724],"shape":"cicle","lineIds":[1]},{"stationId":5,"stationName":"文昌阁","position":[1423,821],"shape":"cicle","lineIds":[1,6]},{"stationId":6,"stationName":"培元桥","position":[1406,887],"shape":"cicle","lineIds":[1]},{"stationId":7,"stationName":"五一广场","position":[1406,995],"shape":"cicle","lineIds":[1,2],"tagDirection":1},{"stationId":8,"stationName":"黄兴广场","position":[1406,1054],"shape":"cicle","lineIds":[1],"tagDirection":6},{"stationId":9,"stationName":"南门口","position":[1403,1114],"shape":"cicle","lineIds":[1],"tagDirection":6},{"stationId":10,"stationName":"侯家塘","position":[1463,1211],"shape":"cicle","lineIds":[1,3]},{"stationId":11,"stationName":"南湖路","position":[1497,1288],"shape":"cicle","lineIds":[1],"tagDirection":6},{"stationId":12,"stationName":"黄土岭","position":[1496,1343],"shape":"cicle","lineIds":[1,4],"tagDirection":5},{"stationId":13,"stationName":"涂家冲","position":[1489,1417],"shape":"cicle","lineIds":[1]},{"stationId":14,"stationName":"铁道学院","position":[1515,1587],"shape":"cicle","lineIds":[1]},{"stationId":15,"stationName":"友谊路","position":[1510,1712],"shape":"cicle","lineIds":[1]},{"stationId":16,"stationName":"省政府·清风","position":[1517,1837],"shape":"cicle","lineIds":[1]},{"stationId":17,"stationName":"桂花坪","position":[1525,1956],"shape":"cicle","lineIds":[1]},{"stationId":18,"stationName":"大托","position":[1533,2123],"shape":"cicle","lineIds":[1]},{"stationId":19,"stationName":"中信广场","position":[1539,2211],"shape":"cicle","lineIds":[1]},{"stationId":20,"stationName":"尚双塘","position":[1564,2329],"shape":"cicle","lineIds":[1]},{"stationId":21,"stationName":"梅溪湖西","position":[460,1089],"shape":"cicle","lineIds":[2]},{"stationId":22,"stationName":"麓云路","position":[562,1040],"shape":"cicle","lineIds":[2]},{"stationId":23,"stationName":"文化艺术中心","position":[646,981],"shape":"cicle","lineIds":[2]},{"stationId":24,"stationName":"梅溪湖东","position":[723,932],"shape":"cicle","lineIds":[2]},{"stationId":25,"stationName":"望城坡(汽车西站)","position":[778,874],"shape":"cicle","lineIds":[2]},{"stationId":26,"stationName":"金星路","position":[927,888],"shape":"cicle","lineIds":[2]},{"stationId":27,"stationName":"西湖公园","position":[1041,922],"shape":"cicle","lineIds":[2]},{"stationId":28,"stationName":"溁湾镇","position":[1156,972],"shape":"cicle","lineIds":[2,4]},{"stationId":29,"stationName":"橘子洲","position":[1267,992],"shape":"cicle","lineIds":[2],"tagDirection":5},{"stationId":30,"stationName":"湘江中路","position":[1335,996],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":31,"stationName":"芙蓉广场","position":[1488,1000],"shape":"cicle","lineIds":[2],"tagDirection":4},{"stationId":32,"stationName":"迎宾路口","position":[1563,1000],"shape":"cicle","lineIds":[2,6],"tagDirection":1},{"stationId":33,"stationName":"袁家岭","position":[1648,1003],"shape":"cicle","lineIds":[2],"tagDirection":4},{"stationId":34,"stationName":"长沙火车站","position":[1751,1012],"shape":"cicle","lineIds":[2,3]},{"stationId":35,"stationName":"锦泰广场","position":[1817,1030],"shape":"cicle","lineIds":[2],"tagDirection":4},{"stationId":36,"stationName":"万家丽广场","position":[1943,1033],"shape":"cicle","lineIds":[2,5]},{"stationId":37,"stationName":"人民东路","position":[2028,1107],"shape":"cicle","lineIds":[2,6],"tagDirection":1},{"stationId":38,"stationName":"长沙大道","position":[2086,1265],"shape":"cicle","lineIds":[2],"tagDirection":2},{"stationId":39,"stationName":"沙湾公园","position":[2090,1372],"shape":"cicle","lineIds":[2,4],"tagDirection":7},{"stationId":40,"stationName":"杜花路","position":[2206,1485],"shape":"cicle","lineIds":[2],"tagDirection":5},{"stationId":41,"stationName":"长沙火车南站","position":[2294,1480],"shape":"cicle","lineIds":[2,4]},{"stationId":42,"stationName":"光达","position":[2492,1464],"shape":"cicle","lineIds":[2,4],"tagDirection":7},{"stationId":43,"stationName":"山塘","position":[900,1961],"shape":"cicle","lineIds":[3,7]},{"stationId":44,"stationName":"洋湖湿地","position":[1029,1801],"shape":"cicle","lineIds":[3]},{"stationId":45,"stationName":"洋湖新城","position":[1096,1663],"shape":"cicle","lineIds":[3]},{"stationId":46,"stationName":"阳光","position":[1012,1534],"shape":"cicle","lineIds":[3]},{"stationId":47,"stationName":"中南大学","position":[1018,1407],"shape":"cicle","lineIds":[3]},{"stationId":48,"stationName":"阜埠河","position":[1117,1285],"shape":"cicle","lineIds":[3,4]},{"stationId":49,"stationName":"灵官渡","position":[1347,1156],"shape":"cicle","lineIds":[3],"tagDirection":5},{"stationId":50,"stationName":"东塘","position":[1571,1252],"shape":"cicle","lineIds":[3],"tagDirection":0},{"stationId":51,"stationName":"桂花公园","position":[1656,1243],"shape":"cicle","lineIds":[3]},{"stationId":52,"stationName":"阿弥岭","position":[1723,1202],"shape":"cicle","lineIds":[3]},{"stationId":53,"stationName":"朝阳村","position":[1749,1083],"shape":"cicle","lineIds":[3,6],"tagDirection":5},{"stationId":54,"stationName":"烈士公园东","position":[1731,867],"shape":"cicle","lineIds":[3]},{"stationId":55,"stationName":"丝茅冲","position":[1711,711],"shape":"cicle","lineIds":[3]},{"stationId":56,"stationName":"四方坪","position":[1712,640],"shape":"cicle","lineIds":[3]},{"stationId":57,"stationName":"雅雀湖","position":[1780,575],"shape":"cicle","lineIds":[3]},{"stationId":58,"stationName":"长沙大学","position":[1953,539],"shape":"cicle","lineIds":[3]},{"stationId":59,"stationName":"月湖公园北","position":[2052,522],"shape":"cicle","lineIds":[3,5]},{"stationId":60,"stationName":"湘龙","position":[2251,492],"shape":"cicle","lineIds":[3]},{"stationId":61,"stationName":"星沙","position":[2510,492],"shape":"cicle","lineIds":[3]},{"stationId":62,"stationName":"松雅湖(南)","position":[2677,494],"shape":"cicle","lineIds":[3],"tagDirection":4},{"stationId":63,"stationName":"星沙文体中心","position":[2757,494],"shape":"cicle","lineIds":[3]},{"stationId":64,"stationName":"螺丝塘","position":[2873,494],"shape":"cicle","lineIds":[3]},{"stationId":65,"stationName":"广生","position":[2997,499],"shape":"cicle","lineIds":[3]},{"stationId":66,"stationName":"罐子岭","position":[873,0],"shape":"cicle","lineIds":[4]},{"stationId":67,"stationName":"月亮岛西","position":[937,76],"shape":"cicle","lineIds":[4]},{"stationId":68,"stationName":"湘江新城","position":[979,131],"shape":"cicle","lineIds":[4]},{"stationId":69,"stationName":"汉王陵公园","position":[1117,271],"shape":"cicle","lineIds":[4]},{"stationId":70,"stationName":"福元大桥西","position":[1229,416],"shape":"cicle","lineIds":[4]},{"stationId":71,"stationName":"茶子山","position":[1237,522],"shape":"cicle","lineIds":[4]},{"stationId":72,"stationName":"观沙岭","position":[1166,643],"shape":"cicle","lineIds":[4]},{"stationId":73,"stationName":"六沟垅","position":[1184,735],"shape":"cicle","lineIds":[4,6]},{"stationId":74,"stationName":"望月湖","position":[1201,882],"shape":"cicle","lineIds":[4]},{"stationId":75,"stationName":"湖南师大","position":[1123,1086],"shape":"cicle","lineIds":[4]},{"stationId":76,"stationName":"湖南大学","position":[1107,1151],"shape":"cicle","lineIds":[4]},{"stationId":77,"stationName":"碧沙湖","position":[1320,1304],"shape":"cicle","lineIds":[4]},{"stationId":78,"stationName":"砂子塘","position":[1570,1325],"shape":"cicle","lineIds":[4],"tagDirection":1},{"stationId":79,"stationName":"赤岗岭","position":[1694,1351],"shape":"cicle","lineIds":[4]},{"stationId":80,"stationName":"树木岭","position":[1842,1389],"shape":"cicle","lineIds":[4]},{"stationId":81,"stationName":"圭塘","position":[1952,1373],"shape":"cicle","lineIds":[4,5]},{"stationId":82,"stationName":"粟塘","position":[2165,1372],"shape":"cicle","lineIds":[4]},{"stationId":83,"stationName":"平阳","position":[2212,1427],"shape":"cicle","lineIds":[4],"tagDirection":1},{"stationId":84,"stationName":"杜家坪","position":[2579,1533],"shape":"cicle","lineIds":[4]},{"stationId":85,"stationName":"水渡河","position":[2281,150],"shape":"cicle","lineIds":[5]},{"stationId":86,"stationName":"土桥","position":[2202,310],"shape":"cicle","lineIds":[5]},{"stationId":87,"stationName":"白茅铺","position":[2129,445],"shape":"cicle","lineIds":[5]},{"stationId":88,"stationName":"马栏山","position":[2049,632],"shape":"cicle","lineIds":[5]},{"stationId":89,"stationName":"鸭子铺","position":[2002,698],"shape":"cicle","lineIds":[5]},{"stationId":90,"stationName":"火炬村","position":[1954,864],"shape":"cicle","lineIds":[5]},{"stationId":91,"stationName":"马王堆","position":[1950,948],"shape":"cicle","lineIds":[5]},{"stationId":92,"stationName":"芙蓉区政府","position":[1945,1095],"shape":"cicle","lineIds":[5,6],"tagDirection":5},{"stationId":93,"stationName":"高桥北","position":[1942,1186],"shape":"cicle","lineIds":[5]},{"stationId":94,"stationName":"高桥南","position":[1938,1278],"shape":"cicle","lineIds":[5]},{"stationId":95,"stationName":"木桥","position":[1950,1461],"shape":"cicle","lineIds":[5]},{"stationId":96,"stationName":"雨花区政府","position":[1992,1573],"shape":"cicle","lineIds":[5]},{"stationId":97,"stationName":"大塘","position":[2006,1639],"shape":"cicle","lineIds":[5]},{"stationId":98,"stationName":"板塘冲","position":[2037,1786],"shape":"cicle","lineIds":[5]},{"stationId":99,"stationName":"毛竹塘","position":[2007,1998],"shape":"cicle","lineIds":[5]},{"stationId":100,"stationName":"谢家桥","position":[0,1228],"shape":"cicle","lineIds":[6]},{"stationId":101,"stationName":"象鼻窝","position":[174,1223],"shape":"cicle","lineIds":[6]},{"stationId":102,"stationName":"中塘","position":[279,1142],"shape":"cicle","lineIds":[6]},{"stationId":103,"stationName":"一师范西校区","position":[290,1018],"shape":"cicle","lineIds":[6]},{"stationId":104,"stationName":"长庆","position":[270,938],"shape":"cicle","lineIds":[6]},{"stationId":105,"stationName":"和馨园","position":[257,803],"shape":"cicle","lineIds":[6]},{"stationId":106,"stationName":"长丰","position":[359,741],"shape":"cicle","lineIds":[6]},{"stationId":107,"stationName":"麓谷体育公园","position":[461,749],"shape":"cicle","lineIds":[6]},{"stationId":108,"stationName":"麓谷公园","position":[605,767],"shape":"cicle","lineIds":[6]},{"stationId":109,"stationName":"涧塘","position":[748,763],"shape":"cicle","lineIds":[6]},{"stationId":110,"stationName":"湖南工商大学","position":[864,755],"shape":"cicle","lineIds":[6]},{"stationId":111,"stationName":"白鸽咀","position":[982,770],"shape":"cicle","lineIds":[6]},{"stationId":112,"stationName":"湘雅三医院","position":[1073,767],"shape":"cicle","lineIds":[6]},{"stationId":113,"stationName":"湘雅医院","position":[1521,814],"shape":"cicle","lineIds":[6]},{"stationId":114,"stationName":"烈士公园南","position":[1554,902],"shape":"cicle","lineIds":[6],"tagDirection":2},{"stationId":115,"stationName":"窑岭湘雅二医院","position":[1612,1064],"shape":"cicle","lineIds":[6]},{"stationId":116,"stationName":"花桥","position":[2128,1101],"shape":"cicle","lineIds":[6]},{"stationId":117,"stationName":"隆平水稻博物馆","position":[2230,1043],"shape":"cicle","lineIds":[6]},{"stationId":118,"stationName":"农科院农大","position":[2433,1035],"shape":"cicle","lineIds":[6]},{"stationId":119,"stationName":"东湖","position":[2600,1013],"shape":"cicle","lineIds":[6]},{"stationId":120,"stationName":"韶光","position":[2713,990],"shape":"cicle","lineIds":[6]},{"stationId":121,"stationName":"龙华","position":[2930,947],"shape":"cicle","lineIds":[6]},{"stationId":122,"stationName":"檀木桥","position":[3144,930],"shape":"cicle","lineIds":[6]},{"stationId":123,"stationName":"曹家坪","position":[3356,913],"shape":"cicle","lineIds":[6]},{"stationId":124,"stationName":"龙峰","position":[3481,927],"shape":"cicle","lineIds":[6]},{"stationId":125,"stationName":"大路村","position":[3698,928],"shape":"cicle","lineIds":[6]},{"stationId":126,"stationName":"木马塅","position":[3760,992],"shape":"cicle","lineIds":[6],"tagDirection":1},{"stationId":127,"stationName":"黄花机场T1T2","position":[3821,1041],"shape":"cicle","lineIds":[6],"tagDirection":6},{"stationId":128,"stationName":"大王山","position":[810,2061],"shape":"cicle","lineIds":[7]},{"stationId":129,"stationName":"桐溪","position":[725,2193],"shape":"cicle","lineIds":[7]},{"stationId":130,"stationName":"红桥","position":[735,2358],"shape":"cicle","lineIds":[7]},{"stationId":131,"stationName":"坪塘","position":[766,2515],"shape":"cicle","lineIds":[7]},{"stationId":132,"stationName":"双湖","position":[918,2685],"shape":"cicle","lineIds":[7]},{"stationId":133,"stationName":"黄家湾","position":[1111,3046],"shape":"cicle","lineIds":[7]},{"stationId":134,"stationName":"船形山","position":[1135,3154],"shape":"cicle","lineIds":[7]},{"stationId":135,"stationName":"湘潭北站","position":[1073,3316],"shape":"cicle","lineIds":[7]},{"stationId":136,"stationName":"磁浮机场站","position":[3832,1032],"shape":"cicle","lineIds":[8],"tagDirection":2},{"stationId":137,"stationName":"磁浮榔梨","position":[2975,1341],"shape":"cicle","lineIds":[8]},{"stationId":138,"stationName":"磁浮高铁站","position":[2316,1460],"shape":"cicle","lineIds":[8]}],"lines":[{"lineId":1,"lineName":"1号线","color":"#CC0000","stationIds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20],"sign":"1","order":1,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":2,"lineName":"2号线","color":"#52A8C9","stationIds":[21,22,23,24,25,26,27,28,29,30,7,31,32,33,34,35,36,37,38,39,40,41,42],"sign":"2","order":2,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true]},{"lineId":3,"lineName":"3号线","color":"#86B81C","stationIds":[43,44,45,46,47,48,49,10,50,51,52,53,34,54,55,56,57,58,59,60,61,62,63,64,65],"sign":"3","order":3,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":4,"lineName":"4号线","color":"#D10195","stationIds":[66,67,68,69,70,71,72,73,74,28,75,76,48,77,12,78,79,80,81,39,82,83,41,42,84],"sign":"4","order":4,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,true,true]},{"lineId":5,"lineName":"5号线","color":"#FCD600","stationIds":[85,86,87,59,88,89,90,91,36,92,93,94,81,95,96,97,98,99],"sign":"5","order":5,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":6,"lineName":"6号线","color":"#0065B3","stationIds":[100,101,102,103,104,105,106,107,108,109,110,111,112,73,5,113,114,32,115,53,92,37,116,117,118,119,120,121,122,123,124,125,126,127],"sign":"6","order":6,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true]},{"lineId":7,"lineName":"长株潭西环线","color":"#EB81B9","stationIds":[43,128,129,130,131,132,133,134,135],"sign":"长株潭西环线","order":7,"bendFirst":[true,true,true,true,true,true,true,true,true]},{"lineId":8,"lineName":"磁浮快线","color":"#B97CAF","stationIds":[136,137,138],"sign":"磁浮快线","order":8,"bendFirst":[true,true,true]}],"title":"长沙"}
================================================
FILE: public/guangzhou.json
================================================
{"stations":[{"stationId":1,"stationName":"西塱","position":[2254,5008],"shape":"cicle","lineIds":[1,19],"tagDirection":4},{"stationId":2,"stationName":"坑口","position":[2259,4874],"shape":"cicle","lineIds":[1]},{"stationId":3,"stationName":"花地湾","position":[2275,4792],"shape":"cicle","lineIds":[1]},{"stationId":4,"stationName":"芳村","position":[2292,4676],"shape":"cicle","lineIds":[1]},{"stationId":5,"stationName":"黄沙","position":[2333,4557],"shape":"cicle","lineIds":[1,8]},{"stationId":6,"stationName":"长寿路","position":[2357,4478],"shape":"cicle","lineIds":[1]},{"stationId":7,"stationName":"陈家祠","position":[2399,4405],"shape":"cicle","lineIds":[1,10],"tagDirection":6},{"stationId":8,"stationName":"西门口","position":[2493,4410],"shape":"cicle","lineIds":[1],"tagDirection":4},{"stationId":9,"stationName":"公园前","position":[2578,4408],"shape":"cicle","lineIds":[1,3],"tagDirection":7},{"stationId":10,"stationName":"农讲所","position":[2691,4396],"shape":"cicle","lineIds":[1],"tagDirection":1},{"stationId":11,"stationName":"烈士陵园","position":[2791,4395],"shape":"cicle","lineIds":[1]},{"stationId":12,"stationName":"东山口","position":[2888,4423],"shape":"cicle","lineIds":[1,8]},{"stationId":13,"stationName":"杨箕","position":[3023,4381],"shape":"cicle","lineIds":[1,7]},{"stationId":14,"stationName":"体育西路","position":[3150,4351],"shape":"cicle","lineIds":[1,4,5],"tagDirection":7},{"stationId":15,"stationName":"体育中心","position":[3218,4313],"shape":"cicle","lineIds":[1],"tagDirection":0},{"stationId":16,"stationName":"广州东站","position":[3183,4153],"shape":"cicle","lineIds":[1,5]},{"stationId":17,"stationName":"广州南站","position":[2633,5775],"shape":"cicle","lineIds":[2,3,9,17]},{"stationId":18,"stationName":"林岳东(2号线)","position":[2431,5723],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":19,"stationName":"林岳西","position":[2306,5725],"shape":"cicle","lineIds":[2]},{"stationId":20,"stationName":"石洲","position":[2096,5756],"shape":"cicle","lineIds":[2]},{"stationId":21,"stationName":"仙涌","position":[1917,5832],"shape":"cicle","lineIds":[2]},{"stationId":22,"stationName":"花卉世界","position":[1548,5928],"shape":"cicle","lineIds":[2]},{"stationId":23,"stationName":"登洲","position":[1423,5869],"shape":"cicle","lineIds":[2]},{"stationId":24,"stationName":"湾华","position":[1252,5742],"shape":"cicle","lineIds":[2]},{"stationId":25,"stationName":"石梁","position":[1122,5735],"shape":"cicle","lineIds":[2]},{"stationId":26,"stationName":"魁奇路","position":[1021,5723],"shape":"cicle","lineIds":[2,19]},{"stationId":27,"stationName":"沙岗","position":[877,5697],"shape":"cicle","lineIds":[2]},{"stationId":28,"stationName":"石湾","position":[767,5609],"shape":"cicle","lineIds":[2]},{"stationId":29,"stationName":"张槎","position":[612,5552],"shape":"cicle","lineIds":[2]},{"stationId":30,"stationName":"智慧新城","position":[489,5551],"shape":"cicle","lineIds":[2]},{"stationId":31,"stationName":"绿岛湖","position":[277,5643],"shape":"cicle","lineIds":[2]},{"stationId":32,"stationName":"湖涌","position":[48,5633],"shape":"cicle","lineIds":[2]},{"stationId":33,"stationName":"南庄","position":[0,5833],"shape":"cicle","lineIds":[2]},{"stationId":34,"stationName":"石壁","position":[2714,5721],"shape":"cicle","lineIds":[3,9]},{"stationId":35,"stationName":"会江","position":[2798,5532],"shape":"cicle","lineIds":[3]},{"stationId":36,"stationName":"南浦","position":[2866,5333],"shape":"cicle","lineIds":[3]},{"stationId":37,"stationName":"洛溪","position":[2921,5233],"shape":"cicle","lineIds":[3]},{"stationId":38,"stationName":"南洲","position":[2908,5014],"shape":"cicle","lineIds":[3,19]},{"stationId":39,"stationName":"东晓南","position":[2876,4949],"shape":"cicle","lineIds":[3]},{"stationId":40,"stationName":"江泰路","position":[2740,4840],"shape":"cicle","lineIds":[3]},{"stationId":41,"stationName":"昌岗","position":[2703,4756],"shape":"cicle","lineIds":[3,10]},{"stationId":42,"stationName":"江南西","position":[2674,4690],"shape":"cicle","lineIds":[3]},{"stationId":43,"stationName":"市二宫","position":[2633,4602],"shape":"cicle","lineIds":[3]},{"stationId":44,"stationName":"海珠广场","position":[2588,4514],"shape":"cicle","lineIds":[3,8],"tagDirection":3},{"stationId":45,"stationName":"纪念堂","position":[2569,4341],"shape":"cicle","lineIds":[3]},{"stationId":46,"stationName":"越秀公园","position":[2548,4262],"shape":"cicle","lineIds":[3]},{"stationId":47,"stationName":"广州火车站","position":[2501,4181],"shape":"cicle","lineIds":[3,7]},{"stationId":48,"stationName":"三元里","position":[2503,4068],"shape":"cicle","lineIds":[3]},{"stationId":49,"stationName":"飞翔公园","position":[2572,3973],"shape":"cicle","lineIds":[3]},{"stationId":50,"stationName":"白云公园","position":[2644,3846],"shape":"cicle","lineIds":[3]},{"stationId":51,"stationName":"白云文化广场","position":[2691,3767],"shape":"cicle","lineIds":[3]},{"stationId":52,"stationName":"萧岗","position":[2741,3677],"shape":"cicle","lineIds":[3]},{"stationId":53,"stationName":"江夏","position":[2769,3549],"shape":"cicle","lineIds":[3]},{"stationId":54,"stationName":"黄边","position":[2808,3446],"shape":"cicle","lineIds":[3]},{"stationId":55,"stationName":"嘉禾望岗","position":[2827,3288],"shape":"cicle","lineIds":[3,5,13]},{"stationId":56,"stationName":"番禺广场","position":[3791,6308],"shape":"cicle","lineIds":[4,15,17]},{"stationId":57,"stationName":"市桥","position":[3552,6165],"shape":"cicle","lineIds":[4]},{"stationId":58,"stationName":"汉溪长隆","position":[3236,5733],"shape":"cicle","lineIds":[4,9]},{"stationId":59,"stationName":"大石","position":[3152,5485],"shape":"cicle","lineIds":[4]},{"stationId":60,"stationName":"厦滘","position":[3143,5282],"shape":"cicle","lineIds":[4]},{"stationId":61,"stationName":"沥滘","position":[3125,5113],"shape":"cicle","lineIds":[4,19]},{"stationId":62,"stationName":"大塘","position":[3151,4883],"shape":"cicle","lineIds":[4]},{"stationId":63,"stationName":"客村","position":[3138,4700],"shape":"cicle","lineIds":[4,10],"tagDirection":3},{"stationId":64,"stationName":"广州塔","position":[3171,4599],"shape":"cicle","lineIds":[4,18]},{"stationId":65,"stationName":"珠江新城","position":[3147,4469],"shape":"cicle","lineIds":[4,7],"tagDirection":5},{"stationId":66,"stationName":"石牌桥","position":[3256,4331],"shape":"cicle","lineIds":[4],"tagDirection":3},{"stationId":67,"stationName":"岗顶","position":[3331,4314],"shape":"cicle","lineIds":[4]},{"stationId":68,"stationName":"华师","position":[3388,4259],"shape":"cicle","lineIds":[4]},{"stationId":69,"stationName":"五山","position":[3453,4134],"shape":"cicle","lineIds":[4]},{"stationId":70,"stationName":"天河客运站","position":[3370,3952],"shape":"cicle","lineIds":[4,8]},{"stationId":71,"stationName":"机场北(2号航站楼)","position":[2991,1700],"shape":"cicle","lineIds":[5]},{"stationId":72,"stationName":"机场南(1号航站楼)","position":[2965,1796],"shape":"cicle","lineIds":[5]},{"stationId":73,"stationName":"高增","position":[2890,2111],"shape":"cicle","lineIds":[5,11]},{"stationId":74,"stationName":"人和","position":[2894,2310],"shape":"cicle","lineIds":[5]},{"stationId":75,"stationName":"龙归","position":[2943,2775],"shape":"cicle","lineIds":[5]},{"stationId":76,"stationName":"白云大道北","position":[2916,3439],"shape":"cicle","lineIds":[5],"tagDirection":4},{"stationId":77,"stationName":"永泰","position":[2997,3460],"shape":"cicle","lineIds":[5]},{"stationId":78,"stationName":"同和","position":[3197,3692],"shape":"cicle","lineIds":[5]},{"stationId":79,"stationName":"京溪南方医院","position":[3196,3810],"shape":"cicle","lineIds":[5]},{"stationId":80,"stationName":"梅花园","position":[3140,3910],"shape":"cicle","lineIds":[5]},{"stationId":81,"stationName":"燕塘","position":[3206,4063],"shape":"cicle","lineIds":[5,8]},{"stationId":82,"stationName":"林和西","position":[3174,4247],"shape":"cicle","lineIds":[5,18],"tagDirection":7},{"stationId":83,"stationName":"黄村","position":[4007,4344],"shape":"cicle","lineIds":[6,16]},{"stationId":84,"stationName":"车陂","position":[3890,4418],"shape":"cicle","lineIds":[6]},{"stationId":85,"stationName":"车陂南","position":[3830,4503],"shape":"cicle","lineIds":[6,7]},{"stationId":86,"stationName":"万胜围","position":[3782,4685],"shape":"cicle","lineIds":[6,10]},{"stationId":87,"stationName":"官洲","position":[3703,4994],"shape":"cicle","lineIds":[6]},{"stationId":88,"stationName":"大学城北","position":[3791,5081],"shape":"cicle","lineIds":[6]},{"stationId":89,"stationName":"大学城南","position":[3940,5229],"shape":"cicle","lineIds":[6,9]},{"stationId":90,"stationName":"新造","position":[4092,5381],"shape":"cicle","lineIds":[6]},{"stationId":91,"stationName":"石碁","position":[4586,6090],"shape":"cicle","lineIds":[6]},{"stationId":92,"stationName":"海傍","position":[4688,6267],"shape":"cicle","lineIds":[6]},{"stationId":93,"stationName":"低涌","position":[4783,6467],"shape":"cicle","lineIds":[6]},{"stationId":94,"stationName":"东涌","position":[4724,6806],"shape":"cicle","lineIds":[6]},{"stationId":95,"stationName":"庆盛","position":[4839,6992],"shape":"cicle","lineIds":[6]},{"stationId":96,"stationName":"黄阁汽车城","position":[5032,7304],"shape":"cicle","lineIds":[6]},{"stationId":97,"stationName":"黄阁","position":[5122,7415],"shape":"cicle","lineIds":[6]},{"stationId":98,"stationName":"蕉门","position":[5210,7655],"shape":"cicle","lineIds":[6]},{"stationId":99,"stationName":"金洲","position":[5323,7741],"shape":"cicle","lineIds":[6]},{"stationId":100,"stationName":"飞沙角","position":[5342,7844],"shape":"cicle","lineIds":[6]},{"stationId":101,"stationName":"广隆","position":[5342,7941],"shape":"cicle","lineIds":[6]},{"stationId":102,"stationName":"大涌","position":[5494,8072],"shape":"cicle","lineIds":[6]},{"stationId":103,"stationName":"塘坑","position":[5665,8131],"shape":"cicle","lineIds":[6]},{"stationId":104,"stationName":"南横","position":[5878,8183],"shape":"cicle","lineIds":[6]},{"stationId":105,"stationName":"南沙客运港","position":[6044,7992],"shape":"cicle","lineIds":[6]},{"stationId":106,"stationName":"滘口","position":[2019,4524],"shape":"cicle","lineIds":[7]},{"stationId":107,"stationName":"坦尾","position":[2118,4410],"shape":"cicle","lineIds":[7,8]},{"stationId":108,"stationName":"中山八","position":[2262,4402],"shape":"cicle","lineIds":[7],"tagDirection":7},{"stationId":109,"stationName":"西场","position":[2315,4289],"shape":"cicle","lineIds":[7]},{"stationId":110,"stationName":"西村","position":[2356,4240],"shape":"cicle","lineIds":[7,10]},{"stationId":111,"stationName":"小北","position":[2704,4268],"shape":"cicle","lineIds":[7]},{"stationId":112,"stationName":"淘金","position":[2800,4299],"shape":"cicle","lineIds":[7]},{"stationId":113,"stationName":"区庄","position":[2902,4318],"shape":"cicle","lineIds":[7,8]},{"stationId":114,"stationName":"动物园","position":[3007,4317],"shape":"cicle","lineIds":[7],"tagDirection":0},{"stationId":115,"stationName":"五羊邨","position":[3077,4464],"shape":"cicle","lineIds":[7],"tagDirection":6},{"stationId":116,"stationName":"猎德","position":[3256,4477],"shape":"cicle","lineIds":[7],"tagDirection":3},{"stationId":117,"stationName":"潭村","position":[3394,4489],"shape":"cicle","lineIds":[7]},{"stationId":118,"stationName":"员村","position":[3571,4505],"shape":"cicle","lineIds":[7,16]},{"stationId":119,"stationName":"科韵路","position":[3707,4470],"shape":"cicle","lineIds":[7]},{"stationId":120,"stationName":"东圃","position":[3950,4568],"shape":"cicle","lineIds":[7]},{"stationId":121,"stationName":"三溪","position":[4092,4621],"shape":"cicle","lineIds":[7]},{"stationId":122,"stationName":"鱼珠","position":[4257,4658],"shape":"cicle","lineIds":[7,12]},{"stationId":123,"stationName":"大沙地","position":[4395,4630],"shape":"cicle","lineIds":[7]},{"stationId":124,"stationName":"大沙东","position":[4520,4600],"shape":"cicle","lineIds":[7,9]},{"stationId":125,"stationName":"文冲","position":[4625,4624],"shape":"cicle","lineIds":[7]},{"stationId":126,"stationName":"双沙","position":[4777,4684],"shape":"cicle","lineIds":[7]},{"stationId":127,"stationName":"庙头","position":[4962,4815],"shape":"cicle","lineIds":[7]},{"stationId":128,"stationName":"夏园","position":[5118,4833],"shape":"cicle","lineIds":[7,12]},{"stationId":129,"stationName":"保盈大道","position":[5195,4920],"shape":"cicle","lineIds":[7]},{"stationId":130,"stationName":"夏港","position":[5187,5027],"shape":"cicle","lineIds":[7]},{"stationId":131,"stationName":"黄埔新港","position":[5183,5139],"shape":"cicle","lineIds":[7]},{"stationId":132,"stationName":"香雪","position":[4947,3939],"shape":"cicle","lineIds":[8]},{"stationId":133,"stationName":"萝岗","position":[4745,3906],"shape":"cicle","lineIds":[8,9],"tagDirection":3},{"stationId":134,"stationName":"苏元","position":[4619,3953],"shape":"cicle","lineIds":[8,16]},{"stationId":135,"stationName":"暹岗","position":[4536,3939],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":136,"stationName":"金峰","position":[4446,3848],"shape":"cicle","lineIds":[8]},{"stationId":137,"stationName":"黄陂","position":[4272,3779],"shape":"cicle","lineIds":[8]},{"stationId":138,"stationName":"高塘石","position":[4039,3752],"shape":"cicle","lineIds":[8]},{"stationId":139,"stationName":"柯木塱","position":[3894,3728],"shape":"cicle","lineIds":[8]},{"stationId":140,"stationName":"龙洞","position":[3710,3753],"shape":"cicle","lineIds":[8]},{"stationId":141,"stationName":"植物园","position":[3591,3761],"shape":"cicle","lineIds":[8]},{"stationId":142,"stationName":"长湴","position":[3429,3877],"shape":"cicle","lineIds":[8]},{"stationId":143,"stationName":"天平架","position":[3148,4067],"shape":"cicle","lineIds":[8]},{"stationId":144,"stationName":"沙河顶","position":[3004,4190],"shape":"cicle","lineIds":[8]},{"stationId":145,"stationName":"黄花岗","position":[2934,4251],"shape":"cicle","lineIds":[8]},{"stationId":146,"stationName":"东湖","position":[2820,4511],"shape":"cicle","lineIds":[8]},{"stationId":147,"stationName":"团一大广场","position":[2717,4481],"shape":"cicle","lineIds":[8],"tagDirection":4},{"stationId":148,"stationName":"北京路","position":[2637,4475],"shape":"cicle","lineIds":[8],"tagDirection":0},{"stationId":149,"stationName":"一德路","position":[2506,4532],"shape":"cicle","lineIds":[8]},{"stationId":150,"stationName":"文化公园","position":[2424,4570],"shape":"cicle","lineIds":[8,10]},{"stationId":151,"stationName":"如意坊","position":[2247,4513],"shape":"cicle","lineIds":[8]},{"stationId":152,"stationName":"河沙","position":[2109,4307],"shape":"cicle","lineIds":[8]},{"stationId":153,"stationName":"沙贝","position":[2048,4138],"shape":"cicle","lineIds":[8]},{"stationId":154,"stationName":"横沙","position":[2002,4080],"shape":"cicle","lineIds":[8]},{"stationId":155,"stationName":"浔峰岗","position":[1954,4021],"shape":"cicle","lineIds":[8]},{"stationId":156,"stationName":"燕山","position":[4685,3711],"shape":"cicle","lineIds":[9]},{"stationId":157,"stationName":"水西","position":[4722,3787],"shape":"cicle","lineIds":[9,16]},{"stationId":158,"stationName":"科丰路","position":[4596,4034],"shape":"cicle","lineIds":[9]},{"stationId":159,"stationName":"加庄","position":[4536,4184],"shape":"cicle","lineIds":[9]},{"stationId":160,"stationName":"姬堂","position":[4494,4341],"shape":"cicle","lineIds":[9]},{"stationId":161,"stationName":"裕丰围","position":[4492,4714],"shape":"cicle","lineIds":[9,12]},{"stationId":162,"stationName":"长洲","position":[4228,4900],"shape":"cicle","lineIds":[9]},{"stationId":163,"stationName":"深井","position":[4030,4975],"shape":"cicle","lineIds":[9]},{"stationId":164,"stationName":"板桥","position":[3816,5494],"shape":"cicle","lineIds":[9]},{"stationId":165,"stationName":"员岗","position":[3591,5503],"shape":"cicle","lineIds":[9]},{"stationId":166,"stationName":"南村万博","position":[3410,5614],"shape":"cicle","lineIds":[9,15]},{"stationId":167,"stationName":"钟村","position":[3104,5798],"shape":"cicle","lineIds":[9]},{"stationId":168,"stationName":"谢村","position":[2893,5757],"shape":"cicle","lineIds":[9]},{"stationId":169,"stationName":"大洲","position":[2507,5824],"shape":"cicle","lineIds":[9]},{"stationId":170,"stationName":"陈村北","position":[2387,5901],"shape":"cicle","lineIds":[9]},{"stationId":171,"stationName":"陈村","position":[2303,5982],"shape":"cicle","lineIds":[9]},{"stationId":172,"stationName":"锦龙","position":[2349,6131],"shape":"cicle","lineIds":[9]},{"stationId":173,"stationName":"南涌","position":[2316,6269],"shape":"cicle","lineIds":[9]},{"stationId":174,"stationName":"美的","position":[2203,6327],"shape":"cicle","lineIds":[9]},{"stationId":175,"stationName":"北滘公园","position":[2054,6363],"shape":"cicle","lineIds":[9]},{"stationId":176,"stationName":"美的大道","position":[2012,6215],"shape":"cicle","lineIds":[9]},{"stationId":177,"stationName":"滘心","position":[2104,3293],"shape":"cicle","lineIds":[10]},{"stationId":178,"stationName":"亭岗","position":[2134,3436],"shape":"cicle","lineIds":[10]},{"stationId":179,"stationName":"石井","position":[2252,3551],"shape":"cicle","lineIds":[10]},{"stationId":180,"stationName":"小坪","position":[2365,3657],"shape":"cicle","lineIds":[10]},{"stationId":181,"stationName":"石潭","position":[2345,3784],"shape":"cicle","lineIds":[10]},{"stationId":182,"stationName":"聚龙","position":[2322,3850],"shape":"cicle","lineIds":[10]},{"stationId":183,"stationName":"上步","position":[2292,3953],"shape":"cicle","lineIds":[10]},{"stationId":184,"stationName":"同德","position":[2282,4027],"shape":"cicle","lineIds":[10]},{"stationId":185,"stationName":"鹅掌坦","position":[2293,4099],"shape":"cicle","lineIds":[10]},{"stationId":186,"stationName":"彩虹桥","position":[2388,4323],"shape":"cicle","lineIds":[10],"tagDirection":2},{"stationId":187,"stationName":"华林寺","position":[2408,4486],"shape":"cicle","lineIds":[10],"tagDirection":2},{"stationId":188,"stationName":"同福西","position":[2458,4645],"shape":"cicle","lineIds":[10]},{"stationId":189,"stationName":"凤凰新村","position":[2494,4715],"shape":"cicle","lineIds":[10]},{"stationId":190,"stationName":"沙园","position":[2540,4776],"shape":"cicle","lineIds":[10,19]},{"stationId":191,"stationName":"宝岗大道","position":[2631,4802],"shape":"cicle","lineIds":[10]},{"stationId":192,"stationName":"晓港","position":[2753,4732],"shape":"cicle","lineIds":[10]},{"stationId":193,"stationName":"中大","position":[2863,4741],"shape":"cicle","lineIds":[10]},{"stationId":194,"stationName":"鹭江","position":[3017,4709],"shape":"cicle","lineIds":[10]},{"stationId":195,"stationName":"赤岗","position":[3283,4698],"shape":"cicle","lineIds":[10]},{"stationId":196,"stationName":"磨碟沙","position":[3347,4675],"shape":"cicle","lineIds":[10,15]},{"stationId":197,"stationName":"新港东","position":[3505,4675],"shape":"cicle","lineIds":[10]},{"stationId":198,"stationName":"琶洲","position":[3601,4677],"shape":"cicle","lineIds":[10]},{"stationId":199,"stationName":"清塘","position":[2563,1940],"shape":"cicle","lineIds":[11]},{"stationId":200,"stationName":"清布","position":[2444,1881],"shape":"cicle","lineIds":[11]},{"stationId":201,"stationName":"莲塘","position":[2386,1771],"shape":"cicle","lineIds":[11]},{"stationId":202,"stationName":"马鞍山公园","position":[2313,1665],"shape":"cicle","lineIds":[11]},{"stationId":203,"stationName":"花都广场","position":[2161,1649],"shape":"cicle","lineIds":[11]},{"stationId":204,"stationName":"花果山公园","position":[2118,1791],"shape":"cicle","lineIds":[11]},{"stationId":205,"stationName":"花城路","position":[2062,1866],"shape":"cicle","lineIds":[11]},{"stationId":206,"stationName":"广州北站","position":[1950,1898],"shape":"cicle","lineIds":[11]},{"stationId":207,"stationName":"花都汽车城","position":[1738,1866],"shape":"cicle","lineIds":[11]},{"stationId":208,"stationName":"飞鹅岭","position":[1486,1879],"shape":"cicle","lineIds":[11]},{"stationId":209,"stationName":"双岗","position":[4678,4718],"shape":"cicle","lineIds":[12]},{"stationId":210,"stationName":"南海神庙","position":[4909,4825],"shape":"cicle","lineIds":[12],"tagDirection":5},{"stationId":211,"stationName":"南岗","position":[5349,4710],"shape":"cicle","lineIds":[12]},{"stationId":212,"stationName":"沙村","position":[5638,4556],"shape":"cicle","lineIds":[12]},{"stationId":213,"stationName":"白江","position":[5891,4430],"shape":"cicle","lineIds":[12]},{"stationId":214,"stationName":"新塘","position":[5979,4345],"shape":"cicle","lineIds":[12]},{"stationId":215,"stationName":"官湖","position":[6300,4290],"shape":"cicle","lineIds":[12]},{"stationId":216,"stationName":"新沙","position":[6429,4294],"shape":"cicle","lineIds":[12]},{"stationId":217,"stationName":"白云东平","position":[3113,3151],"shape":"cicle","lineIds":[13]},{"stationId":218,"stationName":"夏良","position":[3170,2885],"shape":"cicle","lineIds":[13]},{"stationId":219,"stationName":"太和","position":[3406,2669],"shape":"cicle","lineIds":[13]},{"stationId":220,"stationName":"竹料","position":[3618,2177],"shape":"cicle","lineIds":[13]},{"stationId":221,"stationName":"钟落潭","position":[3941,1906],"shape":"cicle","lineIds":[13]},{"stationId":222,"stationName":"马沥","position":[4438,1724],"shape":"cicle","lineIds":[13]},{"stationId":223,"stationName":"新和","position":[4605,1530],"shape":"cicle","lineIds":[13,14]},{"stationId":224,"stationName":"太平","position":[4854,1141],"shape":"cicle","lineIds":[13]},{"stationId":225,"stationName":"神岗","position":[5108,911],"shape":"cicle","lineIds":[13]},{"stationId":226,"stationName":"赤草","position":[5537,650],"shape":"cicle","lineIds":[13]},{"stationId":227,"stationName":"从化客运站","position":[5882,345],"shape":"cicle","lineIds":[13]},{"stationId":228,"stationName":"东风","position":[5943,0],"shape":"cicle","lineIds":[13]},{"stationId":229,"stationName":"红卫","position":[4809,1670],"shape":"cicle","lineIds":[14]},{"stationId":230,"stationName":"新南","position":[4887,1890],"shape":"cicle","lineIds":[14]},{"stationId":231,"stationName":"枫下","position":[5018,2032],"shape":"cicle","lineIds":[14]},{"stationId":232,"stationName":"知识城","position":[5186,2169],"shape":"cicle","lineIds":[14]},{"stationId":233,"stationName":"何棠下","position":[5371,2350],"shape":"cicle","lineIds":[14]},{"stationId":234,"stationName":"旺村","position":[5452,2430],"shape":"cicle","lineIds":[14]},{"stationId":235,"stationName":"汤村","position":[5581,2589],"shape":"cicle","lineIds":[14]},{"stationId":236,"stationName":"镇龙北","position":[5704,2766],"shape":"cicle","lineIds":[14]},{"stationId":237,"stationName":"镇龙","position":[5864,2817],"shape":"cicle","lineIds":[14,16]},{"stationId":238,"stationName":"冼村","position":[3216,4405],"shape":"cicle","lineIds":[15],"tagDirection":2},{"stationId":239,"stationName":"龙潭","position":[3348,4844],"shape":"cicle","lineIds":[15]},{"stationId":240,"stationName":"沙溪","position":[3316,5308],"shape":"cicle","lineIds":[15]},{"stationId":241,"stationName":"横沥","position":[5045,8129],"shape":"cicle","lineIds":[15]},{"stationId":242,"stationName":"万顷沙","position":[5505,8678],"shape":"cicle","lineIds":[15]},{"stationId":243,"stationName":"天河公园","position":[3563,4409],"shape":"cicle","lineIds":[16]},{"stationId":244,"stationName":"棠东","position":[3828,4353],"shape":"cicle","lineIds":[16]},{"stationId":245,"stationName":"大观南路","position":[4004,4217],"shape":"cicle","lineIds":[16]},{"stationId":246,"stationName":"天河智慧城","position":[3966,4030],"shape":"cicle","lineIds":[16]},{"stationId":247,"stationName":"神舟路","position":[4248,4010],"shape":"cicle","lineIds":[16]},{"stationId":248,"stationName":"科学城","position":[4427,4006],"shape":"cicle","lineIds":[16]},{"stationId":249,"stationName":"长平","position":[4832,3559],"shape":"cicle","lineIds":[16]},{"stationId":250,"stationName":"金坑","position":[5202,3169],"shape":"cicle","lineIds":[16]},{"stationId":251,"stationName":"镇龙西","position":[5672,2881],"shape":"cicle","lineIds":[16]},{"stationId":252,"stationName":"中新","position":[6066,2809],"shape":"cicle","lineIds":[16]},{"stationId":253,"stationName":"坑贝","position":[6361,2875],"shape":"cicle","lineIds":[16]},{"stationId":254,"stationName":"凤岗","position":[6641,2903],"shape":"cicle","lineIds":[16]},{"stationId":255,"stationName":"朱村","position":[6949,2939],"shape":"cicle","lineIds":[16]},{"stationId":256,"stationName":"山田","position":[7380,2898],"shape":"cicle","lineIds":[16]},{"stationId":257,"stationName":"钟岗","position":[7855,2869],"shape":"cicle","lineIds":[16]},{"stationId":258,"stationName":"增城广场","position":[8079,2882],"shape":"cicle","lineIds":[16]},{"stationId":259,"stationName":"陈头岗","position":[2591,5530],"shape":"cicle","lineIds":[17]},{"stationId":260,"stationName":"市广路","position":[3304,5949],"shape":"cicle","lineIds":[17]},{"stationId":261,"stationName":"海心沙","position":[3179,4547],"shape":"cicle","lineIds":[18],"tagDirection":2},{"stationId":262,"stationName":"大剧院","position":[3179,4501],"shape":"cicle","lineIds":[18],"tagDirection":2},{"stationId":263,"stationName":"花城大道","position":[3182,4470],"shape":"cicle","lineIds":[18],"tagDirection":2},{"stationId":264,"stationName":"妇儿中心","position":[3180,4437],"shape":"cicle","lineIds":[18]},{"stationId":265,"stationName":"黄埔大道","position":[3179,4392],"shape":"cicle","lineIds":[18]},{"stationId":266,"stationName":"天河南","position":[3179,4348],"shape":"cicle","lineIds":[18]},{"stationId":267,"stationName":"体育中心南","position":[3178,4320],"shape":"cicle","lineIds":[18],"tagDirection":7},{"stationId":268,"stationName":"石溪","position":[2794,4983],"shape":"cicle","lineIds":[19]},{"stationId":269,"stationName":"燕岗","position":[2654,4911],"shape":"cicle","lineIds":[19]},{"stationId":270,"stationName":"沙涌","position":[2402,4831],"shape":"cicle","lineIds":[19]},{"stationId":271,"stationName":"鹤洞","position":[2336,4937],"shape":"cicle","lineIds":[19]},{"stationId":272,"stationName":"菊树","position":[2063,5004],"shape":"cicle","lineIds":[19],"tagDirection":4},{"stationId":273,"stationName":"龙溪","position":[1858,5002],"shape":"cicle","lineIds":[19],"tagDirection":4},{"stationId":274,"stationName":"金融高新区","position":[1520,5004],"shape":"cicle","lineIds":[19]},{"stationId":275,"stationName":"千灯湖","position":[1488,5114],"shape":"cicle","lineIds":[19]},{"stationId":276,"stationName":"礌岗","position":[1489,5236],"shape":"cicle","lineIds":[19]},{"stationId":277,"stationName":"南桂路","position":[1476,5321],"shape":"cicle","lineIds":[19]},{"stationId":278,"stationName":"桂城","position":[1349,5346],"shape":"cicle","lineIds":[19]},{"stationId":279,"stationName":"朝安","position":[1270,5386],"shape":"cicle","lineIds":[19]},{"stationId":280,"stationName":"普君北路","position":[1157,5386],"shape":"cicle","lineIds":[19],"tagDirection":0},{"stationId":281,"stationName":"祖庙","position":[1055,5392],"shape":"cicle","lineIds":[19]},{"stationId":282,"stationName":"同济路","position":[1020,5470],"shape":"cicle","lineIds":[19]},{"stationId":283,"stationName":"季华园","position":[1020,5591],"shape":"cicle","lineIds":[19]},{"stationId":284,"stationName":"澜石","position":[1006,5822],"shape":"cicle","lineIds":[19]},{"stationId":285,"stationName":"世纪莲","position":[1084,6011],"shape":"cicle","lineIds":[19]},{"stationId":286,"stationName":"东平","position":[1226,6010],"shape":"cicle","lineIds":[19]},{"stationId":287,"stationName":"新城东","position":[1335,6076],"shape":"cicle","lineIds":[19]}],"lines":[{"lineId":1,"lineName":"1号线","color":"#FCD600","stationIds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16],"sign":"1","order":1,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":2,"lineName":"佛山2号线","color":"#FC0601","stationIds":[17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33],"sign":"佛山2","order":2,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":3,"lineName":"2号线","color":"#0065B3","stationIds":[17,34,35,36,37,38,39,40,41,42,43,44,9,45,46,47,48,49,50,51,52,53,54,55],"sign":"2","order":3,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":4,"lineName":"3号线","color":"#FFA500","stationIds":[56,57,58,59,60,61,62,63,64,65,14,66,67,68,69,70],"sign":"3","order":4,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":5,"lineName":"3号线(北延段)","color":"#FFA500","stationIds":[71,72,73,74,75,55,76,77,78,79,80,81,16,82,14],"sign":"3","order":5,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,false,true]},{"lineId":6,"lineName":"4号线","color":"#018237","stationIds":[83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105],"sign":"4","order":6,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":7,"lineName":"5号线","color":"#C5003E","stationIds":[106,107,108,109,110,47,111,112,113,114,13,115,65,116,117,118,119,85,120,121,122,123,124,125,126,127,128,129,130,131],"sign":"5","order":7,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":8,"lineName":"6号线","color":"#741D51","stationIds":[132,133,134,135,136,137,138,139,140,141,142,70,81,143,144,145,113,12,146,147,148,44,149,150,5,151,107,152,153,154,155],"sign":"6","order":8,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":9,"lineName":"7号线","color":"#86B81C","stationIds":[156,157,133,158,159,160,124,161,162,163,89,164,165,166,58,167,168,34,17,169,170,171,172,173,174,175,176],"sign":"7","order":9,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":10,"lineName":"8号线","color":"#008187","stationIds":[177,178,179,180,181,182,183,184,185,110,186,7,187,150,188,189,190,191,41,192,193,194,63,195,196,197,198,86],"sign":"8","order":10,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":11,"lineName":"9号线","color":"#64BD91","stationIds":[73,199,200,201,202,203,204,205,206,207,208],"sign":"9","order":11,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true]},{"lineId":12,"lineName":"13号线","color":"#818530","stationIds":[122,161,209,210,128,211,212,213,214,215,216],"sign":"13","order":12,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true]},{"lineId":13,"lineName":"14号线","color":"#871C2A","stationIds":[55,217,218,219,220,221,222,223,224,225,226,227,228],"sign":"14","order":13,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":14,"lineName":"14号线支线(知识城线)","color":"#871C2A","stationIds":[223,229,230,231,232,233,234,235,236,237],"sign":"14","order":14,"bendFirst":[true,true,true,true,true,true,true,true,true,true]},{"lineId":15,"lineName":"18号线","color":"#2249A3","stationIds":[238,196,239,240,166,56,241,242],"sign":"18","order":15,"bendFirst":[false,true,true,true,true,true,true,true]},{"lineId":16,"lineName":"21号线","color":"#0C1335","stationIds":[118,243,244,83,245,246,247,248,134,157,249,250,251,237,252,253,254,255,256,257,258],"sign":"21","order":16,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":17,"lineName":"22号线","color":"#B35A1F","stationIds":[259,17,260,56],"sign":"22","order":17,"bendFirst":[true,true,true,true]},{"lineId":18,"lineName":"APM线","color":"#02B6E3","stationIds":[64,261,262,263,264,265,266,267,82],"sign":"APM线","order":18,"bendFirst":[true,true,true,true,true,true,true,true,true]},{"lineId":19,"lineName":"广佛线","color":"#B8D201","stationIds":[61,38,268,269,190,270,271,1,272,273,274,275,276,277,278,279,280,281,282,283,26,284,285,286,287],"sign":"广佛线","order":19,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]}],"title":"广州"}
================================================
FILE: public/hongkong.json
================================================
{"stations":[{"stationId":1,"stationName":"迪士尼","position":[1084,2120],"shape":"cicle","lineIds":[1]},{"stationId":2,"stationName":"欣澳","position":[928,1949],"shape":"cicle","lineIds":[1,4]},{"stationId":3,"stationName":"金钟","position":[2290,2486],"shape":"cicle","lineIds":[2,5,10,12]},{"stationId":4,"stationName":"会展","position":[2368,2458],"shape":"cicle","lineIds":[2],"tagDirection":7},{"stationId":5,"stationName":"红磡","position":[2452,2256],"shape":"cicle","lineIds":[2,11]},{"stationId":6,"stationName":"旺角东","position":[2362,2058],"shape":"cicle","lineIds":[2],"tagDirection":2},{"stationId":7,"stationName":"九龙塘","position":[2392,1899],"shape":"cicle","lineIds":[2,6]},{"stationId":8,"stationName":"大围","position":[2420,1549],"shape":"cicle","lineIds":[2,11]},{"stationId":9,"stationName":"沙田","position":[2505,1457],"shape":"cicle","lineIds":[2]},{"stationId":10,"stationName":"火炭","position":[2616,1326],"shape":"cicle","lineIds":[2]},{"stationId":11,"stationName":"大学","position":[2735,1140],"shape":"cicle","lineIds":[2]},{"stationId":12,"stationName":"大埔墟","position":[2338,831],"shape":"cicle","lineIds":[2]},{"stationId":13,"stationName":"太和","position":[2249,770],"shape":"cicle","lineIds":[2]},{"stationId":14,"stationName":"粉岭","position":[2022,355],"shape":"cicle","lineIds":[2]},{"stationId":15,"stationName":"上水","position":[1913,262],"shape":"cicle","lineIds":[2,13]},{"stationId":16,"stationName":"落马洲","position":[1294,131],"shape":"cicle","lineIds":[2]},{"stationId":17,"stationName":"罗湖","position":[1771,0],"shape":"cicle","lineIds":[13]},{"stationId":18,"stationName":"东涌","position":[48,2388],"shape":"cicle","lineIds":[4]},{"stationId":19,"stationName":"青衣","position":[1711,1700],"shape":"cicle","lineIds":[4,7]},{"stationId":20,"stationName":"荔景","position":[1896,1790],"shape":"cicle","lineIds":[4,12],"tagDirection":7},{"stationId":21,"stationName":"南昌","position":[2171,2011],"shape":"cicle","lineIds":[4,11]},{"stationId":22,"stationName":"奥运","position":[2235,2097],"shape":"cicle","lineIds":[4],"tagDirection":6},{"stationId":23,"stationName":"九龙","position":[2251,2227],"shape":"cicle","lineIds":[4,7],"tagDirection":6},{"stationId":24,"stationName":"香港","position":[2215,2429],"shape":"cicle","lineIds":[4,7]},{"stationId":25,"stationName":"坚尼地城","position":[1924,2464],"shape":"cicle","lineIds":[5]},{"stationId":26,"stationName":"香港大学","position":[1993,2436],"shape":"cicle","lineIds":[5],"tagDirection":4},{"stationId":27,"stationName":"西营盘","position":[2061,2423],"shape":"cicle","lineIds":[5]},{"stationId":28,"stationName":"上环","position":[2163,2413],"shape":"cicle","lineIds":[5],"tagDirection":0},{"stationId":29,"stationName":"中环","position":[2226,2460],"shape":"cicle","lineIds":[5,12],"tagDirection":4},{"stationId":30,"stationName":"湾仔","position":[2364,2506],"shape":"cicle","lineIds":[5]},{"stationId":31,"stationName":"铜锣湾","position":[2471,2475],"shape":"cicle","lineIds":[5]},{"stationId":32,"stationName":"天后","position":[2553,2450],"shape":"cicle","lineIds":[5]},{"stationId":33,"stationName":"炮台山","position":[2575,2393],"shape":"cicle","lineIds":[5]},{"stationId":34,"stationName":"北角","position":[2640,2368],"shape":"cicle","lineIds":[5,9]},{"stationId":35,"stationName":"鲗鱼涌","position":[2732,2400],"shape":"cicle","lineIds":[5,9]},{"stationId":36,"stationName":"太古","position":[2795,2432],"shape":"cicle","lineIds":[5],"tagDirection":5},{"stationId":37,"stationName":"西湾河","position":[2857,2457],"shape":"cicle","lineIds":[5]},{"stationId":38,"stationName":"筲箕湾","position":[2924,2488],"shape":"cicle","lineIds":[5]},{"stationId":39,"stationName":"杏花邨","position":[3031,2512],"shape":"cicle","lineIds":[5]},{"stationId":40,"stationName":"柴湾","position":[3005,2632],"shape":"cicle","lineIds":[5]},{"stationId":41,"stationName":"黄埔","position":[2531,2228],"shape":"cicle","lineIds":[6]},{"stationId":42,"stationName":"何文田","position":[2463,2184],"shape":"cicle","lineIds":[6,11]},{"stationId":43,"stationName":"油麻地","position":[2342,2147],"shape":"cicle","lineIds":[6,12],"tagDirection":1},{"stationId":44,"stationName":"旺角","position":[2328,2082],"shape":"cicle","lineIds":[6,12],"tagDirection":6},{"stationId":45,"stationName":"太子","position":[2317,2031],"shape":"cicle","lineIds":[6,12]},{"stationId":46,"stationName":"石硖尾","position":[2322,1959],"shape":"cicle","lineIds":[6],"tagDirection":3},{"stationId":47,"stationName":"乐富","position":[2507,1899],"shape":"cicle","lineIds":[6]},{"stationId":48,"stationName":"黄大仙","position":[2575,1861],"shape":"cicle","lineIds":[6],"tagDirection":7},{"stationId":49,"stationName":"钻石山","position":[2647,1876],"shape":"cicle","lineIds":[6,11]},{"stationId":50,"stationName":"彩虹","position":[2724,1931],"shape":"cicle","lineIds":[6]},{"stationId":51,"stationName":"九龙湾","position":[2774,2042],"shape":"cicle","lineIds":[6]},{"stationId":52,"stationName":"牛头角","position":[2825,2124],"shape":"cicle","lineIds":[6]},{"stationId":53,"stationName":"观塘","position":[2898,2156],"shape":"cicle","lineIds":[6]},{"stationId":54,"stationName":"蓝田","position":[2963,2213],"shape":"cicle","lineIds":[6]},{"stationId":55,"stationName":"油塘","position":[3005,2299],"shape":"cicle","lineIds":[6,9]},{"stationId":56,"stationName":"调景岭","position":[3157,2237],"shape":"cicle","lineIds":[6,9]},{"stationId":57,"stationName":"机场","position":[0,2121],"shape":"cicle","lineIds":[7]},{"stationId":58,"stationName":"博览馆","position":[65,2057],"shape":"cicle","lineIds":[7]},{"stationId":59,"stationName":"康城","position":[3326,2325],"shape":"cicle","lineIds":[8]},{"stationId":60,"stationName":"将军澳","position":[3232,2204],"shape":"cicle","lineIds":[8,9]},{"stationId":61,"stationName":"坑口","position":[3277,2124],"shape":"cicle","lineIds":[9]},{"stationId":62,"stationName":"宝琳","position":[3211,2052],"shape":"cicle","lineIds":[9]},{"stationId":63,"stationName":"海洋公园","position":[2380,2790],"shape":"cicle","lineIds":[10]},{"stationId":64,"stationName":"黄竹坑","position":[2316,2797],"shape":"cicle","lineIds":[10]},{"stationId":65,"stationName":"利东","position":[2201,2850],"shape":"cicle","lineIds":[10]},{"stationId":66,"stationName":"海怡半岛","position":[2125,2850],"shape":"cicle","lineIds":[10]},{"stationId":67,"stationName":"屯门","position":[367,1327],"shape":"cicle","lineIds":[11]},{"stationId":68,"stationName":"兆康","position":[424,1159],"shape":"cicle","lineIds":[11]},{"stationId":69,"stationName":"天水围","position":[678,802],"shape":"cicle","lineIds":[11]},{"stationId":70,"stationName":"朗屏","position":[889,802],"shape":"cicle","lineIds":[11]},{"stationId":71,"stationName":"元朗","position":[989,816],"shape":"cicle","lineIds":[11]},{"stationId":72,"stationName":"锦上路","position":[1269,928],"shape":"cicle","lineIds":[11]},{"stationId":73,"stationName":"荃湾西","position":[1737,1594],"shape":"cicle","lineIds":[11]},{"stationId":74,"stationName":"美孚","position":[2013,1898],"shape":"cicle","lineIds":[11]},{"stationId":75,"stationName":"柯士甸","position":[2300,2230],"shape":"cicle","lineIds":[11],"tagDirection":4},{"stationId":76,"stationName":"尖东","position":[2384,2323],"shape":"cicle","lineIds":[11]},{"stationId":77,"stationName":"土瓜湾","position":[2514,2115],"shape":"cicle","lineIds":[11]},{"stationId":78,"stationName":"宋皇台","position":[2545,2020],"shape":"cicle","lineIds":[11]},{"stationId":79,"stationName":"启德","position":[2628,1973],"shape":"cicle","lineIds":[11]},{"stationId":80,"stationName":"显径","position":[2343,1636],"shape":"cicle","lineIds":[11]},{"stationId":81,"stationName":"车公庙","position":[2499,1530],"shape":"cicle","lineIds":[11],"tagDirection":3},{"stationId":82,"stationName":"沙田围","position":[2583,1508],"shape":"cicle","lineIds":[11]},{"stationId":83,"stationName":"第一城","position":[2669,1448],"shape":"cicle","lineIds":[11]},{"stationId":84,"stationName":"石门","position":[2718,1401],"shape":"cicle","lineIds":[11]},{"stationId":85,"stationName":"大水坑","position":[2861,1194],"shape":"cicle","lineIds":[11]},{"stationId":86,"stationName":"恒安","position":[2893,1097],"shape":"cicle","lineIds":[11]},{"stationId":87,"stationName":"马鞍山","position":[2951,1030],"shape":"cicle","lineIds":[11]},{"stationId":88,"stationName":"乌溪沙","position":[3071,987],"shape":"cicle","lineIds":[11]},{"stationId":89,"stationName":"荃湾","position":[1813,1539],"shape":"cicle","lineIds":[12]},{"stationId":90,"stationName":"大窝口","position":[1885,1567],"shape":"cicle","lineIds":[12]},{"stationId":91,"stationName":"葵兴","position":[1948,1644],"shape":"cicle","lineIds":[12]},{"stationId":92,"stationName":"葵芳","position":[1916,1707],"shape":"cicle","lineIds":[12]},{"stationId":93,"stationName":"美孚","position":[1999,1889],"shape":"cicle","lineIds":[12]},{"stationId":94,"stationName":"荔枝角","position":[2118,1903],"shape":"cicle","lineIds":[12]},{"stationId":95,"stationName":"长沙湾","position":[2198,1923],"shape":"cicle","lineIds":[12]},{"stationId":96,"stationName":"深水埗","position":[2259,1969],"shape":"cicle","lineIds":[12],"tagDirection":0},{"stationId":97,"stationName":"佐敦","position":[2351,2229],"shape":"cicle","lineIds":[12],"tagDirection":3},{"stationId":98,"stationName":"尖沙咀","position":[2357,2303],"shape":"cicle","lineIds":[12],"tagDirection":6}],"lines":[{"lineId":1,"lineName":"迪士尼","color":"#EB81B9","stationIds":[1,2],"sign":"迪士尼","order":1,"bendFirst":[true,true]},{"lineId":2,"lineName":"东铁线","color":"#69C7F4","stationIds":[3,4,5,6,7,8,9,10,11,12,13,14,15,16],"sign":"东铁线","order":2,"bendFirst":[true,true,false,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":4,"lineName":"东涌线","color":"#FFA500","stationIds":[18,2,19,20,21,22,23,24],"sign":"东涌线","order":8,"bendFirst":[true,true,true,true,true,true,false,true]},{"lineId":5,"lineName":"港岛线","color":"#3080B7","stationIds":[25,26,27,28,29,3,30,31,32,33,34,35,36,37,38,39,40],"sign":"港岛线","order":5,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":6,"lineName":"观塘线","color":"#00AB4F","stationIds":[41,42,43,44,45,46,7,47,48,49,50,51,52,53,54,55,56],"sign":"观塘线","order":10,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":7,"lineName":"机场快线","color":"#008187","stationIds":[24,23,19,57,58],"sign":"机场快线","order":7,"bendFirst":[true,true,true,true,true]},{"lineId":8,"lineName":"将军澳线","color":"#793E8C","stationIds":[59,60],"sign":"将军澳线","order":8,"bendFirst":[true,true],"subLine":true},{"lineId":9,"lineName":"将军澳线","color":"#793E8C","stationIds":[34,35,55,56,60,61,62],"sign":"将军澳线","order":4,"bendFirst":[true,true,true,true,true,true,true]},{"lineId":10,"lineName":"南港岛线","color":"#B8D201","stationIds":[3,63,64,65,66],"sign":"南港岛线","order":10,"bendFirst":[true,true,true,true,true]},{"lineId":11,"lineName":"屯马线","color":"#871C2A","stationIds":[67,68,69,70,71,72,73,74,21,75,76,5,42,77,78,79,49,80,8,81,82,83,84,85,86,87,88],"sign":"屯马线","order":11,"bendFirst":[true,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true]},{"lineId":12,"lineName":"荃湾线","color":"#FC0601","stationIds":[89,90,91,92,20,93,94,95,96,45,44,43,97,98,3,29],"sign":"荃湾线","order":12,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":13,"lineName":"东铁线","color":"#69C7F4","stationIds":[17,15],"sign":"东铁线","order":13,"bendFirst":[true,true],"subLine":true}],"title":"香港"}
================================================
FILE: public/index.html
================================================
Mini Metro Web
You need to enable JavaScript to run this app.
================================================
FILE: public/manifest.json
================================================
{
"short_name": "Mini Metro Web",
"name": "Mini Metro Web",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "app-icon.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "app-icon.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#ffffff",
"background_color": "#ffffff"
}
================================================
FILE: public/robots.txt
================================================
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:
================================================
FILE: public/shanghai.json
================================================
{"stations":[{"stationId":1,"stationName":"莘庄","position":[3658,2970],"shape":"cicle","lineIds":[1,5]},{"stationId":2,"stationName":"外环路","position":[3735,2872],"shape":"cicle","lineIds":[1]},{"stationId":3,"stationName":"莲花路","position":[3834,2772],"shape":"cicle","lineIds":[1]},{"stationId":4,"stationName":"锦江乐园","position":[3946,2659],"shape":"cicle","lineIds":[1]},{"stationId":5,"stationName":"上海南站","position":[4105,2535],"shape":"cicle","lineIds":[1,3,18]},{"stationId":6,"stationName":"漕宝路","position":[4136,2398],"shape":"cicle","lineIds":[1,15],"tagDirection":3},{"stationId":7,"stationName":"上海体育馆","position":[4179,2253],"shape":"cicle","lineIds":[1,4],"tagDirection":5},{"stationId":8,"stationName":"徐家汇","position":[4171,2126],"shape":"cicle","lineIds":[1,10,14]},{"stationId":9,"stationName":"衡山路","position":[4269,2036],"shape":"cicle","lineIds":[1],"tagDirection":4},{"stationId":10,"stationName":"常熟路","position":[4296,1946],"shape":"cicle","lineIds":[1,8]},{"stationId":11,"stationName":"陕西南路","position":[4392,1930],"shape":"cicle","lineIds":[1,11,15]},{"stationId":12,"stationName":"黄陂南路","position":[4534,1842],"shape":"cicle","lineIds":[1,17],"tagDirection":7},{"stationId":13,"stationName":"人民广场","position":[4556,1754],"shape":"cicle","lineIds":[1,2,9]},{"stationId":14,"stationName":"新闸路","position":[4486,1697],"shape":"cicle","lineIds":[1],"tagDirection":4},{"stationId":15,"stationName":"汉中路","position":[4392,1662],"shape":"cicle","lineIds":[1,15,16],"tagDirection":7},{"stationId":16,"stationName":"上海火车站","position":[4384,1585],"shape":"cicle","lineIds":[1,3,4],"tagDirection":3},{"stationId":17,"stationName":"中山北路","position":[4397,1492],"shape":"cicle","lineIds":[1]},{"stationId":18,"stationName":"延长路","position":[4358,1364],"shape":"cicle","lineIds":[1]},{"stationId":19,"stationName":"上海马戏城","position":[4325,1282],"shape":"cicle","lineIds":[1]},{"stationId":20,"stationName":"汶水路","position":[4307,1156],"shape":"cicle","lineIds":[1]},{"stationId":21,"stationName":"彭浦新村","position":[4291,1015],"shape":"cicle","lineIds":[1]},{"stationId":22,"stationName":"共康路","position":[4275,892],"shape":"cicle","lineIds":[1]},{"stationId":23,"stationName":"通河新村","position":[4220,770],"shape":"cicle","lineIds":[1]},{"stationId":24,"stationName":"呼兰路","position":[4182,684],"shape":"cicle","lineIds":[1]},{"stationId":25,"stationName":"共富新村","position":[4145,530],"shape":"cicle","lineIds":[1]},{"stationId":26,"stationName":"宝安公路","position":[4114,386],"shape":"cicle","lineIds":[1]},{"stationId":27,"stationName":"友谊西路","position":[4084,268],"shape":"cicle","lineIds":[1]},{"stationId":28,"stationName":"富锦路","position":[4051,159],"shape":"cicle","lineIds":[1]},{"stationId":29,"stationName":"浦东国际机场","position":[7861,2572],"shape":"cicle","lineIds":[2,22]},{"stationId":30,"stationName":"海天三路","position":[7773,2397],"shape":"cicle","lineIds":[2]},{"stationId":31,"stationName":"远东大道","position":[7358,2087],"shape":"cicle","lineIds":[2]},{"stationId":32,"stationName":"凌空路","position":[7046,2153],"shape":"cicle","lineIds":[2]},{"stationId":33,"stationName":"川沙","position":[6787,2214],"shape":"cicle","lineIds":[2]},{"stationId":34,"stationName":"华夏东路","position":[6614,2115],"shape":"cicle","lineIds":[2]},{"stationId":35,"stationName":"创新中路","position":[6542,1942],"shape":"cicle","lineIds":[2]},{"stationId":36,"stationName":"唐镇","position":[6367,1940],"shape":"cicle","lineIds":[2]},{"stationId":37,"stationName":"广兰路","position":[6015,1971],"shape":"cicle","lineIds":[2]},{"stationId":38,"stationName":"金科路","position":[5825,2039],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":39,"stationName":"张江高科","position":[5680,2062],"shape":"cicle","lineIds":[2]},{"stationId":40,"stationName":"龙阳路","position":[5381,2045],"shape":"cicle","lineIds":[2,8,19,21,22]},{"stationId":41,"stationName":"世纪公园","position":[5314,1987],"shape":"cicle","lineIds":[2]},{"stationId":42,"stationName":"上海科技馆","position":[5249,1893],"shape":"cicle","lineIds":[2],"tagDirection":5},{"stationId":43,"stationName":"世纪大道","position":[5077,1794],"shape":"cicle","lineIds":[2,4,7,10]},{"stationId":44,"stationName":"东昌路","position":[4960,1748],"shape":"cicle","lineIds":[2],"tagDirection":6},{"stationId":45,"stationName":"陆家嘴","position":[4826,1699],"shape":"cicle","lineIds":[2,17]},{"stationId":46,"stationName":"南京东路","position":[4651,1700],"shape":"cicle","lineIds":[2,11]},{"stationId":47,"stationName":"南京西路","position":[4404,1783],"shape":"cicle","lineIds":[2,15,16]},{"stationId":48,"stationName":"静安寺","position":[4270,1849],"shape":"cicle","lineIds":[2,8,17],"tagDirection":1},{"stationId":49,"stationName":"江苏路","position":[4111,1877],"shape":"cicle","lineIds":[2,14]},{"stationId":50,"stationName":"中山公园","position":[3962,1902],"shape":"cicle","lineIds":[2,3,4]},{"stationId":51,"stationName":"娄山关路","position":[3845,1970],"shape":"cicle","lineIds":[2,18]},{"stationId":52,"stationName":"威宁路","position":[3678,1932],"shape":"cicle","lineIds":[2]},{"stationId":53,"stationName":"北新泾","position":[3545,1917],"shape":"cicle","lineIds":[2]},{"stationId":54,"stationName":"淞虹路","position":[3400,1899],"shape":"cicle","lineIds":[2]},{"stationId":55,"stationName":"虹桥2号航站楼","position":[3068,2139],"shape":"cicle","lineIds":[2,11],"tagDirection":4},{"stationId":56,"stationName":"虹桥火车站","position":[2994,2141],"shape":"cicle","lineIds":[2,20,11],"tagDirection":7},{"stationId":57,"stationName":"徐泾东","position":[2797,2198],"shape":"cicle","lineIds":[2],"tagDirection":4},{"stationId":58,"stationName":"江杨北路","position":[4203,3],"shape":"cicle","lineIds":[3]},{"stationId":59,"stationName":"铁力路","position":[4416,0],"shape":"cicle","lineIds":[3]},{"stationId":60,"stationName":"友谊路","position":[4564,41],"shape":"cicle","lineIds":[3]},{"stationId":61,"stationName":"宝杨路","position":[4600,128],"shape":"cicle","lineIds":[3]},{"stationId":62,"stationName":"水产路","position":[4687,268],"shape":"cicle","lineIds":[3]},{"stationId":63,"stationName":"淞滨路","position":[4733,372],"shape":"cicle","lineIds":[3]},{"stationId":64,"stationName":"张华浜","position":[4791,501],"shape":"cicle","lineIds":[3]},{"stationId":65,"stationName":"淞发路","position":[4809,630],"shape":"cicle","lineIds":[3]},{"stationId":66,"stationName":"长江南路","position":[4719,761],"shape":"cicle","lineIds":[3,21]},{"stationId":67,"stationName":"殷高西路","position":[4653,881],"shape":"cicle","lineIds":[3]},{"stationId":68,"stationName":"江湾镇","position":[4655,1026],"shape":"cicle","lineIds":[3],"tagDirection":6},{"stationId":69,"stationName":"大柏树","position":[4637,1187],"shape":"cicle","lineIds":[3]},{"stationId":70,"stationName":"赤峰路","position":[4629,1269],"shape":"cicle","lineIds":[3]},{"stationId":71,"stationName":"虹口足球场","position":[4596,1367],"shape":"cicle","lineIds":[3,9]},{"stationId":72,"stationName":"东宝兴路","position":[4607,1482],"shape":"cicle","lineIds":[3],"tagDirection":6},{"stationId":73,"stationName":"宝山路","position":[4568,1566],"shape":"cicle","lineIds":[3,4],"tagDirection":4},{"stationId":74,"stationName":"中潭路","position":[4215,1536],"shape":"cicle","lineIds":[3,4]},{"stationId":75,"stationName":"镇坪路","position":[4102,1618],"shape":"cicle","lineIds":[3,4,8]},{"stationId":76,"stationName":"曹杨路","position":[3982,1688],"shape":"cicle","lineIds":[3,4,14,17]},{"stationId":77,"stationName":"金沙江路","position":[3937,1760],"shape":"cicle","lineIds":[3,4,16],"tagDirection":5},{"stationId":78,"stationName":"延安西路","position":[3975,1985],"shape":"cicle","lineIds":[3,4]},{"stationId":79,"stationName":"虹桥路","position":[4013,2106],"shape":"cicle","lineIds":[3,4,11],"tagDirection":1},{"stationId":80,"stationName":"宜山路","position":[4077,2214],"shape":"cicle","lineIds":[3,4,10],"tagDirection":2},{"stationId":81,"stationName":"漕溪路","position":[4189,2314],"shape":"cicle","lineIds":[3],"tagDirection":5},{"stationId":82,"stationName":"龙漕路","position":[4248,2387],"shape":"cicle","lineIds":[3,15]},{"stationId":83,"stationName":"石龙路","position":[4237,2502],"shape":"cicle","lineIds":[3]},{"stationId":84,"stationName":"海伦路","position":[4692,1488],"shape":"cicle","lineIds":[4,11]},{"stationId":85,"stationName":"临平路","position":[4812,1472],"shape":"cicle","lineIds":[4]},{"stationId":86,"stationName":"大连路","position":[4936,1502],"shape":"cicle","lineIds":[4,15]},{"stationId":87,"stationName":"杨树浦路","position":[4977,1562],"shape":"cicle","lineIds":[4],"tagDirection":2},{"stationId":88,"stationName":"浦东大道","position":[4998,1678],"shape":"cicle","lineIds":[4,17],"tagDirection":1},{"stationId":89,"stationName":"浦电路(4号线)","position":[5125,1858],"shape":"cicle","lineIds":[4],"tagDirection":2},{"stationId":90,"stationName":"蓝村路","position":[5081,1964],"shape":"cicle","lineIds":[4,7]},{"stationId":91,"stationName":"塘桥","position":[4992,1984],"shape":"cicle","lineIds":[4],"tagDirection":7},{"stationId":92,"stationName":"南浦大桥","position":[4802,1996],"shape":"cicle","lineIds":[4]},{"stationId":93,"stationName":"西藏南路","position":[4700,2062],"shape":"cicle","lineIds":[4,9],"tagDirection":3},{"stationId":94,"stationName":"鲁班路","position":[4556,2089],"shape":"cicle","lineIds":[4]},{"stationId":95,"stationName":"大木桥路","position":[4437,2141],"shape":"cicle","lineIds":[4,15],"tagDirection":3},{"stationId":96,"stationName":"东安路","position":[4354,2173],"shape":"cicle","lineIds":[4,8]},{"stationId":97,"stationName":"上海体育场","position":[4240,2225],"shape":"cicle","lineIds":[4],"tagDirection":0},{"stationId":98,"stationName":"闵行开发区","position":[3502,4075],"shape":"cicle","lineIds":[24]},{"stationId":99,"stationName":"文井路","position":[3612,4045],"shape":"cicle","lineIds":[24]},{"stationId":100,"stationName":"华宁路","position":[3757,4007],"shape":"cicle","lineIds":[24]},{"stationId":101,"stationName":"金平路","position":[3907,3968],"shape":"cicle","lineIds":[24]},{"stationId":102,"stationName":"东川路","position":[4004,3899],"shape":"cicle","lineIds":[5,24]},{"stationId":103,"stationName":"剑川路","position":[3970,3816],"shape":"cicle","lineIds":[5]},{"stationId":104,"stationName":"北桥","position":[3905,3630],"shape":"cicle","lineIds":[5]},{"stationId":105,"stationName":"颛桥","position":[3823,3411],"shape":"cicle","lineIds":[5]},{"stationId":106,"stationName":"银都路","position":[3707,3188],"shape":"cicle","lineIds":[5]},{"stationId":107,"stationName":"春申路","position":[3663,3099],"shape":"cicle","lineIds":[5]},{"stationId":108,"stationName":"江川路","position":[4039,4027],"shape":"cicle","lineIds":[5]},{"stationId":109,"stationName":"西渡","position":[4129,4188],"shape":"cicle","lineIds":[5]},{"stationId":110,"stationName":"萧塘","position":[4223,4422],"shape":"cicle","lineIds":[5]},{"stationId":111,"stationName":"奉浦大道","position":[4295,4661],"shape":"cicle","lineIds":[5]},{"stationId":112,"stationName":"环城东路","position":[4437,4770],"shape":"cicle","lineIds":[5]},{"stationId":113,"stationName":"望园路","position":[4640,4762],"shape":"cicle","lineIds":[5],"tagDirection":0},{"stationId":114,"stationName":"金海湖","position":[4730,4793],"shape":"cicle","lineIds":[5]},{"stationId":115,"stationName":"奉贤新城","position":[4768,4942],"shape":"cicle","lineIds":[5]},{"stationId":116,"stationName":"东方体育中心","position":[4608,2548],"shape":"cicle","lineIds":[7,9,14]},{"stationId":117,"stationName":"灵岩南路","position":[4758,2595],"shape":"cicle","lineIds":[7]},{"stationId":118,"stationName":"上南路","position":[4869,2593],"shape":"cicle","lineIds":[7],"tagDirection":0},{"stationId":119,"stationName":"华夏西路","position":[4950,2582],"shape":"cicle","lineIds":[7],"tagDirection":2},{"stationId":120,"stationName":"高青路","position":[4963,2482],"shape":"cicle","lineIds":[7]},{"stationId":121,"stationName":"东明路","position":[4913,2355],"shape":"cicle","lineIds":[7,16]},{"stationId":122,"stationName":"高科西路","position":[4903,2224],"shape":"cicle","lineIds":[7,8],"tagDirection":7},{"stationId":123,"stationName":"临沂新村","position":[4972,2149],"shape":"cicle","lineIds":[7]},{"stationId":124,"stationName":"上海儿童医学中心","position":[5039,2048],"shape":"cicle","lineIds":[7]},{"stationId":125,"stationName":"浦电路(6号线)","position":[5098,1880],"shape":"cicle","lineIds":[7],"tagDirection":6},{"stationId":126,"stationName":"源深体育中心","position":[5151,1751],"shape":"cicle","lineIds":[7],"tagDirection":2},{"stationId":127,"stationName":"民生路","position":[5240,1723],"shape":"cicle","lineIds":[7,21]},{"stationId":128,"stationName":"北洋泾路","position":[5328,1690],"shape":"cicle","lineIds":[7]},{"stationId":129,"stationName":"德平路","position":[5447,1627],"shape":"cicle","lineIds":[7],"tagDirection":4},{"stationId":130,"stationName":"云山路","position":[5516,1572],"shape":"cicle","lineIds":[7,17]},{"stationId":131,"stationName":"金桥路","position":[5623,1510],"shape":"cicle","lineIds":[7]},{"stationId":132,"stationName":"博兴路","position":[5672,1446],"shape":"cicle","lineIds":[7]},{"stationId":133,"stationName":"五莲路","position":[5683,1361],"shape":"cicle","lineIds":[7]},{"stationId":134,"stationName":"巨峰路","position":[5688,1274],"shape":"cicle","lineIds":[7,15],"tagDirection":1},{"stationId":135,"stationName":"东靖路","position":[5692,1174],"shape":"cicle","lineIds":[7]},{"stationId":136,"stationName":"五洲大道","position":[5697,1056],"shape":"cicle","lineIds":[7]},{"stationId":137,"stationName":"洲海路","position":[5699,959],"shape":"cicle","lineIds":[7]},{"stationId":138,"stationName":"外高桥保税区南","position":[5824,865],"shape":"cicle","lineIds":[7]},{"stationId":139,"stationName":"航津路","position":[5744,727],"shape":"cicle","lineIds":[7]},{"stationId":140,"stationName":"外高桥保税区北","position":[5674,603],"shape":"cicle","lineIds":[7],"tagDirection":5},{"stationId":141,"stationName":"港城路","position":[5555,549],"shape":"cicle","lineIds":[7,11],"tagDirection":1},{"stationId":142,"stationName":"花木路","position":[5432,1969],"shape":"cicle","lineIds":[8],"tagDirection":2},{"stationId":143,"stationName":"芳华路","position":[5306,2150],"shape":"cicle","lineIds":[8],"tagDirection":7},{"stationId":144,"stationName":"锦绣路","position":[5205,2205],"shape":"cicle","lineIds":[8]},{"stationId":145,"stationName":"杨高南路","position":[5050,2205],"shape":"cicle","lineIds":[8]},{"stationId":146,"stationName":"云台路","position":[4809,2259],"shape":"cicle","lineIds":[8],"tagDirection":3},{"stationId":147,"stationName":"耀华路","position":[4751,2296],"shape":"cicle","lineIds":[8,9],"tagDirection":3},{"stationId":148,"stationName":"长清路","position":[4682,2337],"shape":"cicle","lineIds":[8,16]},{"stationId":149,"stationName":"后滩","position":[4542,2362],"shape":"cicle","lineIds":[8]},{"stationId":150,"stationName":"龙华中路","position":[4375,2237],"shape":"cicle","lineIds":[8,15]},{"stationId":151,"stationName":"肇嘉浜路","position":[4307,2087],"shape":"cicle","lineIds":[8,10],"tagDirection":3},{"stationId":152,"stationName":"昌平路","position":[4231,1744],"shape":"cicle","lineIds":[8]},{"stationId":153,"stationName":"长寿路","position":[4187,1673],"shape":"cicle","lineIds":[8,16]},{"stationId":154,"stationName":"岚皋路","position":[4024,1519],"shape":"cicle","lineIds":[8]},{"stationId":155,"stationName":"新村路","position":[4031,1443],"shape":"cicle","lineIds":[8]},{"stationId":156,"stationName":"大华三路","position":[4034,1341],"shape":"cicle","lineIds":[8]},{"stationId":157,"stationName":"行知路","position":[4022,1236],"shape":"cicle","lineIds":[8]},{"stationId":158,"stationName":"大场镇","position":[3966,1149],"shape":"cicle","lineIds":[8]},{"stationId":159,"stationName":"场中路","position":[3939,1044],"shape":"cicle","lineIds":[8]},{"stationId":160,"stationName":"上大路","position":[3892,932],"shape":"cicle","lineIds":[8]},{"stationId":161,"stationName":"南陈路","position":[3789,864],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":162,"stationName":"上海大学","position":[3694,875],"shape":"cicle","lineIds":[8],"tagDirection":0},{"stationId":163,"stationName":"祁华路","position":[3540,857],"shape":"cicle","lineIds":[8]},{"stationId":164,"stationName":"顾村公园","position":[3535,635],"shape":"cicle","lineIds":[8,18]},{"stationId":165,"stationName":"刘行","position":[3428,505],"shape":"cicle","lineIds":[8]},{"stationId":166,"stationName":"潘广路","position":[3363,439],"shape":"cicle","lineIds":[8]},{"stationId":167,"stationName":"罗南新村","position":[3379,193],"shape":"cicle","lineIds":[8]},{"stationId":168,"stationName":"美兰湖","position":[3304,62],"shape":"cicle","lineIds":[8]},{"stationId":169,"stationName":"沈杜公路","position":[4927,3467],"shape":"cicle","lineIds":[9,23]},{"stationId":170,"stationName":"联航路","position":[4911,3346],"shape":"cicle","lineIds":[9]},{"stationId":171,"stationName":"江月路","position":[4891,3239],"shape":"cicle","lineIds":[9]},{"stationId":172,"stationName":"浦江镇","position":[4867,3115],"shape":"cicle","lineIds":[9]},{"stationId":173,"stationName":"芦恒路","position":[4787,2890],"shape":"cicle","lineIds":[9]},{"stationId":174,"stationName":"凌兆新村","position":[4701,2668],"shape":"cicle","lineIds":[9]},{"stationId":175,"stationName":"杨思","position":[4739,2471],"shape":"cicle","lineIds":[9]},{"stationId":176,"stationName":"成山路","position":[4767,2374],"shape":"cicle","lineIds":[9,16]},{"stationId":177,"stationName":"中华艺术宫","position":[4741,2229],"shape":"cicle","lineIds":[9]},{"stationId":178,"stationName":"陆家浜路","position":[4666,1963],"shape":"cicle","lineIds":[9,10]},{"stationId":179,"stationName":"老西门","position":[4643,1891],"shape":"cicle","lineIds":[9,11]},{"stationId":180,"stationName":"大世界","position":[4596,1807],"shape":"cicle","lineIds":[9,17],"tagDirection":1},{"stationId":181,"stationName":"曲阜路","position":[4520,1658],"shape":"cicle","lineIds":[9,15]},{"stationId":182,"stationName":"中兴路","position":[4495,1549],"shape":"cicle","lineIds":[9],"tagDirection":1},{"stationId":183,"stationName":"西藏北路","position":[4493,1447],"shape":"cicle","lineIds":[9]},{"stationId":184,"stationName":"曲阳路","position":[4716,1316],"shape":"cicle","lineIds":[9]},{"stationId":185,"stationName":"四平路","position":[4820,1332],"shape":"cicle","lineIds":[9,11]},{"stationId":186,"stationName":"鞍山新村","position":[4902,1349],"shape":"cicle","lineIds":[9]},{"stationId":187,"stationName":"江浦路","position":[4988,1332],"shape":"cicle","lineIds":[9,21]},{"stationId":188,"stationName":"黄兴路","position":[5089,1293],"shape":"cicle","lineIds":[9]},{"stationId":189,"stationName":"延吉中路","position":[5154,1196],"shape":"cicle","lineIds":[9]},{"stationId":190,"stationName":"黄兴公园","position":[5138,1127],"shape":"cicle","lineIds":[9]},{"stationId":191,"stationName":"翔殷路","position":[5124,1031],"shape":"cicle","lineIds":[9],"tagDirection":2},{"stationId":192,"stationName":"嫩江路","position":[5124,933],"shape":"cicle","lineIds":[9],"tagDirection":2},{"stationId":193,"stationName":"市光路","position":[5124,854],"shape":"cicle","lineIds":[9],"tagDirection":2},{"stationId":194,"stationName":"曹路","position":[6636,1368],"shape":"cicle","lineIds":[10]},{"stationId":195,"stationName":"民雷路","position":[6486,1398],"shape":"cicle","lineIds":[10]},{"stationId":196,"stationName":"顾唐路","position":[6369,1420],"shape":"cicle","lineIds":[10]},{"stationId":197,"stationName":"金海路","position":[6191,1450],"shape":"cicle","lineIds":[10,15],"tagDirection":1},{"stationId":198,"stationName":"金吉路","position":[6092,1438],"shape":"cicle","lineIds":[10]},{"stationId":199,"stationName":"金桥","position":[5918,1473],"shape":"cicle","lineIds":[10]},{"stationId":200,"stationName":"台儿庄路","position":[5779,1553],"shape":"cicle","lineIds":[10],"tagDirection":3},{"stationId":201,"stationName":"蓝天路","position":[5585,1671],"shape":"cicle","lineIds":[10,17]},{"stationId":202,"stationName":"芳甸路","position":[5389,1763],"shape":"cicle","lineIds":[10]},{"stationId":203,"stationName":"杨高中路","position":[5291,1806],"shape":"cicle","lineIds":[10,21],"tagDirection":6},{"stationId":204,"stationName":"商城路","position":[4967,1779],"shape":"cicle","lineIds":[10],"tagDirection":6},{"stationId":205,"stationName":"小南门","position":[4789,1913],"shape":"cicle","lineIds":[10]},{"stationId":206,"stationName":"马当路","position":[4577,1986],"shape":"cicle","lineIds":[10,16]},{"stationId":207,"stationName":"打浦桥","position":[4491,2018],"shape":"cicle","lineIds":[10]},{"stationId":208,"stationName":"嘉善路","position":[4412,2053],"shape":"cicle","lineIds":[10,15]},{"stationId":209,"stationName":"桂林路","position":[3985,2333],"shape":"cicle","lineIds":[10,18]},{"stationId":210,"stationName":"漕河泾开发区","position":[3782,2375],"shape":"cicle","lineIds":[10]},{"stationId":211,"stationName":"合川路","position":[3652,2416],"shape":"cicle","lineIds":[10]},{"stationId":212,"stationName":"星中路","position":[3494,2500],"shape":"cicle","lineIds":[10]},{"stationId":213,"stationName":"七宝","position":[3297,2528],"shape":"cicle","lineIds":[10]},{"stationId":214,"stationName":"中春路","position":[3182,2587],"shape":"cicle","lineIds":[10]},{"stationId":215,"stationName":"九亭","position":[2999,2709],"shape":"cicle","lineIds":[10]},{"stationId":216,"stationName":"泗泾","position":[2407,2898],"shape":"cicle","lineIds":[10]},{"stationId":217,"stationName":"佘山","position":[2102,3040],"shape":"cicle","lineIds":[10]},{"stationId":218,"stationName":"洞泾","position":[2109,3236],"shape":"cicle","lineIds":[10]},{"stationId":219,"stationName":"松江大学城","position":[2130,3541],"shape":"cicle","lineIds":[10]},{"stationId":220,"stationName":"松江新城","position":[2112,3778],"shape":"cicle","lineIds":[10]},{"stationId":221,"stationName":"松江体育中心","position":[2110,3921],"shape":"cicle","lineIds":[10]},{"stationId":222,"stationName":"醉白池","position":[2098,4070],"shape":"cicle","lineIds":[10]},{"stationId":223,"stationName":"松江南站","position":[2114,4233],"shape":"cicle","lineIds":[10]},{"stationId":224,"stationName":"基隆路","position":[5709,568],"shape":"cicle","lineIds":[11]},{"stationId":225,"stationName":"高桥","position":[5434,555],"shape":"cicle","lineIds":[11],"tagDirection":0},{"stationId":226,"stationName":"高桥西","position":[5297,566],"shape":"cicle","lineIds":[11]},{"stationId":227,"stationName":"双江路","position":[5199,545],"shape":"cicle","lineIds":[11]},{"stationId":228,"stationName":"国帆路","position":[4936,686],"shape":"cicle","lineIds":[11],"tagDirection":3},{"stationId":229,"stationName":"新江湾城","position":[4874,796],"shape":"cicle","lineIds":[11]},{"stationId":230,"stationName":"殷高东路","position":[4873,864],"shape":"cicle","lineIds":[11]},{"stationId":231,"stationName":"三门路","position":[4888,949],"shape":"cicle","lineIds":[11],"tagDirection":1},{"stationId":232,"stationName":"江湾体育场","position":[4938,1038],"shape":"cicle","lineIds":[11],"tagDirection":2},{"stationId":233,"stationName":"五角场","position":[4951,1101],"shape":"cicle","lineIds":[11]},{"stationId":234,"stationName":"国权路","position":[4905,1188],"shape":"cicle","lineIds":[11,21]},{"stationId":235,"stationName":"同济大学","position":[4868,1260],"shape":"cicle","lineIds":[11]},{"stationId":236,"stationName":"邮电新村","position":[4747,1397],"shape":"cicle","lineIds":[11]},{"stationId":237,"stationName":"四川北路","position":[4647,1561],"shape":"cicle","lineIds":[11],"tagDirection":2},{"stationId":238,"stationName":"天潼路","position":[4628,1643],"shape":"cicle","lineIds":[11,15]},{"stationId":239,"stationName":"豫园","position":[4679,1801],"shape":"cicle","lineIds":[11,17]},{"stationId":240,"stationName":"新天地","position":[4556,1918],"shape":"cicle","lineIds":[11,16],"tagDirection":0},{"stationId":241,"stationName":"上海图书馆","position":[4248,2002],"shape":"cicle","lineIds":[11]},{"stationId":242,"stationName":"交通大学","position":[4157,2059],"shape":"cicle","lineIds":[11,14]},{"stationId":243,"stationName":"宋园路","position":[3925,2116],"shape":"cicle","lineIds":[11],"tagDirection":3},{"stationId":244,"stationName":"伊犁路","position":[3844,2092],"shape":"cicle","lineIds":[11],"tagDirection":0},{"stationId":245,"stationName":"水城路","position":[3727,2086],"shape":"cicle","lineIds":[11]},{"stationId":246,"stationName":"龙溪路","position":[3605,2137],"shape":"cicle","lineIds":[11,25]},{"stationId":247,"stationName":"龙柏新村","position":[3509,2312],"shape":"cicle","lineIds":[25]},{"stationId":248,"stationName":"紫藤路","position":[3453,2384],"shape":"cicle","lineIds":[25]},{"stationId":249,"stationName":"航中路","position":[3358,2427],"shape":"cicle","lineIds":[25]},{"stationId":250,"stationName":"上海动物园","position":[3486,2179],"shape":"cicle","lineIds":[11]},{"stationId":251,"stationName":"虹桥1号航站楼","position":[3278,2168],"shape":"cicle","lineIds":[11]},{"stationId":252,"stationName":"嘉定北","position":[2178,166],"shape":"cicle","lineIds":[26]},{"stationId":253,"stationName":"嘉定西","position":[2083,310],"shape":"cicle","lineIds":[26]},{"stationId":254,"stationName":"白银路","position":[2258,628],"shape":"cicle","lineIds":[26]},{"stationId":255,"stationName":"嘉定新城","position":[2348,781],"shape":"cicle","lineIds":[14,26]},{"stationId":256,"stationName":"马陆","position":[2574,884],"shape":"cicle","lineIds":[14]},{"stationId":257,"stationName":"陈翔公路","position":[2873,1016],"shape":"cicle","lineIds":[14]},{"stationId":258,"stationName":"南翔","position":[3036,1112],"shape":"cicle","lineIds":[14]},{"stationId":259,"stationName":"桃浦新村","position":[3301,1265],"shape":"cicle","lineIds":[14]},{"stationId":260,"stationName":"武威路","position":[3451,1315],"shape":"cicle","lineIds":[14]},{"stationId":261,"stationName":"祁连山路","position":[3565,1365],"shape":"cicle","lineIds":[14]},{"stationId":262,"stationName":"李子园","position":[3704,1391],"shape":"cicle","lineIds":[14]},{"stationId":263,"stationName":"上海西站","position":[3814,1454],"shape":"cicle","lineIds":[14,18]},{"stationId":264,"stationName":"真如","position":[3877,1574],"shape":"cicle","lineIds":[14,17]},{"stationId":265,"stationName":"枫桥路","position":[3919,1663],"shape":"cicle","lineIds":[14],"tagDirection":6},{"stationId":266,"stationName":"隆德路","position":[4040,1776],"shape":"cicle","lineIds":[14,16]},{"stationId":267,"stationName":"上海游泳馆","position":[4219,2290],"shape":"cicle","lineIds":[14],"tagDirection":1},{"stationId":268,"stationName":"龙华","position":[4334,2354],"shape":"cicle","lineIds":[14,15]},{"stationId":269,"stationName":"云锦路","position":[4390,2416],"shape":"cicle","lineIds":[14]},{"stationId":270,"stationName":"龙耀路","position":[4402,2484],"shape":"cicle","lineIds":[14]},{"stationId":271,"stationName":"三林","position":[4923,2649],"shape":"cicle","lineIds":[14]},{"stationId":272,"stationName":"三林东","position":[5035,2617],"shape":"cicle","lineIds":[14],"tagDirection":3},{"stationId":273,"stationName":"浦三路","position":[5195,2573],"shape":"cicle","lineIds":[14]},{"stationId":274,"stationName":"御桥","position":[5514,2500],"shape":"cicle","lineIds":[14,21],"tagDirection":7},{"stationId":275,"stationName":"罗山路","position":[5736,2549],"shape":"cicle","lineIds":[14,19]},{"stationId":276,"stationName":"秀沿路","position":[5789,2700],"shape":"cicle","lineIds":[14]},{"stationId":277,"stationName":"康新公路","position":[5977,2777],"shape":"cicle","lineIds":[14]},{"stationId":278,"stationName":"迪士尼","position":[6484,2669],"shape":"cicle","lineIds":[14]},{"stationId":279,"stationName":"上海赛车场","position":[2066,762],"shape":"cicle","lineIds":[14]},{"stationId":280,"stationName":"昌吉东路","position":[1809,1145],"shape":"cicle","lineIds":[14]},{"stationId":281,"stationName":"上海汽车城","position":[1612,1227],"shape":"cicle","lineIds":[14]},{"stationId":282,"stationName":"安亭","position":[1425,1196],"shape":"cicle","lineIds":[14]},{"stationId":283,"stationName":"兆丰路","position":[1308,1191],"shape":"cicle","lineIds":[14]},{"stationId":284,"stationName":"光明路","position":[977,1119],"shape":"cicle","lineIds":[14]},{"stationId":285,"stationName":"花桥","position":[849,1093],"shape":"cicle","lineIds":[14]},{"stationId":286,"stationName":"七莘路","position":[3429,2765],"shape":"cicle","lineIds":[15]},{"stationId":287,"stationName":"虹莘路","position":[3608,2707],"shape":"cicle","lineIds":[15]},{"stationId":288,"stationName":"顾戴路","position":[3723,2672],"shape":"cicle","lineIds":[15]},{"stationId":289,"stationName":"东兰路","position":[3724,2523],"shape":"cicle","lineIds":[15]},{"stationId":290,"stationName":"虹梅路","position":[3777,2478],"shape":"cicle","lineIds":[15]},{"stationId":291,"stationName":"虹漕路","position":[3910,2441],"shape":"cicle","lineIds":[15]},{"stationId":292,"stationName":"桂林公园","position":[4001,2411],"shape":"cicle","lineIds":[15,18],"tagDirection":1},{"stationId":293,"stationName":"国际客运中心","position":[4786,1580],"shape":"cicle","lineIds":[15]},{"stationId":294,"stationName":"提篮桥","position":[4873,1546],"shape":"cicle","lineIds":[15],"tagDirection":7},{"stationId":295,"stationName":"江浦公园","position":[5042,1436],"shape":"cicle","lineIds":[15,21]},{"stationId":296,"stationName":"宁国路","position":[5128,1395],"shape":"cicle","lineIds":[15]},{"stationId":297,"stationName":"隆昌路","position":[5252,1329],"shape":"cicle","lineIds":[15]},{"stationId":298,"stationName":"爱国路","position":[5331,1283],"shape":"cicle","lineIds":[15]},{"stationId":299,"stationName":"复兴岛","position":[5418,1273],"shape":"cicle","lineIds":[15]},{"stationId":300,"stationName":"东陆路","position":[5596,1256],"shape":"cicle","lineIds":[15]},{"stationId":301,"stationName":"杨高北路","position":[5834,1280],"shape":"cicle","lineIds":[15]},{"stationId":302,"stationName":"金京路","position":[5959,1283],"shape":"cicle","lineIds":[15]},{"stationId":303,"stationName":"申江路","position":[6073,1279],"shape":"cicle","lineIds":[15]},{"stationId":304,"stationName":"金运路","position":[2998,1671],"shape":"cicle","lineIds":[16]},{"stationId":305,"stationName":"金沙江西路","position":[3156,1670],"shape":"cicle","lineIds":[16]},{"stationId":306,"stationName":"丰庄","position":[3356,1656],"shape":"cicle","lineIds":[16]},{"stationId":307,"stationName":"祁连山南路","position":[3477,1705],"shape":"cicle","lineIds":[16]},{"stationId":308,"stationName":"真北路","position":[3624,1759],"shape":"cicle","lineIds":[16]},{"stationId":309,"stationName":"大渡河路","position":[3749,1764],"shape":"cicle","lineIds":[16,18],"tagDirection":5},{"stationId":310,"stationName":"武宁路","position":[4110,1738],"shape":"cicle","lineIds":[16,17]},{"stationId":311,"stationName":"江宁路","position":[4253,1638],"shape":"cicle","lineIds":[16]},{"stationId":312,"stationName":"自然博物馆","position":[4428,1717],"shape":"cicle","lineIds":[16],"tagDirection":6},{"stationId":313,"stationName":"淮海中路","position":[4448,1881],"shape":"cicle","lineIds":[16],"tagDirection":6},{"stationId":314,"stationName":"世博会博物馆","position":[4621,2105],"shape":"cicle","lineIds":[16],"tagDirection":4},{"stationId":315,"stationName":"世博大道","position":[4648,2253],"shape":"cicle","lineIds":[16]},{"stationId":316,"stationName":"华鹏路","position":[5070,2318],"shape":"cicle","lineIds":[16]},{"stationId":317,"stationName":"下南路","position":[5207,2288],"shape":"cicle","lineIds":[16]},{"stationId":318,"stationName":"北蔡","position":[5325,2280],"shape":"cicle","lineIds":[16],"tagDirection":0},{"stationId":319,"stationName":"陈春路","position":[5387,2330],"shape":"cicle","lineIds":[16],"tagDirection":5},{"stationId":320,"stationName":"莲溪路","position":[5469,2390],"shape":"cicle","lineIds":[16,21]},{"stationId":321,"stationName":"华夏中路","position":[5636,2324],"shape":"cicle","lineIds":[16,19]},{"stationId":322,"stationName":"中科路","position":[5829,2293],"shape":"cicle","lineIds":[16]},{"stationId":323,"stationName":"学林路","position":[5948,2246],"shape":"cicle","lineIds":[16]},{"stationId":324,"stationName":"张江路","position":[6096,2190],"shape":"cicle","lineIds":[16]},{"stationId":325,"stationName":"封浜","position":[2775,1406],"shape":"cicle","lineIds":[17]},{"stationId":326,"stationName":"乐秀路","position":[2946,1428],"shape":"cicle","lineIds":[17]},{"stationId":327,"stationName":"临洮路","position":[3113,1451],"shape":"cicle","lineIds":[17]},{"stationId":328,"stationName":"嘉怡路","position":[3262,1489],"shape":"cicle","lineIds":[17]},{"stationId":329,"stationName":"定边路","position":[3429,1517],"shape":"cicle","lineIds":[17]},{"stationId":330,"stationName":"真新新村","position":[3524,1526],"shape":"cicle","lineIds":[17]},{"stationId":331,"stationName":"真光路","position":[3646,1548],"shape":"cicle","lineIds":[17],"tagDirection":5},{"stationId":332,"stationName":"铜川路","position":[3775,1574],"shape":"cicle","lineIds":[17,18],"tagDirection":7},{"stationId":333,"stationName":"中宁路","position":[3950,1631],"shape":"cicle","lineIds":[17]},{"stationId":334,"stationName":"武定路","position":[4166,1808],"shape":"cicle","lineIds":[17]},{"stationId":335,"stationName":"浦东南路","position":[4929,1696],"shape":"cicle","lineIds":[17],"tagDirection":7},{"stationId":336,"stationName":"源深路","position":[5115,1668],"shape":"cicle","lineIds":[17]},{"stationId":337,"stationName":"昌邑路","position":[5206,1642],"shape":"cicle","lineIds":[17,21]},{"stationId":338,"stationName":"歇浦路","position":[5320,1574],"shape":"cicle","lineIds":[17]},{"stationId":339,"stationName":"黄杨路","position":[5715,1741],"shape":"cicle","lineIds":[17]},{"stationId":340,"stationName":"云顺路","position":[5808,1711],"shape":"cicle","lineIds":[17]},{"stationId":341,"stationName":"浦东足球场","position":[5959,1665],"shape":"cicle","lineIds":[17]},{"stationId":342,"stationName":"金粤路","position":[6095,1660],"shape":"cicle","lineIds":[17]},{"stationId":343,"stationName":"桂桥路","position":[6156,1587],"shape":"cicle","lineIds":[17]},{"stationId":344,"stationName":"锦秋路","position":[3622,879],"shape":"cicle","lineIds":[18]},{"stationId":345,"stationName":"丰翔路","position":[3613,999],"shape":"cicle","lineIds":[18]},{"stationId":346,"stationName":"南大路","position":[3608,1089],"shape":"cicle","lineIds":[18]},{"stationId":347,"stationName":"祁安路","position":[3653,1166],"shape":"cicle","lineIds":[18]},{"stationId":348,"stationName":"古浪路","position":[3727,1236],"shape":"cicle","lineIds":[18]},{"stationId":349,"stationName":"武威东路","position":[3726,1325],"shape":"cicle","lineIds":[18],"tagDirection":2},{"stationId":350,"stationName":"梅岭北路","position":[3775,1648],"shape":"cicle","lineIds":[18],"tagDirection":6},{"stationId":351,"stationName":"长风公园","position":[3768,1829],"shape":"cicle","lineIds":[18]},{"stationId":352,"stationName":"红宝石路","position":[3788,2093],"shape":"cicle","lineIds":[18]},{"stationId":353,"stationName":"姚虹路","position":[3869,2166],"shape":"cicle","lineIds":[18],"tagDirection":5},{"stationId":354,"stationName":"吴中路","position":[3944,2238],"shape":"cicle","lineIds":[18],"tagDirection":5},{"stationId":355,"stationName":"华东理工大学","position":[4095,2639],"shape":"cicle","lineIds":[18],"tagDirection":2},{"stationId":356,"stationName":"罗秀路","position":[4163,2766],"shape":"cicle","lineIds":[18]},{"stationId":357,"stationName":"朱梅路","position":[4183,2838],"shape":"cicle","lineIds":[18]},{"stationId":358,"stationName":"华泾西","position":[4206,2947],"shape":"cicle","lineIds":[18]},{"stationId":359,"stationName":"虹梅南路","position":[4135,3043],"shape":"cicle","lineIds":[18]},{"stationId":360,"stationName":"景西路","position":[3992,3084],"shape":"cicle","lineIds":[18]},{"stationId":361,"stationName":"曙建路","position":[3984,3175],"shape":"cicle","lineIds":[18]},{"stationId":362,"stationName":"双柏路","position":[4022,3249],"shape":"cicle","lineIds":[18]},{"stationId":363,"stationName":"元江路","position":[4124,3453],"shape":"cicle","lineIds":[18]},{"stationId":364,"stationName":"永德路","position":[4237,3690],"shape":"cicle","lineIds":[18]},{"stationId":365,"stationName":"紫竹高新区","position":[4317,3849],"shape":"cicle","lineIds":[18]},{"stationId":366,"stationName":"周浦东","position":[5874,2981],"shape":"cicle","lineIds":[19]},{"stationId":367,"stationName":"鹤沙航城","position":[5917,3303],"shape":"cicle","lineIds":[19]},{"stationId":368,"stationName":"航头东","position":[5980,3532],"shape":"cicle","lineIds":[19]},{"stationId":369,"stationName":"新场","position":[6294,3626],"shape":"cicle","lineIds":[19]},{"stationId":370,"stationName":"野生动物园","position":[6797,3578],"shape":"cicle","lineIds":[19]},{"stationId":371,"stationName":"惠南","position":[7421,3543],"shape":"cicle","lineIds":[19]},{"stationId":372,"stationName":"惠南东","position":[7743,3817],"shape":"cicle","lineIds":[19]},{"stationId":373,"stationName":"书院","position":[8310,4489],"shape":"cicle","lineIds":[19]},{"stationId":374,"stationName":"临港大道","position":[8913,4846],"shape":"cicle","lineIds":[19]},{"stationId":375,"stationName":"滴水湖","position":[9100,5009],"shape":"cicle","lineIds":[19]},{"stationId":376,"stationName":"诸光路","position":[2735,2164],"shape":"cicle","lineIds":[20]},{"stationId":377,"stationName":"蟠龙路","position":[2591,2218],"shape":"cicle","lineIds":[20]},{"stationId":378,"stationName":"徐盈路","position":[2344,2300],"shape":"cicle","lineIds":[20]},{"stationId":379,"stationName":"徐泾北城","position":[2221,2326],"shape":"cicle","lineIds":[20]},{"stationId":380,"stationName":"嘉松中路","position":[2044,2440],"shape":"cicle","lineIds":[20]},{"stationId":381,"stationName":"赵巷","position":[1727,2469],"shape":"cicle","lineIds":[20]},{"stationId":382,"stationName":"汇金路","position":[1321,2469],"shape":"cicle","lineIds":[20]},{"stationId":383,"stationName":"青浦新城","position":[1061,2492],"shape":"cicle","lineIds":[20]},{"stationId":384,"stationName":"漕盈路","position":[774,2477],"shape":"cicle","lineIds":[20]},{"stationId":385,"stationName":"淀山湖大道","position":[626,2737],"shape":"cicle","lineIds":[20]},{"stationId":386,"stationName":"朱家角","position":[295,3075],"shape":"cicle","lineIds":[20]},{"stationId":387,"stationName":"东方绿舟","position":[0,3096],"shape":"cicle","lineIds":[20]},{"stationId":388,"stationName":"殷高路","position":[4760,864],"shape":"cicle","lineIds":[21]},{"stationId":389,"stationName":"上海财经大学","position":[4768,1004],"shape":"cicle","lineIds":[21],"tagDirection":2},{"stationId":390,"stationName":"复旦大学","position":[4800,1118],"shape":"cicle","lineIds":[21]},{"stationId":391,"stationName":"抚顺路","position":[4963,1245],"shape":"cicle","lineIds":[21]},{"stationId":392,"stationName":"平凉路","position":[5072,1491],"shape":"cicle","lineIds":[21],"tagDirection":2},{"stationId":393,"stationName":"丹阳路","position":[5116,1543],"shape":"cicle","lineIds":[21],"tagDirection":2},{"stationId":394,"stationName":"迎春路","position":[5315,1871],"shape":"cicle","lineIds":[21],"tagDirection":1},{"stationId":395,"stationName":"芳芯路","position":[5397,2170],"shape":"cicle","lineIds":[21],"tagDirection":2},{"stationId":396,"stationName":"北中路","position":[5418,2254],"shape":"cicle","lineIds":[21],"tagDirection":2},{"stationId":397,"stationName":"康桥","position":[5478,2739],"shape":"cicle","lineIds":[21]},{"stationId":398,"stationName":"周浦","position":[5481,2940],"shape":"cicle","lineIds":[21]},{"stationId":399,"stationName":"繁荣路","position":[5510,3056],"shape":"cicle","lineIds":[21]},{"stationId":400,"stationName":"沈梅路","position":[5611,3170],"shape":"cicle","lineIds":[21]},{"stationId":401,"stationName":"鹤涛路","position":[5664,3363],"shape":"cicle","lineIds":[21]},{"stationId":402,"stationName":"下沙","position":[5695,3537],"shape":"cicle","lineIds":[21]},{"stationId":403,"stationName":"航头","position":[5765,3708],"shape":"cicle","lineIds":[21]},{"stationId":404,"stationName":"三鲁公路","position":[5079,3520],"shape":"cicle","lineIds":[23]},{"stationId":405,"stationName":"闵瑞路","position":[5108,3602],"shape":"cicle","lineIds":[23]},{"stationId":406,"stationName":"浦航路","position":[5111,3671],"shape":"cicle","lineIds":[23]},{"stationId":407,"stationName":"东城一路","position":[5126,3777],"shape":"cicle","lineIds":[23]},{"stationId":408,"stationName":"汇臻路","position":[5050,3829],"shape":"cicle","lineIds":[23]}],"lines":[{"lineId":1,"lineName":"1号线","color":"#E3002A","stationIds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28],"sign":1,"order":1,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":2,"lineName":"2号线","color":"#86B81C","stationIds":[29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,13,47,48,49,50,51,52,53,54,55,56,57],"sign":2,"order":2,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":3,"lineName":"3号线","color":"#FCD600","stationIds":[58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,16,74,75,76,77,50,78,79,80,81,82,83,5],"sign":3,"order":4,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,true,false,false,false,false,true,true,true,true,true]},{"lineId":4,"lineName":"4号线","color":"#5A2B8D","stationIds":[80,79,78,50,77,76,75,74,16,73,84,85,86,87,88,43,89,90,91,92,93,94,95,96,97,7,80],"sign":4,"order":3,"bendFirst":[true,true,true,true,false,true,true,false,false,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,false,true]},{"lineId":5,"lineName":"5号线","color":"#96499A","stationIds":[115,114,113,112,111,110,109,108,102,103,104,105,106,107,1],"sign":5,"order":5,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":7,"lineName":"6号线","color":"#F0087D","stationIds":[116,117,118,119,120,121,122,123,124,90,125,43,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141],"sign":"6","order":7,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true]},{"lineId":8,"lineName":"7号线","color":"#EE782E","stationIds":[142,40,143,144,145,122,146,147,148,149,150,96,151,10,48,152,153,75,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168],"sign":"7","order":8,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":9,"lineName":"8号线","color":"#01A2E2","stationIds":[169,170,171,172,173,174,116,175,176,147,177,93,178,179,180,13,181,182,183,71,184,185,186,187,188,189,190,191,192,193],"sign":"8","order":9,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":10,"lineName":"9号线","color":"#69C7F4","stationIds":[194,195,196,197,198,199,200,201,202,203,43,204,205,178,206,207,208,151,8,80,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223],"sign":"9","order":10,"bendFirst":[true,true,true,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":11,"lineName":"10号线","color":"#C6AFD4","stationIds":[224,141,225,226,227,228,229,230,231,232,233,234,235,185,236,84,237,238,46,239,179,240,11,241,242,79,243,244,245,246,250,251,55,56],"sign":"10","order":11,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true]},{"lineId":14,"lineName":"11号线","color":"#871C2A","stationIds":[278,277,276,275,274,273,272,271,116,270,269,268,267,8,242,49,266,76,265,264,263,262,261,260,259,258,257,256,255,279,280,281,282,283,284,285],"sign":"11","order":14,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":15,"lineName":"12号线","color":"#007A61","stationIds":[286,287,288,289,290,291,292,6,82,268,150,95,208,11,47,15,181,238,293,294,86,295,296,297,298,299,300,134,301,302,303,197],"sign":"12","order":15,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":16,"lineName":"13号线","color":"#EB81B9","stationIds":[304,305,306,307,308,309,77,266,310,153,311,15,312,47,313,240,206,314,315,148,176,121,316,317,318,319,320,321,322,323,324],"sign":"13","order":16,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":17,"lineName":"14号线","color":"#818530","stationIds":[325,326,327,328,329,330,331,332,264,333,76,310,334,48,12,180,239,45,335,88,336,337,338,130,201,339,340,341,342,343],"sign":"14","order":17,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":18,"lineName":"15号线","color":"#DAC17D","stationIds":[164,344,345,346,347,348,349,263,332,350,309,351,51,352,353,354,209,292,5,355,356,357,358,359,360,361,362,363,364,365],"sign":"15","order":18,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,false,true,true,true,true,true,true,true,true,true,true]},{"lineId":19,"lineName":"16号线","color":"#98D1C0","stationIds":[40,321,275,366,367,368,369,370,371,372,373,374,375],"sign":"16","order":19,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":20,"lineName":"17号线","color":"#B85A4E","stationIds":[56,376,377,378,379,380,381,382,383,384,385,386,387],"sign":"17","order":20,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":21,"lineName":"18号线","color":"#D0970A","stationIds":[66,388,389,390,234,391,187,295,392,393,337,127,203,394,40,395,396,320,274,397,398,399,400,401,402,403],"sign":"18","order":21,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":22,"lineName":"磁悬浮","color":"#008187","stationIds":[29,40],"sign":"磁浮线","order":22,"bendFirst":[true,true]},{"lineId":23,"lineName":"浦江线","color":"#B5B5B6","stationIds":[169,404,405,406,407,408],"sign":"浦江线","order":23,"bendFirst":[true,true,true,true,true,true]},{"lineId":24,"lineName":"5号线支线","color":"#96499A","stationIds":[98,99,100,101,102],"sign":"5","order":24,"bendFirst":[true,true,true,true,true],"subLine":true},{"lineId":25,"lineName":"10号线支线","color":"#C6AFD4","stationIds":[249,248,247,246],"sign":"10","order":25,"bendFirst":[true,true,true,true],"subLine":true},{"lineId":26,"lineName":"11号线支线","color":"#871C2A","stationIds":[255,254,253,252],"sign":"11","order":26,"bendFirst":[false,true,true,true],"subLine":true}],"title":"上海"}
================================================
FILE: public/shenzhen.json
================================================
{"stations":[{"stationId":1,"stationName":"罗湖","position":[3489,2743],"shape":"cicle","lineIds":[1],"tagDirection":6},{"stationId":2,"stationName":"国贸","position":[3491,2667],"shape":"cicle","lineIds":[1],"tagDirection":1},{"stationId":3,"stationName":"老街","position":[3472,2621],"shape":"cicle","lineIds":[1,4],"tagDirection":7},{"stationId":4,"stationName":"大剧院","position":[3380,2646],"shape":"cicle","lineIds":[1,3]},{"stationId":5,"stationName":"科学馆","position":[3254,2658],"shape":"cicle","lineIds":[1,7],"tagDirection":5},{"stationId":6,"stationName":"华强路","position":[3154,2659],"shape":"cicle","lineIds":[1]},{"stationId":7,"stationName":"岗厦","position":[2984,2715],"shape":"cicle","lineIds":[1,11],"tagDirection":3},{"stationId":8,"stationName":"会展中心","position":[2913,2716],"shape":"cicle","lineIds":[1,5],"tagDirection":4},{"stationId":9,"stationName":"购物公园","position":[2848,2717],"shape":"cicle","lineIds":[1,4]},{"stationId":10,"stationName":"香蜜湖","position":[2689,2675],"shape":"cicle","lineIds":[1],"tagDirection":4},{"stationId":11,"stationName":"车公庙","position":[2561,2701],"shape":"cicle","lineIds":[1,9,10,12],"tagDirection":3},{"stationId":12,"stationName":"竹子林","position":[2441,2730],"shape":"cicle","lineIds":[1]},{"stationId":13,"stationName":"侨城东","position":[2269,2740],"shape":"cicle","lineIds":[1]},{"stationId":14,"stationName":"华侨城","position":[2156,2729],"shape":"cicle","lineIds":[1],"tagDirection":4},{"stationId":15,"stationName":"世界之窗","position":[2044,2695],"shape":"cicle","lineIds":[1,3],"tagDirection":5},{"stationId":16,"stationName":"白石洲","position":[1973,2668],"shape":"cicle","lineIds":[1],"tagDirection":0},{"stationId":17,"stationName":"高新园","position":[1841,2661],"shape":"cicle","lineIds":[1]},{"stationId":18,"stationName":"深大","position":[1744,2677],"shape":"cicle","lineIds":[1]},{"stationId":19,"stationName":"桃园","position":[1550,2749],"shape":"cicle","lineIds":[1,13]},{"stationId":20,"stationName":"大新","position":[1453,2741],"shape":"cicle","lineIds":[1]},{"stationId":21,"stationName":"鲤鱼门","position":[1333,2744],"shape":"cicle","lineIds":[1],"tagDirection":1},{"stationId":22,"stationName":"前海湾","position":[1282,2693],"shape":"cicle","lineIds":[1,6,12]},{"stationId":23,"stationName":"新安","position":[1248,2589],"shape":"cicle","lineIds":[1]},{"stationId":24,"stationName":"宝安中心","position":[1175,2518],"shape":"cicle","lineIds":[1,6],"tagDirection":2},{"stationId":25,"stationName":"宝体","position":[1109,2456],"shape":"cicle","lineIds":[1],"tagDirection":1},{"stationId":26,"stationName":"坪洲","position":[1009,2375],"shape":"cicle","lineIds":[1],"tagDirection":1},{"stationId":27,"stationName":"西乡","position":[933,2312],"shape":"cicle","lineIds":[1],"tagDirection":1},{"stationId":28,"stationName":"固戍","position":[770,2050],"shape":"cicle","lineIds":[1]},{"stationId":29,"stationName":"后瑞","position":[657,1775],"shape":"cicle","lineIds":[1]},{"stationId":30,"stationName":"机场东","position":[529,1594],"shape":"cicle","lineIds":[1,13]},{"stationId":31,"stationName":"比亚迪北","position":[5913,1242],"shape":"cicle","lineIds":[2],"tagDirection":2},{"stationId":32,"stationName":"龙背","position":[5897,1191],"shape":"cicle","lineIds":[2],"tagDirection":1},{"stationId":33,"stationName":"自然博物馆西","position":[5884,1145],"shape":"cicle","lineIds":[2],"tagDirection":1},{"stationId":34,"stationName":"未来城","position":[5828,1080],"shape":"cicle","lineIds":[2],"tagDirection":2},{"stationId":35,"stationName":"燕子岭","position":[5918,1035],"shape":"cicle","lineIds":[2]},{"stationId":36,"stationName":"综合保税区","position":[5914,971],"shape":"cicle","lineIds":[2]},{"stationId":37,"stationName":"中芯国际","position":[5860,948],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":38,"stationName":"坪山中心","position":[5818,954],"shape":"cicle","lineIds":[2],"tagDirection":3},{"stationId":39,"stationName":"文化聚落","position":[5758,962],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":40,"stationName":"站前路东","position":[5707,979],"shape":"cicle","lineIds":[2],"tagDirection":0},{"stationId":41,"stationName":"坪山高铁站","position":[5577,1009],"shape":"cicle","lineIds":[2]},{"stationId":42,"stationName":"赤湾","position":[1277,3272],"shape":"cicle","lineIds":[3,6]},{"stationId":43,"stationName":"蛇口港","position":[1426,3298],"shape":"cicle","lineIds":[3]},{"stationId":44,"stationName":"海上世界","position":[1453,3213],"shape":"cicle","lineIds":[3,13],"tagDirection":3},{"stationId":45,"stationName":"水湾","position":[1499,3180],"shape":"cicle","lineIds":[3]},{"stationId":46,"stationName":"东角头","position":[1614,3201],"shape":"cicle","lineIds":[3]},{"stationId":47,"stationName":"湾厦","position":[1688,3130],"shape":"cicle","lineIds":[3]},{"stationId":48,"stationName":"海月","position":[1679,3060],"shape":"cicle","lineIds":[3]},{"stationId":49,"stationName":"登良","position":[1678,2972],"shape":"cicle","lineIds":[3]},{"stationId":50,"stationName":"后海","position":[1727,2880],"shape":"cicle","lineIds":[3,12]},{"stationId":51,"stationName":"科苑","position":[1764,2794],"shape":"cicle","lineIds":[3],"tagDirection":6},{"stationId":52,"stationName":"红树湾","position":[1987,2812],"shape":"cicle","lineIds":[3],"tagDirection":7},{"stationId":53,"stationName":"侨城北","position":[2194,2624],"shape":"cicle","lineIds":[3]},{"stationId":54,"stationName":"深康","position":[2291,2599],"shape":"cicle","lineIds":[3]},{"stationId":55,"stationName":"安托山","position":[2365,2576],"shape":"cicle","lineIds":[3,9],"tagDirection":0},{"stationId":56,"stationName":"侨香","position":[2446,2566],"shape":"cicle","lineIds":[3]},{"stationId":57,"stationName":"香蜜","position":[2541,2536],"shape":"cicle","lineIds":[3]},{"stationId":58,"stationName":"香梅北","position":[2629,2525],"shape":"cicle","lineIds":[3],"tagDirection":0},{"stationId":59,"stationName":"景田","position":[2736,2530],"shape":"cicle","lineIds":[3,10]},{"stationId":60,"stationName":"莲花西","position":[2803,2597],"shape":"cicle","lineIds":[3]},{"stationId":61,"stationName":"福田","position":[2831,2663],"shape":"cicle","lineIds":[3,4,12]},{"stationId":62,"stationName":"市民中心","position":[2914,2646],"shape":"cicle","lineIds":[3,5]},{"stationId":63,"stationName":"岗厦北","position":[2982,2681],"shape":"cicle","lineIds":[3,11,12,14],"tagDirection":3},{"stationId":64,"stationName":"华强北","position":[3156,2619],"shape":"cicle","lineIds":[3,9],"tagDirection":7},{"stationId":65,"stationName":"燕南","position":[3228,2618],"shape":"cicle","lineIds":[3],"tagDirection":0},{"stationId":66,"stationName":"湖贝","position":[3557,2621],"shape":"cicle","lineIds":[3],"tagDirection":2},{"stationId":67,"stationName":"黄贝岭","position":[3666,2603],"shape":"cicle","lineIds":[3,6]},{"stationId":68,"stationName":"新秀","position":[3790,2587],"shape":"cicle","lineIds":[3]},{"stationId":69,"stationName":"莲塘口岸","position":[3858,2529],"shape":"cicle","lineIds":[3]},{"stationId":70,"stationName":"仙湖路","position":[3966,2423],"shape":"cicle","lineIds":[3]},{"stationId":71,"stationName":"莲塘","position":[4045,2423],"shape":"cicle","lineIds":[3]},{"stationId":72,"stationName":"梧桐山南","position":[4134,2485],"shape":"cicle","lineIds":[3]},{"stationId":73,"stationName":"沙头角","position":[4534,2521],"shape":"cicle","lineIds":[3]},{"stationId":74,"stationName":"海山","position":[4679,2508],"shape":"cicle","lineIds":[3]},{"stationId":75,"stationName":"盐田港西","position":[4771,2438],"shape":"cicle","lineIds":[3]},{"stationId":76,"stationName":"深外高中","position":[4748,2223],"shape":"cicle","lineIds":[3]},{"stationId":77,"stationName":"盐田路","position":[4865,2167],"shape":"cicle","lineIds":[3]},{"stationId":78,"stationName":"鸿安围","position":[4944,2158],"shape":"cicle","lineIds":[3]},{"stationId":79,"stationName":"盐田墟","position":[5066,2191],"shape":"cicle","lineIds":[3]},{"stationId":80,"stationName":"大梅沙","position":[5380,2123],"shape":"cicle","lineIds":[3]},{"stationId":81,"stationName":"小梅沙","position":[5567,2042],"shape":"cicle","lineIds":[3]},{"stationId":82,"stationName":"福保","position":[2858,2985],"shape":"cicle","lineIds":[4]},{"stationId":83,"stationName":"益田","position":[2812,2901],"shape":"cicle","lineIds":[4],"tagDirection":3},{"stationId":84,"stationName":"石厦","position":[2838,2827],"shape":"cicle","lineIds":[4,9],"tagDirection":7},{"stationId":85,"stationName":"少年宫","position":[2904,2578],"shape":"cicle","lineIds":[4,5],"tagDirection":1},{"stationId":86,"stationName":"莲花村","position":[2981,2577],"shape":"cicle","lineIds":[4,11],"tagDirection":1},{"stationId":87,"stationName":"华新","position":[3169,2574],"shape":"cicle","lineIds":[4,9],"tagDirection":1},{"stationId":88,"stationName":"通新岭","position":[3265,2573],"shape":"cicle","lineIds":[4,7],"tagDirection":3},{"stationId":89,"stationName":"红岭","position":[3348,2580],"shape":"cicle","lineIds":[4,10]},{"stationId":90,"stationName":"晒布","position":[3529,2571],"shape":"cicle","lineIds":[4],"tagDirection":7},{"stationId":91,"stationName":"翠竹","position":[3601,2502],"shape":"cicle","lineIds":[4]},{"stationId":92,"stationName":"田贝","position":[3603,2391],"shape":"cicle","lineIds":[4,9]},{"stationId":93,"stationName":"水贝","position":[3552,2324],"shape":"cicle","lineIds":[4]},{"stationId":94,"stationName":"草埔","position":[3467,2219],"shape":"cicle","lineIds":[4]},{"stationId":95,"stationName":"布吉","position":[3514,2040],"shape":"cicle","lineIds":[4,6,14]},{"stationId":96,"stationName":"木棉湾","position":[3605,1994],"shape":"cicle","lineIds":[4]},{"stationId":97,"stationName":"大芬","position":[3686,1922],"shape":"cicle","lineIds":[4]},{"stationId":98,"stationName":"丹竹头","position":[3783,1866],"shape":"cicle","lineIds":[4]},{"stationId":99,"stationName":"六约","position":[4109,1721],"shape":"cicle","lineIds":[4]},{"stationId":100,"stationName":"塘坑","position":[4229,1672],"shape":"cicle","lineIds":[4]},{"stationId":101,"stationName":"横岗","position":[4392,1579],"shape":"cicle","lineIds":[4]},{"stationId":102,"stationName":"永湖","position":[4483,1474],"shape":"cicle","lineIds":[4]},{"stationId":103,"stationName":"荷坳","position":[4545,1333],"shape":"cicle","lineIds":[4]},{"stationId":104,"stationName":"大运","position":[4584,1207],"shape":"cicle","lineIds":[4,14,15]},{"stationId":105,"stationName":"爱联","position":[4646,1089],"shape":"cicle","lineIds":[4]},{"stationId":106,"stationName":"吉祥","position":[4745,969],"shape":"cicle","lineIds":[4]},{"stationId":107,"stationName":"龙城广场","position":[4848,895],"shape":"cicle","lineIds":[4]},{"stationId":108,"stationName":"南联","position":[4958,844],"shape":"cicle","lineIds":[4]},{"stationId":109,"stationName":"双龙","position":[5076,776],"shape":"cicle","lineIds":[4,15]},{"stationId":110,"stationName":"福田口岸","position":[2975,2895],"shape":"cicle","lineIds":[5,11]},{"stationId":111,"stationName":"福民","position":[2961,2832],"shape":"cicle","lineIds":[5,9,11]},{"stationId":112,"stationName":"莲花北","position":[2896,2458],"shape":"cicle","lineIds":[5]},{"stationId":113,"stationName":"上梅林","position":[2897,2359],"shape":"cicle","lineIds":[5,10]},{"stationId":114,"stationName":"民乐","position":[2790,2122],"shape":"cicle","lineIds":[5]},{"stationId":115,"stationName":"白石龙","position":[2727,2049],"shape":"cicle","lineIds":[5]},{"stationId":116,"stationName":"深圳北站","position":[2605,1959],"shape":"cicle","lineIds":[5,6,7]},{"stationId":117,"stationName":"红山","position":[2537,1845],"shape":"cicle","lineIds":[5,7]},{"stationId":118,"stationName":"上塘","position":[2430,1692],"shape":"cicle","lineIds":[5]},{"stationId":119,"stationName":"龙胜","position":[2422,1616],"shape":"cicle","lineIds":[5]},{"stationId":120,"stationName":"龙华","position":[2526,1547],"shape":"cicle","lineIds":[5]},{"stationId":121,"stationName":"清湖","position":[2668,1423],"shape":"cicle","lineIds":[5]},{"stationId":122,"stationName":"清湖北","position":[2706,1343],"shape":"cicle","lineIds":[5]},{"stationId":123,"stationName":"竹村","position":[2705,1197],"shape":"cicle","lineIds":[5]},{"stationId":124,"stationName":"茜坑","position":[2713,1091],"shape":"cicle","lineIds":[5]},{"stationId":125,"stationName":"长湖","position":[2750,956],"shape":"cicle","lineIds":[5]},{"stationId":126,"stationName":"观澜","position":[2850,912],"shape":"cicle","lineIds":[5]},{"stationId":127,"stationName":"松元厦","position":[2951,858],"shape":"cicle","lineIds":[5]},{"stationId":128,"stationName":"观澜湖","position":[3065,848],"shape":"cicle","lineIds":[5]},{"stationId":129,"stationName":"牛湖","position":[3195,818],"shape":"cicle","lineIds":[5]},{"stationId":130,"stationName":"荔湾","position":[1237,3086],"shape":"cicle","lineIds":[6]},{"stationId":131,"stationName":"铁路公园","position":[1202,3035],"shape":"cicle","lineIds":[6]},{"stationId":132,"stationName":"妈湾","position":[1162,2985],"shape":"cicle","lineIds":[6]},{"stationId":133,"stationName":"前湾公园","position":[1198,2903],"shape":"cicle","lineIds":[6]},{"stationId":134,"stationName":"前湾","position":[1260,2840],"shape":"cicle","lineIds":[6]},{"stationId":135,"stationName":"桂湾","position":[1282,2740],"shape":"cicle","lineIds":[6]},{"stationId":136,"stationName":"临海","position":[1195,2622],"shape":"cicle","lineIds":[6]},{"stationId":137,"stationName":"宝华","position":[1153,2567],"shape":"cicle","lineIds":[6]},{"stationId":138,"stationName":"翻身","position":[1235,2467],"shape":"cicle","lineIds":[6]},{"stationId":139,"stationName":"灵芝","position":[1344,2374],"shape":"cicle","lineIds":[6,13]},{"stationId":140,"stationName":"洪浪北","position":[1408,2318],"shape":"cicle","lineIds":[6]},{"stationId":141,"stationName":"兴东","position":[1492,2246],"shape":"cicle","lineIds":[6]},{"stationId":142,"stationName":"留仙洞","position":[1738,2258],"shape":"cicle","lineIds":[6]},{"stationId":143,"stationName":"西丽","position":[1846,2257],"shape":"cicle","lineIds":[6,9]},{"stationId":144,"stationName":"大学城","position":[1955,2244],"shape":"cicle","lineIds":[6]},{"stationId":145,"stationName":"塘朗","position":[2299,2163],"shape":"cicle","lineIds":[6]},{"stationId":146,"stationName":"长岭陂","position":[2405,2070],"shape":"cicle","lineIds":[6]},{"stationId":147,"stationName":"民治","position":[2711,1888],"shape":"cicle","lineIds":[6]},{"stationId":148,"stationName":"五和","position":[2909,1795],"shape":"cicle","lineIds":[6,11]},{"stationId":149,"stationName":"坂田","position":[3011,1789],"shape":"cicle","lineIds":[6]},{"stationId":150,"stationName":"杨美","position":[3102,1796],"shape":"cicle","lineIds":[6]},{"stationId":151,"stationName":"上水径","position":[3356,1843],"shape":"cicle","lineIds":[6]},{"stationId":152,"stationName":"下水径","position":[3409,1915],"shape":"cicle","lineIds":[6]},{"stationId":153,"stationName":"长龙","position":[3440,1983],"shape":"cicle","lineIds":[6]},{"stationId":154,"stationName":"百鸽笼","position":[3606,2109],"shape":"cicle","lineIds":[6]},{"stationId":155,"stationName":"布心","position":[3683,2254],"shape":"cicle","lineIds":[6]},{"stationId":156,"stationName":"太安","position":[3673,2329],"shape":"cicle","lineIds":[6,9]},{"stationId":157,"stationName":"怡景","position":[3703,2505],"shape":"cicle","lineIds":[6]},{"stationId":158,"stationName":"松岗","position":[599,341],"shape":"cicle","lineIds":[7,12]},{"stationId":159,"stationName":"溪头","position":[679,317],"shape":"cicle","lineIds":[7]},{"stationId":160,"stationName":"松岗公园","position":[785,285],"shape":"cicle","lineIds":[7]},{"stationId":161,"stationName":"薯田埔","position":[979,254],"shape":"cicle","lineIds":[7]},{"stationId":162,"stationName":"合水口","position":[1112,248],"shape":"cicle","lineIds":[7]},{"stationId":163,"stationName":"公明广场","position":[1216,289],"shape":"cicle","lineIds":[7]},{"stationId":164,"stationName":"红花山","position":[1375,287],"shape":"cicle","lineIds":[7]},{"stationId":165,"stationName":"楼村","position":[1536,305],"shape":"cicle","lineIds":[7]},{"stationId":166,"stationName":"科学公园","position":[1643,289],"shape":"cicle","lineIds":[7]},{"stationId":167,"stationName":"光明","position":[1755,347],"shape":"cicle","lineIds":[7,8]},{"stationId":168,"stationName":"光明大街","position":[1729,444],"shape":"cicle","lineIds":[7]},{"stationId":169,"stationName":"凤凰城","position":[1631,589],"shape":"cicle","lineIds":[7]},{"stationId":170,"stationName":"长圳","position":[1611,800],"shape":"cicle","lineIds":[7]},{"stationId":171,"stationName":"上屋","position":[1688,1173],"shape":"cicle","lineIds":[7]},{"stationId":172,"stationName":"官田","position":[1799,1231],"shape":"cicle","lineIds":[7]},{"stationId":173,"stationName":"阳台山东","position":[2220,1383],"shape":"cicle","lineIds":[7]},{"stationId":174,"stationName":"元芬","position":[2354,1501],"shape":"cicle","lineIds":[7]},{"stationId":175,"stationName":"上芬","position":[2469,1660],"shape":"cicle","lineIds":[7]},{"stationId":176,"stationName":"梅林关","position":[2834,2119],"shape":"cicle","lineIds":[7]},{"stationId":177,"stationName":"翰岭","position":[2991,2322],"shape":"cicle","lineIds":[7],"tagDirection":1},{"stationId":178,"stationName":"银湖","position":[3201,2381],"shape":"cicle","lineIds":[7,10]},{"stationId":179,"stationName":"八卦岭","position":[3249,2444],"shape":"cicle","lineIds":[7,9],"tagDirection":6},{"stationId":180,"stationName":"体育中心","position":[3263,2517],"shape":"cicle","lineIds":[7],"tagDirection":3},{"stationId":181,"stationName":"圳美","position":[1795,186],"shape":"cicle","lineIds":[8]},{"stationId":182,"stationName":"中大","position":[1838,96],"shape":"cicle","lineIds":[8]},{"stationId":183,"stationName":"深理工","position":[1988,0],"shape":"cicle","lineIds":[8]},{"stationId":184,"stationName":"西丽湖","position":[1959,2128],"shape":"cicle","lineIds":[9]},{"stationId":185,"stationName":"茶光","position":[1845,2322],"shape":"cicle","lineIds":[9]},{"stationId":186,"stationName":"珠光","position":[1908,2381],"shape":"cicle","lineIds":[9]},{"stationId":187,"stationName":"龙井","position":[2049,2424],"shape":"cicle","lineIds":[9]},{"stationId":188,"stationName":"桃源村","position":[2122,2465],"shape":"cicle","lineIds":[9]},{"stationId":189,"stationName":"深云","position":[2240,2498],"shape":"cicle","lineIds":[9]},{"stationId":190,"stationName":"农林","position":[2484,2660],"shape":"cicle","lineIds":[9]},{"stationId":191,"stationName":"上沙","position":[2657,2818],"shape":"cicle","lineIds":[9]},{"stationId":192,"stationName":"沙尾","position":[2728,2856],"shape":"cicle","lineIds":[9]},{"stationId":193,"stationName":"皇岗村","position":[2900,2832],"shape":"cicle","lineIds":[9],"tagDirection":4},{"stationId":194,"stationName":"皇岗口岸","position":[3042,2846],"shape":"cicle","lineIds":[9]},{"stationId":195,"stationName":"赤尾","position":[3138,2750],"shape":"cicle","lineIds":[9],"tagDirection":3},{"stationId":196,"stationName":"华强南","position":[3170,2692],"shape":"cicle","lineIds":[9],"tagDirection":3},{"stationId":197,"stationName":"黄木岗","position":[3175,2507],"shape":"cicle","lineIds":[9,14]},{"stationId":198,"stationName":"红岭北","position":[3345,2443],"shape":"cicle","lineIds":[9,10],"tagDirection":0},{"stationId":199,"stationName":"笋岗","position":[3415,2430],"shape":"cicle","lineIds":[9]},{"stationId":200,"stationName":"洪湖","position":[3527,2403],"shape":"cicle","lineIds":[9]},{"stationId":201,"stationName":"前湾","position":[1260,2840],"shape":"cicle","lineIds":[10],"tagDirection":2},{"stationId":202,"stationName":"梦海","position":[1290,2875],"shape":"cicle","lineIds":[10]},{"stationId":203,"stationName":"怡海","position":[1340,2914],"shape":"cicle","lineIds":[10]},{"stationId":204,"stationName":"荔林","position":[1434,2945],"shape":"cicle","lineIds":[10]},{"stationId":205,"stationName":"南油西","position":[1486,2973],"shape":"cicle","lineIds":[10]},{"stationId":206,"stationName":"南油","position":[1560,2961],"shape":"cicle","lineIds":[10,13]},{"stationId":207,"stationName":"南山书城","position":[1589,2870],"shape":"cicle","lineIds":[10],"tagDirection":3},{"stationId":208,"stationName":"深大南","position":[1659,2804],"shape":"cicle","lineIds":[10]},{"stationId":209,"stationName":"粤海门","position":[1758,2737],"shape":"cicle","lineIds":[10],"tagDirection":4},{"stationId":210,"stationName":"高新南","position":[1803,2736],"shape":"cicle","lineIds":[10]},{"stationId":211,"stationName":"红树湾南","position":[2017,2834],"shape":"cicle","lineIds":[10,12],"tagDirection":4},{"stationId":212,"stationName":"深湾","position":[2088,2833],"shape":"cicle","lineIds":[10]},{"stationId":213,"stationName":"深圳湾公园","position":[2233,2848],"shape":"cicle","lineIds":[10]},{"stationId":214,"stationName":"下沙","position":[2546,2775],"shape":"cicle","lineIds":[10]},{"stationId":215,"stationName":"香梅","position":[2699,2610],"shape":"cicle","lineIds":[10]},{"stationId":216,"stationName":"梅景","position":[2682,2453],"shape":"cicle","lineIds":[10]},{"stationId":217,"stationName":"下梅林","position":[2720,2407],"shape":"cicle","lineIds":[10]},{"stationId":218,"stationName":"梅村","position":[2826,2379],"shape":"cicle","lineIds":[10]},{"stationId":219,"stationName":"孖岭","position":[2980,2379],"shape":"cicle","lineIds":[10,11]},{"stationId":220,"stationName":"泥岗","position":[3279,2386],"shape":"cicle","lineIds":[10]},{"stationId":221,"stationName":"园岭","position":[3347,2501],"shape":"cicle","lineIds":[10],"tagDirection":2},{"stationId":222,"stationName":"红岭南","position":[3350,2664],"shape":"cicle","lineIds":[10]},{"stationId":223,"stationName":"鹿丹村","position":[3388,2707],"shape":"cicle","lineIds":[10]},{"stationId":224,"stationName":"人民南","position":[3484,2709],"shape":"cicle","lineIds":[10],"tagDirection":2},{"stationId":225,"stationName":"向西村","position":[3560,2666],"shape":"cicle","lineIds":[10],"tagDirection":3},{"stationId":226,"stationName":"文锦","position":[3612,2641],"shape":"cicle","lineIds":[10],"tagDirection":3},{"stationId":227,"stationName":"双拥街","position":[3632,1077],"shape":"cicle","lineIds":[11]},{"stationId":228,"stationName":"平湖","position":[3560,1127],"shape":"cicle","lineIds":[11]},{"stationId":229,"stationName":"禾花","position":[3513,1203],"shape":"cicle","lineIds":[11]},{"stationId":230,"stationName":"华南城","position":[3525,1304],"shape":"cicle","lineIds":[11]},{"stationId":231,"stationName":"木古","position":[3527,1370],"shape":"cicle","lineIds":[11]},{"stationId":232,"stationName":"上李朗","position":[3573,1523],"shape":"cicle","lineIds":[11]},{"stationId":233,"stationName":"凉帽山","position":[3492,1601],"shape":"cicle","lineIds":[11]},{"stationId":234,"stationName":"甘坑","position":[3360,1585],"shape":"cicle","lineIds":[11]},{"stationId":235,"stationName":"雪象","position":[3092,1495],"shape":"cicle","lineIds":[11]},{"stationId":236,"stationName":"岗头","position":[3021,1493],"shape":"cicle","lineIds":[11]},{"stationId":237,"stationName":"华为","position":[2974,1559],"shape":"cicle","lineIds":[11]},{"stationId":238,"stationName":"贝尔路","position":[2966,1647],"shape":"cicle","lineIds":[11]},{"stationId":239,"stationName":"坂田北","position":[2936,1715],"shape":"cicle","lineIds":[11]},{"stationId":240,"stationName":"光雅园","position":[2911,1900],"shape":"cicle","lineIds":[11]},{"stationId":241,"stationName":"南坑","position":[2908,1955],"shape":"cicle","lineIds":[11]},{"stationId":242,"stationName":"雅宝","position":[2905,2018],"shape":"cicle","lineIds":[11]},{"stationId":243,"stationName":"冬瓜岭","position":[2978,2459],"shape":"cicle","lineIds":[11]},{"stationId":244,"stationName":"碧头","position":[502,219],"shape":"cicle","lineIds":[12]},{"stationId":245,"stationName":"后亭","position":[571,538],"shape":"cicle","lineIds":[12]},{"stationId":246,"stationName":"沙井","position":[547,755],"shape":"cicle","lineIds":[12]},{"stationId":247,"stationName":"马安山","position":[472,894],"shape":"cicle","lineIds":[12]},{"stationId":248,"stationName":"塘尾","position":[481,1043],"shape":"cicle","lineIds":[12]},{"stationId":249,"stationName":"桥头","position":[412,1182],"shape":"cicle","lineIds":[12]},{"stationId":250,"stationName":"福永","position":[363,1328],"shape":"cicle","lineIds":[12,13]},{"stationId":251,"stationName":"机场北","position":[284,1552],"shape":"cicle","lineIds":[12,16]},{"stationId":252,"stationName":"机场","position":[439,1824],"shape":"cicle","lineIds":[12]},{"stationId":253,"stationName":"碧海湾","position":[863,2313],"shape":"cicle","lineIds":[12]},{"stationId":254,"stationName":"宝安","position":[1106,2518],"shape":"cicle","lineIds":[12],"tagDirection":5},{"stationId":255,"stationName":"南山","position":[1537,2823],"shape":"cicle","lineIds":[12,13]},{"stationId":256,"stationName":"海上田园东","position":[115,752],"shape":"cicle","lineIds":[13]},{"stationId":257,"stationName":"海上田园南","position":[46,881],"shape":"cicle","lineIds":[13],"tagDirection":2},{"stationId":258,"stationName":"国展北","position":[71,1032],"shape":"cicle","lineIds":[13,16]},{"stationId":259,"stationName":"国展","position":[82,1104],"shape":"cicle","lineIds":[13,16]},{"stationId":260,"stationName":"福海西","position":[197,1180],"shape":"cicle","lineIds":[13]},{"stationId":261,"stationName":"桥头西","position":[277,1233],"shape":"cicle","lineIds":[13]},{"stationId":262,"stationName":"怀德","position":[491,1397],"shape":"cicle","lineIds":[13]},{"stationId":263,"stationName":"福围","position":[514,1495],"shape":"cicle","lineIds":[13]},{"stationId":264,"stationName":"兴围","position":[647,1635],"shape":"cicle","lineIds":[13]},{"stationId":265,"stationName":"黄田","position":[759,1762],"shape":"cicle","lineIds":[13],"tagDirection":1},{"stationId":266,"stationName":"钟屋南","position":[894,1878],"shape":"cicle","lineIds":[13]},{"stationId":267,"stationName":"西乡桃源","position":[926,1954],"shape":"cicle","lineIds":[13]},{"stationId":268,"stationName":"平峦山","position":[1000,2032],"shape":"cicle","lineIds":[13]},{"stationId":269,"stationName":"宝田一路","position":[1087,2119],"shape":"cicle","lineIds":[13]},{"stationId":270,"stationName":"宝安客运站","position":[1139,2170],"shape":"cicle","lineIds":[13]},{"stationId":271,"stationName":"流塘","position":[1195,2226],"shape":"cicle","lineIds":[13]},{"stationId":272,"stationName":"上川","position":[1254,2285],"shape":"cicle","lineIds":[13]},{"stationId":273,"stationName":"新安公园","position":[1411,2441],"shape":"cicle","lineIds":[13]},{"stationId":274,"stationName":"同乐南","position":[1490,2500],"shape":"cicle","lineIds":[13]},{"stationId":275,"stationName":"中山公园","position":[1534,2594],"shape":"cicle","lineIds":[13]},{"stationId":276,"stationName":"南头古城","position":[1544,2679],"shape":"cicle","lineIds":[13]},{"stationId":277,"stationName":"南光","position":[1545,2897],"shape":"cicle","lineIds":[13]},{"stationId":278,"stationName":"四海","position":[1522,3060],"shape":"cicle","lineIds":[13]},{"stationId":279,"stationName":"花果山","position":[1493,3120],"shape":"cicle","lineIds":[13],"tagDirection":6},{"stationId":280,"stationName":"太子湾","position":[1413,3336],"shape":"cicle","lineIds":[13]},{"stationId":281,"stationName":"左炮台东","position":[1281,3369],"shape":"cicle","lineIds":[13]},{"stationId":282,"stationName":"罗湖北","position":[3432,2256],"shape":"cicle","lineIds":[14]},{"stationId":283,"stationName":"石芽岭","position":[3682,1779],"shape":"cicle","lineIds":[14]},{"stationId":284,"stationName":"六约北","position":[4060,1638],"shape":"cicle","lineIds":[14]},{"stationId":285,"stationName":"四联","position":[4231,1580],"shape":"cicle","lineIds":[14]},{"stationId":286,"stationName":"坳背","position":[4437,1377],"shape":"cicle","lineIds":[14]},{"stationId":287,"stationName":"嶂背","position":[4798,1141],"shape":"cicle","lineIds":[14]},{"stationId":288,"stationName":"南约","position":[5083,1183],"shape":"cicle","lineIds":[14]},{"stationId":289,"stationName":"宝龙","position":[5280,1181],"shape":"cicle","lineIds":[14]},{"stationId":290,"stationName":"锦龙","position":[5641,1262],"shape":"cicle","lineIds":[14]},{"stationId":291,"stationName":"坪山围","position":[5739,1150],"shape":"cicle","lineIds":[14,15]},{"stationId":292,"stationName":"坪山广场","position":[5790,1058],"shape":"cicle","lineIds":[14]},{"stationId":293,"stationName":"坪山中心","position":[5818,995],"shape":"cicle","lineIds":[14],"tagDirection":5},{"stationId":294,"stationName":"坑梓","position":[6012,610],"shape":"cicle","lineIds":[14]},{"stationId":295,"stationName":"沙田","position":[6308,502],"shape":"cicle","lineIds":[14]},{"stationId":296,"stationName":"大运中心","position":[4510,1111],"shape":"cicle","lineIds":[15]},{"stationId":297,"stationName":"龙城公园","position":[4486,1019],"shape":"cicle","lineIds":[15]},{"stationId":298,"stationName":"黄阁坑","position":[4484,873],"shape":"cicle","lineIds":[15]},{"stationId":299,"stationName":"愉园","position":[4536,781],"shape":"cicle","lineIds":[15]},{"stationId":300,"stationName":"回龙埔","position":[4661,772],"shape":"cicle","lineIds":[15]},{"stationId":301,"stationName":"尚景","position":[4738,770],"shape":"cicle","lineIds":[15]},{"stationId":302,"stationName":"盛平","position":[4829,748],"shape":"cicle","lineIds":[15]},{"stationId":303,"stationName":"龙园","position":[4966,754],"shape":"cicle","lineIds":[15]},{"stationId":304,"stationName":"新塘围","position":[5167,809],"shape":"cicle","lineIds":[15]},{"stationId":305,"stationName":"龙东","position":[5266,849],"shape":"cicle","lineIds":[15]},{"stationId":306,"stationName":"宝龙同乐","position":[5425,972],"shape":"cicle","lineIds":[15]},{"stationId":307,"stationName":"坪山","position":[5577,1007],"shape":"cicle","lineIds":[15],"tagDirection":4},{"stationId":308,"stationName":"新和","position":[5657,1032],"shape":"cicle","lineIds":[15]},{"stationId":309,"stationName":"六和","position":[5680,1104],"shape":"cicle","lineIds":[15]},{"stationId":310,"stationName":"坪环","position":[5769,1210],"shape":"cicle","lineIds":[15],"tagDirection":5},{"stationId":311,"stationName":"东纵纪念馆","position":[5854,1204],"shape":"cicle","lineIds":[15],"tagDirection":4},{"stationId":312,"stationName":"沙壆","position":[5952,1199],"shape":"cicle","lineIds":[15],"tagDirection":2},{"stationId":313,"stationName":"燕子湖","position":[6056,1179],"shape":"cicle","lineIds":[15]},{"stationId":314,"stationName":"石井","position":[6133,1145],"shape":"cicle","lineIds":[15]},{"stationId":315,"stationName":"技术大学","position":[6314,1080],"shape":"cicle","lineIds":[15]},{"stationId":316,"stationName":"田心","position":[6444,1126],"shape":"cicle","lineIds":[15]},{"stationId":317,"stationName":"国展南","position":[86,1226],"shape":"cicle","lineIds":[16]},{"stationId":318,"stationName":"会展城","position":[0,942],"shape":"cicle","lineIds":[16]}],"lines":[{"lineId":1,"lineName":"1号线/罗宝线","color":"#00AB4F","stationIds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30],"sign":"1","order":1,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":2,"lineName":"坪山云巴1号线","color":"#2249A3","stationIds":[31,32,33,34,35,36,37,38,39,40,41],"sign":"坪山云巴1","order":2,"bendFirst":[true,true,true,true,true,true,true,true,true,false,true]},{"lineId":3,"lineName":"2号线/8号线","color":"#B35A1F","stationIds":[42,43,44,45,46,47,48,49,50,51,52,15,53,54,55,56,57,58,59,60,61,62,63,64,65,4,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81],"sign":"2","order":3,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":4,"lineName":"3号线/龙岗线","color":"#01A2E2","stationIds":[82,83,84,9,61,85,86,87,88,89,3,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109],"sign":"3","order":4,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":5,"lineName":"4号线/龙华线","color":"#CC0000","stationIds":[110,111,8,62,85,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129],"sign":"4","order":5,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":6,"lineName":"5号线/环中线","color":"#96499A","stationIds":[42,130,131,132,133,134,135,22,136,137,24,138,139,140,141,142,143,144,145,146,116,147,148,149,150,151,152,153,95,154,155,156,157,67],"sign":"5","order":6,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":7,"lineName":"6号线/光明线","color":"#008187","stationIds":[158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,117,116,176,177,178,179,180,88,5],"sign":"6","order":7,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":8,"lineName":"6号线支线","color":"#00AD8E","stationIds":[167,181,182,183],"sign":"6","order":8,"bendFirst":[true,true,true,true]},{"lineId":9,"lineName":"7号线/西丽线","color":"#2249A3","stationIds":[184,143,185,186,187,188,189,55,190,11,191,192,84,193,111,194,195,196,64,87,197,179,198,199,200,92,156],"sign":"7","order":9,"bendFirst":[true,true,true,true,true,true,true,true,true,false,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":10,"lineName":"9号线/梅林线","color":"#86656D","stationIds":[201,202,203,204,205,206,207,208,209,210,211,212,213,214,11,215,59,216,217,218,113,219,178,220,198,221,89,222,223,224,225,226],"sign":"9","order":10,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":11,"lineName":"10号线/坂田线","color":"#EA6183","stationIds":[227,228,229,230,231,232,233,234,235,236,237,238,239,148,240,241,242,219,243,86,63,7,111,110],"sign":"10","order":11,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":12,"lineName":"11号线/机场线","color":"#741D51","stationIds":[244,158,245,246,247,248,249,250,251,252,253,254,22,255,50,211,11,61,63],"sign":"11","order":12,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,false,true]},{"lineId":13,"lineName":"12号线/南宝线","color":"#A49ABD","stationIds":[256,257,258,259,260,261,250,262,263,30,264,265,266,267,268,269,270,271,272,139,273,274,275,276,19,255,277,206,278,279,44,280,281],"sign":"12","order":13,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":14,"lineName":"14号线/东部快线","color":"#DAC17D","stationIds":[63,197,282,95,283,284,285,286,104,287,288,289,290,291,292,293,294,295],"sign":"14","order":14,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":15,"lineName":"16号线/龙坪线","color":"#1C24A4","stationIds":[104,296,297,298,299,300,301,302,303,109,304,305,306,307,308,309,291,310,311,312,313,314,315,316],"sign":"16","order":15,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":16,"lineName":"20号线","color":"#019AC3","stationIds":[251,317,259,258,318],"sign":"20","order":16,"bendFirst":[true,true,true,true,true]}],"title":"深圳"}
================================================
FILE: public/tianjing.json
================================================
{"stations":[{"stationId":1,"stationName":"刘园","position":[538,599],"shape":"cicle","lineIds":[1]},{"stationId":2,"stationName":"瑞景新苑","position":[598,677],"shape":"cicle","lineIds":[1]},{"stationId":3,"stationName":"佳园里","position":[661,759],"shape":"cicle","lineIds":[1]},{"stationId":4,"stationName":"本溪路","position":[774,824],"shape":"cicle","lineIds":[1]},{"stationId":5,"stationName":"勤俭道","position":[916,895],"shape":"cicle","lineIds":[1]},{"stationId":6,"stationName":"洪湖里","position":[998,970],"shape":"cicle","lineIds":[1]},{"stationId":7,"stationName":"西站","position":[1047,1109],"shape":"cicle","lineIds":[1,6],"tagDirection":4},{"stationId":8,"stationName":"西北角","position":[1096,1213],"shape":"cicle","lineIds":[1]},{"stationId":9,"stationName":"西南角","position":[1099,1305],"shape":"cicle","lineIds":[1,2]},{"stationId":10,"stationName":"二纬路","position":[1126,1376],"shape":"cicle","lineIds":[1]},{"stationId":11,"stationName":"海光寺","position":[1154,1455],"shape":"cicle","lineIds":[1]},{"stationId":12,"stationName":"鞍山道","position":[1252,1475],"shape":"cicle","lineIds":[1]},{"stationId":13,"stationName":"营口道","position":[1356,1492],"shape":"cicle","lineIds":[1,3]},{"stationId":14,"stationName":"小白楼","position":[1533,1530],"shape":"cicle","lineIds":[1],"tagDirection":5},{"stationId":15,"stationName":"下瓦房","position":[1608,1627],"shape":"cicle","lineIds":[1,5]},{"stationId":16,"stationName":"南楼","position":[1618,1719],"shape":"cicle","lineIds":[1],"tagDirection":2},{"stationId":17,"stationName":"土城","position":[1702,1787],"shape":"cicle","lineIds":[1]},{"stationId":18,"stationName":"陈塘庄","position":[1864,1844],"shape":"cicle","lineIds":[1]},{"stationId":19,"stationName":"复兴门","position":[1981,1907],"shape":"cicle","lineIds":[1]},{"stationId":20,"stationName":"华山里","position":[1998,2017],"shape":"cicle","lineIds":[1],"tagDirection":2},{"stationId":21,"stationName":"财经大学","position":[2148,2039],"shape":"cicle","lineIds":[1,9]},{"stationId":22,"stationName":"双林","position":[2300,2105],"shape":"cicle","lineIds":[1]},{"stationId":23,"stationName":"李楼","position":[2551,2134],"shape":"cicle","lineIds":[1]},{"stationId":24,"stationName":"洪泥河东","position":[2828,2203],"shape":"cicle","lineIds":[1]},{"stationId":25,"stationName":"高庄子","position":[2987,2248],"shape":"cicle","lineIds":[1]},{"stationId":26,"stationName":"国家会展中心","position":[3219,2389],"shape":"cicle","lineIds":[1]},{"stationId":27,"stationName":"国瑞路","position":[3286,2459],"shape":"cicle","lineIds":[1]},{"stationId":28,"stationName":"东沽路","position":[3463,2555],"shape":"cicle","lineIds":[1]},{"stationId":29,"stationName":"滨海国际机场","position":[3005,1358],"shape":"cicle","lineIds":[2]},{"stationId":30,"stationName":"空港经济区","position":[2729,1102],"shape":"cicle","lineIds":[2]},{"stationId":31,"stationName":"国山路","position":[2479,1138],"shape":"cicle","lineIds":[2]},{"stationId":32,"stationName":"登州路","position":[2339,1174],"shape":"cicle","lineIds":[2]},{"stationId":33,"stationName":"屿东城","position":[2177,1207],"shape":"cicle","lineIds":[2,9]},{"stationId":34,"stationName":"翠阜新村","position":[1959,1239],"shape":"cicle","lineIds":[2],"tagDirection":3},{"stationId":35,"stationName":"靖江路","position":[1821,1254],"shape":"cicle","lineIds":[2,5],"tagDirection":3},{"stationId":36,"stationName":"顺驰桥","position":[1719,1265],"shape":"cicle","lineIds":[2]},{"stationId":37,"stationName":"远洋国际中心","position":[1589,1314],"shape":"cicle","lineIds":[2],"tagDirection":3},{"stationId":38,"stationName":"天津站","position":[1496,1301],"shape":"cicle","lineIds":[2,3,8],"tagDirection":3},{"stationId":39,"stationName":"建国道","position":[1389,1289],"shape":"cicle","lineIds":[2]},{"stationId":40,"stationName":"东南角","position":[1282,1310],"shape":"cicle","lineIds":[2,4]},{"stationId":41,"stationName":"鼓楼","position":[1191,1307],"shape":"cicle","lineIds":[2]},{"stationId":42,"stationName":"广开四马路","position":[999,1302],"shape":"cicle","lineIds":[2]},{"stationId":43,"stationName":"长虹公园","position":[873,1301],"shape":"cicle","lineIds":[2,6]},{"stationId":44,"stationName":"咸阳路","position":[729,1301],"shape":"cicle","lineIds":[2]},{"stationId":45,"stationName":"芥园西道","position":[579,1252],"shape":"cicle","lineIds":[2]},{"stationId":46,"stationName":"卞兴","position":[469,1217],"shape":"cicle","lineIds":[2],"tagDirection":7},{"stationId":47,"stationName":"曹庄","position":[289,1222],"shape":"cicle","lineIds":[2]},{"stationId":48,"stationName":"小淀","position":[1955,287],"shape":"cicle","lineIds":[3]},{"stationId":49,"stationName":"丰产河","position":[1739,297],"shape":"cicle","lineIds":[3]},{"stationId":50,"stationName":"华北集团","position":[1659,403],"shape":"cicle","lineIds":[3]},{"stationId":51,"stationName":"天士力","position":[1529,620],"shape":"cicle","lineIds":[3],"tagDirection":2},{"stationId":52,"stationName":"宜兴埠","position":[1569,703],"shape":"cicle","lineIds":[3],"tagDirection":2},{"stationId":53,"stationName":"张兴庄","position":[1527,759],"shape":"cicle","lineIds":[3,5]},{"stationId":54,"stationName":"铁东路","position":[1449,880],"shape":"cicle","lineIds":[3]},{"stationId":55,"stationName":"北站","position":[1453,1012],"shape":"cicle","lineIds":[3,6],"tagDirection":1},{"stationId":56,"stationName":"中山路","position":[1439,1061],"shape":"cicle","lineIds":[3]},{"stationId":57,"stationName":"金狮桥","position":[1459,1168],"shape":"cicle","lineIds":[3]},{"stationId":58,"stationName":"津湾广场","position":[1459,1359],"shape":"cicle","lineIds":[3],"tagDirection":6},{"stationId":59,"stationName":"和平路","position":[1426,1410],"shape":"cicle","lineIds":[3,4]},{"stationId":60,"stationName":"西康路","position":[1259,1557],"shape":"cicle","lineIds":[3]},{"stationId":61,"stationName":"吴家窑","position":[1239,1681],"shape":"cicle","lineIds":[3]},{"stationId":62,"stationName":"天塔","position":[1159,1721],"shape":"cicle","lineIds":[3]},{"stationId":63,"stationName":"周邓纪念馆","position":[1019,1752],"shape":"cicle","lineIds":[3]},{"stationId":64,"stationName":"红旗南路","position":[860,1751],"shape":"cicle","lineIds":[3,6],"tagDirection":1},{"stationId":65,"stationName":"王顶堤","position":[769,1758],"shape":"cicle","lineIds":[3]},{"stationId":66,"stationName":"华苑","position":[669,1807],"shape":"cicle","lineIds":[3]},{"stationId":67,"stationName":"大学城","position":[499,1955],"shape":"cicle","lineIds":[3],"tagDirection":3},{"stationId":68,"stationName":"高新区","position":[369,1960],"shape":"cicle","lineIds":[3]},{"stationId":69,"stationName":"学府工业区","position":[169,1960],"shape":"cicle","lineIds":[3]},{"stationId":70,"stationName":"杨伍庄","position":[79,1996],"shape":"cicle","lineIds":[3]},{"stationId":71,"stationName":"南站","position":[0,2108],"shape":"cicle","lineIds":[3]},{"stationId":72,"stationName":"金街","position":[1301,1361],"shape":"cicle","lineIds":[4]},{"stationId":73,"stationName":"徐州道","position":[1564,1524],"shape":"cicle","lineIds":[4],"tagDirection":0},{"stationId":74,"stationName":"六纬路","position":[1654,1522],"shape":"cicle","lineIds":[4],"tagDirection":4},{"stationId":75,"stationName":"成林道","position":[1835,1376],"shape":"cicle","lineIds":[4,5]},{"stationId":76,"stationName":"泰昌路","position":[1944,1456],"shape":"cicle","lineIds":[4]},{"stationId":77,"stationName":"万东路","position":[2053,1484],"shape":"cicle","lineIds":[4]},{"stationId":78,"stationName":"沙柳南路","position":[2268,1505],"shape":"cicle","lineIds":[4,9],"tagDirection":1},{"stationId":79,"stationName":"登州南路","position":[2433,1513],"shape":"cicle","lineIds":[4]},{"stationId":80,"stationName":"跃进北路","position":[2567,1539],"shape":"cicle","lineIds":[4]},{"stationId":81,"stationName":"航双路","position":[2718,1568],"shape":"cicle","lineIds":[4]},{"stationId":82,"stationName":"民航大学","position":[2870,1590],"shape":"cicle","lineIds":[4]},{"stationId":83,"stationName":"新兴村","position":[3084,1663],"shape":"cicle","lineIds":[4]},{"stationId":84,"stationName":"李七庄南","position":[1086,2314],"shape":"cicle","lineIds":[5]},{"stationId":85,"stationName":"中医一附院","position":[1226,2210],"shape":"cicle","lineIds":[5]},{"stationId":86,"stationName":"昌凌路","position":[1226,2112],"shape":"cicle","lineIds":[5,9]},{"stationId":87,"stationName":"凌宾路","position":[1099,1998],"shape":"cicle","lineIds":[5]},{"stationId":88,"stationName":"体育中心","position":[1104,1931],"shape":"cicle","lineIds":[5]},{"stationId":89,"stationName":"肿瘤医院","position":[1224,1889],"shape":"cicle","lineIds":[5,6]},{"stationId":90,"stationName":"天津宾馆","position":[1364,1825],"shape":"cicle","lineIds":[5,6]},{"stationId":91,"stationName":"文化中心","position":[1448,1780],"shape":"cicle","lineIds":[5,6],"tagDirection":7},{"stationId":92,"stationName":"西南楼","position":[1540,1724],"shape":"cicle","lineIds":[5],"tagDirection":7},{"stationId":93,"stationName":"直沽","position":[1718,1569],"shape":"cicle","lineIds":[5,8]},{"stationId":94,"stationName":"津塘路","position":[1793,1513],"shape":"cicle","lineIds":[5]},{"stationId":95,"stationName":"幸福公园","position":[1821,1138],"shape":"cicle","lineIds":[5]},{"stationId":96,"stationName":"月牙河","position":[1837,1031],"shape":"cicle","lineIds":[5]},{"stationId":97,"stationName":"金钟河大街","position":[1798,957],"shape":"cicle","lineIds":[5,6]},{"stationId":98,"stationName":"建昌道","position":[1725,882],"shape":"cicle","lineIds":[5],"tagDirection":5},{"stationId":99,"stationName":"思源路","position":[1654,845],"shape":"cicle","lineIds":[5]},{"stationId":100,"stationName":"志成路","position":[1570,808],"shape":"cicle","lineIds":[5]},{"stationId":101,"stationName":"宜兴埠北","position":[1433,648],"shape":"cicle","lineIds":[5],"tagDirection":5},{"stationId":102,"stationName":"辽河北道","position":[1379,581],"shape":"cicle","lineIds":[5]},{"stationId":103,"stationName":"淮河道","position":[1304,493],"shape":"cicle","lineIds":[5]},{"stationId":104,"stationName":"职业大学","position":[1199,368],"shape":"cicle","lineIds":[5]},{"stationId":105,"stationName":"北辰道","position":[1116,272],"shape":"cicle","lineIds":[5]},{"stationId":106,"stationName":"丹河北道","position":[1016,154],"shape":"cicle","lineIds":[5]},{"stationId":107,"stationName":"北辰科技园北","position":[878,0],"shape":"cicle","lineIds":[5]},{"stationId":108,"stationName":"南孙庄","position":[2563,613],"shape":"cicle","lineIds":[6]},{"stationId":109,"stationName":"南何庄","position":[2289,615],"shape":"cicle","lineIds":[6]},{"stationId":110,"stationName":"大毕庄","position":[2170,649],"shape":"cicle","lineIds":[6]},{"stationId":111,"stationName":"金钟街","position":[2040,680],"shape":"cicle","lineIds":[6]},{"stationId":112,"stationName":"徐庄子","position":[1921,770],"shape":"cicle","lineIds":[6]},{"stationId":113,"stationName":"民权门","position":[1713,992],"shape":"cicle","lineIds":[6]},{"stationId":114,"stationName":"北宁公园","position":[1611,1016],"shape":"cicle","lineIds":[6]},{"stationId":115,"stationName":"新开河","position":[1407,990],"shape":"cicle","lineIds":[6]},{"stationId":116,"stationName":"外院附中","position":[1297,966],"shape":"cicle","lineIds":[6]},{"stationId":117,"stationName":"天泰路","position":[1212,997],"shape":"cicle","lineIds":[6]},{"stationId":118,"stationName":"北竹林","position":[1106,1077],"shape":"cicle","lineIds":[6],"tagDirection":3},{"stationId":119,"stationName":"复兴路","position":[958,1117],"shape":"cicle","lineIds":[6]},{"stationId":120,"stationName":"人民医院","position":[876,1205],"shape":"cicle","lineIds":[6]},{"stationId":121,"stationName":"宜宾道","position":[872,1415],"shape":"cicle","lineIds":[6]},{"stationId":122,"stationName":"鞍山西道","position":[873,1513],"shape":"cicle","lineIds":[6]},{"stationId":123,"stationName":"天拖","position":[873,1587],"shape":"cicle","lineIds":[6]},{"stationId":124,"stationName":"一中心医院","position":[874,1647],"shape":"cicle","lineIds":[6]},{"stationId":125,"stationName":"迎风道","position":[874,1817],"shape":"cicle","lineIds":[6]},{"stationId":126,"stationName":"南翠屏","position":[965,1879],"shape":"cicle","lineIds":[6]},{"stationId":127,"stationName":"水上公园东路","position":[1110,1884],"shape":"cicle","lineIds":[6],"tagDirection":0},{"stationId":128,"stationName":"乐园道","position":[1554,1766],"shape":"cicle","lineIds":[6],"tagDirection":5},{"stationId":129,"stationName":"尖山路","position":[1619,1806],"shape":"cicle","lineIds":[6],"tagDirection":5},{"stationId":130,"stationName":"医大二院","position":[1619,1867],"shape":"cicle","lineIds":[6]},{"stationId":131,"stationName":"梅江道","position":[1635,1991],"shape":"cicle","lineIds":[6]},{"stationId":132,"stationName":"左江道","position":[1556,2079],"shape":"cicle","lineIds":[6]},{"stationId":133,"stationName":"梅江公园","position":[1555,2155],"shape":"cicle","lineIds":[6]},{"stationId":134,"stationName":"梅江会展中心","position":[1556,2236],"shape":"cicle","lineIds":[6]},{"stationId":135,"stationName":"解放南路","position":[1719,2271],"shape":"cicle","lineIds":[6]},{"stationId":136,"stationName":"洞庭路","position":[1838,2243],"shape":"cicle","lineIds":[6]},{"stationId":137,"stationName":"梅林路","position":[1927,2231],"shape":"cicle","lineIds":[6]},{"stationId":138,"stationName":"渌水道","position":[2079,2223],"shape":"cicle","lineIds":[6,7]},{"stationId":139,"stationName":"双港","position":[2155,2376],"shape":"cicle","lineIds":[7]},{"stationId":140,"stationName":"景荷道","position":[2220,2479],"shape":"cicle","lineIds":[7]},{"stationId":141,"stationName":"景荔道","position":[2275,2565],"shape":"cicle","lineIds":[7]},{"stationId":142,"stationName":"天津大学北洋园校区","position":[2465,2752],"shape":"cicle","lineIds":[7]},{"stationId":143,"stationName":"海河教育园区","position":[2601,2789],"shape":"cicle","lineIds":[7]},{"stationId":144,"stationName":"南开大学津南校区","position":[2795,2851],"shape":"cicle","lineIds":[7]},{"stationId":145,"stationName":"和慧南路","position":[2982,2866],"shape":"cicle","lineIds":[7]},{"stationId":146,"stationName":"咸水沽西","position":[3117,2873],"shape":"cicle","lineIds":[7]},{"stationId":147,"stationName":"东海路","position":[6752,2273],"shape":"cicle","lineIds":[8]},{"stationId":148,"stationName":"会展中心","position":[6696,2379],"shape":"cicle","lineIds":[8]},{"stationId":149,"stationName":"太湖路","position":[6613,2480],"shape":"cicle","lineIds":[8]},{"stationId":150,"stationName":"市民广场","position":[6410,2497],"shape":"cicle","lineIds":[8]},{"stationId":151,"stationName":"泰达","position":[6173,2424],"shape":"cicle","lineIds":[8]},{"stationId":152,"stationName":"塘沽","position":[5989,2383],"shape":"cicle","lineIds":[8]},{"stationId":153,"stationName":"胡家园","position":[5453,2274],"shape":"cicle","lineIds":[8],"tagDirection":7},{"stationId":154,"stationName":"钢管公司","position":[4418,2281],"shape":"cicle","lineIds":[8]},{"stationId":155,"stationName":"军粮城","position":[3940,2198],"shape":"cicle","lineIds":[8]},{"stationId":156,"stationName":"小东庄","position":[3412,2035],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":157,"stationName":"东丽开发区","position":[2953,1885],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":158,"stationName":"新立","position":[2739,1820],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":159,"stationName":"张贵庄","position":[2457,1758],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":160,"stationName":"二号桥","position":[2310,1727],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":161,"stationName":"一号桥","position":[2208,1718],"shape":"cicle","lineIds":[8]},{"stationId":162,"stationName":"中山门","position":[2022,1670],"shape":"cicle","lineIds":[8]},{"stationId":163,"stationName":"东兴路","position":[1805,1645],"shape":"cicle","lineIds":[8]},{"stationId":164,"stationName":"十一经路","position":[1619,1465],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":165,"stationName":"大王庄","position":[1564,1407],"shape":"cicle","lineIds":[8],"tagDirection":1},{"stationId":166,"stationName":"崂山道","position":[2215,1292],"shape":"cicle","lineIds":[9]},{"stationId":167,"stationName":"香山道","position":[2244,1362],"shape":"cicle","lineIds":[9]},{"stationId":168,"stationName":"万山道","position":[2255,1431],"shape":"cicle","lineIds":[9]},{"stationId":169,"stationName":"方山道","position":[2273,1577],"shape":"cicle","lineIds":[9],"tagDirection":2},{"stationId":170,"stationName":"金贸产业园","position":[2254,1651],"shape":"cicle","lineIds":[9],"tagDirection":2},{"stationId":171,"stationName":"龙涵道","position":[2250,1734],"shape":"cicle","lineIds":[9]},{"stationId":172,"stationName":"环宇道","position":[2237,1836],"shape":"cicle","lineIds":[9,10]},{"stationId":173,"stationName":"柳林路","position":[2227,1957],"shape":"cicle","lineIds":[9]},{"stationId":174,"stationName":"微山路","position":[1976,2050],"shape":"cicle","lineIds":[9],"tagDirection":4},{"stationId":175,"stationName":"玛钢厂","position":[1906,2053],"shape":"cicle","lineIds":[9],"tagDirection":5},{"stationId":176,"stationName":"春海路","position":[1810,2049],"shape":"cicle","lineIds":[9]},{"stationId":177,"stationName":"南珠桥","position":[1653,2042],"shape":"cicle","lineIds":[9]},{"stationId":178,"stationName":"友谊南路","position":[1554,2041],"shape":"cicle","lineIds":[9]},{"stationId":179,"stationName":"江湾二支路","position":[1438,2041],"shape":"cicle","lineIds":[9],"tagDirection":0},{"stationId":180,"stationName":"丽江道","position":[1321,2051],"shape":"cicle","lineIds":[9]},{"stationId":181,"stationName":"瑶环路","position":[1129,2132],"shape":"cicle","lineIds":[9]},{"stationId":182,"stationName":"于台","position":[1005,2214],"shape":"cicle","lineIds":[9]},{"stationId":183,"stationName":"东江道","position":[1978,1996],"shape":"cicle","lineIds":[10],"tagDirection":7},{"stationId":184,"stationName":"学苑北路","position":[2101,1937],"shape":"cicle","lineIds":[10],"tagDirection":3},{"stationId":185,"stationName":"海河东路","position":[2144,1851],"shape":"cicle","lineIds":[10]},{"stationId":186,"stationName":"雪莲南路","position":[2378,1849],"shape":"cicle","lineIds":[10]},{"stationId":187,"stationName":"招远路","position":[2486,1869],"shape":"cicle","lineIds":[10]},{"stationId":188,"stationName":"东丽文体中心","position":[2666,1889],"shape":"cicle","lineIds":[10]},{"stationId":189,"stationName":"驯海路","position":[2767,1916],"shape":"cicle","lineIds":[10]},{"stationId":190,"stationName":"东丽一经路","position":[2878,1947],"shape":"cicle","lineIds":[10]},{"stationId":191,"stationName":"东丽三经路","position":[2963,1976],"shape":"cicle","lineIds":[10]},{"stationId":192,"stationName":"东丽六经路","position":[3124,2027],"shape":"cicle","lineIds":[10]}],"lines":[{"lineId":1,"lineName":"1号线","color":"#CC0000","stationIds":[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28],"sign":"1","order":1,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":2,"lineName":"2号线","color":"#FCD600","stationIds":[29,30,31,32,33,34,35,36,37,38,39,40,41,9,42,43,44,45,46,47],"sign":"2","order":2,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":3,"lineName":"3号线","color":"#019AC3","stationIds":[48,49,50,51,52,53,54,55,56,57,38,58,59,13,60,61,62,63,64,65,66,67,68,69,70,71],"sign":"3","order":3,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":4,"lineName":"4号线","color":"#018237","stationIds":[40,72,59,73,74,75,76,77,78,79,80,81,82,83],"sign":"4","order":4,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":5,"lineName":"5号线","color":"#EE782E","stationIds":[84,85,86,87,88,89,90,91,92,15,93,94,75,35,95,96,97,98,99,100,53,101,102,103,104,105,106,107],"sign":"5","order":5,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":6,"lineName":"6号线","color":"#A61D7F","stationIds":[108,109,110,111,112,97,113,114,55,115,116,117,118,7,119,120,43,121,122,123,124,64,125,126,127,89,90,91,128,129,130,131,132,133,134,135,136,137,138],"sign":"6","order":6,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":7,"lineName":"6号线","color":"#A61D7F","stationIds":[138,139,140,141,142,143,144,145,146],"sign":"6","order":7,"bendFirst":[true,true,true,true,true,true,true,true,true]},{"lineId":8,"lineName":"9号线","color":"#2249A3","stationIds":[147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,93,164,165,38],"sign":"9","order":8,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":9,"lineName":"10号线","color":"#B8D201","stationIds":[33,166,167,168,78,169,170,171,172,173,21,174,175,176,177,178,179,180,86,181,182],"sign":"10","order":9,"bendFirst":[true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true,true]},{"lineId":10,"lineName":"11号线","color":"#2249A3","stationIds":[183,184,185,172,186,187,188,189,190,191,192],"sign":"11","order":10,"bendFirst":[false,true,true,false,false,false,false,false,false,false,true]}],"title":"天津"}
================================================
FILE: src/Common/AutoGrowthInput.scss
================================================
.auto-growth-container {
position: relative;
width: fit-content;
.auto-growth-span {
// font-size: 36px;
border: none;
// font-weight: 500;
// width: 100px;
// display: inline-block;
// margin-top: 14px;
opacity: 0;
background-color: transparent;
white-space: nowrap;
}
.auto-growth-input {
// font-size: 36px;
white-space: nowrap;
border: none;
// font-weight: 500;
width: calc(100% + 18px);
// margin-top: 14px;
position: absolute;
left: 0;
padding: 0;
background-color: transparent;
&:disabled{
appearance: none;
color: inherit;
opacity: inherit;
cursor: default;
border: none;
&:focus,&:focus-visible{
outline: none;
}
}
}
.click-panel{
position: absolute;
left: 0;
width: calc(100% + 18px);
height: 100%;
top: 0;
// cursor: pointer;
}
&.disabled{
.auto-growth-span{
opacity: 1;
}
.auto-growth-input{
// opacity: 0;
display: none;
}
}
}
================================================
FILE: src/Common/AutoGrowthInput.tsx
================================================
import classNames from "classnames";
import React, {
CSSProperties,
Dispatch,
LegacyRef,
RefAttributes,
RefObject,
SetStateAction,
StyleHTMLAttributes,
forwardRef,
useEffect,
useState,
} from "react";
import "./AutoGrowthInput.scss";
type InputProps = {
value?: number | string;
onInput?: (x: any) => void;
className?: string;
type?: string;
disabled?: boolean;
style?: CSSProperties;
onClick?: (x: any) => void;
}
export const AutoGrowthInput = forwardRef(function ({
value,
onInput,
className = "",
type = "",
disabled = false,
style = {},
onClick,
}, ref) {
return (
{value}
{
if (document.activeElement === e.currentTarget) e.stopPropagation();
}}
onBlur={(e)=>{
if(type === "number")
setTimeout(()=>{
//@ts-ignore
e.target.value = value
})
}}
>
{disabled?
:<>>}
);
})
================================================
FILE: src/Common/api.ts
================================================
export const getExistMap = (id:string) => {
const url = `/${id}.json`;
return fetch(url).then(res=>res.text());
}
================================================
FILE: src/Common/color.ts
================================================
import { arrayToMap } from "./util";
export const colorSH = [
{
"line": "1号线",
"color": "#EA0B2A",
"color_name": "正红色",
"rgb": [234, 11, 42]
},
{
"line": "2号线",
"color": "#94D40B",
"color_name": "绿色",
"rgb": [148, 212, 11]
},
{
"line": "3号线",
"color": "#F8D000",
"color_name": "黄色",
"rgb": [248, 208, 0]
},
{
"line": "4号线",
"color": "#60269E",
"color_name": "紫色",
"rgb": [96, 38, 158]
},
{
"line": "5号线",
"color": "#934C9A",
"color_name": "紫红色",
"rgb": [147, 76, 154]
},
{
"line": "6号线",
"color": "#D80169",
"color_name": "品红色",
"rgb": [216, 1, 105]
},
{
"line": "7号线",
"color": "#FE6B01",
"color_name": "橙色",
"rgb": [254, 107, 1]
},
{
"line": "8号线",
"color": "#00A0E8",
"color_name": "蓝色",
"rgb": [0, 160, 232]
},
{
"line": "9号线",
"color": "#6FC5E8",
"color_name": "淡蓝色",
"rgb": [111, 197, 232]
},
{
"line": "10号线",
"color": "#C3A5E1",
"color_name": "淡紫色",
"rgb": [195, 165, 225]
},
{
"line": "11号线",
"color": "#792330",
"color_name": "棕色",
"rgb": [121, 35, 48]
},
{
"line": "12号线",
"color": "#007A61",
"color_name": "深绿色",
"rgb": [0, 122, 97]
},
{
"line": "13号线",
"color": "#F095CE",
"color_name": "粉色",
"rgb": [240, 149, 206]
},
{
"line": "14号线",
"color": "#827805",
"color_name": "橄榄绿色",
"rgb": [130, 120, 5]
},
{
"line": "15号线",
"color": "#BDA686",
"color_name": "香槟金色",
"rgb": [189, 166, 134]
},
{
"line": "16号线",
"color": "#2AD2C5",
"color_name": "水绿色",
"rgb": [42, 210, 197]
}
];
export const colorSHMap = arrayToMap("color",colorSH);
================================================
FILE: src/Common/const.ts
================================================
const gauge = 10;
const line_radius = 10;
const handleLength = 45;
const handleWidth = 15;
export { gauge, line_radius, handleLength, handleWidth };
================================================
FILE: src/Common/teyvat.ts
================================================
import { UserDataType } from "../Data/UserData";
import { t } from "i18next";
export const teyvat: UserDataType = {
stations: [
{
stationId: 1,
stationName: t("wang-feng-jiao-zhan"),
position: [678, -52],
shape: "cicle",
lineIds: [3],
tagDirection: 2,
},
{
stationId: 2,
stationName: t("zhai-xing-ya-zhan"),
position: [645, 71],
shape: "cicle",
lineIds: [3],
},
{
stationId: 3,
stationName: t("qian-feng-shen-dian-zhan"),
position: [640, 191],
shape: "cicle",
lineIds: [3],
},
{
stationId: 4,
stationName: t("Common-teyvat-192be7b183f6eee79-1"),
position: [463, 418],
shape: "cicle",
lineIds: [2, 3, 8],
},
{
stationId: 5,
stationName: t("Common-teyvat-192be7b183f6eee79-2"),
position: [573, 641],
shape: "cicle",
lineIds: [3],
},
{
stationId: 6,
stationName: t("Common-teyvat-192be7b183f6eee79-3"),
position: [740, 641],
shape: "cicle",
lineIds: [3],
},
{
stationId: 7,
stationName: t("Common-teyvat-192be7b183f6eee79-4"),
position: [813, 533],
shape: "cicle",
lineIds: [3],
},
{
stationId: 8,
stationName: t("Common-teyvat-192be7b183f6eee79-5"),
position: [388, 318],
shape: "cicle",
lineIds: [1, 2],
tagDirection: 6
},
{
stationId: 9,
stationName: t("Common-teyvat-192be7b183f6eee79-6"),
position: [458, 193],
shape: "cicle",
lineIds: [2],
},
{
stationId: 10,
stationName: t("Common-teyvat-192be7b183f6eee79-7"),
position: [460, 86],
shape: "cicle",
lineIds: [2],
},
{
stationId: 11,
stationName: t("Common-teyvat-192be7b183f6eee79-8"),
position: [458, -64],
shape: "cicle",
lineIds: [2],
},
{
stationId: 12,
stationName: t("Common-teyvat-192be7b183f6eee79-9"),
position: [325, -102],
shape: "cicle",
lineIds: [2],
},
{
stationId: 13,
stationName: t("Common-teyvat-192be7b183f6eee79-10"),
position: [245, 168],
shape: "cicle",
lineIds: [1],
},
{
stationId: 14,
stationName: t("Common-teyvat-192be7b183f6eee79-11"),
position: [320, 226],
shape: "cicle",
lineIds: [1],
},
{
stationId: 15,
stationName: t("Common-teyvat-192be7b183f6eee79-12"),
position: [270, 358],
shape: "cicle",
lineIds: [1],
},
{
stationId: 16,
stationName: t("Common-teyvat-192be7b183f6eee79-13"),
position: [70, 428],
shape: "cicle",
lineIds: [1, 4, 8],
},
{
stationId: 17,
stationName: t("Common-teyvat-192be7b183f6eee79-14"),
position: [-2, 336],
shape: "cicle",
lineIds: [4],
},
{
stationId: 18,
stationName: t("Common-teyvat-192be7b183f6eee79-15"),
position: [3, 248],
shape: "cicle",
lineIds: [4],
},
{
stationId: 19,
stationName: t("Common-teyvat-192be7b183f6eee79-16"),
position: [-70, 151],
shape: "cicle",
lineIds: [4],
},
{
stationId: 20,
stationName: t("Common-teyvat-192be7b183f6eee79-17"),
position: [15, 18],
shape: "cicle",
lineIds: [4],
},
{
stationId: 21,
stationName: t("Common-teyvat-192be7b183f6eee79-18"),
position: [-87, -77],
shape: "cicle",
lineIds: [4],
},
{
stationId: 22,
stationName: t("Common-teyvat-192be7b183f6eee79-19"),
position: [83, 628],
shape: "cicle",
lineIds: [4],
tagDirection: 2,
},
{
stationId: 23,
stationName: t("Common-teyvat-192be7b183f6eee79-20"),
position: [92, 748],
shape: "cicle",
lineIds: [4],
tagDirection: 2,
},
{
stationId: 24,
stationName: t("Common-teyvat-192be7b183f6eee79-21"),
position: [248, 748],
shape: "cicle",
lineIds: [4],
},
{
stationId: 25,
stationName: t("Common-teyvat-192be7b183f6eee79-22"),
position: [338, 778],
shape: "cicle",
lineIds: [4],
},
{
stationId: 26,
stationName: t("Common-teyvat-192be7b183f6eee79-23"),
position: [180, 851],
shape: "cicle",
lineIds: [4],
},
{
stationId: 27,
stationName: t("Common-teyvat-192be7b183f6eee79-24"),
position: [-35, 703],
shape: "cicle",
lineIds: [8],
},
{
stationId: 28,
stationName: t("Common-teyvat-192be7b183f6eee79-25"),
position: [-230, 813],
shape: "cicle",
lineIds: [5, 8],
},
{
stationId: 29,
stationName: t("Common-teyvat-192be7b183f6eee79-26"),
position: [-310, 568],
shape: "cicle",
lineIds: [5],
},
{
stationId: 30,
stationName: t("Common-teyvat-192be7b183f6eee79-27"),
position: [-452, 473],
shape: "cicle",
lineIds: [5],
},
{
stationId: 31,
stationName: t("Common-teyvat-192be7b183f6eee79-28"),
position: [-240, 971],
shape: "cicle",
lineIds: [5],
},
{
stationId: 32,
stationName: t("Common-teyvat-192be7b183f6eee79-29"),
position: [-197, 1071],
shape: "cicle",
lineIds: [5],
},
{
stationId: 33,
stationName: t("Common-teyvat-192be7b183f6eee79-30"),
position: [-280, 1226],
shape: "cicle",
lineIds: [5],
},
{
stationId: 34,
stationName: t("Common-teyvat-192be7b183f6eee79-31"),
position: [-285, 1451],
shape: "cicle",
lineIds: [5, 6],
},
{
stationId: 35,
stationName: t("Common-teyvat-192be7b183f6eee79-32"),
position: [-210, 1608],
shape: "cicle",
lineIds: [5, 7],
},
{
stationId: 36,
stationName: t("Common-teyvat-192be7b183f6eee79-33"),
position: [-605, 1601],
shape: "cicle",
lineIds: [7],
},
{
stationId: 37,
stationName: t("Common-teyvat-192be7b183f6eee79-34"),
position: [-671, 1511],
shape: "cicle",
lineIds: [7, 11],
tagDirection: 5,
},
{
stationId: 38,
stationName: t("Common-teyvat-192be7b183f6eee79-35"),
position: [-362, 1351],
shape: "cicle",
lineIds: [6],
},
{
stationId: 39,
stationName: t("Common-teyvat-192be7b183f6eee79-36"),
position: [-642, 1326],
shape: "cicle",
lineIds: [7],
},
{
stationId: 40,
stationName: t("Common-teyvat-192be7b183f6eee79-37"),
position: [-437, 1158],
shape: "cicle",
lineIds: [6],
},
{
stationId: 41,
stationName: t("Common-teyvat-192be7b183f6eee79-38"),
position: [-552, 1008],
shape: "cicle",
lineIds: [6],
},
{
stationId: 42,
stationName: t("Common-teyvat-192be7b183f6eee79-39"),
position: [-602, 946],
shape: "cicle",
lineIds: [6],
},
{
stationId: 43,
stationName: t("Common-teyvat-192be7b183f6eee79-40"),
position: [-525, 829],
shape: "cicle",
lineIds: [8],
tagDirection: 4
},
{
stationId: 44,
stationName: t("Common-teyvat-192be7b183f6eee79-41"),
position: [-742, 828],
shape: "cicle",
lineIds: [6, 7, 8],
},
{
stationId: 45,
stationName: t("Common-teyvat-192be7b183f6eee79-42"),
position: [-805, 728],
shape: "cicle",
lineIds: [7],
},
{
stationId: 46,
stationName: t("Common-teyvat-192be7b183f6eee79-43"),
position: [-955, 903],
shape: "cicle",
lineIds: [7],
},
{
stationId: 47,
stationName: t("Common-teyvat-192be7b183f6eee79-44"),
position: [-857, 943],
shape: "cicle",
lineIds: [8],
tagDirection: 2
},
{
stationId: 48,
stationName: t("Common-teyvat-192be7b183f6eee79-45"),
position: [-855, 1078],
shape: "cicle",
lineIds: [7],
},
{
stationId: 49,
stationName: t("Common-teyvat-192be7b183f6eee79-46"),
position: [-725, 1208],
shape: "cicle",
lineIds: [7],
},
{
stationId: 50,
stationName: t("Common-teyvat-192be7b183f6eee79-47"),
position: [-897, 1411],
shape: "cicle",
lineIds: [11, 12],
},
{
stationId: 51,
stationName: t("Common-teyvat-192be7b183f6eee79-48"),
position: [-1090, 1388],
shape: "cicle",
lineIds: [9, 11],
},
{
stationId: 52,
stationName: t("Common-teyvat-192be7b183f6eee79-49"),
position: [-1090, 1076],
shape: "cicle",
lineIds: [9, 10],
},
{
stationId: 53,
stationName: t("Common-teyvat-192be7b183f6eee79-50"),
position: [-1272, 1173],
shape: "cicle",
lineIds: [10],
},
{
stationId: 54,
stationName: t("Common-teyvat-192be7b183f6eee79-51"),
position: [-1405, 1271],
shape: "cicle",
lineIds: [10],
},
{
stationId: 55,
stationName: t("Common-teyvat-192be7b183f6eee79-52"),
position: [-1360, 1388],
shape: "cicle",
lineIds: [10, 11],
},
{
stationId: 56,
stationName: t("Common-teyvat-192be7b183f6eee79-53"),
position: [-1195, 1576],
shape: "cicle",
lineIds: [10, 12],
},
{
stationId: 57,
stationName: t("Common-teyvat-192be7b183f6eee79-54"),
position: [-1037, 1491],
shape: "cicle",
lineIds: [9, 12],
},
{
stationId: 58,
stationName: t("Common-teyvat-192be7b183f6eee79-55"),
position: [-1067, 1791],
shape: "cicle",
lineIds: [9],
tagDirection: 2,
},
{
stationId: 59,
stationName: t("Common-teyvat-192be7b183f6eee79-56"),
position: [-1177, 1933],
shape: "cicle",
lineIds: [9],
tagDirection: 3,
},
{
stationId: 60,
stationName: t("Common-teyvat-192be7b183f6eee79-57"),
position: [-1310, 1931],
shape: "cicle",
lineIds: [9, 10],
tagDirection: 7,
},
{
stationId: 61,
stationName: t("Common-teyvat-192be7b183f6eee79-58"),
position: [-1247, 2116],
shape: "cicle",
lineIds: [10],
},
{
stationId: 62,
stationName: t("Common-teyvat-192be7b183f6eee79-59"),
position: [-1255, 1701],
shape: "cicle",
lineIds: [10],
tagDirection: 3,
},
{
stationId: 63,
stationName: t("Common-teyvat-192be7b183f6eee79-60"),
position: [-1522, 1681],
shape: "cicle",
lineIds: [11, 12],
},
{
stationId: 64,
stationName: t("Common-teyvat-192be7b183f6eee79-61"),
position: [-1695, 1681],
shape: "cicle",
lineIds: [12],
},
{
stationId: 65,
stationName: t("Common-teyvat-192be7b183f6eee79-62"),
position: [-1865, 1493],
shape: "cicle",
lineIds: [12],
},
{
stationId: 66,
stationName: t("Common-teyvat-192be7b183f6eee79-63"),
position: [-2215, 1326],
shape: "cicle",
lineIds: [12],
},
{
stationId: 67,
stationName: t("Common-teyvat-192be7b183f6eee79-64"),
position: [-1812, 1908],
shape: "cicle",
lineIds: [11],
},
{
stationId: 68,
stationName: t("Common-teyvat-192be7b183f6eee79-65"),
position: [-1482, 1931],
shape: "cicle",
lineIds: [9],
},
{
stationId: 69,
stationName: t("Common-teyvat-192be7b183f6eee79-66"),
position: [-1737, 1998],
shape: "cicle",
lineIds: [9],
tagDirection: 3
},
{
stationId: 70,
stationName: t("Common-teyvat-192be7b183f6eee79-67"),
position: [-1917, 2093],
shape: "cicle",
lineIds: [9, 11],
},
{
stationId: 71,
stationName: t("Common-teyvat-192be7b183f6eee79-68"),
position: [-2077, 1883],
shape: "cicle",
lineIds: [9],
},
{
stationId: 72,
stationName: t("Common-teyvat-192be7b183f6eee79-69"),
position: [-1997, 2211],
shape: "cicle",
lineIds: [11],
},
{
stationId: 73,
stationName: t("Common-teyvat-192be7b183f6eee79-70"),
position: [-2212, 2316],
shape: "cicle",
lineIds: [11],
},
{
stationId: 74,
stationName: t("Common-teyvat-192be7b183f6eee79-71"),
position: [-2470, 2316],
shape: "cicle",
lineIds: [11],
},
].reduce((map, cur) => {
map.set(cur.stationId, cur);
return map;
}, new Map()),
lines: [
{
lineId: 1,
lineName: t("Common-teyvat-192be7b183f6eee79-72"),
color: "#EA0B2A",
stationIds: [13, 14, 8, 15, 16],
sign: "1",
order: 1,
bendFirst: [true, true, true, true, true],
},
{
lineId: 2,
lineName: t("Common-teyvat-192be7b183f6eee79-73"),
color: "#94D40B",
stationIds: [12, 11, 10, 9, 8, 4],
sign: "2",
order: 2,
bendFirst: [false, true, true, true, true, true],
},
{
lineId: 3,
lineName: t("Common-teyvat-192be7b183f6eee79-74"),
color: "#F8D000",
stationIds: [1, 2, 3, 4, 5, 6, 7],
sign: "3",
order: 3,
bendFirst: [true, true, true, false, true, true, true],
},
{
lineId: 4,
lineName: t("Common-teyvat-192be7b183f6eee79-75"),
color: "#60269E",
stationIds: [21, 20, 19, 18, 17, 16, 22, 23, 26, 25, 24],
sign: "4",
order: 4,
bendFirst: [
true,
false,
true,
true,
true,
true,
true,
true,
false,
true,
true,
],
},
{
lineId: 5,
lineName: t("Common-teyvat-192be7b183f6eee79-76"),
color: "#934C9A",
stationIds: [30, 29, 28, 31, 32, 33, 34, 35],
sign: "5",
order: 5,
bendFirst: [true, true, true, true, true, true, true, true],
},
{
lineId: 6,
lineName: t("Common-teyvat-192be7b183f6eee79-77"),
color: "#D80169",
stationIds: [44, 42, 41, 40, 38, 34],
sign: "6",
order: 6,
bendFirst: [false, true, true, true, false, true],
},
{
lineId: 7,
lineName: t("Common-teyvat-192be7b183f6eee79-78"),
color: "#FE6B01",
stationIds: [35, 36, 37, 39, 49, 48, 46, 45, 44],
sign: "7",
order: 7,
bendFirst: [true, true, true, false, true, true, false, true, true],
},
{
lineId: 8,
lineName: t("Common-teyvat-192be7b183f6eee79-79"),
color: "#00A0E8",
stationIds: [47, 44, 43, 28, 27, 16, 4],
sign: "8",
order: 8,
bendFirst: [true, true, false, false, false, false, true],
},
{
lineId: 9,
lineName: t("Common-teyvat-192be7b183f6eee79-80"),
color: "#385f88",
stationIds: [52, 51, 57, 58, 59, 60, 68, 69, 70, 71],
sign: "2",
order: 9,
bendFirst: [true, true, true, false, true, true, true, true, true, true],
},
{
lineId: 10,
lineName: t("Common-teyvat-192be7b183f6eee79-81"),
color: "#e8c148",
stationIds: [52, 53, 54, 55, 56, 62, 60, 61],
sign: "1",
order: 10,
bendFirst: [false, false, false, true, false, true, true, true],
},
{
lineId: 11,
lineName: t("Common-teyvat-192be7b183f6eee79-82"),
color: "#dd8d4e",
stationIds: [37, 50, 51, 55, 63, 67, 70, 72, 73, 74],
sign: "3",
order: 11,
bendFirst: [false, true, true, true, true, true, false, true, true, true],
},
{
lineId: 12,
lineName: t("Common-teyvat-192be7b183f6eee79-83"),
color: "#35743c",
stationIds: [50, 57, 56, 63, 64, 65, 66],
sign: "4",
order: 12,
bendFirst: [true, true, true, true, true, true, true],
},
].reduce((map, cur) => {
map.set(cur.lineId, cur);
return map;
}, new Map()),
title: t("teyvat"),
backgroundColor: "#fffee0",
};
================================================
FILE: src/Common/util.ts
================================================
import { MouseEvent, WheelEventHandler } from "react";
import UAParser from "ua-parser-js";
import { TransformProps, UserDataType } from "../Data/UserData";
export const mapToArr = (map: Map) => {
const arr: V[] = [];
map.forEach((x) => arr.push(x));
return arr;
};
export const scrollOptimize = (e: MouseEvent) => {
const { currentTarget: editTool } = e;
const editTools = editTool.parentElement;
const { scrollHeight, childNodes, offsetHeight } = editTools!;
let index = 0;
childNodes.forEach((x, i) => {
if (x === editTool) index = i;
});
const scrollY =
((scrollHeight - offsetHeight) / (childNodes.length - 1)) * index;
editTools!.scrollTo({ top: scrollY, behavior: "smooth" });
};
export const arrayToMap = (
keyName: keyof T,
arr: T[]
): Map => {
const resultMap = new Map();
for (const item of arr) {
const keyValue = item[keyName];
if (keyValue !== undefined) {
resultMap.set(keyValue, item);
}
}
return resultMap;
};
export const onWheelX: WheelEventHandler = (event) => {
event.stopPropagation();
event.preventDefault();
const { currentTarget, deltaY } = event;
currentTarget.scrollBy({
top: 0,
left: deltaY,
behavior: "auto",
});
};
export const onWheelY: WheelEventHandler = (event) => {
const { currentTarget, deltaY } = event;
const { scrollHeight, clientHeight, scrollTop } = currentTarget;
const deltaHeight = scrollHeight - clientHeight;
const scrollMax =
scrollTop > deltaHeight - 1 && scrollTop < deltaHeight + 1 && deltaY > 0;
const scrollMin = scrollTop === 0 && deltaY < 0;
// console.log(deltaY, scrollTop, scrollHeight - clientHeight);
if (!(scrollMax || scrollMin)) {
currentTarget.scrollBy({
top: deltaY,
left: 0,
behavior: "auto",
});
event.stopPropagation();
event.preventDefault();
}
};
const parser = new UAParser(navigator.userAgent);
export const browserInfo = parser.getResult(); //{engine:{name:''}} //
export function generateRandomColor(): string {
// Generate a random hexadecimal color
const hex = Math.floor(Math.random() * 16777216).toString(16);
// Ensure the color has sufficient contrast relative to white
const luminance =
(parseInt(hex, 16) >> 16) * 0.299 +
((parseInt(hex, 16) >> 8) & 0xff) * 0.587 +
(parseInt(hex, 16) & 0xff) * 0.114;
const isLightColor = luminance > 128; // Determine if the color is light
// Adjust the color to be darker if it's light, or lighter if it's dark
const adjustedHex = isLightColor
? (parseInt(hex, 16) - 0x333333).toString(16)
: (parseInt(hex, 16) + 0x333333).toString(16);
// Ensure the color is 6 digits long
const finalHex = adjustedHex.padStart(6, "0");
return `#${finalHex}`;
}
export async function base64ToFile(base64: string): Promise {
const res = await fetch(base64);
const blob = await res.blob();
return new File([blob], "image", { type: blob.type });
}
export function fileToBase64(file: File): Promise {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
}
export function exportJson(content: string, filename: string) {
const exportContent = (content: string) => {
const link = document.createElement("a");
link.href = URL.createObjectURL(
new Blob([content], { type: "application/json" })
);
link.download = filename;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
};
readFileFromIndexedDB("image")
.then((file) => {
fileToBase64(file!).then(
(image) => {
try {
const data = JSON.parse(content);
const dataWithImage = { ...data, image };
const json = JSON.stringify(dataWithImage);
exportContent(json);
} catch (e) {
exportContent(content);
}
},
() => {
exportContent(content);
}
);
})
.catch((e) => {
console.error(e);
exportContent(content);
});
}
export function exportPNG(content: Blob, filename: string) {
const link = document.createElement("a");
link.href = URL.createObjectURL(content);
link.download = filename;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
export function exportFile(content: string, filename: string, type: string) {
const link = document.createElement("a");
link.href = URL.createObjectURL(new Blob([content], { type }));
link.download = filename;
link.style.display = "none";
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
export function importFromFile() {
const input = document.createElement("input");
const promise = parseSelectedFile(input);
input.type = "file";
input.accept = ".json";
// document.body.appendChild(input);
input.click();
return promise.then((data) => {
// document.body.removeChild(input);
console.log(data);
return data;
});
}
function parseSelectedFile(input: HTMLInputElement): Promise {
return new Promise((resolve, reject) => {
input.addEventListener("change", () => {
const file = input.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
try {
const jsonContent = event.target?.result as string;
resolve(jsonContent);
} catch (error) {
reject(error);
}
};
reader.readAsText(file);
} else {
reject(new Error("No file selected."));
}
});
});
}
export const stringifyData = (data: UserDataType) => {
const {
stations: stationsMap,
lines: linesMap,
title,
backgroundColor,
translateX,
translateY,
scale,
opacity,
} = data;
const stations = mapToArr(stationsMap);
const lines = mapToArr(linesMap);
return JSON.stringify({
stations,
lines,
title,
backgroundColor,
translateX,
translateY,
scale,
opacity,
});
};
export const setLocalStorage = (data: UserDataType, callback: Function) => {
const last = localStorage.getItem("current");
const current = localStorage.getItem("current");
const latest = stringifyData(data);
if (latest !== current) {
if (current) {
localStorage.setItem("last", current);
}
localStorage.setItem("current", latest);
callback();
}
};
export const getBoundary = (data: UserDataType) => {
const { stations } = data;
const allStationsList = mapToArr(stations);
let minX = Infinity,
minY = Infinity,
maxX = -Infinity,
maxY = -Infinity;
allStationsList.forEach((station) => {
const { position } = station;
const [x, y] = position;
minX = Math.min(x, minX);
minY = Math.min(y, minY);
maxX = Math.max(x, maxX);
maxY = Math.max(y, maxY);
});
return { minX, minY, maxX, maxY };
};
export const mediateMap = (
data: UserDataType,
{ setScale, setTranslateX, setTranslateY }: TransformProps
) => {
const { minX, minY, maxX, maxY } = getBoundary(data);
const width = maxX - minX;
const height = maxY - minY;
const { innerHeight, innerWidth } = window;
let scale = 1,
transformX = -minX,
transformY = -minY;
if (innerWidth > innerHeight) {
const margin = innerWidth * 0.05;
scale = (innerWidth - margin) / width;
transformX = margin / 2 - minX * scale;
transformY = (innerHeight - height * scale) / 2 - minY * scale;
} else {
const margin = innerHeight * 0.05;
scale = (innerHeight - margin) / height;
transformY = margin / 2 - minY * scale;
transformX = (innerWidth - width * scale) / 2 - minX * scale;
}
// small size map
if (scale > 1) {
scale = 1;
transformX = (innerWidth - width) / 2 - minX;
transformY = (innerHeight - height) / 2 - minY;
}
setScale(scale);
setTranslateX(transformX);
setTranslateY(transformY);
};
export function importImage(): Promise {
return new Promise((resolve, reject) => {
const input = document.createElement("input");
input.type = "file";
input.accept = "image/*";
input.onchange = (event: Event) => {
const target = event.target as HTMLInputElement;
if (target.files && target.files.length > 0) {
const file = target.files[0];
resolve(file);
} else {
resolve(null);
}
};
input.click();
});
}
interface TransformValues {
translateX: number;
translateY: number;
scale: number;
}
export function calculateTransform(file: File): Promise {
return new Promise((resolve, reject) => {
const img = new Image();
const reader = new FileReader();
reader.onload = (e) => {
img.src = e.target?.result as string;
};
reader.onerror = (e) => {
reject(e);
};
img.onload = () => {
const screenWidth = window.innerWidth;
const screenHeight = window.innerHeight;
const imgWidth = img.width;
const imgHeight = img.height;
const imgAspectRatio = imgWidth / imgHeight;
const screenAspectRatio = screenWidth / screenHeight;
let scale: number;
if (imgAspectRatio > screenAspectRatio) {
// Image is wider than the screen
scale = (screenWidth * 0.8) / imgWidth;
} else {
// Image is taller than the screen
scale = (screenHeight * 0.8) / imgHeight;
}
const scaledWidth = imgWidth * scale;
const scaledHeight = imgHeight * scale;
const translateX = (screenWidth - scaledWidth) / 2;
const translateY = (screenHeight - scaledHeight) / 2;
resolve({ translateX, translateY, scale });
};
reader.readAsDataURL(file);
});
}
export function storeFileInIndexedDB(
file: File,
customFileName: string
): Promise {
return new Promise((resolve, reject) => {
const request = indexedDB.open("FileDatabase", 1);
request.onupgradeneeded = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains("files")) {
db.createObjectStore("files", { keyPath: "name" });
}
};
request.onsuccess = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
if (!db.objectStoreNames.contains("files")) {
db.close();
indexedDB.deleteDatabase("FileDatabase").onsuccess = () => {
storeFileInIndexedDB(file, customFileName).then(resolve).catch(reject);
};
} else {
const transaction = db.transaction("files", "readwrite");
const objectStore = transaction.objectStore("files");
const fileRecord = {
name: customFileName,
content: file,
};
const addRequest = objectStore.put(fileRecord);
addRequest.onsuccess = () => {
resolve();
};
addRequest.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
}
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
}
export function readFileFromIndexedDB(fileName: string): Promise {
return new Promise((resolve, reject) => {
const request = indexedDB.open("FileDatabase", 1);
request.onsuccess = (event) => {
try {
const db = (event.target as IDBOpenDBRequest).result;
const transaction = db.transaction("files", "readonly");
const objectStore = transaction.objectStore("files");
const getRequest = objectStore.get(fileName);
getRequest.onsuccess = (event) => {
const result = (event.target as IDBRequest).result;
if (result) {
resolve(result.content);
} else {
resolve(null);
}
};
getRequest.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
} catch (e) {
console.error(e);
reject(e);
}
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
}
export function deleteFileFromIndexedDB(fileName: string): Promise {
return new Promise((resolve, reject) => {
const request = indexedDB.open("FileDatabase", 1);
request.onsuccess = (event) => {
const db = (event.target as IDBOpenDBRequest).result;
const transaction = db.transaction("files", "readwrite");
const objectStore = transaction.objectStore("files");
const deleteRequest = objectStore.delete(fileName);
deleteRequest.onsuccess = () => {
resolve();
};
deleteRequest.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
};
request.onerror = (event) => {
reject((event.target as IDBRequest).error);
};
});
}
================================================
FILE: src/Data/Shape.ts
================================================
export const Shape = {
'cicle': '圆形', // typo
'square': '正方形',
'triangle': '三角形',
'start': '五角星', // typo
'pentagon': '五边形',
'hexagon': '六边形',
'cross': '十字形',
'rhombus': '菱形',
'diamond': '钻石',
'leaf': '叶子',
'ginkgo': '银杏'
};
================================================
FILE: src/Data/UserData.ts
================================================
import { Dispatch, SetStateAction } from "react";
import { base64ToFile, generateRandomColor, mapToArr } from "../Common/util";
import { colorSH } from "../Common/color";
import { Direct } from "../DataStructure/Direction";
import { teyvat } from "../Common/teyvat";
import i18n from "../i18n/config";
export class StationProps {
stationId!: number;
stationName!: string;
position!: number[];
shape!: string;
lineIds!: number[];
tagDirection?: Direct;
}
export class LineProps {
lineId!: number;
lineName!: string;
color!: string;
stationIds!: number[];
sign!: string;
order!: number;
bendFirst!: boolean[];
subLine?: boolean;
}
export type ChangeSteps = {
fromX: number;
fromY: number;
toX: number;
toY: number;
stationId: number;
};
export type LineChanges = {
stationId: number;
lineId: number;
stationIndex: number;
};
export type RecordType = StationProps[] | ChangeSteps[] | LineChanges[];
export class CardShowing {
constructor() {
this.stationIds = [];
this.lineIds = [];
}
stationIds?: number[];
lineIds?: number[];
stationFirst?: boolean;
}
export type InsertInfo = { insertIndex: number; line: LineProps };
export class UserDataType {
stations!: Map;
lines!: Map;
title?: string;
backgroundColor?: string;
backgroundImage?: File;
opacity?: number;
translateX?: number;
translateY?: number;
scale?: number;
}
export type ShowNameProps = {
showName: boolean;
setShowName: React.Dispatch>;
autoHiddenName: boolean;
setAutoHiddenName: React.Dispatch>;
};
export type DrawProps = {
drawing: boolean;
setDrawing: React.Dispatch>;
};
export type DrawerSize = {
drawerX: number;
drawerY: number;
};
export type TransformProps = {
translateX: number;
setTranslateX: React.Dispatch>;
translateY: number;
setTranslateY: React.Dispatch>;
scale: number;
setScale: React.Dispatch>;
};
export type ShowTourProps = {
showTour: boolean;
setShowTour: React.Dispatch>;
};
export type PageProps = {
page: string;
setPage: React.Dispatch>;
};
export const initData = {
...teyvat,
};
export const setDataFromJson = (
setData: Dispatch>,
jsonString: string
) => {
const res = JSON.parse(jsonString);
const {
stations: stationsArr,
lines: linesArr,
title,
backgroundColor,
translateX,
translateY,
scale,
opacity,
image,
}: {
stations: StationProps[];
lines: LineProps[];
title: string;
backgroundColor: string;
translateX: number;
translateY: number;
scale: number;
opacity: number;
image: string;
} = res;
const stations = stationsArr.reduce((map, cur) => {
map.set(cur.stationId, cur);
return map;
}, new Map());
const lines = linesArr.reduce((map, cur) => {
map.set(cur.lineId, cur);
return map;
}, new Map());
const data = {
stations,
lines,
title,
backgroundColor,
translateX,
translateY,
scale,
opacity,
};
setData(data);
if (image)
base64ToFile(image)
.then((backgroundImage) => {
setData({ ...data, backgroundImage });
})
.catch((e) => {
console.error(e);
});
return data;
};
export const deleteStation = (
data: UserDataType,
setData: Dispatch>,
stationId: number
) => {
const { stations, lines } = data;
const station = stations.get(stationId);
const { lineIds } = station!;
//detele stations in line
lineIds.forEach((lineId) => {
const newStationIds: number[] = [];
const newBendFirst: boolean[] = [];
const line = lines.get(lineId);
const { stationIds, bendFirst } = line!;
stationIds.forEach((id, index) => {
if (stationId !== id) {
// break the cicle
if (id !== newStationIds[newStationIds.length - 1]) {
newStationIds.push(id);
newBendFirst.push(bendFirst[index]);
}
}
});
lines.set(lineId, {
...line!,
stationIds: newStationIds,
bendFirst: newBendFirst,
});
});
// delete station
stations.delete(stationId);
setData({ ...data });
};
export const deleteLine = (
data: UserDataType,
setData: Dispatch>,
lineId: number
) => {
const { stations, lines } = data;
const line = lines.get(lineId);
const { stationIds } = line!;
//detele stations in line
stationIds.forEach((stationId) => {
const newLineIds: number[] = [];
const station = stations.get(stationId);
const { lineIds } = station!;
lineIds.forEach((id) => {
if (lineId !== id) {
newLineIds.push(id);
}
});
stations.set(stationId, {
...station!,
lineIds: newLineIds,
});
});
// delete line
lines.delete(lineId);
setData({ ...data });
};
export const dataProcessor = (
id: number,
setData: Dispatch>,
state: UserDataType
) => {
const { stations, lines } = state;
return {
setStationName: (name: string) => {
setData((state) => {
const station = stations.get(id);
station!.stationName = name;
return { ...state };
});
},
setStationPosition: (x: number, y: number) => {
setData((state) => {
const station = stations.get(id);
station!.position = [x, y].map((x) =>
Number.isNaN(x) ? 0 : Math.round(x)
);
return { ...state };
});
},
setStationShape: (shape: string) => {
setData((state) => {
const station = stations.get(id);
station!.shape = shape;
return { ...state };
});
},
setStationTagDirection: (direct: Direct) => {
setData((state) => {
const station = stations.get(id);
station!.tagDirection = direct;
if (direct === 8) delete station!.tagDirection;
return { ...state };
});
},
getStationById: (stationId: string | number) => {
return stations.get(stationId);
},
getLineById: (lineId: string | number) => {
return lines.get(lineId);
},
getStationsInThisLine: () => {
return lines.get(id)!.stationIds.map((x) => stations.get(x));
},
setLineName: (name: string) => {
setData((state) => {
const line = lines.get(id);
line!.lineName = name;
return { ...state };
});
},
setSign: (sign: string) => {
setData((state) => {
const line = lines.get(id);
line!.sign = sign;
return { ...state };
});
},
setOrder: (order: number) => {
setData((state) => {
const line = lines.get(id);
line!.order = order;
return { ...state };
});
},
setColor: (color: string) => {
setData((state) => {
const line = lines.get(id);
line!.color = color;
return { ...state };
});
},
setSubLine: (subLine: boolean) => {
setData((state) => {
const line = lines.get(id);
line!.subLine = subLine;
if (!subLine) delete line!.subLine;
return { ...state };
});
},
getBendFirst: (stationIndex: number) => {
const line = lines.get(id);
return line?.bendFirst[stationIndex];
},
setBendFirst: (stationIndex: number, bendFirst: boolean) => {
setData((state) => {
const line = lines.get(id);
line!.bendFirst[stationIndex] = bendFirst;
return { ...state };
});
},
deleteStation: () => deleteStation(state, setData, id),
deleteLine: () => deleteLine(state, setData, id),
removeStationFromLine: (lineId: number, stationIndex: number) => {
setData((state) => {
const station = stations.get(id);
const line = lines.get(lineId);
const { stationIds, bendFirst } = line!;
if (stationIds[stationIndex] === id) {
stationIds.splice(stationIndex, 1);
bendFirst.splice(stationIndex, 1);
if (!stationIds.some((stationId) => stationId === id)) {
const { lineIds } = station!;
station!.lineIds = lineIds.filter((id) => lineId !== id);
}
if (stationIds[stationIndex - 1] === stationIds[stationIndex]) {
stationIds.splice(stationIndex, 1);
bendFirst.splice(stationIndex, 1);
}
}
return { ...state };
});
},
addNewLine: () => {
const newLine = new LineProps();
setData((state) => {
const maxId = mapToArr(lines).reduce(
(pre, cur) => Math.max(pre, cur.lineId),
0
);
const lineId = maxId + 1;
Object.assign(newLine, {
lineId: lineId,
lineName:i18n.t("lineNo",{lineId}),
color: colorSH[lineId - 1]
? colorSH[lineId - 1].color
: generateRandomColor(),
stationIds: [id],
sign: lineId.toString(),
order: lineId,
bendFirst: [true],
});
const station = stations.get(id);
const { lineIds } = station!;
station!.lineIds = [...new Set(lineIds.concat([lineId]))];
lines.set(lineId, newLine);
return { ...state };
});
return newLine;
},
addStationToLine: (stationId: number, stationIndex: number) => {
return new Promise(res=>{
setData((state) => {
const line = lines.get(id);
if (!line) debugger;
const { stationIds, bendFirst } = line!;
console.log(structuredClone(stationIds));
if (
stationIds[stationIndex] !== stationId &&
stationIds[stationIndex - 1] !== stationId
) {
stationIds.splice(stationIndex, 0, stationId);
bendFirst.splice(stationIndex, 0, true);
if(stationIds.some((x,index)=>x===stationIds[index-1]||x===stationIds[index+1])) debugger
const station = stations.get(stationId);
const { lineIds } = station!;
station!.lineIds = [...new Set(lineIds.concat([id]))];
res(true);
} else {
res(false);
}
return { ...state };
});
});
},
};
};
export const addNewStation = (
data: UserDataType,
setData: Dispatch>,
x: number,
y: number,
record: StationProps[],
setRecord: React.Dispatch>,
currentRecordIndex: number,
setCurrentRecordIndex: React.Dispatch>,
cardShowing: CardShowing,
setCardShowing: Dispatch>,
defaultShape: string,
) => {
const { stations } = data;
let max = 0;
stations.forEach((station) => {
max = Math.max(station.stationId, max);
});
const stationId = max + 1;
const newStation = {
stationId,
stationName: `${i18n.t('newStation')} ${max + 1}`,
position: [x, y].map(Math.round),
shape: defaultShape,
lineIds: [],
};
setCardShowing({ stationIds: [stationId] });
const newRecord = record.slice(0, currentRecordIndex + 1);
setRecord(newRecord.concat([newStation]));
setCurrentRecordIndex(currentRecordIndex + 1);
stations.set(max + 1, newStation);
setData({ ...data });
};
export const addStationFromRecord = (
data: UserDataType,
setData: Dispatch>,
station: StationProps
) => {
const { stations } = data;
let max = 0;
stations.forEach((station) => {
max = Math.max(station.stationId, max);
});
const newStation = { ...station, stationId: max + 1 };
stations.set(max + 1, newStation);
setData({ ...data });
};
================================================
FILE: src/DataStructure/Bend.ts
================================================
import { Rail } from "./Rail";
import { Track } from "./Track";
export class Bend {
// filter empty indexes
static round1(track: Track) {
const emptyRails = track.getEmptyRails().map((rail) => rail.index);
if (emptyRails.length) return emptyRails;
return [0, 1, 2];
}
// filter opposite rails
static round2(track: Track, round1Indexes: number[], rail?: Rail) {
// rail not exist or in/out rail direction not just opposite to the candidate rail direction
if (!rail || !rail.track.direction.oppositeTo(track.direction))
return round1Indexes;
return round1Indexes.filter(
(railIndex) => rail.oppositeIndex() === railIndex
);
}
// filter center rail
static round3(round2Indexes: number[]) {
if (round2Indexes.find((railIndex) => railIndex === 1)) return 1;
}
// filter most close rail
static round4(track: Track, rail?: Rail) {
if (!rail) return 0;
return rail.track.direction.rotationTo(track.direction) > 0 ? 0 : 2;
}
static getBestRailIndex(track: Track, rail?: Rail) {
const round1Indexes = this.round1(track);
if (round1Indexes.length === 1) return round1Indexes[0];
const round2Indexes = this.round2(track, round1Indexes, rail);
if (round2Indexes.length === 1) return round2Indexes[0];
const round3Res = this.round3(round2Indexes);
if (round3Res === 1) return 1;
const round4Res = this.round4(track, rail);
return round4Res;
}
}
================================================
FILE: src/DataStructure/ConnectType.ts
================================================
export enum ConnectType {
straight,
straightFirst,
bendFirst
}
================================================
FILE: src/DataStructure/Direction.ts
================================================
//clockwise
export enum Direct {
up,
upRight,
right,
rightDown,
down,
downLeft,
left,
leftUp,
// Quadrant I bisected by a diagonal line
// upRightA means the upper half
// upRightB means the lower half
// all the directions is marked in clockwise
upRightA,
upRightB,
rightDownA,
rightDownB,
downLeftA,
downLeftB,
leftUpA,
leftUpB,
coincide,
}
export class Direction {
direct: Direct;
standard: boolean;
lean: boolean;
constructor(direct: Direct) {
this.direct = direct;
this.standard = direct < 8;
this.lean = direct % 2 === 1;
}
delta(direct: Direct){
const diff = Math.abs(direct - this.direct);
return Math.min(diff, 8-diff);
}
opposite() {
if (this.direct < 8) return new Direction((this.direct + 4) % 8);
if (this.direct >= 8 && this.direct < 12)
return new Direction(this.direct + 4);
if (this.direct >= 12 && this.direct < 16)
return new Direction(this.direct - 4);
return new Direction(this.direct);
}
oppositeTo(direction: Direction | undefined) {
if (direction) return direction.opposite().direct === this.direct;
throw new Error("no direction!");
}
sameTo(direction: Direction) {
return this.direct === direction.direct;
}
// how many rotation should this direction do to coincide to given direction
// 1 2 3 means clockwise rotate 1 2 3 times
// -1 -2 -3 means counterclockwise
// 0 means no need rotate
// 4 means opposite direction
rotationTo(direction: Direction) {
if (this.oppositeTo(direction)) return 4;
const side = direction.direct - this.direct;
if (side < -4) return side + 8;
if (side > 4) return side - 8;
return side;
}
getBendSteps(bendFirst: boolean) {
if (this.direct < 8 || this.direct > 15) {
throw new Error("this is not bend direction");
}
const firstStep = new Direction(this.direct - 7 - (this.direct % 2));
const secondStep = new Direction((this.direct - 8 + (this.direct % 2)) % 8);
return bendFirst ? [firstStep, secondStep] : [secondStep, firstStep];
}
}
export const DirectionVictor = [
[0, 1],
[1, 1],
[1, 0],
[1, -1],
[0, -1],
[-1, -1],
[-1, 0],
[-1, 1],
];
export const DirectionVictorReverseY = DirectionVictor.map(([x,y])=>[x,-y]);
================================================
FILE: src/DataStructure/Display.ts
================================================
import { Station } from "./Station";
export class DisplayStation {
stationName: string;
bendFirst: boolean;
constructor(stationName: string, bendFirst: boolean){
this.stationName = stationName;
this.bendFirst = bendFirst;
}
}
================================================
FILE: src/DataStructure/Line.ts
================================================
import { LineProps } from "../Data/UserData";
import { Bend } from "./Bend";
import { ConnectType } from "./ConnectType";
import { LineRecord } from "./LineRecord";
import { Rail } from "./Rail";
import { RailPair } from "./RailPair";
import { Station } from "./Station";
import { Straight } from "./Straight";
import { Vector } from "./Vector";
export class Line {
empty: boolean;
departureRecord: LineRecord | undefined;
_dev_tag: string | undefined;
displayLine?: LineProps;
constructor() {
this.empty = false;
}
getTerminalRecord(){
let p = this.departureRecord;
while(p?.nextLineRecord){
p = p.nextLineRecord;
if(p === this.departureRecord || !p.nextLineRecord) return p;
}
return p;
}
linkAll(stations: Station[]){
stations.reduce((pre,cur)=>{
this.link(pre,cur);
return cur;
});
}
// connect B station and C station
link(B: Station, C: Station, bendFirst: boolean = true) {
const railPair = this.applyBestRailPair(B,C,bendFirst);
railPair.setLine(this);
let bLineRecord = B.getJoint(this);
if (!bLineRecord) {
//if record not exist, add one
bLineRecord = new LineRecord(B, this);
// register cLineRecord in C station
B.addLineRecord(bLineRecord);
this.departureRecord = bLineRecord;
}
let cLineRecord// = C.getJoint(this);
if (!cLineRecord) {
cLineRecord = new LineRecord(C, this);
// register cLineRecord in C station
C.addLineRecord(cLineRecord);
}
// establish doubly linked list
bLineRecord?.establishConnectionTo(cLineRecord);
// update rail and connect type for B and C
LineRecord.updateLineRecords(bLineRecord, cLineRecord, railPair);
}
applyBestRailPair(B: Station, C: Station, bendFirst: boolean) {
const direction = new Vector(B.position, C.position);
if (direction.standard) {
const bOutIndex = Straight.getBestRailIndex(B, C, this);
const cInIndex = Rail.oppositeIndex(bOutIndex);
// if(B.displayStation?.stationName==='风起地站'&&C.displayStation?.stationName==='达达乌帕谷') debugger
const bTrack = B.getTrack(direction);
const cTrack = C.getTrack(direction.opposite());
const bRail = bTrack.getAvailableRail(bOutIndex);
const cRail = cTrack.getAvailableRail(cInIndex);
return new RailPair(bRail, cRail);
} else {
const [bOutDirection, cInDirectionOpposite] =
direction.getBendSteps(bendFirst);
const cInDirection = cInDirectionOpposite.opposite();
const bTrack = B.getTrack(bOutDirection);
const cTrack = C.getTrack(cInDirection);
const bLastRail = B.getJoint(this)?.lastRail;
const cNextRail = C.getJoint(this)?.nextRail;
const bOutIndex = Bend.getBestRailIndex(bTrack, bLastRail);
const cInIndex = Bend.getBestRailIndex(cTrack, cNextRail);
const bRail = bTrack.getAvailableRail(bOutIndex);
const cRail = cTrack.getAvailableRail(cInIndex);
return new RailPair(bRail, cRail);
}
}
}
export class EmptyLine extends Line {
constructor() {
super();
this.empty = true;
}
}
================================================
FILE: src/DataStructure/LineRecord.ts
================================================
import { ConnectType } from "./ConnectType";
import { Line } from "./Line";
import { Rail } from "./Rail";
import { RailPair } from "./RailPair";
import { Station } from "./Station";
export class LineRecord {
station: Station;
line: Line | undefined;
lastLineRecord: LineRecord | undefined;
nextLineRecord: LineRecord | undefined;
lastRail: Rail | undefined;
nextRail: Rail | undefined;
constructor(station: Station, line?: Line) {
this.station = station;
if (line) {
this.line = line;
}
}
getInDirection(){
return this.lastRail?.track.direction;
}
getOutDirection(){
return this.nextRail?.track.direction;
}
establishConnectionTo(BRecord: LineRecord) {
LineRecord.establishConnection(this, BRecord);
}
static establishConnection(ARecord: LineRecord, BRecord: LineRecord) {
ARecord.nextLineRecord = BRecord;
BRecord.lastLineRecord = ARecord;
}
// update the rail and connect type information for connectting 2 linerecords
static updateLineRecords(
ARecord: LineRecord,
BRecord: LineRecord,
railPair: RailPair,
) {
const { departureRail, arrivalRail } = railPair;
ARecord.nextRail = departureRail;
BRecord.lastRail = arrivalRail;
}
}
================================================
FILE: src/DataStructure/Mode.ts
================================================
export enum Mode {
normal,
moving,
touchMoving,
touchScaling
}
export enum FunctionMode{
normal,
addingStation,
dragingStation,
lineEditing,
backgroundEditing,
customBackground,
editingCustomBackgroundPosition,
selectingStation,
choosingExistMap,
}
================================================
FILE: src/DataStructure/Point.ts
================================================
import { MouseEvent, Touch } from "react";
import { Straight } from "./Straight";
export class Point {
x: number;
y: number;
yReversed: boolean;
q_start: boolean;
q: boolean;
q_end: boolean;
constructor(x: number = 0, y: number = 0, yReversed: boolean = false) {
this.x = x;
this.y = y;
this.yReversed = yReversed;
this.q_start = false;
this.q = false;
this.q_end = false;
}
round(){
return new Point(Math.round(this.x), Math.round(this.y))
}
offset(A: Point) {
return new Point(A.x + this.x, A.y + this.y);
}
displacementTo(A: Point) {
return new Point(this.x - A.x, this.y - A.y);
}
distanceTo(A: Point) {
return Math.sqrt(Math.pow(this.x - A.x, 2) + Math.pow(this.y - A.y, 2));
}
sameTo(A: Point) {
return A.x === this.x && A.y === this.y && A.yReversed === this.yReversed;
}
reverseY() {
return new Point(this.x, -this.y, !this.yReversed);
}
static getPointFromTouch(A: Touch) {
return new Point(A.clientX, A.clientY);
}
static getPointFromMouse(A: MouseEvent) {
return new Point(A.clientX, A.clientY);
}
static getMidPoint(A: Point, B: Point) {
return new Point((A.x + B.x) / 2, (A.y + B.y) / 2);
}
static getDisplacement(A: Point, B: Point) {
return new Point(A.x - B.x, A.y - B.y);
}
}
================================================
FILE: src/DataStructure/Rail.ts
================================================
import { EmptyLine, Line } from "./Line";
import { RailPair } from "./RailPair";
import { Track } from "./Track";
export class Rail {
track: Track;
index: number;
line: Line;
extra: boolean;
constructor(track: Track, index: number) {
this.track = track;
this.index = index;
this.line = new EmptyLine();
this.extra = false;
}
oppositeIndex(){
return 2 - this.index;
}
setLine(line: Line){
this.line = line;
}
static getStraightConnectRailPair(aEmptyRails: Rail[], bEmptyRails: Rail[]) {
if (aEmptyRails.length === 0 || bEmptyRails.length === 0) return;
const railPairs: RailPair[] = [];
aEmptyRails.forEach((aRail) => {
bEmptyRails.forEach((bRail) => {
//found just opposite rail
if (aRail.index + bRail.index === 2) {
railPairs.push(new RailPair(aRail,bRail));
}
});
});
return railPairs;
}
static getBestRail(rails: Rail[]){
return rails.find(rail=>rail.index === 1) || rails[0];
}
static getRailByIndex(rails: Rail[], index: number){
return rails.find(rail=>rail.index === index);
}
static oppositeIndex(index: number){
return 2 - index;
}
}
export class ExtraRail extends Rail{
constructor(track: Track, index: number){
super(track, index);
this.extra = true;
}
}
================================================
FILE: src/DataStructure/RailPair.ts
================================================
import { Direction } from "./Direction";
import { Line } from "./Line";
import { Rail } from "./Rail";
import { Station } from "./Station";
export class RailPair {
departureRail: Rail;
arrivalRail: Rail;
center: boolean;
constructor(departureRail: Rail, arrivalRail: Rail) {
this.departureRail = departureRail;
this.arrivalRail = arrivalRail;
this.center = departureRail.index === 1;
}
reverse(){
const temp = this.departureRail;
this.departureRail = this.arrivalRail;
this.arrivalRail = temp;
}
setLine(line: Line){
this.departureRail.setLine(line);
this.arrivalRail.setLine(line);
}
}
================================================
FILE: src/DataStructure/Station.ts
================================================
import { StationProps } from "../Data/UserData";
import { Direct, Direction } from "./Direction";
import { Line } from "./Line";
import { LineRecord } from "./LineRecord";
import { Point } from "./Point";
import { Rail } from "./Rail";
import { Track } from "./Track";
export class Station {
position: Point;
tracks: Track[];
lineRecords: Map;
_dev_tag: string | undefined;
handlers: (Line | undefined | null)[];
displayStation?: StationProps;
constructor(position: Point) {
this.position = position;
this.tracks = new Array(8)
.fill(true)
.map((x, direct: Direct) => new Track(this, new Direction(direct)));
this.lineRecords = new Map();
this.handlers = new Array(8);
}
lineCount(){
const set = new Set();
this.lineRecords.forEach(line=>{
set.add(line);
})
return set.size;
}
isEmpty(direct: Direct){
return !this.handlers[direct] && this.tracks[direct].isEmpty();
}
getBestDirectionForName() {
let space = 0,
endIndex = 0,
maxSpace = 0,
firstSpace;
for (let i = 0; i < 8; i++) {
const empty = !this.handlers[i] && this.tracks[i].isEmpty();
if (empty) {
space++;
if (space > maxSpace) {
maxSpace = space;
endIndex = i;
}
} else {
if (firstSpace === undefined) firstSpace = space;
space = 0;
}
}
// all empty
if (space === 8 || maxSpace===0) {
return Direct.right;
}
if(maxSpace <= 2) {
if(this.isEmpty(2)) return 2;
if(this.isEmpty(6)) return 6;
if(this.isEmpty(1)) return 1;
if(this.isEmpty(3)) return 3;
if(this.isEmpty(5)) return 5;
if(this.isEmpty(7)) return 7;
}
if(firstSpace === undefined) firstSpace =0;
if (space + firstSpace >= maxSpace) {
// max space containing 0
if (firstSpace >= space) {
return Math.floor((firstSpace - space) / 2);
} else if (space > firstSpace) {
return 7 - Math.floor((space - firstSpace) / 2);
}
}
const best = endIndex - Math.floor(maxSpace / 2)
// max space not containing 0
if(maxSpace%2===0){
if(best%2===0){
return best+1;
}
}
return best;
}
getBestDirectionForName2(){
}
addLineRecord(lineRecord: LineRecord) {
const { line, station } = lineRecord;
if (!line) {
throw new Error("line is undefined while add line record to statation");
}
if (station !== this) {
throw new Error(
"you are adding a line record which not belongs to this station"
);
}
// this lineRecords is the array saving linerecords
const lineRecordsArr = this.lineRecords.get(line) || [];
lineRecordsArr.push(lineRecord);
// this.lineRecords is the map Line=>Line
// containing all the information of the lines go through this station
this.lineRecords.set(line, lineRecordsArr);
}
getTrack(direction: Direction) {
return this.tracks[direction.direct];
}
getRail(direction: Direction, index: number) {
return this.getTrack(direction).getRail(index);
}
// getBestRail(direction: Direction){
// return this.getTrack(direction).getBestRail();
// }
// find the start or end of the Line
// if no records find, this station must be the departure station
// if both start and end exist, this station is the loop line joint
getJoint(line: Line) {
const terminal = this.lineRecords
.get(line)
?.find((lineRecord) => !lineRecord.lastLineRecord);
const departure = this.lineRecords
.get(line)
?.find((lineRecord) => !lineRecord.nextLineRecord);
return departure || terminal;
}
}
================================================
FILE: src/DataStructure/Straight.ts
================================================
import { Direction } from "./Direction";
import { Line } from "./Line";
import { Point } from "./Point";
import { Rail } from "./Rail";
import { Station } from "./Station";
import { Track } from "./Track";
import { Vector } from "./Vector";
export class Straight {
// round 1 : filter empty rails
static round1(B: Station, C: Station) {
const direction = new Vector(B.position, C.position);
const bTrack = B.getTrack(direction);
const cTrack = C.getTrack(direction.opposite());
const initScores = [0, 1, 2];
let max = 0;
const round1Indexes = initScores
.map((index) => {
let score = 0;
// if rail in bTrack is empty, this rail add one score
if (bTrack.rails[index].line.empty) {
score++;
}
// if rail in cTrack is empty, this rail add one score
if (cTrack.rails[Rail.oppositeIndex(index)].line.empty) {
score++;
}
// record the highest score
if (score > max) {
max = score;
}
return { index, score };
})
.filter(({ score }) => {
// find the highest score indexes
return score === max;
});
return round1Indexes;
}
// round 2 : filter straight pass rails
static round2(
B: Station,
C: Station,
round1Indexes: RoundResult[],
line: Line
) {
const direction = new Vector(B.position, C.position);
const bLineRecord = B.getJoint(line);
const cLineRecord = C.getJoint(line);
let max = 0;
const round2Indexes = round1Indexes
.map(({ index }) => {
let score = 0;
if (
bLineRecord?.lastRail?.track.direction.oppositeTo(direction) &&
bLineRecord?.lastRail?.oppositeIndex() === index
) {
score++;
}
if (
cLineRecord?.nextRail?.track.direction.sameTo(direction) &&
cLineRecord?.nextRail?.index === index
) {
score++;
}
if (score > max) {
max = score;
}
return { index, score };
})
.filter(({ score }) => {
// find the highest score indexes
return score === max;
});
return round2Indexes;
}
// round 3 : filter center rails
static round3(B: Station, C: Station, round2Indexes: RoundResult[]) {
const round3Res = round2Indexes.find(({ index }) => index === 1);
return round3Res ? 1 : 0;
}
// round 4 : filter most close to the last or next rail
static round4(B: Station, C: Station, line: Line) {
const direction = new Vector(B.position, C.position);
const bLineRecord = B.getJoint(line);
const cLineRecord = C.getJoint(line);
let zeroRailScore = 0;
let secondRailScore = 0;
if (bLineRecord?.lastRail)
if (bLineRecord.lastRail.track.direction.rotationTo(direction) > 0)
zeroRailScore++;
else secondRailScore++;
if (cLineRecord?.nextRail)
if (cLineRecord.nextRail.track.direction.rotationTo(direction) > 0)
zeroRailScore++;
else secondRailScore++;
return zeroRailScore > secondRailScore ? 0 : 2;
}
static getBestRailIndex(B: Station, C: Station, line: Line) {
if (B._dev_tag === "B" && line._dev_tag === "line8") {
// debugger;
}
// round 1:
const round1Indexes = Straight.round1(B, C);
if (round1Indexes.length === 1) {
return round1Indexes[0].index;
}
// round 2:
const round2Indexes = Straight.round2(B, C, round1Indexes, line);
if (round2Indexes.length === 1) {
return round2Indexes[0].index;
}
// round 3:
const round3Index = Straight.round3(B, C, round2Indexes);
if (round3Index === 1) {
return round3Index;
}
// round 4:
const round4Index = Straight.round4(B, C, line);
return round4Index;
}
}
class RoundResult {
score!: number;
index!: number;
}
================================================
FILE: src/DataStructure/Track.ts
================================================
import { Direction } from "./Direction";
import { ExtraRail, Rail } from "./Rail";
import { Station } from "./Station";
export class Track {
station: Station;
direction: Direction;
rails: Rail[];
// extra rail won't occupy the space of rail
// extraRails save extraRail by index, every index has an array
extraRails: ExtraRail[][];
constructor(station: Station, direction: Direction) {
this.station = station;
this.direction = direction;
this.rails = new Array(3)
.fill(true)
.map((x, index) => new Rail(this, index));
this.extraRails = new Array(3).fill(true).map((x, index) => new Array());
}
isEmpty(){
return !this.rails.some(rail=>!rail.line.empty)
}
getEmptyRails() {
return this.rails.filter((rail) => rail.line.empty);
}
getRail(index: number) {
return this.rails[index];
}
applyExtraRail(index: number) {
const extraRail = new ExtraRail(this, index);
this.extraRails[index].push(extraRail);
return extraRail;
}
getAvailableRail(index: number) {
const rail = this.getRail(index);
return rail.line.empty ? rail : this.applyExtraRail(index);
}
}
================================================
FILE: src/DataStructure/Vector.ts
================================================
import { Direct, Direction, DirectionVictorReverseY } from "./Direction";
import { Point } from "./Point";
export class Vector extends Direction {
start: Point; // startPoint
end: Point; // endPoint
constructor(start: Point, end: Point) {
super(Vector.getDirection(start, end));
this.start = start;
this.end = end;
}
verticalProlong(length: number) {
const A = this.start,
B = this.end;
if (A.x === B.x)
return [new Point(B.x + length, B.y), new Point(B.x - length, B.y)];
const diffSign = (A.xB.x&&A.y>B.y);
const tan = (B.y - A.y) / (B.x - A.x);
const alpha = Math.abs(Math.atan(tan));
const sin = Math.sin(alpha);
const cos = Math.cos(alpha);
if(diffSign)
return [
new Point(B.x - sin * length, B.y + cos * length),
new Point(B.x + sin * length, B.y - cos * length),
];
return [
new Point(B.x - sin * length, B.y - cos * length),
new Point(B.x + sin * length, B.y + cos * length),
];
}
prolong(length: number) {
const A = this.start,
B = this.end;
const kX = A.x < B.x ? 1 : -1;
const kY = A.y < B.y ? 1 : -1;
if (A.x === B.x) return new Point(A.x, B.y + kY * length);
const tan = (B.y - A.y) / (B.x - A.x);
const alpha = Math.abs(Math.atan(tan));
const sin = Math.sin(alpha);
const cos = Math.cos(alpha);
return new Point(B.x + kX * cos * length, B.y + kY * sin * length);
}
normalize(k: number = 1) {
const deltaX = this.end.x - this.start.x;
const deltaY = this.end.y - this.start.y;
const module = Math.sqrt(
Math.pow(deltaX, 2) + Math.pow(deltaY, 2)
);
return new Vector(
new Point(0, 0),
new Point((k * deltaX) / module, (k * deltaY) / module)
);
}
passesThroughPoint(A: Point) {
return (
(A.x - this.start.x) * (A.y - this.end.y) ===
(A.y - this.start.y) * (A.x - this.end.x)
);
}
round() {
return new Vector(this.start.round(), this.end.round());
}
passesThroughPointRound(point: Point) {
const A = point.round();
const vector = this.round();
return this.passesThroughPoint.bind(vector)(A);
}
getCrossPointTo(b: Vector) {
const a = this;
const A = a.start,
B = a.end,
C = b.start,
D = b.end;
const m = (A.x - B.x) / (A.y - B.y);
const n = (C.x - D.x) / (C.y - D.y);
// if(!Number.isFinite(m)) return new Point(A.x, (A.x - C.x) / n + C.y);
// if(!Number.isFinite(n)) return new Point(C.x, (C.x - A.x) / m + A.y);
if (!Number.isFinite(m)) return new Point(n * (A.y - C.y) + C.x, A.y);
if (!Number.isFinite(n)) return new Point(m * (C.y - A.y) + A.x, C.y);
const y = (C.x - A.x + m * A.y - n * C.y) / (m - n);
const x = n * (y - C.y) + C.x;
return new Point(x, y);
}
static getVectorByPointAndDirection(A: Point, direction: Direction) {
const [x, y] = DirectionVictorReverseY[direction.direct];
return new Vector(A, new Point(A.x + x, A.y + y));
}
static getDirection(start: Point, end: Point) {
const A = start.reverseY();
const B = end.reverseY();
if (A.x === B.x && A.y === B.y) return Direct.coincide;
if (A.x === B.x) return B.y > A.y ? Direct.up : Direct.down;
if (A.y === B.y) return B.x > A.x ? Direct.right : Direct.left;
const deltaX = B.x - A.x;
const deltaY = B.y - A.y;
if (deltaX > 0 && deltaY > 0)
if (deltaX === deltaY) return Direct.upRight;
else if (deltaX < deltaY) return Direct.upRightA;
else return Direct.upRightB;
if (deltaX > 0 && deltaY < 0)
if (deltaX === -deltaY) return Direct.rightDown;
else if (deltaX > -deltaY) return Direct.rightDownA;
else return Direct.rightDownB;
if (deltaX < 0 && deltaY < 0)
if (-deltaX === -deltaY) return Direct.downLeft;
else if (-deltaX < -deltaY) return Direct.downLeftA;
else return Direct.downLeftB;
if (deltaX < 0 && deltaY > 0)
if (-deltaX === deltaY) return Direct.leftUp;
else if (-deltaX > deltaY) return Direct.leftUpA;
else return Direct.leftUpB;
debugger
throw Error("error happend when getting direction");
}
}
================================================
FILE: src/Entrance/App.scss
================================================
body{
margin: 0;
touch-action: none;
overscroll-behavior: none;
}
html{
touch-action: none;
overscroll-behavior: none;
}
.App{
height: 100vh;
width: 100vw;
overflow: hidden;
user-select: none;
}
*{
font-family: 'PingFang SC';
-webkit-tap-highlight-color: transparent;
}
================================================
FILE: src/Entrance/App.test.tsx
================================================
import { render, screen } from '@testing-library/react';
import App from './App';
import React from 'react';
import { Direct, Direction } from '../DataStructure/Direction';
import { Track } from '../DataStructure/Track';
import { Station } from '../DataStructure/Station';
import { Point } from '../DataStructure/Point';
import { EmptyLine, Line } from '../DataStructure/Line';
import { Vector } from '../DataStructure/Vector';
test('renders learn react link', () => {
render( );
const linkElement = screen;
const direction = new Direction(Direct.up);
const station = new Station(new Point(1,2));
const track = new Track(station,new Direction(Direct.up));
const A = new Point(1,2);
const B = new Point(3,4);
const vector = new Vector(A,B);
console.log(station.getTrack(vector).direction.direct)
console.log(direction)
expect(linkElement).toBeDefined();
});
test('vector cross pointer',()=>{
const a = new Vector(new Point(0,0), new Point(1,1));
const b = new Vector(new Point(0,5), new Point(5,0));
const abCrossPoint = a.getCrossPointTo(b);
console.log(abCrossPoint);
expect(abCrossPoint.x).toEqual(2.5);
expect(abCrossPoint.y).toEqual(2.5);
})
================================================
FILE: src/Entrance/App.tsx
================================================
import '../i18n/config';
import { ErrorBoundary } from "react-error-boundary";
import {
browserInfo,
mapToArr,
mediateMap,
readFileFromIndexedDB,
setLocalStorage,
} from "../Common/util";
import {
CardShowing,
ChangeSteps,
InsertInfo,
LineChanges,
LineProps,
RecordType,
StationProps,
UserDataType,
initData,
setDataFromJson,
} from "../Data/UserData";
import { FunctionMode, Mode } from "../DataStructure/Mode";
import { Cards } from "../Render/Card/Cards";
import {
DeleteConfirmation,
showConfirmationInterface,
} from "../Render/Delete/DeleteConfirmation";
import { Menu } from "../Render/Header/Menu";
import ScaleLayer from "../Render/Layer/ScaleLayer";
import { WelcomeTour } from "../WelcomeTour/WelcomeTour";
import "./App.scss";
import "driver.js/dist/driver.css";
import React, { useEffect, useRef, useState } from "react";
import { ErrorFallback } from "../Render/ErrorFallback/ErrorFallback";
import { Recovery } from "../Render/Recovery/Recovery";
function App() {
const [editingMode, setEditingMode] = useState(Mode.normal);
const [functionMode, setFunctionMode] = useState(FunctionMode.normal);
const [record, setRecord] = useState([]);
const [currentRecordIndex, setCurrentRecordIndex] = useState(-1);
const [insertInfo, setInsertInfo] = useState();
const [data, setDataOriginal] = useState(initData);
const [showName, setShowName] = useState(true);
const [autoHiddenName, setAutoHiddenName] = useState(true);
const [drawing, setDrawing] = useState(false);
const [translateX, setTranslateX] = useState(0);
const [translateY, setTranslateY] = useState(0);
const [page, setPage] = useState("title");
const [scale, setScale] = useState(1);
const ref = useRef();
const menuRef = useRef();
const [saved, setSaved] = useState(true);
const [defaultShape, setDefaultShape] = useState('cicle');
const [showTour, setShowTour] = useState(() => {
return (
window.innerWidth >= 710 && !localStorage.getItem("skip-tour-viewed")
);
});
const [recoveredFromError, setRecoveredFromError] = useState(false);
const [showConfirmation, setShowConfirmation] =
useState();
// keep latest data if crash happend
const setData = (data: React.SetStateAction) => {
if (typeof data === "function") {
setDataOriginal((state) => {
const newState = data(state);
setLocalStorage(newState, () => setSaved(false));
return newState;
});
} else {
setLocalStorage(data, () => setSaved(false));
setDataOriginal(data);
}
};
useEffect(() => {
setShowConfirmation(() => ref.current?.showConfirmation);
}, [ref.current?.showConfirmation]);
const [cardShowing, setCardShowing] = useState(new CardShowing());
const transfromTools = {
scale,
setScale,
translateX,
translateY,
setTranslateX,
setTranslateY,
};
const {backgroundColor} = data;
useEffect(() => {
document.getElementById('theme-color')!.setAttribute('content', backgroundColor?backgroundColor:"#ffffff");
}, [backgroundColor])
return (
{
const last = localStorage.getItem("last");
if (last) {
const data = setDataFromJson(setData, last);
readFileFromIndexedDB("image")
.then((file) => {
setData((data) => ({
...data,
// backgroundColor: "image",
backgroundImage: file as File,
}));
})
.catch((e) => {
console.error(e);
});
mediateMap(data, transfromTools);
}
setRecoveredFromError(true);
}}
>
);
}
export default App;
================================================
FILE: src/Entrance/reportWebVitals.js
================================================
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
================================================
FILE: src/Entrance/setupTests.js
================================================
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';
================================================
FILE: src/Grid/Scale.ts
================================================
import { CardShowing, ChangeSteps, RecordType, StationProps, UserDataType, addNewStation } from "./../Data/UserData";
import {
WheelEvent,
Dispatch,
SetStateAction,
MouseEvent,
TouchEvent,
} from "react";
import { FunctionMode, Mode } from "../DataStructure/Mode";
import { Point } from "../DataStructure/Point";
import React from "react";
const sensitivity = -0.0006;
const scaleByPointer = (
scaleRatio: number,
translateX: number,
translateY: number,
refPoint: Point
) =>
new Point(
refPoint.x - scaleRatio * (refPoint.x - translateX),
refPoint.y - scaleRatio * (refPoint.y - translateY)
);
export const onWheel = (
event: WheelEvent,
preScale: number,
setScale: Dispatch>,
translateX: number,
translateY: number,
setTranslateX: Dispatch>,
setTranslateY: Dispatch>,
functionMode: FunctionMode,
) => {
const { deltaY, clientX, clientY } = event;
const nextScale = deltaY * sensitivity + preScale;
const scaleRatio = nextScale / preScale;
const refPoint = new Point(clientX, clientY);
if (nextScale > 0.1 || (functionMode ===FunctionMode.editingCustomBackgroundPosition && nextScale > 0)) {
const transform = scaleByPointer(
scaleRatio,
translateX,
translateY,
refPoint
);
setTranslateX(transform.x);
setTranslateY(transform.y);
setScale(nextScale);
}
};
export const onMouseDown = (
event: MouseEvent,
translateX: number,
translateY: number,
setEditingMode: Dispatch>,
setMouseRefPoint: Dispatch>,
setMouseStartTranslate: Dispatch>,
setMoved: Dispatch>
) => {
const point = Point.getPointFromMouse(event);
setMouseRefPoint(point);
setEditingMode(Mode.moving);
setMouseStartTranslate(new Point(translateX, translateY));
setMoved(false);
};
export const onMouseMove = (
event: MouseEvent,
translateX: number,
translateY: number,
setTranslateX: Dispatch>,
setTranslateY: Dispatch>,
editingMode: Mode,
mouseRefPoint: Point,
mouseStartTranslate: Point,
setMoved: Dispatch>
) => {
// setMoved(true);
switch (editingMode) {
case Mode.moving: {
// console.log(event);
const point = Point.getPointFromMouse(event);
const displacement = point.displacementTo(mouseRefPoint);
const distance = point.distanceTo(mouseRefPoint);
if(distance>5) setMoved(true);
setTranslateX(mouseStartTranslate.x + displacement.x);
setTranslateY(mouseStartTranslate.y + displacement.y);
break;
}
}
};
export const onMouseUp = (
event: MouseEvent,
setEditingMode: Dispatch>,
editingMode: Mode,
functionMode: FunctionMode,
data: UserDataType,
setData: Dispatch>,
moved: boolean,
translateX: number,
translateY: number,
scale: number,
record: RecordType,
setRecord: React.Dispatch>,
currentRecordIndex: number,
setCurrentRecordIndex: React.Dispatch>,
cardShowing: CardShowing,
setCardShowing: Dispatch>,
defaultShape: string,
) => {
// console.log(event);
setEditingMode(Mode.normal);
if (functionMode === FunctionMode.addingStation && !moved) {
console.log({ translateX, translateY, scale });
const { clientX, clientY } = event;
const x = (clientX - translateX) / scale;
const y = (clientY - translateY) / scale;
addNewStation(
data,
setData,
x,
y,
record as StationProps[],
setRecord,
currentRecordIndex,
setCurrentRecordIndex,
cardShowing,
setCardShowing,
defaultShape,
);
}
const {currentTarget, target} = event;
if(currentTarget === target && !moved){
setCardShowing({});
}
};
export const onMouseLeave = (
event: MouseEvent,
setEditingMode: Dispatch>
) => {
// console.log(event);
setEditingMode(Mode.normal);
};
export const onTouchStart = (
event: TouchEvent,
setEditingMode: Dispatch>,
setTouchRefPoint: Dispatch>,
setTouchStartDistance: Dispatch>,
setTouchStartScale: Dispatch>,
scale: number,
setTouchStartTranslate: Dispatch>,
translateX: number,
translateY: number,
setMoved: Dispatch>
) => {
event.preventDefault();
const { touches } = event;
// record touch start translate position
setTouchStartTranslate(new Point(translateX, translateY));
setMoved(false);
switch (touches.length) {
//one finger
case 1: {
const touch = touches[0];
setTouchRefPoint(Point.getPointFromTouch(touch));
setEditingMode(Mode.touchMoving);
break;
}
//two finger
case 2: {
const touchA = touches[0];
const touchB = touches[1];
const pointA = Point.getPointFromTouch(touchA);
const pointB = Point.getPointFromTouch(touchB);
setTouchRefPoint(Point.getMidPoint(pointA, pointB));
setEditingMode(Mode.touchScaling);
//start touch distance
const distance = pointA.distanceTo(pointB);
setTouchStartDistance(distance);
setTouchStartScale(scale);
break;
}
}
};
export const onTouchMove = (
event: TouchEvent,
editingMode: Mode,
touchRefPoint: Point,
translateX: number,
translateY: number,
setTranslateX: Dispatch>,
setTranslateY: Dispatch>,
touchStartDistance: number,
touchStartScale: number,
setScale: Dispatch>,
touchStartTranslate: Point,
setMoved: Dispatch>
) => {
event.preventDefault();
const { touches } = event;
setMoved(true);
// console.log(event, touches.length, editingMode);
switch (touches.length) {
//one finger
case 1: {
if (editingMode === Mode.touchMoving) {
const touch = touches[0];
const point = Point.getPointFromTouch(touch);
// console.log(touchRefPoint);
const displacement = point.displacementTo(touchRefPoint);
setTranslateX(displacement.x + touchStartTranslate.x);
setTranslateY(displacement.y + touchStartTranslate.y);
}
break;
}
//two finger
case 2: {
if (editingMode === Mode.touchScaling) {
const touchA = touches[0];
const touchB = touches[1];
const pointA = Point.getPointFromTouch(touchA);
const pointB = Point.getPointFromTouch(touchB);
// for scale
const distance = pointA.distanceTo(pointB);
const nextScale = (touchStartScale * distance) / touchStartDistance;
setScale(nextScale);
// for translate
const midPoint = Point.getMidPoint(pointA, pointB);
const scaleRatio = nextScale / touchStartScale;
const transform = scaleByPointer(
scaleRatio,
touchStartTranslate.x,
touchStartTranslate.y,
touchRefPoint
);
//displacement between preMidPoint and midPoint
const displacement = midPoint.displacementTo(touchRefPoint);
setTranslateX(displacement.x + transform.x);
setTranslateY(displacement.y + transform.y);
}
break;
}
}
};
export const onTouchEnd = (
event: TouchEvent,
setEditingMode: Dispatch>,
editingMode: Mode,
functionMode: FunctionMode,
data: UserDataType,
setData: Dispatch>,
moved: boolean,
translateX: number,
translateY: number,
scale: number,
record: RecordType,
setRecord: React.Dispatch>,
currentRecordIndex: number,
setCurrentRecordIndex: React.Dispatch>,
cardShowing: CardShowing,
setCardShowing: Dispatch>,
defaultShape: string,
) => {
event.preventDefault();
const { changedTouches } = event;
// console.log(event,editingMode);
if (
functionMode === FunctionMode.addingStation &&
!moved &&
changedTouches.length === 1
) {
const touch = changedTouches[0];
console.log({ touch, translateX, translateY, scale });
const { clientX, clientY } = touch;
const x = (clientX - translateX) / scale;
const y = (clientY - translateY) / scale;
addNewStation(
data,
setData,
x,
y,
record as StationProps[],
setRecord,
currentRecordIndex,
setCurrentRecordIndex,
cardShowing,
setCardShowing,
defaultShape,
);
}
const {currentTarget, target} = event;
if(currentTarget === target && !moved){
setCardShowing({});
}
setEditingMode(Mode.normal);
};
================================================
FILE: src/Line/Handle.ts
================================================
import { Station } from "./../DataStructure/Station";
import {
Direct,
Direction,
DirectionVictorReverseY,
} from "../DataStructure/Direction";
import { Line } from "../DataStructure/Line";
import { LineRecord } from "../DataStructure/LineRecord";
import { Point } from "../DataStructure/Point";
import { Vector } from "../DataStructure/Vector";
import { handleLength, handleWidth } from "../Common/const";
const getLPLPoints = (allKeyPoints: Point[]) => {
const LQLPoints = allKeyPoints.slice();
// LQLPoints.pop();
// LQLPoints.shift();
return LQLPoints;
};
const checkIfStraightTrackHasHanderOrLine = (
direction: Direction,
station: Station
) => {
const { direct } = direction.opposite();
return station.handlers[direct] || !station.tracks[direct].isEmpty();
};
const checkifHandeCanGoStraight = (
outDirection: Direction,
station: Station
) => {
const ifStraightTrackHasHander = !checkIfStraightTrackHasHanderOrLine(
outDirection,
station
);
return ifStraightTrackHasHander;
};
const getDepartureGoStraightHandeCommand = (A: Point, B: Point) => {
const BA = new Vector(B, A);
const C = BA.prolong(handleLength);
const BC = new Vector(B, C);
const [D, E] = BC.verticalProlong(handleWidth);
return `M ${D.x} ${D.y} L ${E.x} ${E.y} M ${C.x} ${C.y}`;
};
const addHandleForStation = (station: Station, line: Line, direct: Direct) => {
station.handlers[direct] = line;
};
const getBestDirectionForHandle = (station: Station, direction: Direction) => {
let min = Infinity,
bestChoice = 0;
for (let i = 0; i < station.handlers.length; i++) {
const notEmpty = station.handlers[i] || !station.tracks[i].isEmpty();
if (!notEmpty) {
const delta = direction.opposite().delta(i);
if (delta < min) {
bestChoice = i;
min = delta;
}
}
}
return bestChoice;
};
const getDepartureBestChoiceHandeCommand = (
A: Point,
B: Point,
pathStartPoint: Point
) => {
const F = pathStartPoint;
const AB = new Vector(A, B);
const C = AB.prolong(handleLength - 1);
const BC = new Vector(B, C);
const [D, E] = BC.verticalProlong(handleWidth);
return `M ${D.x} ${D.y} L ${E.x} ${E.y} M ${C.x} ${C.y} L ${A.x} ${A.y} L ${F.x} ${F.y}`;
};
const getBestChoiceHandleCommand = (
station: Station,
direction: Direction,
pathStartPoint: Point,
line: Line
) => {
const handleDirect = getBestDirectionForHandle(station, direction);
if (handleDirect === undefined) {
return ` M ${pathStartPoint.x} ${pathStartPoint.y}`;
} else {
const A = station.position;
const [x, y] = DirectionVictorReverseY[handleDirect];
const B = new Point(A.x + x, A.y + y);
addHandleForStation(station, line!, handleDirect);
return getDepartureBestChoiceHandeCommand(A, B, pathStartPoint);
}
};
const getStartHandleCommand = (
A: Point,
B: Point,
departureRecord: LineRecord
) => {
let command = "";
const outDirection = departureRecord?.getOutDirection();
const { station, line } = departureRecord;
const ifHandeCanGoStraight = checkifHandeCanGoStraight(
outDirection!,
station
);
if (ifHandeCanGoStraight) {
command = getDepartureGoStraightHandeCommand(A, B);
addHandleForStation(station, line!, outDirection!.opposite().direct);
} else {
command = getBestChoiceHandleCommand(station, outDirection!, A, line!);
}
return command;
};
const getTerminalGoStraightHandeCommand = (C: Point, D: Point) => {
const CD = new Vector(C, D);
const E = CD.prolong(handleLength);
const CE = new Vector(C, E);
const [F, G] = CE.verticalProlong(handleWidth);
return ` L ${E.x} ${E.y} M ${F.x} ${F.y} L ${G.x} ${G.y}`;
};
const getTerminalBestChoiceHandeCommand = (
A: Point,
B: Point,
pathEndPoint: Point
) => {
const F = pathEndPoint;
const AB = new Vector(A, B);
const C = AB.prolong(handleLength - 1);
const BC = new Vector(B, C);
const [D, E] = BC.verticalProlong(handleWidth);
return `L ${F.x} ${F.y} L ${A.x} ${A.y} L ${C.x} ${C.y} M ${D.x} ${D.y} L ${E.x} ${E.y} `;
};
const getBestChoiceTerminalHandleCommand = (
station: Station,
direction: Direction,
pathStartPoint: Point,
line: Line
) => {
const handleDirect = getBestDirectionForHandle(station, direction);
if (handleDirect === undefined) {
return ` L ${pathStartPoint.x} ${pathStartPoint.y}`;
} else {
const A = station.position;
const [x, y] = DirectionVictorReverseY[handleDirect];
const B = new Point(A.x + x, A.y + y);
addHandleForStation(station, line!, handleDirect);
return getTerminalBestChoiceHandeCommand(A, B, pathStartPoint);
}
};
const getEndHandleCommand = (
C: Point,
D: Point,
terminalRecord: LineRecord
) => {
let command = "";
const inDirection = terminalRecord?.getInDirection();
const { station, line } = terminalRecord;
const ifHandeCanGoStraight = checkifHandeCanGoStraight(inDirection!, station);
if (ifHandeCanGoStraight) {
command = getTerminalGoStraightHandeCommand(C, D);
addHandleForStation(station, line!, inDirection!.opposite().direct);
} else {
command = getBestChoiceTerminalHandleCommand(
station,
inDirection!,
D,
line!
);
}
return command;
};
const getHandleCommand = (line: Line, allKeyPoints: Point[]) => {
const [A, B] = allKeyPoints;
const C = allKeyPoints[allKeyPoints.length - 2],
D = allKeyPoints[allKeyPoints.length - 1];
const { departureRecord, displayLine } = line;
const { subLine } = displayLine!;
const terminalRecord = line.getTerminalRecord();
let startHandleCommand = getStartHandleCommand(A, B, departureRecord!);
let endHandleCommand = ` L ${A.x} ${A.y}`; // loop line
if (!(departureRecord?.station === terminalRecord?.station)) {
endHandleCommand = getEndHandleCommand(C, D, terminalRecord!);
}
const LQLPoints = getLPLPoints(allKeyPoints);
// subline no need handler in joint
if (
subLine &&
departureRecord?.station?.lineCount &&
departureRecord?.station?.lineCount() >= 2
) {
startHandleCommand = ` M ${A.x} ${A.y} `;
}
if (
subLine &&
terminalRecord?.station?.lineCount &&
terminalRecord?.station?.lineCount() >= 2
) {
endHandleCommand = "";
}
return { startHandleCommand, LQLPoints, endHandleCommand };
};
const clearHandleFromRecord = (lineRecord: LineRecord | undefined) => {
if (lineRecord) {
const { station, line } = lineRecord;
station.handlers.forEach((handle, index) => {
if (handle === line) {
station.handlers[index] = null;
}
});
} else {
console.error("no linerecord to clear handle");
}
};
const clearHandle = (line: Line) => {
const { departureRecord } = line;
const terminalRecord = line.getTerminalRecord();
clearHandleFromRecord(departureRecord);
clearHandleFromRecord(terminalRecord);
};
export { getHandleCommand, clearHandle };
================================================
FILE: src/Line/LinePoints.ts
================================================
import { gauge, line_radius } from "../Common/const";
import { Direction } from "../DataStructure/Direction";
import { Line } from "../DataStructure/Line";
import { LineRecord } from "../DataStructure/LineRecord";
import { Point } from "../DataStructure/Point";
import { Rail } from "../DataStructure/Rail";
import { Vector } from "../DataStructure/Vector";
const getTurningPoint = (
A: Point,
B: Point,
aDirection: Direction,
bDirection: Direction
) => {
const aVector = Vector.getVectorByPointAndDirection(A, aDirection);
const bVector = Vector.getVectorByPointAndDirection(B, bDirection);
const crossPoint = aVector.getCrossPointTo(bVector);
return crossPoint;
};
const getOffsetPointFromDirectionAndRail = (
point: Point,
direction: Direction,
rail: Rail
) => {
// if(rail.track.station.displayStation!.stationName==='风起地站' && rail.line.displayLine?.lineName==='2号线')
// debugger
const s = Math.SQRT1_2;
// lean
const directionOffset = [
[[-1,0],[0,0],[1,0]],// 0 up
[[-s,-s],[0,0],[s,s]],// 1 upRight
[[0,-1],[0,0],[0,1]],// 2 right
[[s,-s],[0,0],[-s,s]],// 3 rightDown
[[1,0],[0,0],[-1,0]],// 4 down
[[s,s],[0,0],[-s,-s]],// 5 downLeft
[[0,1],[0,0],[0,-1]],// 6 left
[[-s,s],[0,0],[s,-s]],// 7 leftUp
];
// const directionOffset = [
// [0, -1],
// [SQRT1_2, -SQRT1_2],
// [1, 0],
// [SQRT1_2, SQRT1_2],
// [0, 1],
// [-SQRT1_2, SQRT1_2],
// [-1, 0],
// [-SQRT1_2, -SQRT1_2],
// ];
// const offsetIndex = (direction.direct + rail.index - 1 + 8) % 8;
const offset = directionOffset[direction.direct];
const [offsetX, offsetY] = offset[rail.index].map((x) => x * gauge);
return new Point(offsetX + point.x, offsetY + point.y);
};
const getStartOffsetPointOfStation = (lineRecord: LineRecord) => {
const { station, nextRail } = lineRecord;
return getOffsetPointFromDirectionAndRail(
station.position,
nextRail!.track.direction,
nextRail!
);
};
const getEndOffsetPointOfStation = (lineRecord: LineRecord) => {
const { station, lastRail } = lineRecord;
return getOffsetPointFromDirectionAndRail(
station.position,
lastRail!.track.direction,
lastRail!
);
};
const getInOffsetPointOfStation = (lineRecord: LineRecord) => {
const { station, lastRail } = lineRecord;
return getOffsetPointFromDirectionAndRail(
station.position,
lastRail!.track.direction,
lastRail!
);
};
const getOutOffsetPointOfStation = (lineRecord: LineRecord) => {
const { station, nextRail } = lineRecord;
return getOffsetPointFromDirectionAndRail(
station.position,
nextRail!.track.direction,
nextRail!
);
};
const getPointsBetweenStations = (lineRecord: LineRecord) => {
const { nextLineRecord } = lineRecord;
if (nextLineRecord) {
const AOffsetPoint = getStartOffsetPointOfStation(lineRecord);
const BOffsetPoint = getEndOffsetPointOfStation(nextLineRecord);
if (
lineRecord.getOutDirection()!.oppositeTo(nextLineRecord.getInDirection())
) {
//direct to next station
return [AOffsetPoint, BOffsetPoint];
} else {
//has turning
const turningPoint = getTurningPoint(
AOffsetPoint,
BOffsetPoint,
lineRecord.getOutDirection()!,
nextLineRecord.getInDirection()!
);
return [AOffsetPoint, turningPoint, BOffsetPoint];
}
} else throw new Error("No NextLineRecord!");
return [];
};
const getPointsInStation = (lineRecord: LineRecord) => {
const { nextLineRecord, lastLineRecord } = lineRecord;
if (nextLineRecord && lastLineRecord) {
const AOffsetPoint = getInOffsetPointOfStation(lineRecord);
const BOffsetPoint = getOutOffsetPointOfStation(lineRecord);
const inDirection = lineRecord.getInDirection();
const outDirection = lineRecord.getOutDirection();
// same point in and out
if (
AOffsetPoint.sameTo(BOffsetPoint) ||
inDirection?.oppositeTo(outDirection)
)
return [];
else {
// same in same out, need two points
if (inDirection?.sameTo(outDirection!)) {
return []; // no need points cause last and next will add end and start points
// return [AOffsetPoint, BOffsetPoint];
}
const crossPointInStation = getTurningPoint(
AOffsetPoint,
BOffsetPoint,
inDirection!,
outDirection!
);
// if(crossPointInStation.x === Infinity) debugger
// not same point, but cross in inPoint or outPoint
if (
crossPointInStation.sameTo(AOffsetPoint) ||
crossPointInStation.sameTo(BOffsetPoint)
)
return [];
return [crossPointInStation];
}
} else return [];
};
const isPointInStationInNextLine = ([A]: Point[], [B, C]: Point[]) => {
if (A) {
const BC = new Vector(B, C);
return BC.passesThroughPoint(A);
}
return false;
};
// keypoints record all points to draw line
const getAllKeyPoints = (line: Line) => {
let lineRecord = line.departureRecord;
let keyPoints: Point[] = [];
if (!lineRecord) {
console.error("No DepartureStation!");
return [];
}
while (lineRecord.nextLineRecord) {
const pointsInStation = getPointsInStation(lineRecord);
// if(pointsInStation[0]&&(pointsInStation[0].x === 500 && pointsInStation[0].y === 600)) debugger;
keyPoints = keyPoints.concat(pointsInStation);
const pointsBetweenStations = getPointsBetweenStations(lineRecord);
// cross point in next line, delete next line start point
if (isPointInStationInNextLine(pointsInStation, pointsBetweenStations))
pointsBetweenStations.shift();
// next line start point is the same with this line's end,
// delete next line start point
if (
keyPoints.length &&
pointsBetweenStations[0].sameTo(keyPoints[keyPoints.length - 1])
)
pointsBetweenStations.shift();
keyPoints = keyPoints.concat(pointsBetweenStations);
lineRecord = lineRecord.nextLineRecord;
if (lineRecord === line.departureRecord) break;
}
// to-do why duplicate
return keyPoints.filter((p,index)=>index>0?!(p.x===keyPoints[index-1].x&&p.y===keyPoints[index-1].y):true);
};
const deleteDuplicatedPoints = (keyPoints: Point[]) => {
if (keyPoints.length <= 2) return keyPoints; // no need delete duplicate point
const start = keyPoints[0],
end = keyPoints[keyPoints.length - 1];
const QKeyPoints: Point[] = [start];
for (let i = 1; i < keyPoints.length - 1; i++) {
const A = keyPoints[i - 1];
const B = keyPoints[i];
const C = keyPoints[i + 1];
const AC = new Vector(A, C);
const AB = new Vector(A, B);
const BC = new Vector(B, C);
if (!AC.passesThroughPointRound(B) //|| AB.direct !== BC.direct
)
QKeyPoints.push(B);
}
QKeyPoints.push(end);
return QKeyPoints;
};
const addLPointsAroundQPoints = (qPoints: Point[]) => {
if (qPoints.length <= 2) return qPoints; // no need add L points
const start = qPoints[0],
end = qPoints[qPoints.length - 1];
const LQLKeyPoints: Point[] = [start];
for (let i = 1; i < qPoints.length - 1; i++) {
const A = qPoints[i - 1];
const B = qPoints[i];
const C = qPoints[i + 1];
const BA = new Vector(B, A);
const BC = new Vector(B, C);
const BAOffsetPoint = BA.normalize(line_radius).end.offset(B);
const BCOffsetPoint = BC.normalize(line_radius).end.offset(B);
BAOffsetPoint.q_start = true;
B.q = true;
BCOffsetPoint.q_end = true;
LQLKeyPoints.push(BAOffsetPoint);
LQLKeyPoints.push(B);
LQLKeyPoints.push(BCOffsetPoint);
}
LQLKeyPoints.push(end);
return LQLKeyPoints;
};
const getRoundedPoints = (keyPoints: Point[]) => {
const QKeyPoints = deleteDuplicatedPoints(keyPoints);
const LQLPoints = addLPointsAroundQPoints(QKeyPoints);
return LQLPoints;
};
const generateLineCommand = (LQLPoints: Point[]) => {
if (LQLPoints.length <= 1) {
console.error(
"no enough point to draw line. now we have " + LQLPoints.length
);
return "";
}
const start = LQLPoints[0],
end = LQLPoints[LQLPoints.length - 1];
const MCommand = ``;
const EndCommand = ` L ${end.x} ${end.y} `;
let path = "";
for (let i = 1; i < LQLPoints.length - 1; i++) {
const P = LQLPoints[i];
switch (true) {
case P.q_start:
path += ` L ${P.x} ${P.y} `;
break;
case P.q:
path += ` Q ${P.x} ${P.y} `;
break;
case P.q_end:
path += ` , ${P.x} ${P.y} `;
break;
default:
throw new Error("no Point flag for command!");
}
}
return MCommand + path + EndCommand;
};
export { getAllKeyPoints, getRoundedPoints, generateLineCommand };
================================================
FILE: src/Render/Card/Cards.scss
================================================
.cards {
position: fixed;
bottom: 0;
// left: -65px;
// zoom: 0.85
width: fit-content;
max-width: 100vw;
height: 500px;
overflow-x: scroll;
overflow-y: visible;
white-space: nowrap;
padding-top: 200px;
// padding-right: 100px;
box-sizing: border-box;
pointer-events: none;
&::-webkit-scrollbar {
display: none;
// width: 0.01px;
}
.card-container {
display: inline-block;
pointer-events: auto;
& > div {
margin-left: 50px;
position: relative;
box-shadow: 0 4px 159px 7px rgba(0, 0, 0, 0.25);
// filter: drop-shadow(0 0 2px #999);
}
&:last-child {
margin-right: 50px;
}
}
// @media (max-width: 555px) {
// height: 285px;
// &>div{
// margin-left: 50px;
// // box-shadow: none;
// // border: 1px solid rgba(0, 0, 0, 0.266);
// &:last-child{
// margin-right: 50px;
// }
// zoom: 0.7;
// }
// }
}
================================================
FILE: src/Render/Card/Cards.tsx
================================================
import React, {
CSSProperties,
Dispatch,
RefObject,
SetStateAction,
useEffect,
useLayoutEffect,
useRef,
useState,
} from "react";
import { Line } from "../../DataStructure/Line";
import { Station } from "../../DataStructure/Station";
import { DisplayStation } from "../../DataStructure/Display";
import { LineCard } from "./LineCard";
import "./Cards.scss";
import { StationCard } from "./StationCard";
import { CardShowing, InsertInfo, UserDataType } from "../../Data/UserData";
import { browserInfo, mapToArr, onWheelX, onWheelY } from "../../Common/util";
import { showConfirmationInterface } from "../Delete/DeleteConfirmation";
import { FunctionMode } from "../../DataStructure/Mode";
export function Cards({
data,
setData,
showConfirmation,
menuRef,
functionMode,
setFunctionMode,
insertInfo,
setInsertInfo,
cardShowing,
setCardShowing,
}: {
data: UserDataType;
setData: Dispatch>;
showConfirmation?: showConfirmationInterface;
menuRef: RefObject;
functionMode: FunctionMode;
setFunctionMode: React.Dispatch>;
insertInfo?: InsertInfo;
setInsertInfo: React.Dispatch>;
cardShowing: CardShowing;
setCardShowing: Dispatch>;
}) {
const { lines, stations } = data;
const { engine } = browserInfo;
const [pointerEvents, setPointerEvents] = useState<"auto" | "none">("none");
const { lineIds, stationIds, stationFirst } = cardShowing;
const linesComp = lineIds?.map((lineId) => {
const line = lines.get(lineId);
if (line)
return (
);
});
const stationComp = stationIds?.map((stationId) => {
const station = stations.get(stationId);
if (station)
return (
);
});
const [style, setStyle] = useState();
const handleStyle = () => {
const style: CSSProperties =
engine.name === "WebKit"
? { pointerEvents: "auto", height: 370, paddingTop: 70 }
: {};
if (
((stationIds?.length || 0) + (lineIds?.length || 0)) * 555 <
window.innerWidth
) {
style.paddingRight = 100;
style.pointerEvents = "none";
}
setStyle(style);
};
useEffect(() => {
handleStyle();
window.addEventListener("resize", handleStyle);
return () => window.removeEventListener("resize", handleStyle);
}, [cardShowing]);
return (
{
const { target, currentTarget } = e;
if (target === currentTarget) setPointerEvents("none");
else setPointerEvents("auto");
}}
>
{stationFirst ? stationComp : linesComp}
{stationFirst ? linesComp : stationComp}
);
}
================================================
FILE: src/Render/Card/LineCard.scss
================================================
.line-card {
width: 455px;
height: 250px;
border-radius: 23px;
display: inline-block;
// margin-left: 50px;
// margin-bottom: 50px;
// zoom: 0.8;
--margin-left: 33px;
background-color: rgba(255, 255, 255, 0.863);
backdrop-filter: blur(15px);
position: relative;
transition: 0.3s ease-in-out width;
overflow-x: hidden;
.tools {
position: absolute;
right: 0;
.tool-item {
vertical-align: top;
height: 34px;
width: 34px;
display: inline-flex;
justify-content: center;
align-items: center;
margin: 14.5px 11.3px 0 0;
background-color: #d9d9d9;
border-radius: 7.25px;
.expand-icon {
height: 15px;
width: 15px;
}
.edit-icon {
height: 17.74px;
width: 17.74px;
}
}
}
.stations-count {
font-size: 18px;
font-weight: 500;
cursor: pointer;
margin: 23px 0 0 var(--margin-left);
width: fit-content;
}
.line-name {
& > * {
font-size: 24px;
font-weight: 500;
}
margin: 2px 0 0 var(--margin-left);
}
.from-to {
font-size: 18px;
font-weight: 500;
margin: 5px 0 0 var(--margin-left);
color: #ababab;
}
.edit-detail {
.name-detail {
margin-top: 22.58px;
white-space: nowrap;
width: 254.2px;
overflow-y: visible;
overflow-x: scroll;
height: 100px;
&::-webkit-scrollbar {
display: none;
// width: 0.01px;
}
.name-item {
display: inline-block;
margin-right: 24.2px;
vertical-align: top;
&:first-child {
margin-left: 32.25px;
}
.title {
font-size: 11.25px;
font-weight: 500;
color: #ea0b2a;
}
input {
margin-top: 21.77px;
padding: 0;
}
.sign-input {
// width: 35.5px;
* {
min-width: 35.5px;
height: 35.5px;
border: none;
border-radius: 8.87px;
background-color: #ea0b2a;
font-size: 23.25px;
color: white;
text-align: center;
}
}
.line-name-input,
.order-input {
* {
font-size: 36px;
border: none;
font-weight: 500;
// width: 100px;
margin-top: 14px;
}
}
.order-input {
// width: 30px;
// white-space: nowrap;
vertical-align: top;
}
}
}
.color-detail {
.color-detail-choosing {
box-sizing: border-box;
padding: 21px 32.25px;
width: 255px;
display: grid;
grid-template-columns: repeat(5, 20%);
grid-template-rows: repeat(2, 50%);
grid-row-gap: 9px;
grid-column-gap: 6px;
.color-preview {
cursor: pointer;
width: 19.5px;
height: 19.5px;
border-radius: 50%;
border: 2px solid;
display: flex;
justify-content: center;
align-items: center;
.color-preview-inner {
width: 16.9px;
height: 16.9px;
border-radius: 50%;
}
}
}
.custom-color {
margin: 0 32.25px;
border-top: 1px dashed black;
width: 197px;
box-sizing: border-box;
padding-top: 15px;
// padding-left: 10px;
.selected-color-preview {
.color-input {
// width: 19.5px;
// height: 19.5px;
// border-radius: 50%;
display: inline-block;
}
.color-value {
display: inline-block;
margin-left: 15px;
font-size: 15.25px;
font-weight: 500;
width: 100px;
text-decoration: underline;
vertical-align: top;
margin-top: 1px;
border: none;
}
}
}
}
.operation-detail {
.operation-item {
&:first-child {
margin-top: 15px;
}
&:last-child {
margin-bottom: 15px;
}
margin-left: 32.25px;
margin-top: 8px;
margin-bottom: 8px;
color: #2196f3;
font-size: 18px;
font-weight: 500;
cursor: pointer;
&.delete {
color: #ea0b2a;
}
}
}
}
.station-bar {
margin: 30px 0 0 0;
white-space: nowrap;
width: 455px;
overflow-x: scroll;
overflow-y: hidden;
transition: 0.3s ease-in-out width;
&::-webkit-scrollbar {
display: none;
// width: 0.01px;
}
.add-first {
width: var(--margin-left);
display: inline-block;
}
.station-block {
display: inline-block;
width: 161px;
height: 78.22px;
border-left: 1px solid #cccccc;
position: relative;
vertical-align: top;
.track {
display: flex;
background-color: #f0f0f0;
width: 161px;
height: 48.3px;
.sleeper {
border-left: 1px solid #cccccc;
width: 53.6px;
&:first-child {
border: none;
}
}
}
.bend-first {
position: absolute;
top: 7.215px;
left: 9.5px;
height: 33.87px;
width: 133.8px;
background-color: #d9d9d9;
border-radius: 4px;
display: flex;
align-items: center;
cursor: pointer;
transition: ease-in-out 0.3s background-color;
&.current-inserting {
background-color: #c3c3c3;
}
.bend-icon {
svg {
width: 16px;
height: 16px;
transform-origin: center;
}
width: 16px;
height: 16px;
display: inline-block;
margin: -1px 13px 0 18.5px;
&.bend {
svg {
transform: rotate(-45deg);
}
}
}
.bend-des {
display: inline-block;
font-size: 14.25px;
font-weight: 500;
color: rgb(0, 0, 0, 0.5);
}
}
.station-name {
font-size: 18px;
font-weight: 500;
margin: 8px;
color: #ea0b2a;
cursor: pointer;
}
}
}
.edit-panel {
&.edit {
right: 0;
}
width: 200.8px;
background-color: #f2f2f2;
position: absolute;
right: -200.8px;
top: 0;
height: 250px;
transition: 0.3s ease-in-out right;
.edit-tools {
width: 200.8px;
height: 250px;
overflow-y: scroll;
overflow-x: hidden;
&::-webkit-scrollbar {
display: none;
// width: 0.01px;
}
.edit-tool {
&:first-child {
margin: 21.7px 21.7px 2px 21.7px;
}
&:last-child {
margin: 2px 21.7px 21.7px 21.7px;
}
cursor: pointer;
margin: 2px 21.7px 2px 21.7px;
width: 158px;
height: 75px;
border-radius: 11.3px;
font-weight: 500;
display: flow-root;
&.selected {
background-color: #d9d9d9;
}
.title {
font-size: 18px;
margin: 12.1px 0 0 20.2px;
text-transform: capitalize;
}
.value {
font-size: 11.25px;
margin: 4.83px 0 0 20.2px;
}
&.color > .value {
color: #ea0b2a;
}
}
}
.done {
background-color: #2196f3;
border-radius: 25px;
width: 74px;
height: 28.2px;
position: absolute;
bottom: 23.4px;
right: 23.4px;
display: flex;
align-items: center;
.done-icon {
height: 17.74px;
width: 17.74px;
margin: 0 6.5px 0 11.3px;
path {
fill: #ffffff;
fill-opacity: 1;
}
}
.done-des {
font-size: 12px;
font-weight: 500;
color: white;
}
}
}
}
================================================
FILE: src/Render/Card/LineCard.tsx
================================================
import React, {
Dispatch,
RefObject,
SetStateAction,
useEffect,
useState,
useTransition,
} from "react";
import { Line } from "../../DataStructure/Line";
import { Station } from "../../DataStructure/Station";
import { DisplayStation } from "../../DataStructure/Display";
import "./LineCard.scss";
import ArrowIcon from "../../Resource/Icon/arrow";
import PlusIcon from "../../Resource/Icon/plus";
import ExpandIcon from "../../Resource/Icon/expand";
import ShrinkIcon from "../../Resource/Icon/shrink";
import EditIcon from "../../Resource/Icon/edit";
import classNames from "classnames";
import {
CardShowing,
InsertInfo,
LineProps,
StationProps,
UserDataType,
dataProcessor,
} from "../../Data/UserData";
import {
browserInfo,
mapToArr,
onWheelX,
onWheelY,
scrollOptimize,
} from "../../Common/util";
import { AutoGrowthInput } from "../../Common/AutoGrowthInput";
import { colorSH, colorSHMap } from "../../Common/color";
import { showConfirmationInterface } from "../Delete/DeleteConfirmation";
import { FunctionMode } from "../../DataStructure/Mode";
import { Point } from "../../DataStructure/Point";
import { useTranslation } from "react-i18next";
import i18n from "../../i18n/config";
export function LineCard({
line,
setData,
data,
showConfirmation,
functionMode,
setFunctionMode,
insertInfo,
setInsertInfo,
menuRef,
cardShowing,
setCardShowing,
}: {
line: LineProps;
setData: Dispatch>;
data: UserDataType;
showConfirmation?: showConfirmationInterface;
functionMode: FunctionMode;
setFunctionMode: React.Dispatch>;
insertInfo?: InsertInfo;
setInsertInfo: React.Dispatch>;
menuRef: RefObject;
cardShowing: CardShowing;
setCardShowing: Dispatch>;
}) {
const {
lineId,
lineName,
stationIds,
sign,
order,
color: colorSelected,
subLine = false,
} = line;
const {
getStationById,
getStationsInThisLine,
setLineName,
setSign,
setOrder,
setColor,
getBendFirst,
setBendFirst,
deleteLine,
setSubLine,
} = dataProcessor(lineId, setData, data);
const colorName = i18n.language === "zh" && colorSHMap.get(colorSelected)?.color_name || colorSelected;
const firstStation = getStationById(stationIds[0]);
const lastStation = getStationById(stationIds[stationIds.length - 1]);
// if (!firstStation) {
// firstStation = new StationProps();
// lastStation = firstStation;
// }
const [expand, setExpand] = useState(false);
const [edit, setEdit] = useState(false);
const [expandWidth, setExpandWidth] = useState(455);
const [tab, setTab] = useState("name");
const addingStation =
functionMode === FunctionMode.selectingStation ||
functionMode === FunctionMode.lineEditing ||
!firstStation;
const getExpandWidth = () => {
const { stationIds: stations } = cardShowing;
const expected =
33 + 161 * (addingStation ? stationIds.length + 1 : stationIds.length);
const hasStation = stations && stations.length;
const width =
expected > window.innerWidth
? window.innerWidth - (hasStation ? 505 : 100)
: expected;
// console.log(width);
setExpandWidth(width);
};
useEffect(() => {
getExpandWidth();
window.addEventListener("resize", getExpandWidth);
return () => window.removeEventListener("resize", getExpandWidth);
}, [stationIds, addingStation, cardShowing]);
useEffect(() => {
getExpandWidth();
}, [expand]);
const { t } = useTranslation();
const editTools = (tab: string) => {
switch (tab) {
case "name": {
return (
{t('menu.symbol')}
setSign(e.currentTarget.value)}
style={{ backgroundColor: colorSelected }}
/>
{t('line.name')}
setLineName(e.currentTarget.value)}
className="line-name-input"
value={lineName}
/>
{t('line.order')}
setOrder(parseInt(e.currentTarget.value))}
/>
);
}
case "color": {
return (
{new Array(10).fill(1).map((x, index) => {
const { color } = colorSH[index];
const choosed = color === colorSelected;
return (
);
})}
);
}
case "operation": {
return (
{
setSubLine(!subLine);
}}
>
{subLine?t('line.notSubLineAnymore'):t('line.asSubline')}...
{
showConfirmation!({ line }, deleteLine);
}}
>
{t('line.deleteLine')}
);
}
}
};
const { engine } = browserInfo;
const stationsInThisLine = getStationsInThisLine();
if (addingStation) {
stationsInThisLine.unshift(new StationProps());
}
return (
{expand || edit ? (
<>>
) : (
{
setEdit(true);
// setTab("name");
}}
>
)}
{edit ? (
<>>
) : (
setExpand(!expand)}>
{expand ? : }
)}
{
setCardShowing({ lineIds: [lineId], stationIds });
}}
>
{stationIds.length}{t('line.stations')}
setLineName(e.currentTarget.value)}
className="line-name"
value={lineName}
disabled
/>
{edit ? (
{editTools(tab)}
) : (
<>
{firstStation
? t('line.fromTo', {from:firstStation!.stationName,to: lastStation!.stationName})
: t('line.notInUse')}
{stationsInThisLine.map((station, index) => {
const { stationName, stationId } = station!;
const bendFirst = getBendFirst(index);
const last = index === stationsInThisLine.length - 1;
let currentInserting = false;
if (insertInfo) {
const { insertIndex, line: insertLine } = insertInfo;
if (insertIndex === index && insertLine === line) {
currentInserting = true;
}
}
return (
{
if (addingStation) {
if (!firstStation) {
if (menuRef?.current?.showTools) {
menuRef.current.showTools(
e,
FunctionMode.selectingStation
);
}
}
setInsertInfo({ insertIndex: index, line });
setFunctionMode(FunctionMode.selectingStation);
} else if (last) {
setInsertInfo({ insertIndex: index + 1, line });
if (menuRef?.current?.showTools) {
menuRef.current.showTools(
e,
FunctionMode.selectingStation
);
}
} else {
setBendFirst(index, !bendFirst);
}
}}
>
{addingStation || last ?
:
}
{addingStation || last
? currentInserting
? t('line.insertHere')
: t('line.insertStation')
: bendFirst
? t('line.bendFirst')
: t('line.straight')}
{
setCardShowing({
lineIds: [lineId],
stationIds: [stationId],
});
}}
>
{stationName}
);
})}
>
)}
{
{
scrollOptimize(e);
setTab("name");
}}
>
{t('line.name')}
{lineName}
{
scrollOptimize(e);
setTab("color");
}}
>
{t('line.color')}
{colorName}
{
scrollOptimize(e);
setTab("operation");
}}
>
{t('line.operation')}
{t('line.delete')}
setEdit(false)}>
{t('line.done')}
}
);
}
================================================
FILE: src/Render/Card/StationCard.scss
================================================
.station-card {
width: 455px;
height: 250px;
border-radius: 23px;
display: inline-block;
// margin-left: 50px;
// margin-bottom: 50px;
// zoom: 0.8;
--margin-left: 33px;
background-color: rgba(255, 255, 255, 0.863);
backdrop-filter: blur(15px);
position: relative;
transition: 0.3s ease-in-out width;
overflow: hidden;
.tools {
position: absolute;
right: 0;
.tool-item {
vertical-align: top;
height: 34px;
width: 34px;
display: inline-flex;
justify-content: center;
align-items: center;
margin: 14.5px 11.3px 0 0;
background-color: #d9d9d9;
border-radius: 7.25px;
.expand-icon {
height: 15px;
width: 15px;
}
.edit-icon {
height: 17.74px;
width: 17.74px;
}
}
}
.line-count {
font-size: 18px;
font-weight: 500;
margin: 23px 0 0 var(--margin-left);
cursor: pointer;
}
.station-name {
& > * {
font-size: 25px;
font-weight: 500;
}
margin: 2px 0 0 var(--margin-left);
}
.from-to {
font-size: 18px;
font-weight: 500;
margin: 5px 0 0 var(--margin-left);
color: #ababab;
}
.edit-detail {
height: 164px;
overflow-y: scroll;
&::-webkit-scrollbar {
display: none;
// width: 0.01px;
}
.name-detail {
margin-top: 22.58px;
white-space: nowrap;
width: fit-content;
.name-item {
display: inline-block;
margin-right: 24.2px;
vertical-align: top;
position: relative;
&:first-child {
margin-left: 32.25px;
}
.title {
font-size: 11.25px;
font-weight: 500;
color: #ea0b2a;
}
input {
margin-top: 21.77px;
padding: 0;
}
.auto-growth-span {
font-size: 36px;
border: none;
font-weight: 500;
width: 100px;
margin-top: 14px;
opacity: 0;
}
.auto-growth-input {
font-size: 36px;
border: none;
font-weight: 500;
width: calc(100% + 18px);
margin-top: 14px;
position: absolute;
left: 0;
}
}
}
.color-detail {
.color-detail-choosing {
box-sizing: border-box;
padding: 10px 32.25px;
width: 255px;
display: grid;
border: white 1px solid;
grid-template-columns: repeat(4, 25%);
grid-template-rows: repeat(3, 33.33%);
// grid-row-gap: 9px;
// grid-column-gap: 6px;
.shape-container {
height: 46.13px;
display: flex;
justify-content: center;
align-items: center;
border-left: 1px black dotted;
border-bottom: 1px black solid;
transform: translate(-1px, 1px);
cursor: pointer;
&.left {
border-left: none;
}
&.bottom {
border-bottom: none;
}
.shape-preview {
width: 19.5px;
height: 19.5px;
// border-radius: 50%;
// border: 2px solid;
display: flex;
justify-content: center;
align-items: center;
&.shape-selected {
svg {
* {
stroke: #ea0b2a;
}
}
}
&.square {
zoom: 0.9;
}
&.triangle {
zoom: 1.05;
}
&.start {
zoom: 1.15;
}
&.hexagon {
zoom: 0.9;
}
&.pentagon {
zoom: 1.1;
}
&.diamond{
zoom: 1.15;
}
&.leaf{
zoom: 0.9;
}
}
}
}
}
.operation-detail {
.operation-item {
width: fit-content;
&:first-child {
margin-top: 15px;
}
&:last-child {
margin-bottom: 15px;
}
margin-left: 32.25px;
margin-top: 8px;
margin-bottom: 8px;
color: #2196f3;
font-size: 18px;
font-weight: 500;
cursor: pointer;
&.delete {
color: #ea0b2a;
}
}
}
.tag-detail {
display: grid;
grid-template-columns: repeat(3, 33.33%);
grid-template-rows: repeat(3, 33.33%);
width: 200px;
height: 126px;
margin: 18px 30.25px;
align-content: center;
justify-content: center;
align-items: center;
justify-items: center;
.tag-item {
text-align: center;
font-size: 11.25px;
font-weight: 500;
cursor: pointer;
background-color: rgb(0, 0, 0, 13%);
height: 30px;
width: 55.65px;
border-radius: 3px;
border: 1px dotted;
display: flex;
justify-content: center;
align-items: center;
&.selected{
background-color: rgb(0, 0, 0, 23%);
border: 1px solid;
}
&.center{
background-color: transparent;
border: none;
}
}
}
}
.station-bar {
margin: 30px 0 0 0;
white-space: nowrap;
width: 455px;
overflow-x: scroll;
overflow-y: hidden;
transition: 0.3s ease-in-out width;
&::-webkit-scrollbar {
display: none;
// width: 0.01px;
}
.add-first {
width: var(--margin-left);
display: inline-block;
}
.station-block {
display: inline-block;
width: 161px;
height: 78.22px;
border-left: 1px solid #cccccc;
position: relative;
.track {
display: flex;
background-color: #f0f0f0;
width: 161px;
height: 48.3px;
.sleeper {
border-left: 1px solid #cccccc;
width: 53.6px;
&:first-child {
border: none;
}
}
}
.bend-first {
position: absolute;
top: 7.215px;
left: 9.5px;
height: 33.87px;
width: 133.8px;
background-color: #d9d9d9;
border-radius: 4px;
display: flex;
align-items: center;
.bend-icon {
width: 16px;
height: 16px;
display: inline-block;
margin: 0 13px 0 18.5px;
&.bend {
transform: rotate(-45deg);
}
}
.bend-des {
display: inline-block;
font-size: 14.25px;
font-weight: 500;
color: rgb(0, 0, 0, 0.5);
}
}
.station-name {
font-size: 18px;
font-weight: 500;
margin: 8px;
color: #ea0b2a;
}
}
}
.edit-panel {
&.edit {
right: 0;
}
width: 200.8px;
background-color: #f2f2f2;
position: absolute;
right: -200.8px;
top: 0;
height: 250px;
transition: 0.3s ease-in-out right;
.edit-tools {
width: 200.8px;
height: 250px;
overflow-y: scroll;
overflow-x: hidden;
&::-webkit-scrollbar {
display: none;
// width: 0.01px;
}
.edit-tool {
&:first-child {
margin: 21.7px 21.7px 2px 21.7px;
}
&:last-child {
margin: 2px 21.7px 21.7px 21.7px;
}
cursor: pointer;
margin: 2px 21.7px 2px 21.7px;
width: 158px;
height: 75px;
border-radius: 11.3px;
font-weight: 500;
display: flow-root;
&.selected {
background-color: #d9d9d9;
}
.title {
font-size: 18px;
margin: 12.1px 0 0 20.2px;
text-transform: capitalize;
}
.value {
font-size: 11.25px;
margin: 4.83px 0 0 20.2px;
.position {
color: #5e5e5e;
margin: 0 5px;
text-decoration: underline;
}
}
}
}
.done {
background-color: #2196f3;
border-radius: 25px;
width: 74px;
height: 28.2px;
position: absolute;
bottom: 23.4px;
right: 23.4px;
display: flex;
align-items: center;
.done-icon {
height: 17.74px;
width: 17.74px;
margin: 0 6.5px 0 11.3px;
path {
fill: #ffffff;
fill-opacity: 1;
}
}
.done-des {
font-size: 12px;
font-weight: 500;
color: white;
}
}
}
}
================================================
FILE: src/Render/Card/StationCard.tsx
================================================
import React, {
Dispatch,
RefObject,
SetStateAction,
useEffect,
useState,
} from "react";
import "./StationCard.scss";
import classNames from "classnames";
import { Point } from "../../DataStructure/Point";
import {
CardShowing,
InsertInfo,
StationProps,
UserDataType,
dataProcessor,
} from "../../Data/UserData";
import shapes from "../../Resource/Shape/shape";
import { Shape } from "../../Data/Shape";
import { AutoGrowthInput } from "../../Common/AutoGrowthInput";
import {
browserInfo,
onWheelX,
onWheelY,
scrollOptimize,
} from "../../Common/util";
import { showConfirmationInterface } from "../Delete/DeleteConfirmation";
import { FunctionMode } from "../../DataStructure/Mode";
import { useTranslation } from "react-i18next";
export function StationCard({
station,
setData,
data,
showConfirmation,
menuRef,
functionMode,
setFunctionMode,
insertInfo,
setInsertInfo,
cardShowing,
setCardShowing,
}: {
station: StationProps;
setData: Dispatch>;
data: UserDataType;
showConfirmation?: showConfirmationInterface;
menuRef: RefObject;
functionMode: FunctionMode;
setFunctionMode: React.Dispatch>;
insertInfo?: InsertInfo;
setInsertInfo: React.Dispatch>;
cardShowing: CardShowing;
setCardShowing: Dispatch>;
}) {
const {
stationName,
lineIds,
position,
shape: shapeSelected,
stationId,
tagDirection=8,
} = station;
const {
setStationName,
setStationPosition,
setStationShape,
getLineById,
deleteStation,
removeStationFromLine,
addNewLine,
setStationTagDirection,
} = dataProcessor(stationId, setData, data);
const [x, y] = position;
const setX = (x: number) => setStationPosition(x, y);
const setY = (y: number) => setStationPosition(x, y);
const lineCount = lineIds.length;
const [tab, setTab] = useState("name");
const { engine } = browserInfo;
const { t } = useTranslation();
const tags = [
t('station.up'),
t('station.upRight'),
t('station.right'),
t('station.rightDown'),
t('station.down'),
t('station.downLeft'),
t('station.left'),
t('station.leftUp'),
t('station.auto')
];
const editTools = (tab: string) => {
switch (tab) {
case "name": {
return (
{t('station.x')}
setX(Number(x.currentTarget.value))}
type="number"
/>
{t('station.y')}
setY(Number(y.currentTarget.value))}
type="number"
/>
);
}
case "color": {
const column = 4;
const row = 3;
const grid = new Array(column * row).fill(0);
Object.keys(shapes).forEach((shape, index) => (grid[index] = shape));
return (
{grid.map((shape, index) => {
const left = index % column === 0;
const bottom = Math.floor(index / column) === row - 1;
// console.log({ shape });
return (
{
if (shape) setStationShape(shape);
}}
>
{
//@ts-ignore
shapes[shape]
}
);
})}
);
}
case "operation": {
return (
{
const newLine = addNewLine();
setInsertInfo({ insertIndex: 1, line: newLine! });
// setFunctionMode(FunctionMode.selectingStation);
if (menuRef?.current?.showTools) {
menuRef.current.showTools(e, FunctionMode.selectingStation);
}
}}
>
{t('station.startHere')}
{lineIds.map((lineId) => {
const line = getLineById(lineId);
const stationIndexes: number[] = [];
line?.stationIds.forEach((x, index) => {
if (x === stationId) stationIndexes.push(index);
});
const { lineName } = line!;
const removeStations = stationIndexes.map((stationIndex) => (
{
showConfirmation!({ line, station, stationIndex }, () => {
removeStationFromLine(lineId, stationIndex);
});
}}
>
{t('station.remove',{lineName,stationIndex: stationIndex! + 1})}...
));
return removeStations;
})}
{
showConfirmation!({ station }, () => {
deleteStation();
if (menuRef?.current?.backToTitle) {
menuRef.current.backToTitle();
}
});
}}
>
{t('station.delete')}
);
}
case "tag": {
const directMapIndex = [7, 0, 1, 6, 8, 2, 5, 4, 3];
return (
{directMapIndex.map((direct, index) => {
if (index === 4)
return (
{
setStationTagDirection(8);
}}
className="tag-item center"
>
{tags[tagDirection]}
);
else
return (
{
setStationTagDirection(direct);
}}
className={classNames({
"tag-item": 1,
selected: tagDirection === direct,
})}
>
);
})}
);
}
}
};
return (
{
setCardShowing({
stationIds: [stationId],
lineIds,
stationFirst: true,
});
}}
>
{lineCount}{t('station.lines')}
setStationName(e.currentTarget.value)}
className="station-name"
value={stationName}
/>
{editTools(tab)}
{
{
scrollOptimize(e);
setTab("name");
}}
>
{t('station.poisition')}
X{x} Y
{y}
{
scrollOptimize(e);
setTab("color");
}}
>
{t('station.shape')}
{
t(`shape.${shapeSelected}`)
}
{
scrollOptimize(e);
setTab("operation");
}}
>
{t('station.operation')}
{t('station.operateDes')}
{
scrollOptimize(e);
setTab("tag");
}}
>
{t('station.stationName')}
{tags[tagDirection]}
}
);
}
================================================
FILE: src/Render/Component/LineRender.tsx
================================================
import React, {
CSSProperties,
Dispatch,
SetStateAction,
memo,
useEffect,
useState,
} from "react";
import { Line } from "../../DataStructure/Line";
import { Point } from "../../DataStructure/Point";
import {
generateLineCommand,
getAllKeyPoints,
getRoundedPoints,
} from "../../Line/LinePoints";
import { clearHandle, getHandleCommand } from "../../Line/Handle";
import { gauge } from "../../Common/const";
import {
CardShowing,
DrawProps,
DrawerSize,
UserDataType,
} from "../../Data/UserData";
function LineRender({
line,
cardShowing,
setCardShowing,
command,
data,
setData,
drawing,
drawerX,
drawerY,
}: {
line: Line;
cardShowing: CardShowing;
setCardShowing: Dispatch>;
command: string;
data: UserDataType;
setData: Dispatch>;
} & DrawProps &
DrawerSize) {
const { stations } = data;
const { displayLine, departureRecord } = line;
const { color, lineId, subLine, lineName } = displayLine!;
const { lineIds, stationIds } = cardShowing;
const showing = lineIds?.length || stationIds?.length;
const emphasis =
lineIds?.includes(lineId) ||
(stationIds &&
stationIds.length === 1 &&
stationIds[0] &&
stations.get(stationIds[0]) &&
stations.get(stationIds[0])?.lineIds?.includes(lineId));
const [moved, setMoved] = useState(false);
const onClick = () => {
if (!moved) {
setCardShowing({ lineIds: [lineId] });
}
};
return (
<>
{/* */}
setMoved(false)}
onTouchStart={() => setMoved(false)}
onTouchMove={() => (!moved) && setMoved(true)}
onMouseMove={() => (!moved) && setMoved(true)}
onMouseUp={onClick}
onTouchEnd={onClick}
/>
{/* */}
{/* {renderPoints(allKeyPoints)} */}
>
);
}
export default memo(LineRender);
================================================
FILE: src/Render/Delete/DeleteConfirmation.scss
================================================
.delete-confirmation-container {
position: fixed;
top: 0;
left: 0;
z-index: -1000;
height: 100vh;
width: 100vw;
background-color: rgba(255, 255, 255, 0.2);
display: flex;
justify-content: center;
align-items: center;
opacity: 0;
transition:
0.3s ease-in-out opacity,
0.3s ease-in-out z-index;
&.before-animated,
&.before-disappear {
backdrop-filter: blur(1px);
}
&.animated {
opacity: 1;
backdrop-filter: blur(10px);
z-index: 1000;
}
.delete-confirmation {
text-align: center;
.title {
font-weight: 500;
font-size: 36px;
padding-left: 13px;
}
.sub-title {
font-weight: 500;
font-size: 18px;
}
.preview {
margin-top: 74.2px;
display: inline-block;
// border-top: 1px solid black;
width: fit-content;
// padding: 0 40px;
text-align: center;
position: relative;
// display: flex;
// align-items: center;
// justify-content: center;
// &>div{
// transform: translateY(-50%);
// }
.preview-content {
display: flex;
justify-content: center;
align-items: center;
.icon {
display: inline-block;
margin-left: 40px;
.line {
// width: 35.5px;
margin-right: 35.5px;
.sign-input {
// width: 35.5px;
* {
min-width: 35.5px;
height: 35.5px;
border: none;
border-radius: 8.87px;
background-color: #ea0b2a;
font-size: 23.25px;
color: white;
text-align: center;
}
}
}
.station {
display: flex;
justify-content: center;
align-items: center;
margin-right: 25.8px;
svg {
&.square {
zoom: 0.9;
}
&.triangle {
zoom: 1.05;
}
&.start {
zoom: 1.15;
}
&.hexagon {
zoom: 0.9;
}
&.pentagon {
zoom: 1.1;
}
&.diamond {
zoom: 1.15;
}
&.leaf {
zoom: 0.9;
}
&.ginkgo {
zoom: 0.8;
}
}
}
}
.text {
margin-right: 40px;
display: inline-block;
font-weight: 500;
font-size: 18px;
line-height: 35.5px;
// vertical-align: middle;
}
}
.delete-line {
border-top: 1px solid #ea0b2a;
position: absolute;
top: 50%;
width: 100%;
transition: 0.3s ease-in-out;
// animation-delay: 5s;
transition-delay: 0.3s;
}
}
.delete {
font-weight: 500;
font-size: 18px;
margin-top: 104.8px;
cursor: pointer;
color: #ea0b2a;
}
.back {
margin-top: 16.12px;
font-weight: 500;
font-size: 18px;
cursor: pointer;
}
}
}
================================================
FILE: src/Render/Delete/DeleteConfirmation.tsx
================================================
import React, {
Dispatch,
SetStateAction,
forwardRef,
useEffect,
useImperativeHandle,
useState,
} from "react";
import { LineProps, StationProps } from "../../Data/UserData";
import "./DeleteConfirmation.scss";
import { AutoGrowthInput } from "../../Common/AutoGrowthInput";
import shapes from "../../Resource/Shape/shape";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
import i18n from "../../i18n/config";
export interface showConfirmationInterface {
(
{
line,
station,
stationIndex,
}: { line?: LineProps; station?: StationProps; stationIndex?: number },
callback?: any
): void;
}
enum ShowMode {
none,
beforeAnimate,
animated,
beforeDisappear,
}
export const DeleteConfirmation = forwardRef(function (
{}: any,
ref: React.Ref | undefined
) {
const [show, setShow] = useState(ShowMode.none);
// const [title, setTitle] = useState();
// const [subTitle, setSubTitle] = useState();
const [line, setLine] = useState();
const [station, setStation] = useState();
const [stationIndex, setStationIndex] = useState();
const [callback, setCallback] = useState(() => () => {});
const {t} = useTranslation();
//@ts-ignore
window.setShow = setShow
const showWithAnimate = () => {
setShow(ShowMode.beforeAnimate);
setTimeout(() => setShow(ShowMode.animated));
};
const disappearWithAnimate = () => {
setShow(ShowMode.beforeDisappear);
setTimeout(() => setShow(ShowMode.none),300);
};
const showConfirmation: showConfirmationInterface = (
{ line, station, stationIndex },
callback
) => {
showWithAnimate();
setLine(line);
setStation(station);
setCallback(() => callback);
setStationIndex(stationIndex);
};
useImperativeHandle(
ref,
() => {
return {
showConfirmation,
};
},
[]
);
const remove = line && station;
const deleteText = remove ? t('delete.remove') : t('delete.delete');
const title = t('que-shi-yao-deletetext-ma', {deleteText});
const { stationName, shape } = station || {};
const { lineName, sign, color } = line || {};
const index = stationIndex ? stationIndex + 1 : 1;
const getOrdinalSuffix = (number: number)=> {
const suffixes = ["th", "st", "nd", "rd"];
const v = number % 100;
return number + (suffixes[(v - 20) % 10] || suffixes[v] || suffixes[0]);
}
const subTitle = t('delete.subtile', {lineName, index:i18n.language === 'en-US'? getOrdinalSuffix(index):index});
return (
{title}
{remove ?
{subTitle}
: <>>}
{line && !station ? (
) : (
{
//@ts-ignore
shapes[shape]
}
)}
{stationName || lineName}
{
disappearWithAnimate();
if (typeof callback === "function") callback();
}}
>
{deleteText}
{t('delete.cancel')}
);
});
================================================
FILE: src/Render/ErrorFallback/ErrorFallback.scss
================================================
.error-layer {
text-align: center;
.title {
margin-top: 20vh;
font-size: 36px;
font-weight: 1000;
}
.sub-title {
margin-top: 0.5vh;
font-size: 18px;
font-weight: 500;
}
.error-btn {
span{
margin-left: 55px;
text-align: left;
}
user-select: none;
font-size: 18px;
font-weight: 500;
cursor: pointer;
--gap: min(20px, 2vw);
position: relative;
svg{
position: absolute;
left: calc(var(--gap) + 20px);
}
&.recover-from-cache {
margin-top: 12vh;
background-color: rgb(234, 11, 42, 0.09);
color: #ea0b2a;
svg {
width: 20px;
}
}
&.export-from-cache {
margin-top: 0.5vh;
background-color: #D9D9D9;
svg {
width: 16px;
padding: 0 2px;
}
}
padding: 20px 30px;
width: 315px;
margin: auto;
border-radius: 10px;
display: flex;
justify-content: left;
align-items: center;
}
.export-error{
position: absolute;
bottom: 10vh;
margin: auto;
left: 0;
right: 0;
font-size: 18px;
font-weight: 500;
.export-error-file{
color: #ea0b2a;
cursor: pointer;
}
}
}
================================================
FILE: src/Render/ErrorFallback/ErrorFallback.tsx
================================================
import React from "react";
import "./ErrorFallback.scss";
import { ReactComponent as RecoverIcon } from "../../Resource/Icon/clock.arrow.circlepath.svg";
import { ReactComponent as ExportIcon } from "../../Resource/Icon/export.svg";
import { exportFile, exportJson } from "../../Common/util";
import moment from "moment";
import { useTranslation } from "react-i18next";
export const ErrorFallback = ({
error,
resetErrorBoundary,
}: {
error: Error;
resetErrorBoundary: () => void;
}) => {
const exportFile = (key: string) => {
const current = localStorage.getItem(key);
exportJson(
current!,
`recovery-${key}-${moment().format("YYYY-MM-DD_HH-mm-ss")}.json`
);
};
const { t } = useTranslation();
return (
{t('error.metError')}
{t('error.dontWorry')}
{t('error.recoverFromCache')}
exportFile("last")}
>
{t('error.exportNoErrorFile')}
{t('error.orYouCould')}
exportFile("current")}
>
{t('error.exportError')}
{t('error.sendAuthor')}
);
};
================================================
FILE: src/Render/Header/Component/OpacityControl.scss
================================================
.tool {
position: relative;
display: inline-block;
cursor: pointer;
perspective: 1000px;
perspective-origin: 0 50%;
&:has(.slider-container) {
z-index: 600;
}
.slider-container {
transition:
transform 0s,
transform 0.3s ease-in-out,
all 0.3s ease-in-out;
&.show {
opacity: 1;
pointer-events: unset;
top: 100%;
transform: scale(calc(1 + var(--value) * 0.075)) rotate3d(0, 1, 0, 0deg);
}
opacity: 0;
pointer-events: none;
position: absolute;
top: 50%;
left: 0;
margin-top: 8px;
background: rgba(255, 255, 255, 0.136);
box-shadow: 0 0 100px rgba(#000000, 0.15);
// border: #ea0b2a 1px solid;
z-index: 600;
width: 400%;
max-width: 100vw;
overflow: hidden;
height: 40px;
border-radius: 5px;
backdrop-filter: blur(20px);
transform: scale(calc(1 + var(--value) * 0.075)) rotate3d(0, 1, 0, var(--deg));
transform-origin: 0 0;
.opacity-text {
position: absolute;
top: 0;
bottom: 0;
margin: auto;
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
margin-left: 20px;
font-size: 14px;
opacity: var(--value);
span {
margin-left: 2px;
font-size: 10px;
font-weight: 600;
vertical-align: baseline;
margin-top: 2.5px;
}
}
.slider {
appearance: none;
width: 100%;
height: 100%;
appearance: none;
margin: 0;
background-color: transparent;
background-image: repeating-linear-gradient(
to right,
transparent,
transparent calc(18% - 1px),
#05051a1c 18%
);
&::-webkit-slider-thumb {
box-shadow: -20rem 0 0 20rem rgba(#ea0b2a, 0.2);
cursor: col-resize;
background-color: rgba(#ea0b2a, 0.2);
}
@supports not (-webkit-touch-callout: none) {
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 0;
}
}
@supports (-webkit-min-device-pixel-ratio: 0) {
&::-webkit-slider-thumb {
-webkit-appearance: auto;
appearance: auto;
width: auto;
}
}
&::-webkit-slider-runnable-track {
// pointer-events: none;
}
}
}
}
================================================
FILE: src/Render/Header/Component/OpacityControl.tsx
================================================
import React, {
useState,
useRef,
useEffect,
CSSProperties,
useLayoutEffect,
} from "react";
import "./OpacityControl.scss";
import classNames from "classnames";
import { browserInfo } from "../../../Common/util";
import { useTranslation } from "react-i18next";
interface OpacityControlProps {
opacity: number;
setOpacity: (value: number) => void;
}
const OpacityControl: React.FC = ({
opacity,
setOpacity,
}) => {
const [showSlider, setShowSlider] = useState(false);
const sliderRef = useRef(null);
const inputRef = useRef(null);
const toolRef = useRef(null);
const { t } = useTranslation();
const handleClick = () => {
setShowSlider(!showSlider);
};
const handleSliderChange = (event: React.ChangeEvent) => {
setOpacity(Number(event.target.value));
};
useEffect(() => {
if (showSlider && sliderRef.current && toolRef.current) {
const toolRect =
toolRef.current.getBoundingClientRect();
const viewportWidth = window.innerWidth;
if (toolRect.left + 230 > viewportWidth) {
sliderRef.current.style.left = "unset";
sliderRef.current.style.right = "0";
(sliderRef.current.style as any)['--deg'] = "-25deg";
sliderRef.current.style.transformOrigin = "100% 50%";
toolRef.current.style.perspectiveOrigin = "100% 50%";
} else {
sliderRef.current.style.left = "0";
sliderRef.current.style.transformOrigin = "0 50%";
toolRef.current.style.perspectiveOrigin = "0 50%";
(sliderRef.current.style as any)['--deg'] = "25deg";
}
}
}, [showSlider]);
useLayoutEffect(() => {
if (sliderRef.current) {
sliderRef.current.style.transition = "0.3s ease-in-out";
}
}, [showSlider]);
useEffect(() => {
const handleClickCapture = (event: TouchEvent | MouseEvent) => {
if (event.target !== inputRef.current && event.target !== toolRef.current) {
console.log("focusout");
setShowSlider(false);
}
};
const resize = () => setShowSlider(false);
document.addEventListener("touchstart", handleClickCapture, true);
document.addEventListener("click", handleClickCapture, true);
document.addEventListener("resize",resize);
return () => {
document.removeEventListener("touchstart", handleClickCapture, true);
document.addEventListener("click", handleClickCapture, true);
document.removeEventListener("resize",resize);
};
}, []);
const { engine } = browserInfo;
const safari = engine.name === "WebKit";
return (
{t('opacity')}
{
e.stopPropagation()}
style={{ "--value": opacity } as CSSProperties}
onTransitionEnd={() => {
if (showSlider && sliderRef.current) {
sliderRef.current.style.transition = "none";
}
}}
>
{Math.floor(opacity * 100)}
%
e.stopPropagation()}
style={{ "--value": opacity , "--safari": safari} as CSSProperties}
onBlur={() => setShowSlider(false)}
/>
}
);
};
export default OpacityControl;
================================================
FILE: src/Render/Header/Component/ShapeSelector.scss
================================================
.tool {
position: relative;
display: inline-block;
cursor: pointer;
perspective: 1000px;
perspective-origin: 0 50%;
&:has(.shape-selector-container) {
z-index: 600;
}
.shape-selector-container {
.color-detail {
.color-detail-choosing {
box-sizing: border-box;
padding: 10px 10px;
width: 222.75px;
display: grid;
border: white 1px solid;
grid-template-columns: repeat(4, 25%);
grid-template-rows: repeat(3, 33.33%);
// grid-row-gap: 9px;
// grid-column-gap: 6px;
.shape-container {
height: 46.13px;
display: flex;
justify-content: center;
align-items: center;
border-left: 1px black dotted;
border-bottom: 1px black solid;
transform: translate(-1px, 1px);
cursor: pointer;
&.left {
border-left: none;
}
&.bottom {
border-bottom: none;
}
.shape-preview {
width: 19.5px;
height: 19.5px;
// border-radius: 50%;
// border: 2px solid;
display: flex;
justify-content: center;
align-items: center;
&.shape-selected {
svg {
* {
stroke: #ea0b2a;
}
}
}
&.square {
zoom: 0.9;
}
&.triangle {
zoom: 1.05;
}
&.start {
zoom: 1.15;
}
&.hexagon {
zoom: 0.9;
}
&.pentagon {
zoom: 1.1;
}
&.diamond{
zoom: 1.15;
}
&.leaf{
zoom: 0.9;
}
}
}
}
}
transition:
transform 0s,
transform 0.3s ease-in-out,
all 0.3s ease-in-out;
&.show {
opacity: 1;
pointer-events: unset;
top: 100%;
transform: scale(calc(1 + var(--value) * 0.075)) rotate3d(0, 1, 0, 0deg);
}
opacity: 0;
pointer-events: none;
position: absolute;
top: 50%;
left: 0;
margin-top: 8px;
background: rgba(255, 255, 255, 0.136);
box-shadow: 0 0 100px rgba(#000000, 0.15);
// border: #ea0b2a 1px solid;
z-index: 600;
width: 222.75px;
max-width: 100vw;
overflow: hidden;
height: 162px;
border-radius: 5px;
backdrop-filter: blur(20px);
transform: scale(calc(1 + var(--value) * 0.075)) rotate3d(0, 1, 0, var(--deg));
transform-origin: 0 0;
.opacity-text {
position: absolute;
top: 0;
bottom: 0;
margin: auto;
pointer-events: none;
display: flex;
justify-content: center;
align-items: center;
margin-left: 20px;
font-size: 14px;
opacity: var(--value);
span {
margin-left: 2px;
font-size: 10px;
font-weight: 600;
vertical-align: baseline;
margin-top: 2.5px;
}
}
.slider {
appearance: none;
width: 100%;
height: 100%;
appearance: none;
margin: 0;
background-color: transparent;
background-image: repeating-linear-gradient(
to right,
transparent,
transparent calc(18% - 1px),
#05051a1c 18%
);
&::-webkit-slider-thumb {
box-shadow: -20rem 0 0 20rem rgba(#ea0b2a, 0.2);
cursor: col-resize;
background-color: rgba(#ea0b2a, 0.2);
}
@supports not (-webkit-touch-callout: none) {
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 0;
}
}
@supports (-webkit-min-device-pixel-ratio: 0) {
&::-webkit-slider-thumb {
-webkit-appearance: auto;
appearance: auto;
width: auto;
}
}
&::-webkit-slider-runnable-track {
// pointer-events: none;
}
}
}
}
================================================
FILE: src/Render/Header/Component/ShapeSelector.tsx
================================================
import React, {
useState,
useRef,
useEffect,
CSSProperties,
useLayoutEffect,
SetStateAction,
Dispatch,
} from "react";
import "./ShapeSelector.scss";
import classNames from "classnames";
import { browserInfo } from "../../../Common/util";
import shapes from "../../../Resource/Shape/shape";
import { Shape } from "../../../Data/Shape";
import { useTranslation } from "react-i18next";
interface OpacityControlProps {
defaultShape: string;
setDefaultShape: Dispatch>;
}
const ShapeSelector: React.FC = ({
defaultShape,
setDefaultShape,
}) => {
const [showSlider, setShowSlider] = useState(false);
const sliderRef = useRef(null);
const toolRef = useRef(null);
const { t } = useTranslation();
const handleClick = () => {
setShowSlider(!showSlider);
};
useEffect(() => {
if (showSlider && sliderRef.current && toolRef.current) {
const toolRect = toolRef.current.getBoundingClientRect();
const viewportWidth = window.innerWidth;
if (toolRect.left + 230 > viewportWidth) {
sliderRef.current.style.left = "unset";
sliderRef.current.style.right = "0";
(sliderRef.current.style as any)["--deg"] = "-25deg";
sliderRef.current.style.transformOrigin = "100% 50%";
toolRef.current.style.perspectiveOrigin = "100% 50%";
} else {
sliderRef.current.style.left = "0";
sliderRef.current.style.transformOrigin = "0 50%";
toolRef.current.style.perspectiveOrigin = "0 50%";
(sliderRef.current.style as any)["--deg"] = "25deg";
}
}
}, [showSlider]);
useLayoutEffect(() => {
if (sliderRef.current) {
sliderRef.current.style.transition = "0.3s ease-in-out";
}
}, [showSlider]);
useEffect(() => {
const handleClickCapture = (event: TouchEvent | MouseEvent) => {
if (!toolRef.current?.contains(event.target as Node)) {
console.log("focusout");
setShowSlider(false);
}
};
const resize = () => setShowSlider(false);
document.addEventListener("touchstart", handleClickCapture, true);
document.addEventListener("click", handleClickCapture, true);
document.addEventListener("resize", resize);
return () => {
document.removeEventListener("touchstart", handleClickCapture, true);
document.addEventListener("click", handleClickCapture, true);
document.removeEventListener("resize", resize);
};
}, []);
const { engine } = browserInfo;
const safari = engine.name === "WebKit";
const column = 4;
const row = 3;
const grid = new Array(column * row).fill(0);
Object.keys(shapes).forEach((shape, index) => (grid[index] = shape));
return (
{
t(`shape.${defaultShape}`)
}
{
e.stopPropagation()}
style={{} as CSSProperties}
onTransitionEnd={() => {
if (showSlider && sliderRef.current) {
sliderRef.current.style.transition = "none";
}
}}
>
{grid.map((shape, index) => {
const left = index % column === 0;
const bottom = Math.floor(index / column) === row - 1;
// console.log({ shape });
return (
{
if (shape) setDefaultShape(shape);
}}
>
{
//@ts-ignore
shapes[shape]
}
);
})}
}
);
};
export default ShapeSelector;
================================================
FILE: src/Render/Header/Menu.scss
================================================
.menu {
&.page-menu {
position: fixed;
top: 0;
left: 0;
height: 100vh;
width: 100vw;
z-index: 500;
background-color: rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
.title {
.tools {
margin-left: -50px;
.tool {
margin-left: -5px;
opacity: 0;
}
}
}
.dots {
width: calc(100vw - 51.6px - 51.6px);
transition:
0.3s ease-in-out opacity,
0.3s ease-in-out width;
opacity: 1;
}
.menus {
transition: 0.3s ease-in-out opacity;
opacity: 1;
.columns {
.column {
@media (max-width: 710px) {
margin-top: 28.5px;
opacity: 1;
}
}
}
}
}
&.page-title {
// position: fixed;
// top: 0;
// left: 0;
// height: 100vh;
// width: 100vw;
.title {
.click-panel {
cursor: pointer;
}
.tools {
margin-left: -50px;
.tool {
margin-left: -5px;
opacity: 0;
}
}
}
user-select: none;
z-index: -500;
.dots {
margin-top: 100.66px;
position: fixed;
opacity: 1;
width: 0;
transition:
0.3s ease-in-out opacity,
0.3s ease-in-out width;
}
.menus {
margin-top: 101.66px;
position: fixed;
opacity: 0;
transition: 0.3s ease-in-out opacity;
.columns {
justify-content: space-between;
transition: 0.3s ease-in-out width;
width: calc(70vw - 51.6px - 51.6px);
}
}
}
&.page-tools {
.title {
}
.dots {
margin-top: 100.66px;
position: fixed;
opacity: 1;
width: 0;
transition:
0.3s ease-in-out opacity,
0.3s ease-in-out width;
}
.menus {
margin-top: 101.66px;
position: fixed;
opacity: 0;
transition: 0.3s ease-in-out opacity;
.columns {
justify-content: space-between;
transition: 0.3s ease-in-out width;
width: calc(70vw - 51.6px - 51.6px);
}
}
}
.title {
position: fixed;
margin: 35.5px 0 0 51.6px;
z-index: 500;
.auto-growth-container {
display: inline-block;
* {
font-size: 36px;
font-weight: 1000;
}
input {
// background-color: rgba(255, 255, 255, 0.901);
// backdrop-filter: blur(10px);
// padding-left: 18px;
// margin-left: -18px;
}
}
.tools {
@media (max-width: 710px) {
// display: block !important;
margin-left: -21.77px;
.tool {
margin-top: 5px;
&:first-child {
// display: none;
}
}
}
display: inline-block;
transition: 0.3s ease-in-out;
.tool {
transition: 0.3s ease-in-out;
opacity: 1;
display: inline-block;
font-size: 18px;
font-weight: 500;
color: #ea0b2a;
&.tool-title{
color: #00A0E8;
cursor:auto;
}
margin-left: 21.77px;
cursor: pointer;
&.disabled {
color: #ea0b2a55;
cursor:auto;
}
svg{
vertical-align: text-bottom;
line-height: 26px;
height: 26px;
[fill="black"]{
fill: rgba(234, 11, 42, 0.3333333333) !important;
fill-opacity: 1 !important;
}
}
}
}
}
.dots {
// margin-top: 20.16px;
border-bottom: dotted black 1px;
margin: 100.66px 51.6px 0 51.6px;
}
.menus {
height: calc(100vh - 101px);
overflow-y: overlay;
overflow-x: hidden;
&::-webkit-scrollbar {
display: none;
}
.columns {
margin: 0 51.6px 0 51.6px;
display: flex;
@media (max-width: 710px) {
display: grid;
}
justify-content: space-between;
transition: 0.3s ease-in-out width;
width: calc(100vw - 51.6px - 51.6px);
.column {
margin-top: 25.8px;
display: inline-block;
vertical-align: top;
width: 20vw;
transition: 0.3s ease-in-out;
@media (max-width: 710px) {
width: auto;
margin-top: -15px;
opacity: 0;
&:last-child{
padding-bottom: 150px;
}
}
.column-title {
font-size: 36px;
font-weight: 300;
}
.column-items {
// margin-top: 17px;
.column-item {
width: fit-content;
margin-top: 17px;
font-size: 18px;
font-weight: 500;
color: #ea0b2a;
cursor: pointer;
&.sub-menu{
margin-top: 10px;
margin-left: 10px;
.sub-item{
margin-right: 10px;
}
}
&.small{
font-size: small;
}
&.author{
margin-top: 4px;
&:hover{
// text-decoration: underline;
}
}
&.friend{
position: relative;
svg{
margin-left: 5px;
position: absolute;
margin-top: 3px;
width: 17px;
// height: 25px;
// vertical-align: bottom;
}
}
}
}
}
}
.notice{
position: fixed;
left: 0;
bottom: 0;
}
}
}
================================================
FILE: src/Render/Header/Menu.tsx
================================================
import React, {
Dispatch,
SetStateAction,
forwardRef,
useEffect,
useImperativeHandle,
useRef,
useState,
} from "react";
import { ReactComponent as ShareIcon } from "../../Resource/Icon/share.svg";
import { AutoGrowthInput } from "../../Common/AutoGrowthInput";
import "./Menu.scss";
import classNames from "classnames";
import { FunctionMode, Mode } from "../../DataStructure/Mode";
import {
ChangeSteps,
StationProps,
UserDataType,
addNewStation,
addStationFromRecord,
deleteStation,
dataProcessor,
InsertInfo,
RecordType,
LineChanges,
LineProps,
ShowNameProps,
DrawProps,
CardShowing,
setDataFromJson,
TransformProps,
ShowTourProps,
PageProps,
} from "../../Data/UserData";
import PlusIcon from "../../Resource/Icon/plus";
import { ReactComponent as GlobeIcon } from "../../Resource/Icon/globe.svg";
import {
browserInfo,
calculateTransform,
deleteFileFromIndexedDB,
exportFile,
exportJson,
importFromFile,
importImage,
mediateMap,
readFileFromIndexedDB,
storeFileInIndexedDB,
stringifyData,
} from "../../Common/util";
import moment from "moment";
import { toPng, toJpeg, toBlob, toPixelData, toSvg } from "html-to-image";
import download from "downloadjs";
import { getExistMap } from "../../Common/api";
import OpacityControl from "./Component/OpacityControl";
import ShapeSelector from "./Component/ShapeSelector";
import { useTranslation } from "react-i18next";
import i18n from "../../i18n/config";
type MenuType = {
setEditingMode: React.Dispatch>;
functionMode: FunctionMode;
setFunctionMode: React.Dispatch>;
record: RecordType;
setRecord: React.Dispatch>;
currentRecordIndex: number;
setCurrentRecordIndex: React.Dispatch>;
data: UserDataType;
setData: Dispatch>;
insertInfo?: InsertInfo;
setInsertInfo: React.Dispatch>;
cardShowing: CardShowing;
setCardShowing: Dispatch>;
saved: boolean;
setSaved: Dispatch>;
defaultShape: string;
setDefaultShape: Dispatch>;
} & ShowNameProps &
DrawProps &
TransformProps &
ShowTourProps &
PageProps;
export const Menu = forwardRef(function (
{
setEditingMode,
functionMode,
setFunctionMode,
record,
setRecord,
currentRecordIndex,
setCurrentRecordIndex,
data,
setData,
insertInfo,
setInsertInfo,
showName,
setShowName,
autoHiddenName,
setAutoHiddenName,
setDrawing,
drawing,
cardShowing,
setCardShowing,
scale,
setScale,
translateX,
translateY,
setTranslateX,
setTranslateY,
saved,
setSaved,
setShowTour,
page,
setPage,
defaultShape,
setDefaultShape,
}: MenuType,
ref
) {
const [titleEditable, setTitleEditable] = useState(false);
const [display, setDisplay] = useState("none");
const inputRef = useRef(null);
const [toolsDisPlay, setToolsDisPlay] = useState("none");
const [selectedMap, setSelectedMap] = useState();
const [originalMap, setOriginalMap] = useState();
const [mapData, setMapData] = useState>(new Map());
const undoCondition = currentRecordIndex >= 0;
const redoCondition = currentRecordIndex < record.length - 1;
const { title } = data;
const { t } = useTranslation();
const setTitle = (title: string) => setData({ ...data, title });
const backToTitle = () => {
setPage("title");
setTitleEditable(false);
setFunctionMode(FunctionMode.normal);
setDrawing(false);
};
const { engine } = browserInfo;
const showTools = (e: React.MouseEvent, functionMode: FunctionMode) => {
e.stopPropagation();
setRecord([]);
setCurrentRecordIndex(-1);
setFunctionMode(functionMode);
setTitleEditable(false);
setToolsDisPlay(window.innerWidth >= 710 ? "inline-block" : "block");
setTimeout(() => setPage("tools"));
};
const importImageClick = (e: React.MouseEvent) => {
importImage()
.then((file) => {
if (file) {
calculateTransform(file).then(
({ translateX: x, translateY: y, scale: s }) => {
showTools(e, FunctionMode.editingCustomBackgroundPosition);
setData((data) => ({
...data,
// backgroundColor: "image",
backgroundImage: file,
translateX: (x - translateX) / scale,
translateY: (y - translateY) / scale,
scale: s / scale,
}));
storeFileInIndexedDB(file, "image");
}
);
console.log("图片 Blob 对象:", file);
} else {
console.log("未选择图片");
}
})
.catch((error) => {
console.error("导入图片时出错:", error);
});
};
const transfromTools = {
scale,
setScale,
translateX,
translateY,
setTranslateX,
setTranslateY,
};
useEffect(() => {
mediateMap(data, transfromTools);
}, []);
useImperativeHandle(
ref,
() => {
return {
backToTitle,
showTools,
};
},
[]
);
const tools = () => {
switch (functionMode) {
case FunctionMode.addingStation: {
const stationRecords = record as StationProps[];
return (
<>
{currentRecordIndex >= 0
? t("menu.alreadyAddStations", {
count: currentRecordIndex + 1,
})
: t("menu.clickBlankToAddStation")}
{
e.stopPropagation();
if (undoCondition) {
const station = stationRecords[currentRecordIndex];
const { stationId } = station;
deleteStation(data, setData, stationId);
setCurrentRecordIndex(currentRecordIndex - 1);
}
}}
>
{t("menu.withdraw")}
{
e.stopPropagation();
if (redoCondition) {
const station = stationRecords[currentRecordIndex + 1];
addStationFromRecord(data, setData, station);
setCurrentRecordIndex(currentRecordIndex + 1);
}
}}
>
{t("menu.redo")}
{
setPage("title");
setTitleEditable(false);
}}
>
{t("menu.done")}
>
);
}
case FunctionMode.dragingStation: {
const changeRecords = record as ChangeSteps[];
return (
<>
{currentRecordIndex >= 0
? t("menu.alreadyModified", { count: currentRecordIndex + 1 })
: t("menu.dragToChangePoistion")}
{
e.stopPropagation();
if (undoCondition) {
const change = changeRecords[currentRecordIndex];
const { stationId, fromX, fromY } = change;
const { setStationPosition } = dataProcessor(
stationId,
setData,
data
);
setStationPosition(fromX, fromY);
setCurrentRecordIndex(currentRecordIndex - 1);
}
}}
>
{t("menu.withdraw")}
{
e.stopPropagation();
if (redoCondition) {
const change = changeRecords[currentRecordIndex + 1];
const { stationId, toX, toY } = change;
const { setStationPosition } = dataProcessor(
stationId,
setData,
data
);
setStationPosition(toX, toY);
setCurrentRecordIndex(currentRecordIndex + 1);
}
}}
>
{t("menu.redo")}
{
setPage("title");
setTitleEditable(false);
}}
>
{t("menu.done")}
>
);
}
case FunctionMode.lineEditing: {
return (
<>
{t("menu.chooseALineFirst")}“
”
{
setPage("title");
setTitleEditable(false);
}}
>
{t("menu.done")}
>
);
}
case FunctionMode.backgroundEditing: {
const { backgroundColor = "#ffffff", backgroundImage } = data;
const setColor = (color: string) =>
{setData((data) => ({ ...data, backgroundColor: color }));
};
return (
<>
{t("menu.backgroudSetting")}
setColor("#ffffff")}
>
{t("menu.white")}
setColor("#fffee0")}
>
{t("menu.lightYellow")}
setColor("transparent")}
>
{t("menu.transparent")}
setColor(e.currentTarget.value)}
/>
{backgroundImage ? (
{
showTools(e, FunctionMode.editingCustomBackgroundPosition);
}}
>
{t("menu.changeBackgroudImage")}...
) : (
{t("menu.importBackgroudImage")}...
)}
{
setPage("title");
setTitleEditable(false);
}}
>
{t("menu.done")}
>
);
}
case FunctionMode.customBackground:
case FunctionMode.editingCustomBackgroundPosition: {
const { backgroundColor, backgroundImage, opacity = 1 } = data;
const setColor = (color: string) =>
setData((data) => ({ ...data, backgroundColor: color }));
const setOpacity = (opacity: number) =>
setData((data) => ({ ...data, opacity }));
return (
<>
{t("menu.backgroudSetting")} / {t("menu.changeBackgroudImage")}
{t("menu.importImage")}
{/*
{functionMode === FunctionMode.editingCustomBackgroundPosition ? (
showTools(e, FunctionMode.customBackground)}>调整地图
) : (
showTools(e, FunctionMode.editingCustomBackgroundPosition)}>调整位置与缩放
)} */}
{
showTools(e, FunctionMode.customBackground);
setData((data) => ({
...data,
backgroundImage: undefined,
}));
deleteFileFromIndexedDB("image");
}}
>
{t("menu.deleteImage")}
showTools(e, FunctionMode.backgroundEditing)}
>
{t("menu.back")}
>
);
}
case FunctionMode.selectingStation: {
const { insertIndex, line } = insertInfo!;
const { lineName } = line;
const changeRecords = record as LineChanges[];
return (
<>
{t("menu.clickAddStationToLine", {
lineName,
insertIndex: insertIndex + 1,
})}
{
e.stopPropagation();
if (undoCondition) {
const change = changeRecords[currentRecordIndex];
const { stationId, lineId, stationIndex } = change;
const { removeStationFromLine } = dataProcessor(
stationId,
setData,
data
);
removeStationFromLine(lineId, stationIndex);
setInsertInfo({
insertIndex: insertIndex === 0 ? 0 : insertIndex - 1,
line,
});
setCurrentRecordIndex(currentRecordIndex - 1);
}
}}
>
{t("menu.withdraw")}
{
e.stopPropagation();
if (redoCondition) {
const change = changeRecords[currentRecordIndex + 1];
const { stationId, lineId, stationIndex } = change;
const { addStationToLine } = dataProcessor(
lineId,
setData,
data
);
addStationToLine(stationId, stationIndex);
setCurrentRecordIndex(currentRecordIndex + 1);
setInsertInfo({
insertIndex: insertIndex === 0 ? 0 : insertIndex + 1,
line,
});
}
}}
>
{t("menu.redo")}
{
setPage("title");
setTitleEditable(false);
setInsertInfo({ insertIndex: -1, line });
}}
>
{t("menu.done")}
>
);
}
case FunctionMode.choosingExistMap: {
const existingMap = [
{ name: t("shang-hai"), id: "shanghai", webkit: true },
{ name: t("bei-jing"), id: "beijing" },
{ name: t("guang-zhou"), id: "guangzhou", webkit: true },
{ name: t("shen-zhen"), id: "shenzhen", webkit: true },
{ name: t("xiang-gang"), id: "hongkong", webkit: true },
{ name: t("chang-sha"), id: "changsha", webkit: true },
{ name: t("tian-jin"), id: "tianjing", webkit: true },
];
const webkit = engine.name === "WebKit";
return (
<>
{t("menu.chooseAMap")}
{existingMap
.filter((x) => (webkit ? x.webkit : true))
.map(({ name, id }) => {
return (
{
let data = mapData.get(id);
if (!data) {
const res = await getExistMap(id);
data = setDataFromJson(setData, res);
mapData.set(id, data);
}
setData({ ...data, title });
mediateMap(data, transfromTools);
setSelectedMap(id);
}}
className={classNames({
tool: 1,
disabled: selectedMap !== id,
[id]: 1,
})}
>
{name}
);
})}
{selectedMap ? (
{
setPage("title");
setTitleEditable(false);
const data = mapData.get(selectedMap!);
if (data) setData(data);
}}
>
{t("menu.useTemplate", {
name: existingMap.find((x) => x.id === selectedMap)?.name,
})}
) : (
<>>
)}
{
setData(originalMap!);
mediateMap(originalMap!, transfromTools);
setPage("title");
setTitleEditable(false);
}}
>
{t("menu.cancel")}
>
);
}
}
};
useEffect(() => {
if (drawing) {
toPng(document.querySelector(".transform-layer")! as HTMLElement).then(
(dataUrl) => {
setDrawing(false);
download(
dataUrl,
`${title}-${moment().format("YYYY-MM-DD_HH-mm-ss")}.png`
);
}
);
}
}, [drawing]);
return (
{
if (page === "title" || page === "tools") setDisplay("none");
}}
>
e.stopPropagation()}>
{
e.stopPropagation();
if (page === "title" || page === "tools") {
setDisplay("block");
setTimeout(() => setPage("menu"));
setTitleEditable(true);
}
}}
onInput={(e) => setTitle(e.currentTarget.value)}
ref={inputRef}
style={{
cursor:
page === "title"
? "pointer"
: page === "tools"
? "default"
: titleEditable
? "auto"
: "default",
}}
disabled={!titleEditable}
/>
{
if (page !== "tools") {
setToolsDisPlay("none");
setFunctionMode(FunctionMode.normal);
}
}}
>
{tools()}
{t("menu.station")}
showTools(e, FunctionMode.dragingStation)}
>
{t("menu.changeStationPosition")}...
{
e.stopPropagation();
setShowName(!showName);
}}
>
{showName ? t("menu.hidden") : t("menu.show")}
{t("menu.stationName")}...
{showName ? (
{
e.stopPropagation();
setAutoHiddenName(!autoHiddenName);
}}
>
{autoHiddenName ? t("menu.turnOff") : t("menu.turnOn")}
{t("menu.AutoHidden")}...
) : (
<>>
)}
{t("menu.line")}
showTools(e, FunctionMode.lineEditing)}
>
{t("menu.insertStation")}
{
mediateMap(data, transfromTools);
}}
>
{t("menu.mediateMap")}
showTools(e, FunctionMode.backgroundEditing)}
>
{t("menu.addBackgroundImage")}
{t("menu.data")}
{
const current = localStorage.getItem("current");
if (current && !saved)
exportJson(
current!,
`${title}-autosave-${moment().format(
"YYYY-MM-DD_HH-mm-ss"
)}.json`
);
setData({
stations: new Map(),
lines: new Map(),
title: t("menu.newMap"),
});
showTools(e, FunctionMode.addingStation);
}}
>
{t("menu.addNewMap")}
{
setSelectedMap("");
setOriginalMap(data);
showTools(e, FunctionMode.choosingExistMap);
}}
>
{t("menu.addFromTemplate")}
{
e.stopPropagation();
importFromFile().then((res) => {
const data = setDataFromJson(setData, res);
mediateMap(data, transfromTools);
});
}}
>
{t("menu.importFile")}
{engine.name === "WebKit" ? (
<>>
) : (
{
e.stopPropagation();
setDrawing(true);
}}
>
{t("menu.exportAsImage")}
)}
{
e.stopPropagation();
setDrawing(true);
setTimeout(() => {
toSvg(
document.querySelector(".transform-layer")! as HTMLElement
).then((dataUrl) => {
const svg = decodeURIComponent(
dataUrl.replace(
/data:image\/svg\+xml;charset=utf-8,/,
""
)
);
console.log(svg);
exportFile(
svg!,
`${title}-${moment().format(
"YYYY-MM-DD_HH-mm-ss"
)}.svg`,
"image/svg+xml"
);
});
}, 300);
}}
>
{t("menu.exportAsSVG")}
{
e.stopPropagation();
const current = stringifyData(data);
setSaved(true);
exportJson(
current!,
`${title}-${moment().format("YYYY-MM-DD_HH-mm-ss")}.json`
);
}}
>
{t("menu.exportAsFile")}
{
e.stopPropagation();
const current = localStorage.getItem("current");
if (current) {
const data = setDataFromJson(setData, current);
readFileFromIndexedDB("image")
.then((file) => {
setData((data) => ({
...data,
// backgroundColor: "image",
backgroundImage: file as File,
}));
})
.catch((e) => {
console.error(e);
});
mediateMap(data, transfromTools);
}
}}
>
{t("menu.recoverData")}
{
e.stopPropagation();
const current = localStorage.getItem("last");
exportJson(
current!,
`${title}-recovery-${moment().format(
"YYYY-MM-DD_HH-mm-ss"
)}.json`
);
}}
>
{t("menu.exportRecoveryData")}
{t("menu.about")}
{
e.stopPropagation();
window.open(
"https://github.com/RyanEdo/mini-metro-web",
"_blank"
);
}}
>
{t("menu.projectAddress")}
{
if (window.innerWidth >= 710) setShowTour(true);
else {
e.stopPropagation();
window.open(
"https://www.bilibili.com/video/BV1E4421D7Yn/",
"_blank"
);
}
}}
>
{t("menu.guide")}
{window.innerWidth >= 710 ? (
{
e.stopPropagation();
window.open(
"https://www.bilibili.com/video/BV1E4421D7Yn/",
"_blank"
);
}}
>
{t("menu.videoGuild")}
) : (
<>>
)}
{
e.stopPropagation();
console.log(i18n)
i18n.changeLanguage(i18n.language === "zh-CN"? "en-US": "zh-CN");
}}
>
{t("menu.switchLanguage")}...
{/* */}
{
e.stopPropagation();
window.open(
"https://railmapgen.org/?utm_source=mini-metro-web",
"_blank"
);
}}
>
{t("menu.RMP")}
{t("menu.version")}1.2.1
{
e.stopPropagation();
window.open("https://space.bilibili.com/8217854", "_blank");
}}
>
{t("menu.author")}
);
});
================================================
FILE: src/Render/Layer/DevelopLayer.scss
================================================
.DevelopLayer {
.grid {
display: grid;
grid-template-columns: repeat(100, 100px);
grid-template-rows: repeat(50, 100px);
.grid-item {
border: 1px solid black;
height: 100px;
width: 100px;
display: flex;
justify-content: center;
align-items: center;
}
}
}
================================================
FILE: src/Render/Layer/DevelopLayer.tsx
================================================
import React from "react";
import "./DevelopLayer.scss";
import { Point } from "../../DataStructure/Point";
import { Station } from "../../DataStructure/Station";
import { Line } from "../../DataStructure/Line";
import LineRender from "../Component/LineRender";
import { CardShowing } from "../../Data/UserData";
function DevelopLayer() {
// test line and station
const pointA = new Point(200, 200);
const pointB = new Point(300, 300);
const pointC = new Point(600, 300);
const pointD = new Point(800, 300);
const pointE = new Point(200, 400);
const pointF = new Point(800, 200);
const pointG = new Point(500, 500);
const A = new Station(pointA);
const B = new Station(pointB);
B._dev_tag = "B";
const C = new Station(pointC);
const D = new Station(pointD);
const E = new Station(pointE);
const F = new Station(pointF);
const G = new Station(pointG);
const line1 = new Line();
const line3 = new Line();
const line8 = new Line();
const line9 = new Line();
line1.linkAll([A, B, C, D]);
// line3.linkAll([E, B, C, D, F, E]);
line8._dev_tag='line8';
line8.linkAll([E, B, C, D]);
line9.linkAll([A, E, G,D,F]);
// console.log(line1, line3, line8);
console.log(A, B, C, D, E, F);
const allStationsList = [A, B, C, D, E, F, G];
const allLinesList = [line1, line3, line8, line9];
const renderStations = (allStationsList: Station[]) => {
return (
{allStationsList.map((station, index) => (
{String.fromCharCode("A".charCodeAt(0) + index)}
))}
);
};
// const renderLines = (allLinesList: Line[], cardShowing: CardShowing, setCardShowing: Dispatch>) => {
// return (
//
// {allLinesList.map((line) => {
// return ;
// })}
//
// );
// };
return (
{/*
{new Array(100 * 50).fill(1).map((x, index) => (
{index}
))}
*/}
{renderStations(allStationsList)}
{/* {renderLines(allLinesList)} */}
);
}
export default DevelopLayer;
================================================
FILE: src/Render/Layer/RenderLayer.scss
================================================
.station-render {
cursor: pointer;
.station-shape {
height: 30px;
width: 30px;
// transform: translate(-50%,-50%);
display: inline-flex;
// align-content: center;
justify-content: center;
align-items: center;
svg {
fill: white;
// &.square {
// zoom: 0.9;
// }
// &.triangle {
// zoom: 1.05;
// }
// &.start {
// zoom: 1.15;
// }
// &.hexagon {
// zoom: 0.9;
// }
// &.pentagon {
// zoom: 1.1;
// }
&.shadow{
position: absolute;
transform: scale(1.5);
transform-origin: center;
*{
fill: inherit;
stroke: inherit;
}
}
}
}
.station-name {
display: inline-block;
font-size: 18px;
font-weight: 500;
// font-size: 30px;
// font-weight: 1000;
vertical-align: top;
// margin-left: 5px;
white-space: nowrap;
}
}
================================================
FILE: src/Render/Layer/RenderLayer.tsx
================================================
import React, {
CSSProperties,
Dispatch,
MouseEventHandler,
SetStateAction,
useEffect,
useLayoutEffect,
useState,
} from "react";
import DevelopLayer from "./DevelopLayer";
import { LineCard } from "../Card/LineCard";
import { Cards } from "../Card/Cards";
import {
CardShowing,
ChangeSteps,
DrawProps,
DrawerSize,
InsertInfo,
LineChanges,
LineProps,
RecordType,
ShowNameProps,
StationProps,
UserDataType,
dataProcessor,
} from "../../Data/UserData";
import { Station } from "../../DataStructure/Station";
import { Point } from "../../DataStructure/Point";
import { mapToArr } from "../../Common/util";
import { Line } from "../../DataStructure/Line";
import LineRender from "../Component/LineRender";
import shapes, { shapesWithStyle } from "../../Resource/Shape/shape";
import "./RenderLayer.scss";
import { FunctionMode, Mode } from "../../DataStructure/Mode";
import { Direct } from "../../DataStructure/Direction";
import { clearHandle, getHandleCommand } from "../../Line/Handle";
import {
getAllKeyPoints,
getRoundedPoints,
generateLineCommand,
} from "../../Line/LinePoints";
import classNames from "classnames";
type RenderProps = {
data: UserDataType;
setData: Dispatch>;
functionMode: FunctionMode;
record: RecordType;
setRecord: React.Dispatch>;
currentRecordIndex: number;
setCurrentRecordIndex: React.Dispatch>;
translateX: number;
translateY: number;
scale: number;
insertInfo?: InsertInfo;
setInsertInfo: React.Dispatch>;
setFunctionMode: React.Dispatch>;
cardShowing: CardShowing;
setCardShowing: Dispatch>;
editingMode: Mode;
} & ShowNameProps &
DrawProps &
DrawerSize;
const buildStations = (
stations: Map
): Map => {
const stationMap = new Map();
mapToArr(stations).forEach((station) => {
const { position, stationId } = station;
const [x, y] = position;
const dStation = new Station(new Point(x, y));
dStation.displayStation = station;
stationMap.set(stationId, dStation);
});
return stationMap;
};
const buildLines = (
lines: Map,
stationMap: Map
) => {
const lineMap = new Map();
mapToArr(lines)
.sort((a, b) => a.order - b.order)
.forEach((line) => {
const { stationIds, lineId, bendFirst } = line;
const dLine = new Line();
dLine.displayLine = line;
for (let i = 1; i < stationIds.length; i++) {
const B = stationMap.get(stationIds[i - 1])!;
const C = stationMap.get(stationIds[i])!;
dLine.link(B, C, !!bendFirst[i - 1]);
}
lineMap.set(lineId, dLine);
});
return lineMap;
};
const renderLines = (
allLinesList: Line[],
cardShowing: CardShowing,
setCardShowing: Dispatch>,
commandMap: Map,
data: UserDataType,
setData: Dispatch>,
{ drawing, setDrawing }: DrawProps,
{ drawerX, drawerY }: DrawerSize
) => {
return (
{allLinesList.map((line) => {
const command = commandMap.get(line);
return (
);
})}
);
};
function RenderLayer({
data,
setData,
functionMode,
setFunctionMode,
record,
setRecord,
currentRecordIndex,
setCurrentRecordIndex,
translateX,
translateY,
scale,
insertInfo,
setInsertInfo,
cardShowing,
setCardShowing,
editingMode,
showName,
setShowName,
autoHiddenName,
setAutoHiddenName,
drawing,
setDrawing,
drawerX,
drawerY,
}: RenderProps) {
const { lines, stations } = data;
const stationMap = buildStations(stations);
const lineMap = buildLines(lines, stationMap);
const allStationsList = mapToArr(stationMap);
const allLinesList = mapToArr(lineMap);
const [mouseDown, setMouseDown] = useState(false);
const [stationBeingDrag, setStationBeingDrag] = useState();
const mouseUp = (e: Event) => {
setMouseDown(false);
if (stationBeingDrag) {
const { displayStation, position } = stationBeingDrag;
const { stationId } = displayStation!;
const { x: fromX, y: fromY } = position;
const { setStationPosition } = dataProcessor(stationId, setData, data);
if (functionMode === FunctionMode.dragingStation && mouseDown) {
const { clientX, clientY } = e as MouseEvent;
const toX = (clientX - translateX) / scale;
const toY = (clientY - translateY) / scale;
setStationPosition(toX, toY);
const newRecord = record.slice(
0,
currentRecordIndex + 1
) as ChangeSteps[];
setRecord(newRecord.concat([{ stationId, fromX, fromY, toX, toY }]));
setCurrentRecordIndex(currentRecordIndex + 1);
}
setStationBeingDrag(undefined);
}
};
const mouseMove = (e: Event) => {
if (stationBeingDrag) {
const { displayStation } = stationBeingDrag;
const { stationId } = displayStation!;
const { setStationPosition } = dataProcessor(stationId, setData, data);
if (functionMode === FunctionMode.dragingStation && mouseDown) {
setCardShowing({ stationIds: [stationId] });
const { clientX, clientY } = e as MouseEvent;
const x = (clientX - translateX) / scale;
const y = (clientY - translateY) / scale;
setStationPosition(x, y);
}
}
};
const touchMove = (e: Event) => {
if (stationBeingDrag) {
const { displayStation } = stationBeingDrag;
const { stationId } = displayStation!;
const { setStationPosition } = dataProcessor(stationId, setData, data);
if (functionMode === FunctionMode.dragingStation && mouseDown) {
const { touches } = e as TouchEvent;
if (touches.length === 1) {
e.stopPropagation();
const touch = touches[0];
const { clientX, clientY } = touch;
console.log(clientX, clientY);
const x = (clientX - translateX) / scale;
const y = (clientY - translateY) / scale;
setStationPosition(x, y);
}
}
}
};
const touchEnd = (e: Event) => {
setMouseDown(false);
if (stationBeingDrag) {
const { displayStation, position } = stationBeingDrag;
const { stationId } = displayStation!;
const { x: fromX, y: fromY } = position;
const { setStationPosition } = dataProcessor(stationId, setData, data);
if (functionMode === FunctionMode.dragingStation && mouseDown) {
const { changedTouches } = e as TouchEvent;
if (changedTouches.length === 1) {
e.stopPropagation();
const touch = changedTouches[0];
const { clientX, clientY } = touch;
const toX = (clientX - translateX) / scale;
const toY = (clientY - translateY) / scale;
console.log("touch end:", clientX, clientY);
setStationPosition(toX, toY);
const newRecord = record.slice(
0,
currentRecordIndex + 1
) as ChangeSteps[];
setRecord(newRecord.concat([{ stationId, fromX, fromY, toX, toY }]));
setCurrentRecordIndex(currentRecordIndex + 1);
}
}
setStationBeingDrag(undefined);
}
};
useEffect(() => {
const scaleLayer = document.querySelector(".ScaleLayer");
scaleLayer?.addEventListener("mouseup", mouseUp);
scaleLayer?.addEventListener("mouseleave", mouseUp);
scaleLayer?.addEventListener("mousemove", mouseMove);
scaleLayer?.addEventListener("touchmove", touchMove);
scaleLayer?.addEventListener("touchend", touchEnd);
return () => {
scaleLayer?.removeEventListener("mouseup", mouseUp);
scaleLayer?.removeEventListener("mouseleave", mouseUp);
scaleLayer?.removeEventListener("mousemove", mouseMove);
scaleLayer?.removeEventListener("touchmove", touchMove);
scaleLayer?.removeEventListener("touchend", touchEnd);
};
}, [stationBeingDrag]);
const [moved, setMoved] = useState(false);
const operationStart = (
e: React.MouseEvent | React.TouchEvent,
station: Station
) => {
setMoved(false);
if (functionMode === FunctionMode.dragingStation) {
e.stopPropagation();
setMouseDown(true);
setStationBeingDrag(station);
}
};
const renderStations = (allStationsList: Station[]) => {
return (
{allStationsList.map((station, index) => {
const { displayStation, position } = station;
const { x, y } = position;
const {
stationName,
shape,
stationId,
lineIds: stationLineIds,
tagDirection,
} = displayStation!;
const descend = stations.size - index;
const add = async () => {
if (!moved) setCardShowing({ stationIds: [stationId] });
if (functionMode === FunctionMode.selectingStation) {
const { insertIndex, line } = insertInfo!;
const { lineId } = line;
const { addStationToLine } = dataProcessor(lineId, setData, data);
const added = await addStationToLine(stationId, insertIndex);
console.log(insertIndex);
if(added)
setInsertInfo({
insertIndex: insertIndex ? insertIndex + 1 : 0,
line,
});
const newRecord = record.slice(
0,
currentRecordIndex + 1
) as LineChanges[];
setRecord(
newRecord.concat([
{ stationId, lineId, stationIndex: insertIndex },
])
);
setCurrentRecordIndex(currentRecordIndex + 1);
setCardShowing({ stationIds: [stationId], lineIds: [lineId] });
// setFunctionMode(FunctionMode.lineEditing);
}
};
let nameDirection: Direct = station.getBestDirectionForName();
if (tagDirection || tagDirection === 0) nameDirection = tagDirection;
const SQRT1_2 = Math.SQRT1_2;
const directionOffset = [
[0, -1],
[SQRT1_2, -SQRT1_2],
[1, 0],
[SQRT1_2, SQRT1_2],
[0, 1],
[-SQRT1_2, SQRT1_2],
[-1, 0],
[-SQRT1_2, -SQRT1_2],
];
const k = 20; // distance from station to name
const namePosition = directionOffset[nameDirection];
const [dX, dY] = namePosition;
const nameX = dX * k + x;
const nameY = dY * k + y;
const translate = [
[-1, -2],
[0, -2],
[0, -1],
[0, 0],
[-1, 0],
[-2, 0],
[-2, -1],
[-2, -2],
];
const [tX, tY] = translate[nameDirection];
const { lineIds, stationIds } = cardShowing;
const emphasis = stationIds?.includes(stationId);
const getEmphasisColor = (lineIds?: number[]) =>
Array.isArray(lineIds) &&
lineIds.length &&
lines.get(lineIds[0])?.color;
const emphasisColor =
getEmphasisColor(lineIds) ||
getEmphasisColor(stationLineIds) ||
"#00000055";
const nameStyle: CSSProperties = {
position: "absolute",
left: nameX,
top: nameY,
transform: `translate(${50 * tX}%,${50 * tY}%)`,
};
return (
operationStart(e, station)}
onTouchStart={(e) => operationStart(e, station)}
className={classNames({ "station-render": 1 , [`station-trigger-descend-${descend}`]: 1})}
onTouchMove={() => setMoved(true)}
onMouseMove={() => setMoved(true)}
onTouchEnd={add}
onClick={add}
>
{emphasis && //@ts-ignore
shapesWithStyle(
{
fill: emphasisColor,
stroke: emphasisColor,
// width: 31,
// height: 31
},
"shadow"
)[shape]}
{
//@ts-ignore
shapesWithStyle({ zIndex: 100 })[shape]
}
{stationName}
{/* {String.fromCharCode("A".charCodeAt(0) + index)} */}
);
})}
);
};
const commandMap = new Map();
allLinesList.map((line: Line) => {
const { displayLine } = line;
const { color, lineId, subLine } = displayLine!;
let command = "",
allKeyPoints: Point[] = [];
if (line.departureRecord?.nextLineRecord) {
allKeyPoints = getAllKeyPoints(line);
clearHandle(line);
const { startHandleCommand, LQLPoints, endHandleCommand } =
getHandleCommand(line, allKeyPoints);
const roundedPoints = getRoundedPoints(LQLPoints);
const pathCommand = generateLineCommand(roundedPoints);
command = startHandleCommand + pathCommand + endHandleCommand;
commandMap.set(line, command);
}
});
const stationComp = renderStations(allStationsList);
const lineComp = renderLines(
allLinesList,
cardShowing,
setCardShowing,
commandMap,
data,
setData,
{ drawing, setDrawing },
{ drawerX, drawerY }
);
const [debugError, setDebugError] = useState(false);
//@ts-ignore
window.throwError = () => {
setDebugError(true);
}
if(debugError){
throw new Error();
}
return (
{lineComp}
{stationComp}
{/* */}
);
}
export default RenderLayer;
================================================
FILE: src/Render/Layer/ScaleLayer.scss
================================================
.ScaleLayer{
width: 100vw;
height: 100vh;
overflow: hidden;
.layer-for-welcome-tour{
position: fixed;
z-index: -500;
width: 70vw;
height: 70vh;
left: 15vw;
top: 15vw;
}
.transform-layer{
transform-origin: left top;
// never use will-change here, it makes child elements blurry
// will-change: transform;
}
.background-layer{
position: absolute;
pointer-events: none;
transform-origin: left top;
appearance: none;
border: none;
box-shadow: none;
border-image-width: 0;
&:not([src]) {
opacity: 0 !important;
}
}
}
================================================
FILE: src/Render/Layer/ScaleLayer.tsx
================================================
import React, {
CSSProperties,
Dispatch,
SetStateAction,
useMemo,
useState,
} from "react";
import { FunctionMode, Mode } from "../../DataStructure/Mode";
import RenderLayer from "./RenderLayer";
import {
onMouseDown,
onMouseLeave,
onMouseMove,
onMouseUp,
onTouchEnd,
onTouchMove,
onTouchStart,
onWheel,
} from "../../Grid/Scale";
import "./ScaleLayer.scss";
import { getCursor } from "../../Style/Cursor";
import { Point } from "../../DataStructure/Point";
import {
CardShowing,
ChangeSteps,
DrawProps,
InsertInfo,
PageProps,
RecordType,
ShowNameProps,
StationProps,
TransformProps,
UserDataType,
} from "../../Data/UserData";
import { browserInfo, getBoundary, mapToArr } from "../../Common/util";
type ScaleLayerProp = {
editingMode: Mode;
setEditingMode: React.Dispatch>;
data: UserDataType;
setData: Dispatch>;
functionMode: FunctionMode;
setFunctionMode: React.Dispatch>;
record: RecordType;
setRecord: React.Dispatch>;
currentRecordIndex: number;
setCurrentRecordIndex: React.Dispatch>;
insertInfo?: InsertInfo;
setInsertInfo: React.Dispatch>;
cardShowing: CardShowing;
setCardShowing: Dispatch>;
defaultShape: string;
} & ShowNameProps &
DrawProps &
TransformProps &
PageProps;
function ScaleLayer({
editingMode,
setEditingMode,
data,
setData,
functionMode,
setFunctionMode,
record,
setRecord,
currentRecordIndex,
setCurrentRecordIndex,
insertInfo,
setInsertInfo,
cardShowing,
setCardShowing,
showName,
setShowName,
autoHiddenName,
setAutoHiddenName,
drawing,
setDrawing,
scale: scaleForMap,
setScale: setScaleForMap,
translateX: translateXForMap,
translateY: translateYForMap,
setTranslateX: setTranslateXForMap,
setTranslateY: setTranslateYForMap,
page,
defaultShape
}: ScaleLayerProp) {
// mouseRefPoint
// in mouse drag mode: this point record mouse start point
const [mouseRefPoint, setMouseRefPoint] = useState(new Point());
// mouseStartTranslate
// record translate position when mouse start
// translate should add this value when mouse moving
const [mouseStartTranslate, setMouseStartTranslate] = useState(new Point());
// touchRefPoint
// 1. in touch move mode: this point record touch start point
// 2. in touch scale mode: this point record the center position of start touch
const [touchRefPoint, setTouchRefPoint] = useState(new Point());
// touchStartDistance
// record distance between two finger touch points for scaling operation
const [touchStartDistance, setTouchStartDistance] = useState(1);
// touchStartScale
// record the scale when touch start
// the finnal scale will be calculated by this value
const [touchStartScale, setTouchStartScale] = useState(1);
// touchStartTranslate
// record translate position when touch start
// translate should add this value when touch moving
const [touchStartTranslate, setTouchStartTranslate] = useState(new Point());
// touches or mouse moved, that means user trying to scale or move, not adding station
const [moved, setMoved] = useState(false);
const {
scale: scaleForImage = 1,
translateX: translateXForImage = 0,
translateY: translateYForImage = 0,
} = data;
let scale = scaleForMap,
translateX = translateXForMap,
translateY = translateYForMap;
if (functionMode === FunctionMode.editingCustomBackgroundPosition) {
scale = scaleForImage * scaleForMap;
translateX = translateXForImage * scaleForMap + translateXForMap;
translateY = translateYForImage * scaleForMap + translateYForMap;
}
const getRelativeValue = (
value: number,
type: "scale" | "translateX" | "translateY"
) => {
switch (type) {
case "scale": {
return value / scaleForMap;
}
case "translateX": {
return (value - translateXForMap) / scaleForMap;
}
case "translateY": {
return (value - translateYForMap) / scaleForMap;
}
}
};
// translate and scale used for map or background image
const createSetter = (
key: "scale" | "translateX" | "translateY",
originalSetter: (value: number | ((prev: number) => number)) => void
) => {
return functionMode === FunctionMode.editingCustomBackgroundPosition
? (value: number | ((prev: number) => number)) => {
if (typeof value === "number") {
setData((data: UserDataType) => ({
...data,
[key]: getRelativeValue(value, key),
}));
} else {
setData((data: UserDataType) => ({
...data,
[key]: getRelativeValue(value(data[key]!), key),
}));
}
}
: originalSetter;
};
const setScale = createSetter("scale", setScaleForMap);
const setTranslateX = createSetter("translateX", setTranslateXForMap);
const setTranslateY = createSetter("translateY", setTranslateYForMap);
// const scaleRelative = scaleForImage/scale;
// const translateXRelative = translateX - translateXForImage;
// const translateYRelative = translateY - translateYForImage;
const { stations, backgroundColor, backgroundImage, opacity } = data;
// const backgroundColor =
// _backgroundColor === "image" ? "transparent" : _backgroundColor;
const allStationsList = mapToArr(stations);
const { minX, minY, maxX, maxY } = getBoundary(data);
const drawerX = maxX - minX + 400;
const drawerY = maxY - minY + 400;
const style: CSSProperties = {
backgroundColor,
transform: drawing
? `scale(2)`
: `translate(${translateXForMap}px,${translateYForMap}px) scale(${scaleForMap})`,
width: drawing ? drawerX * 2 : undefined,
height: drawing ? drawerY * 2 : undefined,
};
const drawingStationMap = new Map();
allStationsList.forEach((station) => {
const { stationId } = station;
const [x, y] = station.position;
const position = [x - minX + 200, y - minY + 200];
drawingStationMap.set(stationId, { ...station, position });
});
const drawingData = { ...data, stations: drawingStationMap };
const { engine } = browserInfo;
const webkit = engine.name === "WebKit";
const display =
stations.size > 100 && webkit && page === "menu" ? "none" : undefined;
const imageSrc = useMemo(
() => (backgroundImage ? URL.createObjectURL(backgroundImage) : undefined),
[backgroundImage]
);
return (
onWheel(
event,
scale,
setScale,
translateX,
translateY,
setTranslateX,
setTranslateY,
functionMode
)
}
onMouseDown={(event) =>
onMouseDown(
event,
translateX,
translateY,
setEditingMode,
setMouseRefPoint,
setMouseStartTranslate,
setMoved
)
}
onMouseUp={(event) =>
onMouseUp(
event,
setEditingMode,
editingMode,
functionMode,
data,
setData,
moved,
translateX,
translateY,
scale,
record,
setRecord,
currentRecordIndex,
setCurrentRecordIndex,
cardShowing,
setCardShowing,
defaultShape
)
}
onMouseLeave={(event) => onMouseLeave(event, setEditingMode)}
onMouseMove={(event) =>
onMouseMove(
event,
translateX,
translateY,
setTranslateX,
setTranslateY,
editingMode,
mouseRefPoint,
mouseStartTranslate,
setMoved
)
}
onTouchStart={(event) =>
onTouchStart(
event,
setEditingMode,
setTouchRefPoint,
setTouchStartDistance,
setTouchStartScale,
scale,
setTouchStartTranslate,
translateX,
translateY,
setMoved
)
}
onTouchMove={(event) =>
onTouchMove(
event,
editingMode,
touchRefPoint,
translateX,
translateY,
setTranslateX,
setTranslateY,
touchStartDistance,
touchStartScale,
setScale,
touchStartTranslate,
setMoved
)
}
onTouchEnd={(event) =>
onTouchEnd(
event,
setEditingMode,
editingMode,
functionMode,
data,
setData,
moved,
translateX,
translateY,
scale,
record,
setRecord,
currentRecordIndex,
setCurrentRecordIndex,
cardShowing,
setCardShowing,
defaultShape
)
}
style={{ cursor: getCursor(editingMode), backgroundColor }}
>
{/*
*/}
{imageSrc?
:<>>}
);
}
export default ScaleLayer;
================================================
FILE: src/Render/Recovery/Recovery.scss
================================================
.App{
perspective: 1000px;
perspective-origin: 100vw 105px;
z-index: 10000;
}
.recovery-notification-container {
position: fixed;
right: 0;
top: 0;
transform-origin: right center;
transition: ease-in-out 0.5s;
&.show {
transform: rotate3d(0, 1, 0, 0deg);
opacity: 1;
}
&:not(.show) {
transform: rotate3d(0, 1, 0, -90deg);
opacity: 0;
}
.recovery-notification {
@media screen and (max-width: 520px) {
display: none;
}
position: fixed;
top: 30px;
right: 30px;
width: 363px;
height: 75px;
display: flex;
// justify-content: space-between;
align-items: center;
background-color: rgb(255, 255, 255, 0.72);
box-shadow: 0 0 80px 0 rgba(0, 0, 0, 0.25);
backdrop-filter: blur(15px);
border-radius: 15px;
transition: ease-in-out 0.3s;
// zoom: 1.2;
&:hover {
background-color: rgba(255, 255, 255, 0.001);
}
.icon {
margin: 0 20px;
}
.icon,
.ok,
.no {
width: 36px;
height: 36px;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
}
.ok,
.no {
cursor: pointer;
position: absolute;
}
.icon,
.ok {
background-color: rgb(235, 10, 40, 0.1);
}
.no {
background-color: #d9d9d9;
right: 20px;
}
.icon {
svg {
width: 20px;
height: 20px;
}
}
.ok {
right: 60px;
transition: ease-in-out 0.3s;
&:hover {
background-color: rgb(235, 10, 40, 0.2);
}
svg {
width: 16px;
}
}
.no {
transition: ease-in-out 0.3s;
&:hover {
background-color: #c9c9c9;
}
svg {
width: 12px;
}
}
.text {
.title {
font-size: 16px;
}
.sub-title {
font-size: 12px;
}
font-weight: 500;
}
}
}
================================================
FILE: src/Render/Recovery/Recovery.tsx
================================================
import React, {
Dispatch,
SetStateAction,
useEffect,
useRef,
useState,
} from "react";
import { ReactComponent as RecoverIcon } from "../../Resource/Icon/clock.arrow.circlepath.svg";
import { ReactComponent as OkIcon } from "../../Resource/Icon/ok.svg";
import { ReactComponent as NoIcon } from "../../Resource/Icon/no.svg";
import "./Recovery.scss";
import { setDataFromJson, UserDataType } from "../../Data/UserData";
import { mediateMap, readFileFromIndexedDB } from "../../Common/util";
import classNames from "classnames";
import { useTranslation } from "react-i18next";
export function Recovery({
data,
setData,
recoveredFromError,
setRecoveredFromError,
transfromTools,
}: {
data: UserDataType;
setData: Dispatch>;
recoveredFromError: boolean;
setRecoveredFromError: Dispatch>;
transfromTools: {
scale: number;
setScale: React.Dispatch>;
translateX: number;
translateY: number;
setTranslateX: React.Dispatch>;
setTranslateY: React.Dispatch>;
};
}) {
const {t} = useTranslation();
const notificationRef = useRef(null);
const [showNotification, setShowNotification] = useState(false);
useEffect(() => {
const current = localStorage.getItem("current");
const show = current && !recoveredFromError;
setRecoveredFromError(false);
setShowNotification(!!show);
const handleClickCapture = (event: TouchEvent | MouseEvent) => {
if (
notificationRef.current &&
!notificationRef.current.contains(event.target as Node)
) {
console.log("recovery focusout");
setShowNotification(false);
}
};
document.addEventListener("touchstart", handleClickCapture, true);
document.addEventListener("click", handleClickCapture, true);
return () => {
document.removeEventListener("touchstart", handleClickCapture, true);
document.addEventListener("click", handleClickCapture, true);
};
}, []);
return (
{t('recover.text')}
{t('recover.subTitle')}
{
const current = localStorage.getItem("current");
if (current) {
const data = setDataFromJson(setData, current);
readFileFromIndexedDB("image")
.then((file) => {
setData((data) => ({
...data,
// backgroundColor: "image",
backgroundImage: file as File,
}));
})
.catch((e) => {
console.error(e);
});
mediateMap(data, transfromTools);
}
setShowNotification(false);
}}
>
{
setShowNotification(false);
}}
>
);
}
================================================
FILE: src/Resource/Icon/airwave.tsx
================================================
import React from "react";
const AirWaveIcon =({className}:{className?: string})=> (
);
export default AirWaveIcon;
================================================
FILE: src/Resource/Icon/arrow.tsx
================================================
import React from "react";
const arrowIcon = ({ className }: { className?: string }) => (
);
export default arrowIcon;
================================================
FILE: src/Resource/Icon/auto.tsx
================================================
import React from "react";
const AutoIcon = ({ className }: { className?: string }) => (
);
export default AutoIcon;
================================================
FILE: src/Resource/Icon/bolt.tsx
================================================
import React from "react";
const BoltIcon = ({ className }: { className?: string }) => (
);
export default BoltIcon;
================================================
FILE: src/Resource/Icon/edit.tsx
================================================
import React from "react";
const edit =({className}:{className: string})=> (
);
export default edit;
================================================
FILE: src/Resource/Icon/expand.tsx
================================================
import React from "react";
const expand =({className}:{className?: string})=> (
);
export default expand;
================================================
FILE: src/Resource/Icon/export.tsx
================================================
import React from "react";
const ExportIcon = ({ className }: { className?: string }) => (
);
export default ExportIcon;
================================================
FILE: src/Resource/Icon/finished.tsx
================================================
import React from "react";
const FinishedIcon =({className}:{className?: string})=> (
);
export default FinishedIcon;
================================================
FILE: src/Resource/Icon/goto.tsx
================================================
import React from "react";
const GoToIcon = ({ className }: { className?: string }) => (
);
export default GoToIcon;
================================================
FILE: src/Resource/Icon/infinity.tsx
================================================
import React from "react";
const Infinity = ({ className }: { className?: string }) => (
);
export default Infinity;
================================================
FILE: src/Resource/Icon/pageview.tsx
================================================
import React from "react";
const PageViewIcon =({className}:{className?: string})=> (
);
export default PageViewIcon;
================================================
FILE: src/Resource/Icon/plus.tsx
================================================
import React from "react";
const plusIcon = ({ className }: { className?: string }) => (
);
export default plusIcon;
================================================
FILE: src/Resource/Icon/shrink.tsx
================================================
import React from "react";
const shrink =({className}:{className: string})=> (
);
export default shrink;
================================================
FILE: src/Resource/Icon/touch.tsx
================================================
import React from "react";
const TouchIcon = ({ className }: { className?: string }) => (
);
export default TouchIcon;
================================================
FILE: src/Resource/Shape/shape.tsx
================================================
import React, { CSSProperties } from "react";
const shapes = {
cicle: (
),
square: (
),
triangle: (
),
start: (
),
pentagon: (
),
hexagon: (
),
cross: (
),
rhombus: (
),
diamond: (
),
leaf: (
),
ginkgo: (
),
};
export const shapesWithStyle = (style?: CSSProperties, className?: string) => ({
cicle: (
),
square: (
),
triangle: (
),
start: (
),
pentagon: (
),
hexagon: (
),
cross: (
),
rhombus: (
),
diamond: (
),
leaf: (
),
ginkgo: (
),
});
export default shapes;
================================================
FILE: src/Style/Cursor.ts
================================================
import { Mode } from "../DataStructure/Mode";
export const getCursor = (mode: Mode)=>{
let cursor;
//chrome has bug here, the cursor won't change sometimes while DOM already updated.
switch(mode){
case Mode.moving:{
cursor = 'grabbing';
break;
}
default:{
cursor = 'default';
break;
}
}
return cursor;
}
================================================
FILE: src/WelcomeTour/Driver.ts
================================================
import { driver as Driver, Config } from "driver.js";
import {t} from 'i18next';
export const showTour = async (id: string, callback: Function) => {
const driver = Driver();
const { getSteps } = await import(/* webpackMode: "eager" */ `./Steps/${id}`);
const eventListeners: EventListenerOrEventListenerObject[] = [];
const touch = window.ontouchend === null;
const eventName =// touch ? "touchend" :
"click";
const config: Config = {
prevBtnText: t('shang-yi-bu'),
doneBtnText: t('wan-cheng'),
nextBtnText: t('xia-yi-bu'),
progressText: "{{current}} / {{total}}",
showProgress: true,
allowClose: false,
showButtons: ["close"],
// onPrevClick:(ele)=>{
// if(ele){
// const element = ele as HTMLElement;
// eventListeners.forEach(listener=>{
// element.removeEventListener('click',listener);
// })
// driver.movePrevious();
// }
// },
onHighlighted: (ele, step, opt) => {
const steps = opt.config.steps!;
const last = step === steps[steps.length - 1];
const next = steps[opt.state.activeIndex! + 1];
if (ele) {
const element = ele as HTMLElement;
const moveToNext = () => {
const gotoNext = () => {
// element.removeEventListener(eventName, moveToNext);
element.removeEventListener("click", moveToNext);
element.removeEventListener("touchend", moveToNext);
if(driver.getActiveStep()===step)
driver.moveNext();
};
setTimeout(() => {
if (!next || document.querySelector(next.element as string)) {
gotoNext();
}else{
setTimeout(()=>{
if (!next || document.querySelector(next.element as string))
gotoNext()
},300)
}
}, 100);
};
eventListeners.push(moveToNext);
element.addEventListener("click", moveToNext);
element.addEventListener("touchend", moveToNext);
}
},
onNextClick: (ele) => {
if (ele) {
const element = ele as HTMLElement;
element.dispatchEvent(
new Event("click", { bubbles: true, cancelable: true })
);
}
},
steps: getSteps(driver),
onDestroyed: (ele,step) => {
if (ele) {
const element = ele as HTMLElement;
if(step.element!==".tour-btn")
element.dispatchEvent(
new Event("click", { bubbles: true, cancelable: true })
);
}
callback();
},
};
driver.setConfig(config);
driver.drive();
};
================================================
FILE: src/WelcomeTour/HightLights.tsx
================================================
import React from "react";
import Infinity from "../Resource/Icon/infinity";
import AirWaveIcon from "../Resource/Icon/airwave";
import PageViewIcon from "../Resource/Icon/pageview";
import BoltIcon from "../Resource/Icon/bolt";
import TouchIcon from "../Resource/Icon/touch";
import AutoIcon from "../Resource/Icon/auto";
import ExportIcon from "../Resource/Icon/export";
import {t} from 'i18next';
export const hightLights = [
{
id:'common-operation',
icon: ,
title: t('wu-xian-zi-chan'),
subTitle: t('wu-xian-da-hua-bu'),
introText: [
[false, t('zhi-chi'), t('wu-xian-tiao-xian-lu')],
[false, t('zhi-chi'), t('wu-xian-ge-zhan-dian')],
],
more: t('le-jie-ji-ben-cao-zuo')
},
{
id:'line-card',
icon: ,
title: t('ling-huo-zou-xian'),
subTitle: t('yi-tiao-xian-lu-neng-duo-ci-chuan-guo-tong-yi-zhan'),
introText: [
[false, t('zhi-chi'), t('q-zi-zou-xian-yu-zou-xian')],
[false, t('zhi-chi-she-zhi'), t('zhi-xian')],
],
more: t('le-jie-xian-lu-she-zhi')
},
{
id:'station-card',
icon: ,
title: t('fang-bian-cha-kan'),
subTitle: t('ka-pian-hua-zhan-shi-zhan-dian-yu-xian-lu'),
introText: [
[false, t('zhan-dian-xian-lu-hu-xiang'), t('guan-lian')],
[true, t('gao-liang'), t("zhan-shi-xuan-zhong-zhan-dian-yu-xian-lu"),]
],
more: t('le-jie-zhan-dian-ka-pian')
},
{
id:'quick-edit',
icon: ,
title: t('gao-xiao-bian-ji'),
subTitle: t('lian-xu-chuang-jian-zhan-dian-mo-shi'),
introText: [
[true, t('lian-xu-tian-jia-zhan-dian'), t('mo-shi')],
[false, t('zhi-chi'), t('che-hui'), t('yu'), t('zhong-zuo')],
],
more: t('le-jie-kuai-su-chuang-jian')
},
{
id:'mobile',
icon: ,
title: t('yi-dong-duan-zhi-chi'),
subTitle: t('wan-zheng-de-chu-kong-shi-jian-zhi-chi'),
introText: [
[true, t('dan-zhi'), t('tuo-dong'), t('shuang-zhi'), t('suo-fang-di-tu')],
[false, t('shi-bie-dong-zuo-yi-tu'), t('jian-shao-wu-cao-zuo')],
],
more: t('zai-ping-ban-shang-ti-yan')
},
{
id:'tag-setting',
icon: ,
title: t('zi-dong-bi-rang'),
subTitle: t('zi-dong-tian-jia-pian-yi-zhi'),
introText: [
[false, t('gong-xian-xian-lu'), t('bu-hui-zhong-die')],
[false, t('zhan-dian-ming-cheng-zi-dong-xuan-ze-bai-fang-wei-zhi')],
],
more: t('le-jie-biao-qian-she-zhi')
},
{
id:'export',
icon: ,
title: t('dao-ru-dao-chu'),
subTitle: t('dao-chu-gao-fen-bian-shuai-tu-pian'),
introText: [
[false, t('dao-chu'), t('pngsvg-tu-pian')],
[false, t('zhi-chi-cong-mo-ban-chuang-jian')],
],
more: t('le-jie-dao-ru-dao-chu')
},
];
================================================
FILE: src/WelcomeTour/Steps/common-operation.ts
================================================
import { DriveStep, Driver } from "driver.js";
import {t} from 'i18next';
export const getSteps = (driver: Driver): DriveStep[] => [
{
element: ".ScaleLayer",
onHighlighted:()=>{},
popover: {
title: t('tour.tryScale'),
description: window.ontouchend === null? t('tour.touchScale'): t('tour.mouseScale'),
showButtons:['next'],
onNextClick:driver.moveNext
},
},
{
element: ".title .click-panel",
popover: {
title: t('tour.clickTile'),
description: t('tour.clickOpenMenu'),
},
},
{
element: ".title",
popover: {
title: t('zai-ci-dian-ji-biao-ti'),
description: t('ke-yi-xiu-gai-biao-ti'),
},
},
{
element: ".menu",
popover: {
title: t('dian-ji-ren-yi-kong-bai-qu-yu-tui-chu-cai-dan'),
description: t('tui-chu-cai-dan'),
},
},
{
element: ".station-descend-31",
popover: {
title: t('dian-ji-zhan-dian'),
description: t('yi-da-kai-zhan-dian-ka-pian'),
},
},
{
element: ".station-card",
popover: {
title: t('zhan-dian-ka-pian'),
description: t('ke-yi-zai-zhe-li-bian-ji-zhan-dian-de-suo-you-she-zhi'),
showButtons:["next"],
// onNextClick:driver.moveNext
},
},
{
element: ".ScaleLayer",
popover: {
title: t('chang-shi-dian-ji-xian-lu'),
description: t('tu-zhong-de-cai-se-xian-tiao-ji-wei-xian-lu-ru-guo-wu-fa-xuan-zhong-ke-yi-chang-shi-fang-da-di-tu-zai-dian-xuan'),
},
},
{
element: ".line-card",
popover: {
title: t('xian-lu-ka-pian'),
description: t('ke-yi-zai-zhe-li-bian-ji-xian-lu-de-suo-you-she-zhi'),
showButtons:["next"],
// onNextClick:driver.moveNext
},
},
];
================================================
FILE: src/WelcomeTour/Steps/export.ts
================================================
import { DriveStep, Driver } from "driver.js";
import { browserInfo } from "../../Common/util";
import {t} from 'i18next';
export const getSteps = (driver: Driver): DriveStep[] => {
const { engine } = browserInfo;
const webkit = engine.name === "WebKit";
return[
{
element: ".title .click-panel",
popover: {
title: t('dian-ji-biao-ti'),
description: t('dian-ji-biao-ti-da-kai-cai-dan'),
},
},
{
element: ".existed-map-btn",
popover: {
title: t('cong-mo-ban-xin-jian'),
description: t('zai-yi-you-de-di-tu-shang-xiu-gai'),
},
},
{
element: ".tools",
onHighlighted:()=>{},
popover: {
title: t('xuan-ze-yi-ge-ni-xi-huan-de-cheng-shi'),
description: t('ran-hou-dian-ji-xia-yi-bu'),
showButtons:["next"],
onNextClick:()=>{
driver.moveNext();
}
},
},
{
element: ".confirm-add-from-existed-map-btn",
popover: {
title: t('yi-ci-wei-mo-ban-xin-jian'),
description: t('que-ren-xin-jian-qian-qing-que-bao-yi-jing-bao-cun-le-dang-qian-de-di-tu'),
},
},
{
element: ".title .click-panel",
popover: {
title: t('da-kai-cai-dan'),
description: t('xuan-ze-zuo-wei-wen-jian-dao-chu'),
},
},
{
element: ".export-as-file-btn",
popover: {
title: t('zuo-wei-wen-jian-dao-chu'),
description: t('xuan-ze-zuo-wei-wen-jian-dao-chu-0'),
},
},
{
element: ".import-file-btn",
onHighlighted:()=>{},
popover: {
title: t('dao-ru-gang-cai-dao-chu-wen-jian'),
description: t('xia-ci-dian-ji-dao-ru-wen-jian-ke-yi-ji-xu-bian-ji'),
showButtons:["next"],
onNextClick:driver.moveNext
},
},
{
element: `.export-as${webkit?'-svg':''}-image-btn`,
popover: {
title: t('dao-chu-tu-pian'),
description: t('dian-ji-dao-chu-tu-pian-dao-chu-ke-neng-hui-dao-zhi-ka-dun-ji-miao-shu-yu-zheng-chang-xian-xiang'),
},
},
{
element: `.recover-btn`,
popover: {
title: t('cong-huan-cun-zhong-hui-fu-shu-ju'),
description: t('ru-guo-mei-bao-cun-de-shi-hou-bu-xiao-xin-shua-xin-ke-yi-dian-ji-zhe-li-hui-fu-shu-ju'),
},
},
{
element: `.export-recover-btn`,
popover: {
title: t('dao-chu-hui-fu-shu-ju'),
description: t('ru-guo-mou-yi-bu-cao-zuo-hou-dao-zhi-bao-cuo-huo-beng-kui-dian-ji-zhe-li-dao-chu-zui-hou-yi-ci-zheng-que-de-shu-ju-ru-guo-yu-dao-zhe-zhong-qing-kuang-qing-fan-kui-gei-zuo-zhe'),
},
},
];
}
================================================
FILE: src/WelcomeTour/Steps/line-card.ts
================================================
import { DriveStep, Driver } from "driver.js";
import {t} from 'i18next';
export const getSteps = (driver: Driver): DriveStep[] => [
{
element: ".ScaleLayer",
popover: {
title: t('dian-ji-ren-yi-xian-lu'),
description: t('tu-zhong-cai-se-qu-xian-biao-shi-di-tie-xian-lu'),
},
},
{
element: ".line-card",
popover: {
title: t('xian-lu-ka-pian-0'),
description: t('ke-yi-zai-zhe-li-bian-ji-xian-lu-de-suo-you-she-zhi-0'),
showButtons:["next"],
// onNextClick:driver.moveNext
},
},
{
element: ".bend-first",
popover: {
title: t('dian-ji-ci-chu-kong-zhi-xian-lu-zai-liang-zhan-zhi-jian-de-zou-xiang'),
description: t('zhan-dian-qu-jian-lian-xian-zhi-you-liang-zhong-fang-shi-xie-xian-yu-zhi-xian'),
},
},
{
element: ".expand",
popover: { title: t('fang-da-an-niu'), description: t('ke-yi-tuo-kuan-xian-lu-ka-pian') },
},
{
element: ".shrink",
popover: { title: t('suo-xiao-an-niu'), description: t('zai-suo-xiao-zhuang-tai-ke-yi-dian-ji-geng-duo-she-zhi') },
},
{
element: ".edit",
popover: { title: t('bian-ji-an-niu'), description: t('jin-ru-geng-duo-she-zhi-mian-ban') },
},
{
element: ".name-detail",
popover: { title: t('ci-chu-xiu-gai-xian-lu-ming-cheng-yu-biao-shi'), description: t('pai-xu-ke-yi-kong-zhi-xian-lu-jian-de-zhe-dang-guan-xi') ,showButtons:["next"],onNextClick:driver.moveNext},
},
{
element: ".edit-tool.color",
popover: { title: t('dian-ji-ci-chu-xiu-gai-xian-lu-biao-shi-se'), description: t('zuo-xia-jiao-de-yan-se-xuan-ze-qi-ke-yi-qu-se') ,side:"right"},
},
{
element: ".edit-tool.operation",
popover: { title: t('dian-ji-ci-chu-shan-chu-xian-lu-huo-zhe-she-zhi-zhi-xian'), description: t('she-zhi-zhi-xian-hou-xian-lu-hui-yi-xu-xian-xian-shi-qie-lian-jie-chu-bu-zai-xian-shi-ba-shou'),side:"right" },
},
{
element: ".done",
popover: { title: t('dian-ji-ci-chu-wan-cheng-she-zhi'), description: t('hui-dao-zhan-dian-ye') },
},
{
element: ".stations-count",
popover: { title: t('xian-shi-tu-jing-zhan-dian-ka-pian'), description: t('dian-ji-ci-chu-suo-yi-tu-jing-de-zhan-dian-ka-pian') },
},
];
================================================
FILE: src/WelcomeTour/Steps/quick-edit.ts
================================================
import { DriveStep, Driver } from "driver.js";
import {t} from 'i18next';
export const getSteps = (driver: Driver): DriveStep[] => [
{
element: ".title .click-panel",
popover: {
title: t('da-kai-cai-dan-0'),
description: t('dian-ji-biao-ti-da-kai-cai-dan-0'),
},
},
{
element: "#menu-add-station",
popover: {
title: t('dian-ji-tian-jia-zhan-dian'),
description: t('jin-ru-tian-jia-zhan-dian-mo-shi'),
},
},
{
element: ".ScaleLayer",
onHighlighted: () => {},
popover: {
title: t('dian-ji-ren-yi-kong-bai-chu-tian-jia-zhan-dian'),
description: t('tian-jia-hao-zhi-hou-dian-ji-xia-yi-bu-jian-yi-nin-zai-ping-mu-zuo-shang-jiao-de-kong-bai-qu-yu-tian-jia-san-ge-zhan-dian'),
showButtons: ["next"],
onNextClick: () => {
driver.moveNext();
},
},
},
{
element: "#add-station-finish-btn",
popover: { title: t('dian-ji-wan-cheng'), description: t('tui-chu-bian-ji-mo-shi') },
},
{
element: ".station-descend-1",
popover: {
title: t('dian-ji-gang-cai-chuang-jian-de-zhan-dian'),
description: t('da-kai-zhan-dian-xin-xi-ka-pian'),
},
},
{
element: ".station-card-operation",
popover: { title: t('dian-ji-cao-zuo-xuan-xiang-ka'), description: t('wo-men-lai-tian-jia-xian-lu') },
},
{
element: ".add-new-line-btn",
popover: {
title: t('dian-ji-yi-ci-wei-qi-dian-xin-jian-xian-lu'),
description: t('jin-ru-tian-jia-xian-lu-mo-shi'),
},
},
{
element: ".station-descend-2",
popover: {
title: t('dian-ji-zhan-dian-0'),
description: t('lian-jie-zhan-dian'),
},
},
{
element: ".station-descend-3",
popover: {
title: t('dian-ji-zhan-dian'),
description: t('lian-jie-zhan-dian'),
},
},
{
element: "#add-line-finish-btn",
popover: { title: t('dian-ji-wan-cheng'), description: t('xian-lu-jiu-chuang-jian-hao-le') },
},
];
================================================
FILE: src/WelcomeTour/Steps/skip.ts
================================================
import { DriveStep, Driver } from "driver.js";
export const getSteps = (driver: Driver): DriveStep[] => [
{
element: ".title .click-panel",
// onHighlighted:()=>{},
popover: {
title: "下次可以点这里再次打开教程",
description: "点击标题打开菜单",
showButtons:["next"],
// onNextClick:driver.moveNext
},
},
{
element: ".tour-btn",
onHighlighted:()=>{},
popover: {
title: "使用教程",
description: "点这里再次打开",
showButtons:["next"],
onNextClick: driver.moveNext,
},
},
];
================================================
FILE: src/WelcomeTour/Steps/station-card.ts
================================================
import { DriveStep, Driver } from "driver.js";
import {t} from 'i18next';
export const getSteps = (driver: Driver): DriveStep[] => [
{
element: ".station-descend-31",
popover: {
title: t('dian-ji-zhan-dian'),
description: t('yi-da-kai-zhan-dian-ka-pian-0'),
},
},
{
element: ".station-card",
popover: {
title: t('zhan-dian-ka-pian'),
description: t('ke-yi-zai-zhe-li-bian-ji-zhan-dian-de-suo-you-she-zhi'),
showButtons:["next"],
// onNextClick:driver.moveNext
},
},
{
element: ".name-detail",
popover: {
title: t('bian-ji-zhan-dian-wei-zhi'),
description: t('dian-ji-zuo-biao-zhi-hou-shi-yong-shu-biao-gun-lun-ke-yi-jing-que-tiao-jie'),
showButtons:["next"],
// onNextClick:driver.moveNext
},
},
{
element: ".edit-tool.color",
popover: { title: t('dian-ji-ci-chu-xiu-gai-zhan-dian-xing-zhuang'), description: t('xiu-gai-xing-zhuang') ,side:"right"},
},
{
element: ".edit-tool.operation",
popover: { title: t('dian-ji-ci-chu-shan-chu-zhan-dian-huo-xin-jian-xian-lu'), description: t('shan-chu-huo-xin-jian'),side:"right" },
},
{
element: ".edit-tool.operation.tag",
popover: { title: t('dian-ji-ci-chu-she-zhi-zhan-dian-ming-cheng-wei-zhi'), description: t('zhan-dian-ming-wei-zhi') },
},
{
element: ".tag-detail",
popover: { title: t('dian-ji-hui-se-de-fang-kuai-xuan-ze-fang-wei'), description: t('dian-ji-zhong-jian-de-wen-zi-hui-fu-zi-dong-wei-zhi'),showButtons:['next'],onNextClick:driver.moveNext },
},
];
================================================
FILE: src/WelcomeTour/Steps/tag-setting.ts
================================================
import { DriveStep, Driver } from "driver.js";
import {t} from 'i18next';
export const getSteps = (driver: Driver): DriveStep[] => [
{
element: ".title .click-panel",
popover: {
title: t('dian-ji-biao-ti'),
description: t('dian-ji-biao-ti-da-kai-cai-dan-1'),
},
},
{
element: ".auto-hidden-btn",
popover: {
title: t('guan-bi-zi-dong-yin-cang'),
description: t('zhan-dian-ming-hui-zai-di-tu-suo-fang-dao-xiao-chi-du-shi-zi-dong-yin-cang-guan-bi-zi-dong-yin-cang-yi-que-bao-ke-yi-yi-zhi-xian-shi-zhan-dian-ming-cheng'),
},
},
{
element: ".menu",
popover: {
title: t('dian-ji-ren-yi-kong-bai-qu-yu-tui-chu-cai-dan-0'),
description: t('tui-chu-cai-dan-0'),
},
},
{
element: ".station-descend-31",
popover: {
title: t('dian-ji-zhan-dian'),
description: t('yi-da-kai-zhan-dian-ka-pian-1'),
},
},
{
element: ".edit-tools",
popover: { title: t('zai-zhe-li-xiang-xia-gun-dong-yi-xia'), description: t('zai-dian-ji-biao-qian-an-niu') },
},
{
element: ".tag-detail",
popover: { title: t('chang-shi-gai-bian-zhan-dian-ming-dao-you-xia-jiao'), description: t('dian-ji-you-xia-jiao-de-hui-se-fang-kuai') },
},
{
element: ".station-name-descend-31",
popover: { title: t('ke-yi-kan-dao-zhan-dian-ming-yi-jing-wei-yu-zhan-dian-you-xia-jiao-le'), description: t('ke-yi-yong-zhe-zhong-fang-fa-xiu-gai-zhan-dian-ming-wei-zhi'), showButtons:["next"],onNextClick:driver.moveNext },
},
{
element: ".tag-item.center",
popover: { title: t('dian-ji-zhong-jian-de-wen-zi-hui-fu-dao-zi-dong-xuan-ze-wei-zhi'), description: t('hui-fu-dao-zi-dong-xuan-ze-wei-zhi') },
},
];
================================================
FILE: src/WelcomeTour/WelcomeTour.scss
================================================
.welcome-tour-container {
position: fixed;
height: 100vh;
width: 100vw;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, 0.4);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
font-weight: 500;
font-size: 18px;
.welcome-tour {
margin-top: -10vh;
// width: 800px;
// min-width: 45vw;
width: 80vw;
max-width: 800px;
height: 475px;
background-color: #454545;
border-radius: 12px;
.header {
margin: 33.3px 33.3px 20px 33.3px;
white-space: nowrap;
position: relative;
.icon {
img {
width: 40px;
height: 40px;
border-radius: 5px;
}
}
.title {
display: inline-block;
margin-left: 13px;
vertical-align: top;
.sub-title {
font-size: 12px;
color: white;
width: fit-content;
margin-top: -2px;
}
.main-title {
color: white;
font-weight: 800;
width: fit-content;
}
}
.control {
display: inline-flex;
vertical-align: top;
font-size: 12px;
position: absolute;
right: 0;
height: 40px;
justify-content: center;
align-items: center;
.skip-tour {
color: rgba(255, 255, 255, 0.6);
margin: 0 20px;
cursor: pointer;
// text-transform: capitalize;
}
.start-tour {
text-transform: capitalize;
cursor: pointer;
color: white;
padding: 7.33px 20px;
background-color: #2196f3;
border-radius: 35px;
}
}
}
.divider {
border-bottom: rgba(255, 255, 255, 0.27) solid 1px;
}
.body {
white-space: nowrap;
overflow-x: scroll;
position: relative;
&::-webkit-scrollbar {
display: none;
}
.intro {
display: inline-block;
color: white;
width: 285px;
margin-left: 38px;
margin-top: 36.67px;
vertical-align: top;
&:last-child{
margin-right: 38px;
}
.hight-light {
display: inline-flex;
justify-content: center;
align-items: center;
background-color: #727272;
padding: 4px 15px;
border-radius: 35px;
.icon {
height: 20px;
width: 20px;
svg {
height: 20px;
width: 20px;
}
.air{
width: 16px;
margin-left: 2px;
}
}
.title {
margin-left: 5px;
text-transform: capitalize;
}
}
.detail-card {
height: 240px;
background-color: #727272;
border-radius: 14px;
margin-top: 17.3px;
display: flow-root;
position: relative;
.qrcode-container{
position: absolute;
border-radius: 14px;
margin: auto;
left: 0;
right: 0;
top:0;
bottom: 0;
width: 100%;
height: 100%;
background-color: #0000002e;
backdrop-filter: blur(10px);
cursor: pointer;
opacity: 0;
pointer-events: none;
transition: 0.3s ease-in-out;
&.show{
opacity: 1;
pointer-events: auto;
}
canvas{
position: absolute;
border-radius: 14px;
margin: auto;
left: 0;
right: 0;
top:0;
bottom: 0;
}
}
.title {
margin-top: 20.67px;
margin-left: 24px;
width: 265px;
white-space: normal;
text-transform: capitalize;
}
.left-down {
position: absolute;
left: 24px;
bottom: 20.67px;
.intro-text {
.intro-text-line {
&:not(.line-card-text){
text-transform: capitalize;
}
:not(.emphasis) {
color: rgba(255, 255, 255, 0.6);
}
}
}
.more {
margin-top: 26px;
cursor: pointer;
&:hover{
// text-decoration: underline;
opacity: 0.9;
}
.more-text {
text-transform: capitalize;
&.finished{
opacity: 0.6;
}
}
.more-icon {
height: 20px;
width: 28px;
vertical-align: top;
display: inline-block;
svg {
height: 28px;
width: 28px;
&.finished{
width: 21px;
// margin-top: -1px;
margin-left: 2px;
opacity: 0.6;
}
}
}
}
}
}
}
}
}
}
================================================
FILE: src/WelcomeTour/WelcomeTour.tsx
================================================
import React, {
LegacyRef,
MutableRefObject,
RefObject,
useEffect,
useRef,
useState,
useTransition,
} from "react";
import "./WelcomeTour.scss";
import Infinity from "../Resource/Icon/infinity";
import GoToIcon from "../Resource/Icon/goto";
import { hightLights } from "./HightLights";
import classNames from "classnames";
import { onWheelX } from "../Common/util";
import { showTour } from "./Driver";
import { ShowTourProps, UserDataType } from "../Data/UserData";
import FinishedIcon from "../Resource/Icon/finished";
import QRCode from "qrcode";
import { useTranslation } from "react-i18next";
export function WelcomeTour({
showTour: show,
setShowTour: setShow,
}: ShowTourProps) {
const [qrCode, setQRCode] = useState(false);
const {t} = useTranslation();
const [visitedSteps, setVisitedSteps] = useState(() => {
const visitedStepsJson = localStorage.getItem("visited-steps");
return visitedStepsJson ? JSON.parse(visitedStepsJson) : [];
});
const setVisited = (step: string) => {
const steps = visitedSteps.concat([step]);
localStorage.setItem("visited-steps", JSON.stringify(steps));
setVisitedSteps(steps);
};
const reset = () => {
localStorage.setItem("visited-steps", JSON.stringify([]));
setVisitedSteps([]);
localStorage.setItem("skip-tour-viewed", "Y");
};
const next = hightLights.find(
(highlight) => !visitedSteps.includes(highlight.id)
);
const canvasRef = useRef(null);
const showQRCode = () => {
setQRCode(true);
setVisited("mobile");
setTimeout(() => {
QRCode.toCanvas(canvasRef.current, window.location.href);
});
};
const scrollToNext = ()=>{
setTimeout(() => {
const contianer = document.querySelector(".welcome-tour .body");
if(next && contianer){
const nextIntro = document.querySelector(`.intro-${next.id}`)
if(nextIntro){
const {offsetLeft} = nextIntro as HTMLDivElement;
contianer.scrollTo(offsetLeft-38,0);
}
}
},100);
}
useEffect(() => {
scrollToNext();
}, []);
return (
{t('welcome.welcome')}
{t('welecome.minimetroweb')}
{next ? (
<>
{
setShow(false);
// if (!localStorage.getItem("skip-tour-viewed"))
// showTour("skip", () => {
localStorage.setItem("skip-tour-viewed", "Y");
// });
}}
>
{t('welcome.skip')}
{
if (next.id === "mobile") {
showQRCode();
} else {
setShow(false);
showTour(next.id, () => {
setShow(true);
setVisited(next.id);
scrollToNext();
});
}
}}
>
{visitedSteps.length ? t('welcome.goOn') : ""}
{next.more}
>
) : (
<>
{t('welcome.restart')}
{
localStorage.setItem("skip-tour-viewed", "Y");
setShow(false);
}}
>
{t('welcome.done')}
>
)}
{hightLights.map((hightLight) => {
const { id, icon, title, subTitle, introText, more } = hightLight;
const finished = visitedSteps.includes(id);
return (
{icon}
{title}
{subTitle}
{introText.map((line) => {
const emphasisStart = line[0];
const lineText = line.slice(1);
return (
{lineText.map((text, index) => {
const emphasis =
(Number(emphasisStart) + index) % 2;
return (
{text}
);
})}
);
})}
{
if (id === "mobile") {
showQRCode();
} else {
showTour(id, () => {
setVisited(id);
setShow(true);
scrollToNext();
});
setShow(false);
}
}}
>
{more}
{finished ? (
) : (
)}
{id === "mobile" ? (
setQRCode(false)}
>
) : (
<>>
)}
);
})}
);
}
================================================
FILE: src/i18n/config.ts
================================================
import i18n from 'i18next';
import { initReactI18next } from 'react-i18next';
import translation_en from './locales/en.json';
import translation_zh from './locales/zh.json';
import LanguageDetector from 'i18next-browser-languagedetector';
const resources = {
en: { translation: translation_en },
zh: { translation: translation_zh },
};
i18n
.use(LanguageDetector)
.use(initReactI18next)
.init({
resources,
fallbackLng: 'en',
detection: {
order: ['queryString', 'cookie', 'localStorage', 'navigator', 'htmlTag', 'path', 'subdomain'],
caches: ['localStorage', 'cookie']
},
interpolation: { escapeValue: false },
});
export default i18n;
================================================
FILE: src/i18n/locales/en.json
================================================
{
"language": "english",
"menu": {
"alreadyAddStations": "{{count}} stations added",
"clickBlankToAddStation": "Click on the blank space to add a new station",
"redo": "Redo",
"withdraw": "Withdraw",
"AutoHidden": " Auto Hide",
"addStation": "Add Station",
"alreadyModified": "Modified {{count}} times",
"back": "Return",
"backgroudSetting": "Background Settings",
"cancel": "Cancel",
"changeBackgroudImage": "Modify Background Image",
"changeStationPosition": "Adjust Station Location",
"chooseALineFirst": "First select a line, then click",
"chooseAMap": "Choose a Map",
"clickAddStationToLine": "Click the station to insert it into the {{insertIndex}} station of {{lineName}}",
"deleteImage": "Delete Picture",
"done": "Finish",
"dragToChangePoistion": "Drag station to change location",
"hidden": "Hide",
"importBackgroudImage": "Import Background Image",
"importImage": "Import Picture",
"lightYellow": "Yellow",
"line": "Line",
"show": "Show",
"station": "Station",
"stationName": " Station Name",
"transparent": "Transparent",
"turnOff": "Disable",
"turnOn": "Enable",
"useTemplate": "Confirm",
"white": "White",
"RMP": "Rail Map Toolkit",
"about": "About",
"addBackgroundImage": "Set Background...",
"addFromTemplate": "Create Map From Template...",
"addNewMap": "New Blank Map...",
"author": "Author: Ryan",
"data": "Data",
"exportAsFile": "Export as File...",
"exportAsImage": "Export as Image...",
"exportAsSVG": "Export as Vector Image...",
"exportRecoveryData": "Export Recovered Data...",
"guide": "Usage Tutorial...",
"importFile": "Import File...",
"insertStation": "Insert Station...",
"mediateMap": "Centered Roadmap...",
"projectAddress": "Project Address...",
"recoverData": "Recover Data...",
"version": "Version: ",
"videoGuild": "Video Tutorial...",
"symbol": "logo",
"newMap": "New Map",
"switchLanguage": "使用中文"
},
"bei-jing": "Beijing",
"chang-sha": "Changsha",
"guang-zhou": "Guangzhou",
"shang-hai": "Shanghai",
"shen-zhen": "Shenzhen",
"tian-jin": "Tianjin",
"xiang-gang": "Hongkong",
"station": {
"auto": "automatic",
"delete": "Delete Station...",
"down": "bottom",
"downLeft": "lower left",
"left": "left side",
"leftUp": "upper left",
"lines": " Lines",
"operateDes": "new line / delete",
"operation": "Operate",
"poisition": "Position",
"remove": "Remove From {{lineName}}({{stationIndex}})",
"right": "right side",
"rightDown": "lower right",
"shape": "Shape",
"startHere": "New Line Starts Here...",
"stationName": "Label",
"up": "top",
"upRight": "upper right",
"x": "x-axios",
"y": "y-axios"
},
"shape": {
"cicle": "circle",
"cross": "cross",
"diamond": "diamond",
"ginkgo": "ginkgo",
"hexagon": "hexagon",
"leaf": "leaf",
"pentagon": "pentagon",
"rhombus": "diamond",
"square": "square",
"start": "star",
"triangle": "triangle"
},
"line": {
"asSubline": "Set as Subline line",
"bendFirst": "Bend First",
"color": "Color",
"delete": "delete",
"deleteLine": "Delete Line...",
"done": "Done",
"fromTo": "From {{from}} to {{to}}",
"insertHere": "insert here",
"insertStation": "insert here",
"name": "name",
"notInUse": "Not Yet Open",
"notSubLineAnymore": "Not as Subline",
"operation": "Operate",
"order": "order",
"stations": " Stations",
"straight": "Linar First"
},
"opacity": "Opacity",
"recover": {
"subTitle": "Recently edited maps detected.",
"text": "Need to recover data?"
},
"error": {
"dontWorry": "But don't worry, your map won't lost",
"exportError": "export the map file contains error...",
"exportNoErrorFile": "Export the map file before the error occurred",
"metError": "Sorry! \nProgram Error",
"orYouCould": "Or you can ",
"recoverFromCache": "Restore the pre-error map from cache",
"sendAuthor": "In order to send it to the author to analyze the cause of the error."
},
"huan-ying-shi-yong": "Welcome",
"welcome": {
"done": "Finish",
"goOn": "continue ",
"restart": "restart ",
"skip": "Skip for Now",
"welcome": "Welcome to"
},
"welecome": {
"minimetroweb": "Mini Metro Map Building Tool"
},
"chang-shi-dian-ji-xian-lu": "Try clicking the line",
"dian-ji-ren-yi-kong-bai-qu-yu-tui-chu-cai-dan": "Click on any empty area to exit the menu",
"dian-ji-zhan-dian": "click station",
"ke-yi-xiu-gai-biao-ti": "Title can be modified",
"ke-yi-zai-zhe-li-bian-ji-xian-lu-de-suo-you-she-zhi": "All settings for the route can be edited here",
"ke-yi-zai-zhe-li-bian-ji-zhan-dian-de-suo-you-she-zhi": "All settings for the station can be edited here",
"tour": {
"clickOpenMenu": "Click on the title to open the menu",
"clickTile": "click title",
"mouseScale": "Hold down the left mouse button and drag to move the map, scroll the mouse wheel to zoom the map",
"touchScale": "Drag the map with one finger and zoom the map with two fingers",
"tryScale": "Try zooming or moving the map"
},
"tu-zhong-de-cai-se-xian-tiao-ji-wei-xian-lu-ru-guo-wu-fa-xuan-zhong-ke-yi-chang-shi-fang-da-di-tu-zai-dian-xuan": "The colored lines in the picture are the lines. If you cannot select it, you can try to enlarge the map and click on it.",
"tui-chu-cai-dan": "Exit menu",
"xian-lu-ka-pian": "line card",
"yi-da-kai-zhan-dian-ka-pian": "to open the station card",
"zai-ci-dian-ji-biao-ti": "Click on the title again",
"zhan-dian-ka-pian": "station card",
"bu-hui-zhong-die": " won't overlap",
"che-hui": " withdraw",
"dan-zhi": "using finger",
"dao-chu": "Export",
"dao-chu-gao-fen-bian-shuai-tu-pian": "Export high-resolution images",
"dao-ru-dao-chu": "Import and export",
"fang-bian-cha-kan": "Easy to view",
"gao-liang": "Highlight",
"gao-xiao-bian-ji": "Efficient editing",
"gong-xian-xian-lu": "collinear lines",
"guan-lian": " linked",
"jian-shao-wu-cao-zuo": "Reduce misoperation",
"ka-pian-hua-zhan-shi-zhan-dian-yu-xian-lu": "Card display of stations and routes",
"le-jie-biao-qian-she-zhi": "Learn about label settings",
"le-jie-dao-ru-dao-chu": "Learn import and export",
"le-jie-ji-ben-cao-zuo": "Learn basic operations",
"le-jie-kuai-su-chuang-jian": "Learn about quick creat",
"le-jie-xian-lu-she-zhi": "Learn about line settings",
"le-jie-zhan-dian-ka-pian": "Learn about station cards",
"lian-xu-chuang-jian-zhan-dian-mo-shi": "Continuous station creation mode",
"lian-xu-tian-jia-zhan-dian": "Add stations continuously",
"ling-huo-zou-xian": "Flexible wiring",
"mo-shi": "",
"pngsvg-tu-pian": " PNG/SVG images",
"q-zi-zou-xian-yu-zou-xian": " Q shaped routing",
"shi-bie-dong-zuo-yi-tu": "",
"shuang-zhi": " or ",
"suo-fang-di-tu": "Zoom",
"tuo-dong": " drag",
"wan-zheng-de-chu-kong-shi-jian-zhi-chi": "Complete touch event support",
"wu-xian-da-hua-bu": "infinite canvas",
"wu-xian-ge-zhan-dian": " Unlimited stations",
"wu-xian-tiao-xian-lu": " Unlimited lines",
"wu-xian-zi-chan": "unlimited assets",
"yi-dong-duan-zhi-chi": "Mobile support",
"yi-tiao-xian-lu-neng-duo-ci-chuan-guo-tong-yi-zhan": "A line can pass through the same station multiple times",
"yu": " and",
"zai-ping-ban-shang-ti-yan": "Experience it on tablet",
"zhan-dian-ming-cheng-zi-dong-xuan-ze-bai-fang-wei-zhi": "label automatically place",
"zhan-dian-xian-lu-hu-xiang": "stations and lines",
"zhan-shi-xuan-zhong-zhan-dian-yu-xian-lu": " selected",
"zhi-chi": "support",
"zhi-chi-cong-mo-ban-chuang-jian": "creat from templates",
"zhi-chi-she-zhi": "Support settings",
"zhi-xian": " sub line",
"zhong-zuo": " redo",
"zi-dong-bi-rang": "automatic avoidance",
"zi-dong-tian-jia-pian-yi-zhi": "Automatically add offset values",
"cong-huan-cun-zhong-hui-fu-shu-ju": "Recover data from cache",
"cong-mo-ban-xin-jian": "New from template",
"da-kai-cai-dan": "Open menu",
"dao-chu-hui-fu-shu-ju": "Export recovery data",
"dao-chu-tu-pian": "Export pictures",
"dao-ru-gang-cai-dao-chu-wen-jian": "Import the file you just exported",
"dian-ji-biao-ti": "click title",
"dian-ji-biao-ti-da-kai-cai-dan": "Click on the title to open the menu",
"dian-ji-dao-chu-tu-pian-dao-chu-ke-neng-hui-dao-zhi-ka-dun-ji-miao-shu-yu-zheng-chang-xian-xiang": "Click to export the image. \nExporting may cause lag for a few seconds, which is normal.",
"que-ren-xin-jian-qian-qing-que-bao-yi-jing-bao-cun-le-dang-qian-de-di-tu": "Please make sure you have saved the current map before confirming the new creation.",
"ran-hou-dian-ji-xia-yi-bu": "Then click next",
"ru-guo-mei-bao-cun-de-shi-hou-bu-xiao-xin-shua-xin-ke-yi-dian-ji-zhe-li-hui-fu-shu-ju": "If you accidentally refresh without saving, you can click here to restore the data.",
"ru-guo-mou-yi-bu-cao-zuo-hou-dao-zhi-bao-cuo-huo-beng-kui-dian-ji-zhe-li-dao-chu-zui-hou-yi-ci-zheng-que-de-shu-ju-ru-guo-yu-dao-zhe-zhong-qing-kuang-qing-fan-kui-gei-zuo-zhe": "If a certain step results in an error or crash, click here to export the last correct data. \n(If you encounter this situation, please give feedback to the author)",
"xia-ci-dian-ji-dao-ru-wen-jian-ke-yi-ji-xu-bian-ji": "Click to import the file next time to continue editing.",
"xuan-ze-yi-ge-ni-xi-huan-de-cheng-shi": "Choose a city you like",
"xuan-ze-zuo-wei-wen-jian-dao-chu": "Select Export as file",
"xuan-ze-zuo-wei-wen-jian-dao-chu-0": "Select Export as file",
"yi-ci-wei-mo-ban-xin-jian": "Use this as a template to create a new",
"zai-yi-you-de-di-tu-shang-xiu-gai": "Modify on existing map",
"zuo-wei-wen-jian-dao-chu": "Export as file",
"bian-ji-an-niu": "edit button",
"ci-chu-xiu-gai-xian-lu-ming-cheng-yu-biao-shi": "Modify the line name and identification here",
"dian-ji-ci-chu-kong-zhi-xian-lu-zai-liang-zhan-zhi-jian-de-zou-xiang": "Click here to control the direction of the line between two stations",
"dian-ji-ci-chu-shan-chu-xian-lu-huo-zhe-she-zhi-zhi-xian": "Click here to delete a line or set up a sub line",
"dian-ji-ci-chu-suo-yi-tu-jing-de-zhan-dian-ka-pian": "Click here for site cards for all routes",
"dian-ji-ci-chu-wan-cheng-she-zhi": "Click here to complete setup",
"dian-ji-ci-chu-xiu-gai-xian-lu-biao-shi-se": "Click here to modify the line identification color",
"dian-ji-ren-yi-xian-lu": "Click on any line",
"fang-da-an-niu": "magnify button",
"hui-dao-zhan-dian-ye": "Return to site page",
"jin-ru-geng-duo-she-zhi-mian-ban": "Enter more settings panel",
"ke-yi-tuo-kuan-xian-lu-ka-pian": "Line cards can be expanded",
"ke-yi-zai-zhe-li-bian-ji-xian-lu-de-suo-you-she-zhi-0": "All settings for the route can be edited here",
"pai-xu-ke-yi-kong-zhi-xian-lu-jian-de-zhe-dang-guan-xi": "Sorting can control the occlusion relationship between lines",
"she-zhi-zhi-xian-hou-xian-lu-hui-yi-xu-xian-xian-shi-qie-lian-jie-chu-bu-zai-xian-shi-ba-shou": "After setting up the sub line, the line will be displayed as a dotted line, and the handle will no longer be displayed at the connection.",
"suo-xiao-an-niu": "zoom out button",
"tu-zhong-cai-se-qu-xian-biao-shi-di-tie-xian-lu": "The colored curves in the figure represent subway lines",
"xian-lu-ka-pian-0": "line card",
"xian-shi-tu-jing-zhan-dian-ka-pian": "Show route site cards",
"zai-suo-xiao-zhuang-tai-ke-yi-dian-ji-geng-duo-she-zhi": "In the zoomed-out state, you can click on more settings",
"zhan-dian-qu-jian-lian-xian-zhi-you-liang-zhong-fang-shi-xie-xian-yu-zhi-xian": "There are only two ways to connect sites, diagonal lines and straight lines.",
"zuo-xia-jiao-de-yan-se-xuan-ze-qi-ke-yi-qu-se": "The color picker in the lower left corner can select colors",
"da-kai-cai-dan-0": "Open menu",
"da-kai-zhan-dian-xin-xi-ka-pian": "Open the site information card",
"dian-ji-biao-ti-da-kai-cai-dan-0": "Click on the title to open the menu",
"dian-ji-cao-zuo-xuan-xiang-ka": "Click on the Actions tab",
"dian-ji-gang-cai-chuang-jian-de-zhan-dian": "Click on the site you just created",
"dian-ji-ren-yi-kong-bai-chu-tian-jia-zhan-dian": "Click any blank space to add a site",
"dian-ji-tian-jia-zhan-dian": "Click to add site",
"dian-ji-wan-cheng": "Click Finish",
"dian-ji-yi-ci-wei-qi-dian-xin-jian-xian-lu": "Click to create a new route starting from this point",
"dian-ji-zhan-dian-0": "click site",
"jin-ru-tian-jia-xian-lu-mo-shi": "Enter add line mode",
"jin-ru-tian-jia-zhan-dian-mo-shi": "Enter add site mode",
"lian-jie-zhan-dian": "connect site",
"tian-jia-hao-zhi-hou-dian-ji-xia-yi-bu-jian-yi-nin-zai-ping-mu-zuo-shang-jiao-de-kong-bai-qu-yu-tian-jia-san-ge-zhan-dian": "After adding, click Next (it is recommended that you add three sites in the blank area in the upper left corner of the screen)",
"tui-chu-bian-ji-mo-shi": "Exit edit mode",
"wo-men-lai-tian-jia-xian-lu": "Let's add the line",
"xian-lu-jiu-chuang-jian-hao-le": "The line is created",
"bian-ji-zhan-dian-wei-zhi": "Edit site location",
"dian-ji-ci-chu-shan-chu-zhan-dian-huo-xin-jian-xian-lu": "Click here to delete a site or create a new route",
"dian-ji-ci-chu-she-zhi-zhan-dian-ming-cheng-wei-zhi": "Click here to set site name location",
"dian-ji-ci-chu-xiu-gai-zhan-dian-xing-zhuang": "Click here to modify site shape",
"dian-ji-hui-se-de-fang-kuai-xuan-ze-fang-wei": "Click on the gray square to select the direction",
"dian-ji-zhong-jian-de-wen-zi-hui-fu-zi-dong-wei-zhi": "Click on the text in the middle to restore the automatic position",
"dian-ji-zuo-biao-zhi-hou-shi-yong-shu-biao-gun-lun-ke-yi-jing-que-tiao-jie": "Click the coordinates and use the mouse wheel to adjust accurately",
"shan-chu-huo-xin-jian": "Delete or create new",
"xiu-gai-xing-zhuang": "Modify shape",
"yi-da-kai-zhan-dian-ka-pian-0": "to open the site card",
"zhan-dian-ming-wei-zhi": "Site name location",
"chang-shi-gai-bian-zhan-dian-ming-dao-you-xia-jiao": "Try changing the site name to the lower right corner",
"dian-ji-biao-ti-da-kai-cai-dan-1": "Click on the title to open the menu",
"dian-ji-ren-yi-kong-bai-qu-yu-tui-chu-cai-dan-0": "Click on any empty area to exit the menu",
"dian-ji-you-xia-jiao-de-hui-se-fang-kuai": "Click on the gray square in the lower right corner",
"dian-ji-zhong-jian-de-wen-zi-hui-fu-dao-zi-dong-xuan-ze-wei-zhi": "Click on the text in the middle to return to the automatically selected position",
"guan-bi-zi-dong-yin-cang": "Turn off auto-hide",
"hui-fu-dao-zi-dong-xuan-ze-wei-zhi": "Revert to automatically selected location",
"ke-yi-kan-dao-zhan-dian-ming-yi-jing-wei-yu-zhan-dian-you-xia-jiao-le": "You can see that the site name is already in the lower right corner of the site",
"ke-yi-yong-zhe-zhong-fang-fa-xiu-gai-zhan-dian-ming-wei-zhi": "You can use this method to modify the location of the site name",
"tui-chu-cai-dan-0": "Exit menu",
"yi-da-kai-zhan-dian-ka-pian-1": "to open the site card",
"zai-dian-ji-biao-qian-an-niu": "Click the label button again",
"zai-zhe-li-xiang-xia-gun-dong-yi-xia": "Scroll down a bit here",
"zhan-dian-ming-hui-zai-di-tu-suo-fang-dao-xiao-chi-du-shi-zi-dong-yin-cang-guan-bi-zi-dong-yin-cang-yi-que-bao-ke-yi-yi-zhi-xian-shi-zhan-dian-ming-cheng": "Site names are automatically hidden when the map is zoomed to a smaller scale. \nTurn off auto-hide to ensure the site name is always visible",
"di-current-bu-gong-total-bu": "Step {0} of {1} steps",
"shang-yi-bu": "Previous Step",
"wan-cheng": "Finish",
"xia-yi-bu": "Next Step",
"delete": {
"cancel": "Cancel",
"delete": "delete",
"remove": "Remove",
"subtile": "No longer as the {{index}} station of {{lineName}}"
},
"que-shi-yao-deletetext-ma": "Do you really want to {{deleteText}}?",
"teyvat": "Teyvat",
"newStation": "New Station",
"lineNo": "Line {{lineId}}",
"Common-teyvat-192be7b183f6eee79-1": "Eagle Beach",
"Common-teyvat-192be7b183f6eee79-10": "Fengshen Statue Station",
"Common-teyvat-192be7b183f6eee79-11": "Mondstadt",
"Common-teyvat-192be7b183f6eee79-12": "Qingquan Town",
"Common-teyvat-192be7b183f6eee79-13": "Dawn Winery",
"Common-teyvat-192be7b183f6eee79-14": "Benlangling",
"Common-teyvat-192be7b183f6eee79-15": "Benlangling North",
"Common-teyvat-192be7b183f6eee79-16": "Fenglong Scenic Area",
"Common-teyvat-192be7b183f6eee79-17": "Mingguan Gorge",
"Common-teyvat-192be7b183f6eee79-18": "Fenglong International Airport",
"Common-teyvat-192be7b183f6eee79-19": "Snow Mountain Foot Station",
"Common-teyvat-192be7b183f6eee79-2": "dadaupa valley",
"Common-teyvat-192be7b183f6eee79-20": "formless ice",
"Common-teyvat-192be7b183f6eee79-21": "Xueshanshan Station",
"Common-teyvat-192be7b183f6eee79-22": "Seven-day God Statue Station",
"Common-teyvat-192be7b183f6eee79-23": "snow mountain lake",
"Common-teyvat-192be7b183f6eee79-24": "salt of the earth",
"Common-teyvat-192be7b183f6eee79-25": "Wangshu Inn",
"Common-teyvat-192be7b183f6eee79-26": "Shimen",
"Common-teyvat-192be7b183f6eee79-27": "Qingce Village",
"Common-teyvat-192be7b183f6eee79-28": "Wangshunan",
"Common-teyvat-192be7b183f6eee79-29": "Return to the original",
"Common-teyvat-192be7b183f6eee79-3": "Formless Thunder",
"Common-teyvat-192be7b183f6eee79-30": "Luhuachi South",
"Common-teyvat-192be7b183f6eee79-31": "Liyue Port",
"Common-teyvat-192be7b183f6eee79-32": "gold house",
"Common-teyvat-192be7b183f6eee79-33": "Qingxupu",
"Common-teyvat-192be7b183f6eee79-34": "Lingju International Airport",
"Common-teyvat-192be7b183f6eee79-35": "Tianheng Mountain",
"Common-teyvat-192be7b183f6eee79-36": "Escape Yuling",
"Common-teyvat-192be7b183f6eee79-37": "Luhuachi",
"Common-teyvat-192be7b183f6eee79-38": "cuijuepo",
"Common-teyvat-192be7b183f6eee79-39": "Taishan Mansion",
"Common-teyvat-192be7b183f6eee79-4": "Oath Point Airport",
"Common-teyvat-192be7b183f6eee79-40": "Airport East",
"Common-teyvat-192be7b183f6eee79-41": "Qingyun International Airport",
"Common-teyvat-192be7b183f6eee79-42": "Mount Ozura",
"Common-teyvat-192be7b183f6eee79-43": "Hulao Mountain",
"Common-teyvat-192be7b183f6eee79-44": "Fulong tree",
"Common-teyvat-192be7b183f6eee79-45": "Nantianmen",
"Common-teyvat-192be7b183f6eee79-46": "Tianqiu Valley",
"Common-teyvat-192be7b183f6eee79-47": "Fuao Valley",
"Common-teyvat-192be7b183f6eee79-48": "Xiangzuipo",
"Common-teyvat-192be7b183f6eee79-49": "No thick forest",
"Common-teyvat-192be7b183f6eee79-5": "Fengqidi Station",
"Common-teyvat-192be7b183f6eee79-50": "Casazzale",
"Common-teyvat-192be7b183f6eee79-51": "Fungus Cave",
"Common-teyvat-192be7b183f6eee79-52": "Xumi City",
"Common-teyvat-192be7b183f6eee79-53": "Huachengguo",
"Common-teyvat-192be7b183f6eee79-54": "dansha cliff",
"Common-teyvat-192be7b183f6eee79-55": "Subdue all the demons in Shandong",
"Common-teyvat-192be7b183f6eee79-56": "Subdue all the demonic mountains",
"Common-teyvat-192be7b183f6eee79-57": "Porto Ormos North",
"Common-teyvat-192be7b183f6eee79-58": "puerto olmos",
"Common-teyvat-192be7b183f6eee79-59": "vimo village",
"Common-teyvat-192be7b183f6eee79-6": "Star falls in Hunan",
"Common-teyvat-192be7b183f6eee79-60": "jhana garden",
"Common-teyvat-192be7b183f6eee79-61": "Praise to the secluded place",
"Common-teyvat-192be7b183f6eee79-62": "Huana Lanna",
"Common-teyvat-192be7b183f6eee79-63": "Five Oasis",
"Common-teyvat-192be7b183f6eee79-64": "Kawanyi",
"Common-teyvat-192be7b183f6eee79-65": "Water and Sky Jungle",
"Common-teyvat-192be7b183f6eee79-66": "The seat of Tuha",
"Common-teyvat-192be7b183f6eee79-67": "A Ru Village",
"Common-teyvat-192be7b183f6eee79-68": "Transfiguration Hall",
"Common-teyvat-192be7b183f6eee79-69": "Vibrant House",
"Common-teyvat-192be7b183f6eee79-7": "Xingluo Lake",
"Common-teyvat-192be7b183f6eee79-70": "Oasis of plenty",
"Common-teyvat-192be7b183f6eee79-71": "Tomb of the Red King",
"Common-teyvat-192be7b183f6eee79-72": "Line 1",
"Common-teyvat-192be7b183f6eee79-73": "Line 2",
"Common-teyvat-192be7b183f6eee79-74": "Line 3",
"Common-teyvat-192be7b183f6eee79-75": "Line 4",
"Common-teyvat-192be7b183f6eee79-76": "Line 5",
"Common-teyvat-192be7b183f6eee79-77": "Line 6",
"Common-teyvat-192be7b183f6eee79-78": "Line 7",
"Common-teyvat-192be7b183f6eee79-79": "Line 8",
"Common-teyvat-192be7b183f6eee79-8": "Phaseless Wind Station",
"Common-teyvat-192be7b183f6eee79-80": "Xumi Line 2",
"Common-teyvat-192be7b183f6eee79-81": "Xumi Line 1",
"Common-teyvat-192be7b183f6eee79-82": "Xumi Line 3",
"Common-teyvat-192be7b183f6eee79-83": "Xumi Line 4",
"Common-teyvat-192be7b183f6eee79-9": "Wangfeng Development Zone",
"qian-feng-shen-dian-zhan": "Thousand Winds Temple Station",
"wang-feng-jiao-zhan": "Lookout Point Station",
"zhai-xing-ya-zhan": "Zhaixingya Station"
}
================================================
FILE: src/i18n/locales/zh.json
================================================
{
"language": "中文",
"menu": {
"alreadyAddStations": "已添加{{count}}站",
"clickBlankToAddStation": "点击空白处新增站点",
"redo": "重做",
"done": "完成",
"alreadyModified": "已修改{{count}}次",
"dragToChangePoistion": "拖动站点更改位置",
"withdraw": "撤销",
"chooseALineFirst": "先选择一条线路,再点击线路卡片上的",
"backgroudSetting": "背景设定",
"white": "纯白",
"lightYellow": "浅黄",
"transparent": "透明",
"changeBackgroudImage": "修改背景图",
"importBackgroudImage": "导入背景图",
"importImage": "导入图片",
"deleteImage": "删除图片",
"back": "返回",
"clickAddStationToLine": "点击站点将它插入到{{lineName}}的第{{insertIndex}}站",
"chooseAMap": "选择一张地图",
"useTemplate": "以{{name}}为模板新建地图",
"cancel": "取消",
"station": "站点",
"addStation": "添加站点",
"changeStationPosition": "调整站点位置",
"hidden": "隐藏",
"show": "显示",
"stationName": "站点名称",
"turnOn": "启用",
"AutoHidden": "自动隐藏",
"line": "线路",
"turnOff": "关闭",
"insertStation": "插入站点...",
"mediateMap": "居中路线图...",
"addBackgroundImage": "设定背景...",
"data": "数据",
"addNewMap": "新建空白地图...",
"addFromTemplate": "从已有地图新建...",
"importFile": "导入文件...",
"exportAsImage": "作为图片导出...",
"exportAsSVG": "作为矢量图片导出...",
"exportAsFile": "作为文件导出...",
"recoverData": "恢复数据...",
"exportRecoveryData": "导出恢复数据...",
"about": "关于",
"projectAddress": "项目地址...",
"guide": "使用教程...",
"videoGuild": "视频教程...",
"RMP": "线路图工具包",
"version": "版本 : ",
"author": "作者 : 江户川瑞安",
"symbol": "标识",
"newMap": "新地图",
"switchLanguage": "Switch to English"
},
"shang-hai": "上海",
"bei-jing": "北京",
"guang-zhou": "广州",
"shen-zhen": "深圳",
"xiang-gang": "香港",
"chang-sha": "长沙",
"tian-jin": "天津",
"station": {
"up": "顶端",
"upRight": "右上角",
"right": "右侧",
"rightDown": "右下角",
"down": "底部",
"downLeft": "左下角",
"left": "左侧",
"leftUp": "左上角",
"auto": "自动",
"x": "横坐标",
"y": "纵坐标",
"startHere": "以此为起点新建线路...",
"remove": "从{{lineName}}的第{{stationIndex}}站移除",
"delete": "删除站点...",
"lines": "条线路",
"poisition": "位置",
"shape": "形状",
"operation": "操作",
"operateDes": "删除",
"stationName": "标签"
},
"shape": {
"cicle": "圆形",
"square": "正方形",
"triangle": "三角形",
"start": "五角星",
"pentagon": "五边形",
"hexagon": "六边形",
"cross": "十字形",
"rhombus": "菱形",
"diamond": "钻石",
"leaf": "叶子",
"ginkgo": "银杏"
},
"line": {
"name": "名称",
"order": "排序",
"notSubLineAnymore": "不再作为支线",
"asSubline": "设为支线",
"deleteLine": "删除线路...",
"stations": "个站点",
"notInUse": "尚未开通",
"fromTo": "从{{from}}开往{{to}}",
"insertHere": "插入到这",
"insertStation": "插入站点",
"bendFirst": "斜向优先",
"straight": "直线优先",
"color": "颜色",
"operation": "操作",
"delete": "删除",
"done": "完成"
},
"opacity": "透明度",
"recover": {
"text": "需要恢复数据吗?",
"subTitle": "缓存中有不久前编辑过的地图"
},
"error": {
"metError": "抱歉!程序出现错误",
"dontWorry": "不过不用担心,您的地图没有丢失",
"recoverFromCache": "从缓存中恢复错误发生前的地图",
"exportNoErrorFile": "导出错误发生前的地图文件",
"orYouCould": "或者您可以",
"exportError": "导出当前包含错误的地图文件...",
"sendAuthor": "以便发送给作者分析错误原因。"
},
"huan-ying-shi-yong": "欢迎使用",
"welcome": {
"welcome": "欢迎使用",
"skip": "暂时跳过",
"goOn": "继续",
"restart": "重新开始",
"done": "完成"
},
"welecome": {
"minimetroweb": "迷你地铁地图构建工具"
},
"tour": {
"tryScale": "尝试缩放或者移动地图",
"touchScale": "单指拖动地图,双指缩放地图",
"mouseScale": "按住鼠标左键拖动移动地图,滚动鼠标滚轮缩放地图",
"clickTile": "点击标题",
"clickOpenMenu": "点击标题打开菜单"
},
"zai-ci-dian-ji-biao-ti": "再次点击标题",
"ke-yi-xiu-gai-biao-ti": "可以修改标题",
"dian-ji-ren-yi-kong-bai-qu-yu-tui-chu-cai-dan": "点击任意空白区域退出菜单",
"tui-chu-cai-dan": "退出菜单",
"dian-ji-zhan-dian": "点击站点",
"yi-da-kai-zhan-dian-ka-pian": "以打开站点卡片",
"zhan-dian-ka-pian": "站点卡片",
"ke-yi-zai-zhe-li-bian-ji-zhan-dian-de-suo-you-she-zhi": "可以在这里编辑站点的所有设置",
"chang-shi-dian-ji-xian-lu": "尝试点击线路",
"tu-zhong-de-cai-se-xian-tiao-ji-wei-xian-lu-ru-guo-wu-fa-xuan-zhong-ke-yi-chang-shi-fang-da-di-tu-zai-dian-xuan": "图中的彩色线条即为线路,如果无法选中可以尝试放大地图再点选",
"xian-lu-ka-pian": "线路卡片",
"ke-yi-zai-zhe-li-bian-ji-xian-lu-de-suo-you-she-zhi": "可以在这里编辑线路的所有设置",
"wu-xian-zi-chan": "无限资产",
"wu-xian-da-hua-bu": "无限大画布",
"zhi-chi": "支持",
"wu-xian-tiao-xian-lu": "无限条线路",
"wu-xian-ge-zhan-dian": "无限个站点",
"le-jie-ji-ben-cao-zuo": "了解基本操作",
"ling-huo-zou-xian": "灵活走线",
"yi-tiao-xian-lu-neng-duo-ci-chuan-guo-tong-yi-zhan": "一条线路能多次穿过同一站",
"q-zi-zou-xian-yu-zou-xian": "Q字走线与α走线",
"zhi-chi-she-zhi": "支持设置",
"zhi-xian": "支线",
"le-jie-xian-lu-she-zhi": "了解线路设置",
"fang-bian-cha-kan": "方便查看",
"ka-pian-hua-zhan-shi-zhan-dian-yu-xian-lu": "卡片化展示站点与线路",
"zhan-dian-xian-lu-hu-xiang": "站点线路互相",
"guan-lian": "关联",
"gao-liang": "高亮",
"zhan-shi-xuan-zhong-zhan-dian-yu-xian-lu": "展示选中站点与线路",
"le-jie-zhan-dian-ka-pian": "了解站点卡片",
"gao-xiao-bian-ji": "高效编辑",
"lian-xu-chuang-jian-zhan-dian-mo-shi": "连续创建站点模式",
"lian-xu-tian-jia-zhan-dian": "连续添加站点",
"mo-shi": "模式",
"che-hui": "撤回",
"yu": "与",
"zhong-zuo": "重做",
"le-jie-kuai-su-chuang-jian": "了解快速创建",
"yi-dong-duan-zhi-chi": "移动端支持",
"wan-zheng-de-chu-kong-shi-jian-zhi-chi": "完整的触控事件支持",
"dan-zhi": "单指",
"tuo-dong": "拖动",
"shuang-zhi": "双指",
"suo-fang-di-tu": "缩放地图",
"shi-bie-dong-zuo-yi-tu": "识别动作意图",
"jian-shao-wu-cao-zuo": "减少误操作",
"zai-ping-ban-shang-ti-yan": "在平板上体验",
"zi-dong-bi-rang": "自动避让",
"zi-dong-tian-jia-pian-yi-zhi": "自动添加偏移值",
"gong-xian-xian-lu": "共线线路",
"bu-hui-zhong-die": "不会重叠",
"zhan-dian-ming-cheng-zi-dong-xuan-ze-bai-fang-wei-zhi": "站点名称自动选择摆放位置",
"le-jie-biao-qian-she-zhi": "了解标签设置",
"dao-ru-dao-chu": "导入导出",
"dao-chu-gao-fen-bian-shuai-tu-pian": "导出高分辨率图片",
"dao-chu": "导出",
"pngsvg-tu-pian": "PNG/SVG图片",
"zhi-chi-cong-mo-ban-chuang-jian": "支持从模版创建",
"le-jie-dao-ru-dao-chu": "了解导入导出",
"dian-ji-biao-ti": "点击标题",
"dian-ji-biao-ti-da-kai-cai-dan": "点击标题打开菜单",
"cong-mo-ban-xin-jian": "从模板新建",
"zai-yi-you-de-di-tu-shang-xiu-gai": "在已有的地图上修改",
"xuan-ze-yi-ge-ni-xi-huan-de-cheng-shi": "选择一个你喜欢的城市",
"ran-hou-dian-ji-xia-yi-bu": "然后点击下一步",
"yi-ci-wei-mo-ban-xin-jian": "以此为模板新建",
"que-ren-xin-jian-qian-qing-que-bao-yi-jing-bao-cun-le-dang-qian-de-di-tu": "确认新建前请确保已经保存了当前的地图",
"da-kai-cai-dan": "打开菜单",
"xuan-ze-zuo-wei-wen-jian-dao-chu": "选择作为文件导出",
"zuo-wei-wen-jian-dao-chu": "作为文件导出",
"xuan-ze-zuo-wei-wen-jian-dao-chu-0": "选择作为文件导出",
"dao-ru-gang-cai-dao-chu-wen-jian": "导入刚才导出文件",
"xia-ci-dian-ji-dao-ru-wen-jian-ke-yi-ji-xu-bian-ji": "下次点击导入文件可以继续编辑",
"dao-chu-tu-pian": "导出图片",
"dian-ji-dao-chu-tu-pian-dao-chu-ke-neng-hui-dao-zhi-ka-dun-ji-miao-shu-yu-zheng-chang-xian-xiang": "点击导出图片。导出可能会导致卡顿几秒属于正常现象",
"cong-huan-cun-zhong-hui-fu-shu-ju": "从缓存中恢复数据",
"ru-guo-mei-bao-cun-de-shi-hou-bu-xiao-xin-shua-xin-ke-yi-dian-ji-zhe-li-hui-fu-shu-ju": "如果没保存的时候不小心刷新,可以点击这里恢复数据",
"dao-chu-hui-fu-shu-ju": "导出恢复数据",
"ru-guo-mou-yi-bu-cao-zuo-hou-dao-zhi-bao-cuo-huo-beng-kui-dian-ji-zhe-li-dao-chu-zui-hou-yi-ci-zheng-que-de-shu-ju-ru-guo-yu-dao-zhe-zhong-qing-kuang-qing-fan-kui-gei-zuo-zhe": "如果某一步操作后导致报错或崩溃,点击这里导出最后一次正确的数据。(如果遇到这种情况,请反馈给作者)",
"dian-ji-ren-yi-xian-lu": "点击任意线路",
"tu-zhong-cai-se-qu-xian-biao-shi-di-tie-xian-lu": "图中彩色曲线表示地铁线路",
"xian-lu-ka-pian-0": "线路卡片",
"ke-yi-zai-zhe-li-bian-ji-xian-lu-de-suo-you-she-zhi-0": "可以在这里编辑线路的所有设置",
"dian-ji-ci-chu-kong-zhi-xian-lu-zai-liang-zhan-zhi-jian-de-zou-xiang": "点击此处控制线路在两站之间的走向",
"zhan-dian-qu-jian-lian-xian-zhi-you-liang-zhong-fang-shi-xie-xian-yu-zhi-xian": "站点区间连线只有两种方式,斜线与直线",
"fang-da-an-niu": "放大按钮",
"ke-yi-tuo-kuan-xian-lu-ka-pian": "可以拓宽线路卡片",
"suo-xiao-an-niu": "缩小按钮",
"zai-suo-xiao-zhuang-tai-ke-yi-dian-ji-geng-duo-she-zhi": "在缩小状态可以点击更多设置",
"bian-ji-an-niu": "编辑按钮",
"jin-ru-geng-duo-she-zhi-mian-ban": "进入更多设置面板",
"ci-chu-xiu-gai-xian-lu-ming-cheng-yu-biao-shi": "此处修改线路名称与标识",
"pai-xu-ke-yi-kong-zhi-xian-lu-jian-de-zhe-dang-guan-xi": "排序可以控制线路间的遮挡关系",
"dian-ji-ci-chu-xiu-gai-xian-lu-biao-shi-se": "点击此处修改线路标识色",
"zuo-xia-jiao-de-yan-se-xuan-ze-qi-ke-yi-qu-se": "左下角的颜色选择器可以取色",
"dian-ji-ci-chu-shan-chu-xian-lu-huo-zhe-she-zhi-zhi-xian": "点击此处删除线路或者设置支线",
"she-zhi-zhi-xian-hou-xian-lu-hui-yi-xu-xian-xian-shi-qie-lian-jie-chu-bu-zai-xian-shi-ba-shou": "设置支线后线路会以虚线显示,且连接处不再显示把手",
"dian-ji-ci-chu-wan-cheng-she-zhi": "点击此处完成设置",
"hui-dao-zhan-dian-ye": "回到站点页",
"xian-shi-tu-jing-zhan-dian-ka-pian": "显示途径站点卡片",
"dian-ji-ci-chu-suo-yi-tu-jing-de-zhan-dian-ka-pian": "点击此处所以途径的站点卡片",
"da-kai-cai-dan-0": "打开菜单",
"dian-ji-biao-ti-da-kai-cai-dan-0": "点击标题打开菜单",
"dian-ji-tian-jia-zhan-dian": "点击添加站点",
"jin-ru-tian-jia-zhan-dian-mo-shi": "进入添加站点模式",
"dian-ji-ren-yi-kong-bai-chu-tian-jia-zhan-dian": "点击任意空白处添加站点",
"tian-jia-hao-zhi-hou-dian-ji-xia-yi-bu-jian-yi-nin-zai-ping-mu-zuo-shang-jiao-de-kong-bai-qu-yu-tian-jia-san-ge-zhan-dian": "添加好之后点击下一步(建议您在屏幕左上角的空白区域添加三个站点)",
"dian-ji-wan-cheng": "点击完成",
"tui-chu-bian-ji-mo-shi": "退出编辑模式",
"dian-ji-gang-cai-chuang-jian-de-zhan-dian": "点击刚才创建的站点",
"da-kai-zhan-dian-xin-xi-ka-pian": "打开站点信息卡片",
"dian-ji-cao-zuo-xuan-xiang-ka": "点击操作选项卡",
"wo-men-lai-tian-jia-xian-lu": "我们来添加线路",
"dian-ji-yi-ci-wei-qi-dian-xin-jian-xian-lu": "点击以此为起点新建线路",
"jin-ru-tian-jia-xian-lu-mo-shi": "进入添加线路模式",
"dian-ji-zhan-dian-0": "点击站点",
"lian-jie-zhan-dian": "连接站点",
"xian-lu-jiu-chuang-jian-hao-le": "线路就创建好了",
"yi-da-kai-zhan-dian-ka-pian-0": "以打开站点卡片",
"bian-ji-zhan-dian-wei-zhi": "编辑站点位置",
"dian-ji-zuo-biao-zhi-hou-shi-yong-shu-biao-gun-lun-ke-yi-jing-que-tiao-jie": "点击坐标之后使用鼠标滚轮可以精确调节",
"dian-ji-ci-chu-xiu-gai-zhan-dian-xing-zhuang": "点击此处修改站点形状",
"xiu-gai-xing-zhuang": "修改形状",
"dian-ji-ci-chu-shan-chu-zhan-dian-huo-xin-jian-xian-lu": "点击此处删除站点或新建线路",
"shan-chu-huo-xin-jian": "删除或新建",
"dian-ji-ci-chu-she-zhi-zhan-dian-ming-cheng-wei-zhi": "点击此处设置站点名称位置",
"zhan-dian-ming-wei-zhi": "站点名位置",
"dian-ji-hui-se-de-fang-kuai-xuan-ze-fang-wei": "点击灰色的方块选择方位",
"dian-ji-zhong-jian-de-wen-zi-hui-fu-zi-dong-wei-zhi": "点击中间的文字恢复自动位置",
"dian-ji-biao-ti-da-kai-cai-dan-1": "点击标题打开菜单",
"guan-bi-zi-dong-yin-cang": "关闭自动隐藏",
"zhan-dian-ming-hui-zai-di-tu-suo-fang-dao-xiao-chi-du-shi-zi-dong-yin-cang-guan-bi-zi-dong-yin-cang-yi-que-bao-ke-yi-yi-zhi-xian-shi-zhan-dian-ming-cheng": "站点名会在地图缩放到小尺度时自动隐藏。关闭自动隐藏以确保可以一直显示站点名称",
"dian-ji-ren-yi-kong-bai-qu-yu-tui-chu-cai-dan-0": "点击任意空白区域退出菜单",
"tui-chu-cai-dan-0": "退出菜单",
"yi-da-kai-zhan-dian-ka-pian-1": "以打开站点卡片",
"zai-zhe-li-xiang-xia-gun-dong-yi-xia": "在这里向下滚动一下",
"zai-dian-ji-biao-qian-an-niu": "再点击标签按钮",
"chang-shi-gai-bian-zhan-dian-ming-dao-you-xia-jiao": "尝试改变站点名到右下角",
"dian-ji-you-xia-jiao-de-hui-se-fang-kuai": "点击右下角的灰色方块",
"ke-yi-kan-dao-zhan-dian-ming-yi-jing-wei-yu-zhan-dian-you-xia-jiao-le": "可以看到站点名已经位于站点右下角了",
"ke-yi-yong-zhe-zhong-fang-fa-xiu-gai-zhan-dian-ming-wei-zhi": "可以用这种方法修改站点名位置",
"dian-ji-zhong-jian-de-wen-zi-hui-fu-dao-zi-dong-xuan-ze-wei-zhi": "点击中间的文字恢复到自动选择位置",
"hui-fu-dao-zi-dong-xuan-ze-wei-zhi": "恢复到自动选择位置",
"shang-yi-bu": "上一步",
"wan-cheng": "完成",
"xia-yi-bu": "下一步",
"di-current-bu-gong-total-bu": "第{0}步,共{1}步",
"delete": {
"remove": "移除",
"delete": "删除",
"subtile": "不再作为{{lineName}}的第{{index}}站",
"cancel": "取消"
},
"que-shi-yao-deletetext-ma": "确实要{{deleteText}}吗?",
"teyvat": "提瓦特",
"wang-feng-jiao-zhan": "望风角站",
"zhai-xing-ya-zhan": "摘星崖站",
"qian-feng-shen-dian-zhan": "千风神殿站",
"newStation": "新增站点",
"lineNo": "{{lineId}} 号线",
"Common-teyvat-192be7b183f6eee79-1": "鹰翔海滩",
"Common-teyvat-192be7b183f6eee79-2": "达达乌帕谷",
"Common-teyvat-192be7b183f6eee79-3": "无相之雷",
"Common-teyvat-192be7b183f6eee79-4": "誓言岬机场",
"Common-teyvat-192be7b183f6eee79-5": "风起地站",
"Common-teyvat-192be7b183f6eee79-6": "星落湖南",
"Common-teyvat-192be7b183f6eee79-7": "星落湖",
"Common-teyvat-192be7b183f6eee79-8": "无相之风站",
"Common-teyvat-192be7b183f6eee79-9": "望风开发区",
"Common-teyvat-192be7b183f6eee79-10": "风神像站",
"Common-teyvat-192be7b183f6eee79-11": "蒙德站",
"Common-teyvat-192be7b183f6eee79-12": "清泉镇",
"Common-teyvat-192be7b183f6eee79-13": "晨曦酒庄",
"Common-teyvat-192be7b183f6eee79-14": "奔狼岭",
"Common-teyvat-192be7b183f6eee79-15": "奔狼岭北",
"Common-teyvat-192be7b183f6eee79-16": "风龙风景区",
"Common-teyvat-192be7b183f6eee79-17": "明冠峡",
"Common-teyvat-192be7b183f6eee79-18": "风龙国际机场",
"Common-teyvat-192be7b183f6eee79-19": "雪山脚站",
"Common-teyvat-192be7b183f6eee79-20": "无相之冰",
"Common-teyvat-192be7b183f6eee79-21": "雪山顶站",
"Common-teyvat-192be7b183f6eee79-22": "七天神像站",
"Common-teyvat-192be7b183f6eee79-23": "雪山湖",
"Common-teyvat-192be7b183f6eee79-24": "地中之盐",
"Common-teyvat-192be7b183f6eee79-25": "望舒客栈",
"Common-teyvat-192be7b183f6eee79-26": "石门",
"Common-teyvat-192be7b183f6eee79-27": "轻策庄",
"Common-teyvat-192be7b183f6eee79-28": "望舒南",
"Common-teyvat-192be7b183f6eee79-29": "归离原",
"Common-teyvat-192be7b183f6eee79-30": "渌华池南",
"Common-teyvat-192be7b183f6eee79-31": "璃月港",
"Common-teyvat-192be7b183f6eee79-32": "黄金屋",
"Common-teyvat-192be7b183f6eee79-33": "青墟浦",
"Common-teyvat-192be7b183f6eee79-34": "灵矩国际机场",
"Common-teyvat-192be7b183f6eee79-35": "天衡山",
"Common-teyvat-192be7b183f6eee79-36": "遁玉陵",
"Common-teyvat-192be7b183f6eee79-37": "渌华池",
"Common-teyvat-192be7b183f6eee79-38": "翠玦坡",
"Common-teyvat-192be7b183f6eee79-39": "太山府",
"Common-teyvat-192be7b183f6eee79-40": "机场东",
"Common-teyvat-192be7b183f6eee79-41": "庆云国际机场",
"Common-teyvat-192be7b183f6eee79-42": "奥藏山",
"Common-teyvat-192be7b183f6eee79-43": "琥牢山",
"Common-teyvat-192be7b183f6eee79-44": "伏龙树",
"Common-teyvat-192be7b183f6eee79-45": "南天门",
"Common-teyvat-192be7b183f6eee79-46": "天遒谷",
"Common-teyvat-192be7b183f6eee79-47": "伏鳌谷",
"Common-teyvat-192be7b183f6eee79-48": "香醉坡",
"Common-teyvat-192be7b183f6eee79-49": "无郁稠林",
"Common-teyvat-192be7b183f6eee79-50": "卡萨扎莱宫",
"Common-teyvat-192be7b183f6eee79-51": "茸蕈窟",
"Common-teyvat-192be7b183f6eee79-52": "须弥城",
"Common-teyvat-192be7b183f6eee79-53": "化城郭",
"Common-teyvat-192be7b183f6eee79-54": "丹砂崖",
"Common-teyvat-192be7b183f6eee79-55": "降诸魔山东",
"Common-teyvat-192be7b183f6eee79-56": "降诸魔山",
"Common-teyvat-192be7b183f6eee79-57": "奥摩斯港北",
"Common-teyvat-192be7b183f6eee79-58": "奥摩斯港",
"Common-teyvat-192be7b183f6eee79-59": "维摩庄",
"Common-teyvat-192be7b183f6eee79-60": "禅那园",
"Common-teyvat-192be7b183f6eee79-61": "谒颂幽境",
"Common-teyvat-192be7b183f6eee79-62": "桓那兰那",
"Common-teyvat-192be7b183f6eee79-63": "五绿洲",
"Common-teyvat-192be7b183f6eee79-64": "喀万驿",
"Common-teyvat-192be7b183f6eee79-65": "水天丛林",
"Common-teyvat-192be7b183f6eee79-66": "荼诃之座",
"Common-teyvat-192be7b183f6eee79-67": "阿如村",
"Common-teyvat-192be7b183f6eee79-68": "圣显厅",
"Common-teyvat-192be7b183f6eee79-69": "活力之家",
"Common-teyvat-192be7b183f6eee79-70": "丰饶绿洲",
"Common-teyvat-192be7b183f6eee79-71": "赤王陵",
"Common-teyvat-192be7b183f6eee79-72": "1号线",
"Common-teyvat-192be7b183f6eee79-73": "2号线",
"Common-teyvat-192be7b183f6eee79-74": "3号线",
"Common-teyvat-192be7b183f6eee79-75": "4号线",
"Common-teyvat-192be7b183f6eee79-76": "5号线",
"Common-teyvat-192be7b183f6eee79-77": "6号线",
"Common-teyvat-192be7b183f6eee79-78": "7号线",
"Common-teyvat-192be7b183f6eee79-79": "8号线",
"Common-teyvat-192be7b183f6eee79-80": "须弥2号线",
"Common-teyvat-192be7b183f6eee79-81": "须弥1号线",
"Common-teyvat-192be7b183f6eee79-82": "须弥3号线",
"Common-teyvat-192be7b183f6eee79-83": "须弥4号线"
}
================================================
FILE: src/index.js
================================================
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './Entrance/App';
import reportWebVitals from './Entrance/reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
================================================
FILE: src/types/svg.d.ts
================================================
declare module '*.svg' {
import React = require('react');
export const ReactComponent: React.FC>;
const src: string;
export default src;
}
================================================
FILE: tsconfig.json
================================================
{
"compilerOptions": {
"jsx": "react",
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
// "declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
// "outDir": "./", /* Redirect output structure to the directory. */
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"resolveJsonModule": true,
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}