From 3ddb33e3214a701b72e83deffbdb5b209d2d9399 Mon Sep 17 00:00:00 2001 From: que01 Date: Wed, 26 Aug 2026 15:01:07 +0800 Subject: [PATCH] feat: migrate workbench to React --- .prettierignore | 3 + .prettierrc.json | 5 + .trellis/spec/frontend/quality-guidelines.md | 37 + .../check.jsonl | 4 + .../08-26-react-workbench-migration/design.md | 32 + .../implement.jsonl | 4 + .../implement.md | 20 + .../08-26-react-workbench-migration/prd.md | 43 + .../08-26-react-workbench-migration/task.json | 26 + assets/lane-icons/manifest.json | 90 +- assets/lane-icons/upstream/DESIGN.md | 20 +- assets/lane-icons/upstream/LICENSE.md | 62 +- bin/road-compiler.js | 22 +- bin/road-workbench.js | 16 +- package-lock.json | 1143 ++- package.json | 19 +- src/check.js | 41 +- src/compile/compiler.js | 124 +- src/compile/complex-junction.js | 568 +- src/compile/layer-manifest.js | 73 +- src/compile/native-road.js | 2310 ++++- src/compile/turn-lane-arrows.js | 399 +- src/export/native-road-package.js | 49 +- src/geometry/lane-geometry.js | 18 +- src/index.js | 26 +- src/native-traffic-signals.js | 33 +- src/osm.js | 48 +- src/reference/gaode.js | 171 +- src/traffic-signals.js | 449 +- test/baseline/fengshu-er-road.json | 5 +- .../nantaizi-lake-innovation-valley.json | 5 +- test/fixtures.js | 74 +- .../fengshu-junction-reference.geojson | 8770 ++++++++++++++++- test/index.js | 46 +- workbench/client/app.css | 224 +- workbench/client/app.js | 1523 ++- workbench/client/index.html | 14 +- workbench/client/src/App.tsx | 638 ++ workbench/client/src/components/MapCanvas.tsx | 88 + workbench/client/src/lib/api.ts | 40 + workbench/client/src/main.tsx | 11 + workbench/client/src/map/layers.ts | 160 + workbench/client/src/styles.css | 223 + workbench/client/src/types/state.ts | 92 + workbench/client/src/ui/button.tsx | 12 + workbench/client/tsconfig.json | 15 + workbench/client/vite.config.ts | 9 + workbench/server.js | 453 +- 48 files changed, 16748 insertions(+), 1509 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc.json create mode 100644 .trellis/spec/frontend/quality-guidelines.md create mode 100644 .trellis/tasks/08-26-react-workbench-migration/check.jsonl create mode 100644 .trellis/tasks/08-26-react-workbench-migration/design.md create mode 100644 .trellis/tasks/08-26-react-workbench-migration/implement.jsonl create mode 100644 .trellis/tasks/08-26-react-workbench-migration/implement.md create mode 100644 .trellis/tasks/08-26-react-workbench-migration/prd.md create mode 100644 .trellis/tasks/08-26-react-workbench-migration/task.json create mode 100644 workbench/client/src/App.tsx create mode 100644 workbench/client/src/components/MapCanvas.tsx create mode 100644 workbench/client/src/lib/api.ts create mode 100644 workbench/client/src/main.tsx create mode 100644 workbench/client/src/map/layers.ts create mode 100644 workbench/client/src/styles.css create mode 100644 workbench/client/src/types/state.ts create mode 100644 workbench/client/src/ui/button.tsx create mode 100644 workbench/client/tsconfig.json create mode 100644 workbench/client/vite.config.ts diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..e47f536 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,3 @@ +node_modules/ +workbench/client/dist/ +.trellis/ diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..4b9a2d9 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,5 @@ +{ + "printWidth": 120, + "singleQuote": true, + "trailingComma": "all" +} diff --git a/.trellis/spec/frontend/quality-guidelines.md b/.trellis/spec/frontend/quality-guidelines.md new file mode 100644 index 0000000..2003aa4 --- /dev/null +++ b/.trellis/spec/frontend/quality-guidelines.md @@ -0,0 +1,37 @@ +# Quality Guidelines + +> Code quality standards for frontend development. + +--- + +## Overview + +The workbench frontend uses Prettier as the source formatting authority. Code is formatted before review; formatting is not left to individual editor settings. + +--- + +## Forbidden Patterns + +- Do not commit dense, manually minified, or single-line JSX/TypeScript. +- Do not hand-format around Prettier output. + +--- + +## Required Patterns + +- Run `npm run format` after editing frontend files. +- Run `npm run format:check` in the validation gate. +- Keep React components, hooks, types, and API clients in separately formatted modules once a component becomes non-trivial. + +--- + +## Testing Requirements + +- Run `npm run format:check`, `npm run test:client`, and `npm run build` for frontend changes. + +--- + +## Code Review Checklist + +- Formatting check passes. +- UI behavior changes do not rebuild the OpenLayers map unless the map lifecycle explicitly requires it. diff --git a/.trellis/tasks/08-26-react-workbench-migration/check.jsonl b/.trellis/tasks/08-26-react-workbench-migration/check.jsonl new file mode 100644 index 0000000..b1f2642 --- /dev/null +++ b/.trellis/tasks/08-26-react-workbench-migration/check.jsonl @@ -0,0 +1,4 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} +{"file":".trellis/spec/frontend/index.md","reason":"Review frontend quality, component, hook, and state conventions."} +{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"Verify API-to-React-to-OpenLayers data flow and cleanup boundaries."} +{"file":".trellis/spec/guides/code-reuse-thinking-guide.md","reason":"Check that migration does not leave duplicate DOM and React implementations or duplicate map logic."} diff --git a/.trellis/tasks/08-26-react-workbench-migration/design.md b/.trellis/tasks/08-26-react-workbench-migration/design.md new file mode 100644 index 0000000..2bf7767 --- /dev/null +++ b/.trellis/tasks/08-26-react-workbench-migration/design.md @@ -0,0 +1,32 @@ +# Technical Design + +## Frontend Boundary + +Create a Vite app under `workbench/client/` (or a clearly scoped `workbench/frontend/` directory) with React 19 and TypeScript. Keep Node/server code CommonJS and keep `/api/*` payloads unchanged. The production build should emit a deterministic directory consumed by `workbench/server.js`; development mode may use a Vite proxy to the workbench API. + +## OpenLayers Migration Strategy + +- Import OpenLayers modules directly from the installed `ol` package in TypeScript; remove the runtime `/vendor/ol/*.js` import map from the React build. +- Keep one `Map` instance per mounted map component. A `useOpenLayersMap` hook creates it once after the container ref is available, registers interactions/listeners, and calls `map.setTarget(undefined)` plus listener cleanup on unmount. +- Keep the layer registry in an adapter module/hook (`map/layers.ts`), created once with stable `VectorSource` and `VectorLayer` instances. React state changes update sources, styles, visibility, and selection overlays through explicit adapter methods rather than recreating the map. +- Use typed GeoJSON/read helpers at the API boundary; preserve EPSG:4326 to EPSG:3857 conversion and current fit/selection behavior. +- OpenLayers event callbacks publish typed selection/pointer events to React state. React panels render from that state and issue API mutations; they do not query or mutate DOM nodes owned by OpenLayers. +- Import `ol/ol.css` from the Vite entry and keep map container dimensions in application CSS. + +## UI and State + +- Use shadcn/ui primitives for buttons, inputs, checkboxes, select, tabs/segmented filters, dialogs, sheets, alerts, and toast feedback. Keep the existing dense three-column workbench layout. +- Define API/state types in `src/types/` based on the actual `/api/state` and mutation payloads. Use a small typed client in `src/lib/api.ts`. +- Use React hooks/context for session and editor state. Keep transient map selection and staged overrides separate from server state; avoid introducing a large state library unless the migration proves one necessary. +- Split components by existing workflow boundaries: `ImportScreen`, `WorkbenchShell`, `LayerPanel`, `DiagnosticsPanel`, `MapCanvas`, `RoadInspector`, `SignalEditor`, and shared `ui/` primitives. + +## Static Hosting + +- Update `workbench/server.js` to serve Vite `index.html` and hashed assets in production, with a safe fallback for the SPA route while retaining `/vendor/*` only for compatibility during transition. +- Keep `npm run road:workbench` self-contained for production assets. Add Vite dev/proxy scripts without requiring the Node workbench server to become an ESM application. + +## Risks and Compatibility + +- OpenLayers owns imperative objects and must not be recreated on every React render; the hook/adapter boundary is mandatory. +- Existing style functions close over selected road state. Convert them to adapter-managed mutable selection refs or call `layer.changed()` after selection updates. +- The first migration should preserve behavior and layout; visual redesign is explicitly deferred. diff --git a/.trellis/tasks/08-26-react-workbench-migration/implement.jsonl b/.trellis/tasks/08-26-react-workbench-migration/implement.jsonl new file mode 100644 index 0000000..52d64f2 --- /dev/null +++ b/.trellis/tasks/08-26-react-workbench-migration/implement.jsonl @@ -0,0 +1,4 @@ +{"_example": "Fill with {\"file\": \"\", \"reason\": \"\"}. Put spec/research files only — no code paths. Run `python3 .trellis/scripts/get_context.py --mode packages` to list available specs. Delete this line once real entries are added."} +{"file":".trellis/spec/frontend/index.md","reason":"Load the project frontend conventions before introducing Vite, React, TypeScript, and shadcn/ui."} +{"file":".trellis/spec/guides/cross-layer-thinking-guide.md","reason":"The migration changes the browser build, static hosting, API client, and imperative map lifecycle together."} +{"file":".trellis/spec/guides/code-reuse-thinking-guide.md","reason":"Reuse current OpenLayers styles, API payloads, and editor behavior instead of duplicating domain logic."} diff --git a/.trellis/tasks/08-26-react-workbench-migration/implement.md b/.trellis/tasks/08-26-react-workbench-migration/implement.md new file mode 100644 index 0000000..8efe6e4 --- /dev/null +++ b/.trellis/tasks/08-26-react-workbench-migration/implement.md @@ -0,0 +1,20 @@ +# Implementation Plan + +1. Add Vite, React 19, TypeScript, Tailwind/shadcn/ui dependencies and scripts; establish `tsconfig`, Vite config, aliases, CSS variables, and production output location. +2. Define typed API/state contracts and a fetch client from the existing `/api/*` payloads; add focused serialization tests. +3. Build the OpenLayers adapter and `useOpenLayersMap` hook. Port the current layer registry, styles, source updates, selection interaction, fit behavior, and cleanup tests before moving inspector UI. +4. Port the import-first flow and shell/layout to React, then migrate layer controls, summaries, diagnostics, road inspector, connection editor, centerline editor, signal editor, and save/compile actions. +5. Replace the server's legacy `/app.js`/`/app.css` production serving with Vite build output while retaining a compatibility path only where needed for rollback. +6. Remove the old DOM-driven entry after parity is demonstrated; keep OpenLayers vendor serving only if legacy mode still needs it. +7. Add browser/component smoke coverage for no-session import, active-session map render, a road selection/edit, and compile/export request wiring. +8. Run `npm run build`, frontend tests, existing `npm test`, and a manual workbench smoke check at desktop/mobile widths; fix parity regressions. + +Validation commands: + +- `npm run build` +- `npm run format:check` +- frontend test command added by this task +- `npm test` +- `node --check workbench/server.js` + +Rollback point: retain the legacy client in a separate compatibility path until the React build serves the same API workflow and OpenLayers interaction checks pass. diff --git a/.trellis/tasks/08-26-react-workbench-migration/prd.md b/.trellis/tasks/08-26-react-workbench-migration/prd.md new file mode 100644 index 0000000..730db72 --- /dev/null +++ b/.trellis/tasks/08-26-react-workbench-migration/prd.md @@ -0,0 +1,43 @@ +# 迁移工作台到 React 前端 + +## Goal + +将现有原生 HTML/CSS/JS 工作台迁移到 Vite、React 19、TypeScript 和 shadcn/ui,获得可维护、可扩展且具备类型安全的成熟前端工程结构,同时保留当前 OSM 导入、地图查看、道路参数编辑、诊断、信号灯编辑、重新编译和道路包导出能力。 + +## Confirmed Facts + +- 当前前端位于 `workbench/client/index.html`、`workbench/client/app.js` 和 `workbench/client/app.css`。 +- `workbench/client/app.js` 使用 OpenLayers 浏览器模块,并动态创建部分控制面板和图层开关。 +- `workbench/server.js` 提供静态资源和 `/api/state`、`/api/import`、`/api/overrides`、`/api/traffic-signals`、`/api/compile`、`/api/export.zip` 等接口。 +- 当前项目是 CommonJS Node 包,尚无前端构建脚本、TypeScript 配置、Vite 配置或 shadcn/ui 依赖。 +- 地图核心继续使用已有的 OpenLayers 10;本任务是前端工程迁移,不替换地图引擎。 + +## Requirements + +1. 建立独立、可构建的 Vite React 19 TypeScript 前端入口并迁移工作台代码。 +2. 配置 shadcn/ui 所需的 CSS 变量、组件基础设施和图标/交互约定,优先使用其组件承载按钮、表单、面板、弹层和提示。 +3. 将页面状态、API 调用、地图图层/选择逻辑和编辑表单拆分为 React 组件、hooks、类型和服务模块,避免主要业务 UI 继续依赖全局 `querySelector` 和动态 `innerHTML`。 +4. 保留现有服务端 API 契约和 OSM 导入工作流;必要时只调整静态资源托管和开发/生产构建入口。 +5. 保留 OpenLayers 地图的现有图层、样式、选择和交互行为,并确保状态变化正确驱动地图与检查器更新。 +6. 提供开发模式和生产构建命令;Node 工作台服务能提供构建后的前端资源。 +7. 为核心 API 客户端、状态转换和至少一个关键交互增加 TypeScript/自动化验证。 + +## Acceptance Criteria + +- `npm run build` 能在干净依赖安装后生成前端生产构建产物。 +- `npm run dev`(或等价命令)能启动 Vite 开发服务器并加载工作台页面。 +- `npm run road:workbench` 使用构建产物时仍能打开工作台;无活动工作区时显示 OSM 导入界面,有活动工作区时显示地图和编辑器。 +- OSM 导入、图层开关、道路选择/参数暂存、诊断筛选、交通信号编辑、保存、重新编译和 ZIP 导出均保持可用。 +- 页面不再通过原生 `querySelector`/`innerHTML` 组织主要业务 UI;业务状态和 API 数据具有明确 TypeScript 类型。 +- 现有 Node 编译器测试继续通过;新增前端构建和关键工作流验证通过。 + +## Out Of Scope + +- 不修改道路编译算法、OSM 解析规则、GeoJSON 数据结构或现有 HTTP API 语义。 +- 不更换 OpenLayers 为其他地图引擎。 +- 不引入认证、多用户协作、服务端渲染或远程部署平台。 +- 不在本任务内重新定义道路参数模型或增加新的业务功能。 + +## Open Product Decision + +迁移是否同时允许明显的视觉和信息架构重设计?推荐第一阶段以“功能和工作流等价 + shadcn/ui 统一视觉”为目标,保留现有三栏地图工作台布局;这样可以把风险集中在工程迁移,后续再单独做 UX 重构。若本任务同时重做布局,交付周期和回归范围会显著增加。 diff --git a/.trellis/tasks/08-26-react-workbench-migration/task.json b/.trellis/tasks/08-26-react-workbench-migration/task.json new file mode 100644 index 0000000..5fe553e --- /dev/null +++ b/.trellis/tasks/08-26-react-workbench-migration/task.json @@ -0,0 +1,26 @@ +{ + "id": "react-workbench-migration", + "name": "react-workbench-migration", + "title": "迁移工作台到 React 前端", + "description": "将现有原生 HTML/CSS/JS 工作台迁移到 Vite、React 19、TypeScript 和 shadcn/ui,保持现有工作流能力", + "status": "in_progress", + "dev_type": null, + "scope": null, + "package": null, + "priority": "P2", + "creator": "dingkang", + "assignee": "dingkang", + "createdAt": "2026-08-26", + "completedAt": null, + "branch": null, + "base_branch": "main", + "worktree_path": null, + "commit": null, + "pr_url": null, + "subtasks": [], + "children": [], + "parent": null, + "relatedFiles": [], + "notes": "", + "meta": {} +} \ No newline at end of file diff --git a/assets/lane-icons/manifest.json b/assets/lane-icons/manifest.json index c971643..0bc7521 100644 --- a/assets/lane-icons/manifest.json +++ b/assets/lane-icons/manifest.json @@ -6,19 +6,81 @@ "license_file": "upstream/LICENSE.md" }, "assets": [ - {"id":"through","source":"upstream/through.svg","anchor_x":12.5,"supported":true,"tested":true,"template":"through"}, - {"id":"left","source":"upstream/left.svg","anchor_x":17,"supported":true,"tested":true,"template":"left"}, - {"id":"right","source":"upstream/left.svg","derived_from":"left","mirror_x":true,"anchor_x":17,"supported":true,"tested":true,"template":"right"}, - {"id":"through;left","source":"upstream/left-through.svg","anchor_x":17,"supported":true,"tested":true,"template":"through_left"}, - {"id":"through;right","source":"upstream/left-through.svg","derived_from":"through;left","mirror_x":true,"anchor_x":17,"supported":true,"tested":true,"template":"through_right"}, - {"id":"through;left;right","source":"upstream/left-slight_left-through.svg","derived_from":"through;left + through;right","anchor_x":17,"supported":true,"tested":true,"template":"through_left_right"}, - {"id":"slight_left","source":"upstream/slight_left.svg","supported":false,"tested":false}, - {"id":"slight_left;through","source":"upstream/slight_left-through.svg","supported":false,"tested":false}, - {"id":"left;slight_left;through","source":"upstream/left-slight_left-through.svg","supported":false,"tested":false}, - {"id":"sharp_left","source":"upstream/sharp_left.svg","supported":false,"tested":false}, - {"id":"sharp_left;through","source":"upstream/sharp_left-through.svg","supported":false,"tested":false}, - {"id":"reverse_left","source":"upstream/reverse_left.svg","supported":false,"tested":false}, - {"id":"reverse_left;through","source":"upstream/reverse_left-through.svg","supported":false,"tested":false}, - {"id":"reverse_left;left;slight_left;through","source":"upstream/reverse_left-left-slight_left-through.svg","supported":false,"tested":false} + { + "id": "through", + "source": "upstream/through.svg", + "anchor_x": 12.5, + "supported": true, + "tested": true, + "template": "through" + }, + { + "id": "left", + "source": "upstream/left.svg", + "anchor_x": 17, + "supported": true, + "tested": true, + "template": "left" + }, + { + "id": "right", + "source": "upstream/left.svg", + "derived_from": "left", + "mirror_x": true, + "anchor_x": 17, + "supported": true, + "tested": true, + "template": "right" + }, + { + "id": "through;left", + "source": "upstream/left-through.svg", + "anchor_x": 17, + "supported": true, + "tested": true, + "template": "through_left" + }, + { + "id": "through;right", + "source": "upstream/left-through.svg", + "derived_from": "through;left", + "mirror_x": true, + "anchor_x": 17, + "supported": true, + "tested": true, + "template": "through_right" + }, + { + "id": "through;left;right", + "source": "upstream/left-slight_left-through.svg", + "derived_from": "through;left + through;right", + "anchor_x": 17, + "supported": true, + "tested": true, + "template": "through_left_right" + }, + { "id": "slight_left", "source": "upstream/slight_left.svg", "supported": false, "tested": false }, + { "id": "slight_left;through", "source": "upstream/slight_left-through.svg", "supported": false, "tested": false }, + { + "id": "left;slight_left;through", + "source": "upstream/left-slight_left-through.svg", + "supported": false, + "tested": false + }, + { "id": "sharp_left", "source": "upstream/sharp_left.svg", "supported": false, "tested": false }, + { "id": "sharp_left;through", "source": "upstream/sharp_left-through.svg", "supported": false, "tested": false }, + { "id": "reverse_left", "source": "upstream/reverse_left.svg", "supported": false, "tested": false }, + { + "id": "reverse_left;through", + "source": "upstream/reverse_left-through.svg", + "supported": false, + "tested": false + }, + { + "id": "reverse_left;left;slight_left;through", + "source": "upstream/reverse_left-left-slight_left-through.svg", + "supported": false, + "tested": false + } ] } diff --git a/assets/lane-icons/upstream/DESIGN.md b/assets/lane-icons/upstream/DESIGN.md index 7aecd3d..ac49d5f 100644 --- a/assets/lane-icons/upstream/DESIGN.md +++ b/assets/lane-icons/upstream/DESIGN.md @@ -9,11 +9,11 @@ and as such it is not copyrighted. Each icon should be -* 25px x 25px -* SVG -* Single color fill -* Path outline only, no stroke -* Pixel grid aligned (where possible) +- 25px x 25px +- SVG +- Single color fill +- Path outline only, no stroke +- Pixel grid aligned (where possible) ### Arrows @@ -25,7 +25,6 @@ lane arrows. Standard Arrow - Arrowhead rotation angles are chosen based on the turn lane indication: @@ -84,13 +83,12 @@ As the number of arrows increases, the arrows scale down:
- Other important rules for arrows: -* Bottom aligned with each other, 2px from icon bottom edge -* Left-right centered -* Smooth curves, no kinks -* Arrow shaft path connects to the arrowhead anchor point +- Bottom aligned with each other, 2px from icon bottom edge +- Left-right centered +- Smooth curves, no kinks +- Arrow shaft path connects to the arrowhead anchor point ### Other Icons diff --git a/assets/lane-icons/upstream/LICENSE.md b/assets/lane-icons/upstream/LICENSE.md index f3dc09b..6bd7a1b 100644 --- a/assets/lane-icons/upstream/LICENSE.md +++ b/assets/lane-icons/upstream/LICENSE.md @@ -33,27 +33,27 @@ protected by copyright and related or neighboring rights ("Copyright and Related Rights"). Copyright and Related Rights include, but are not limited to, the following: - i. the right to reproduce, adapt, distribute, perform, display, communicate, - and translate a Work; +i. the right to reproduce, adapt, distribute, perform, display, communicate, +and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); +ii. moral rights retained by the original author(s) and/or performer(s); - iii. publicity and privacy rights pertaining to a person's image or likeness - depicted in a Work; +iii. publicity and privacy rights pertaining to a person's image or likeness +depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; +iv. rights protecting against unfair competition in regards to a Work, +subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data in - a Work; +v. rights protecting the extraction, dissemination, use and reuse of data in +a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation thereof, - including any amended or successor version of such directive); and +vi. database rights (such as those arising under Directive 96/9/EC of the +European Parliament and of the Council of 11 March 1996 on the legal +protection of databases, and under any national implementation thereof, +including any amended or successor version of such directive); and - vii. other similar, equivalent or corresponding rights throughout the world - based on applicable law or treaty, and any national implementations thereof. +vii. other similar, equivalent or corresponding rights throughout the world +based on applicable law or treaty, and any national implementations thereof. **2. Waiver.** To the greatest extent permitted by, but not in contravention of, applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and @@ -94,25 +94,25 @@ Affirmer's express Statement of Purpose. **4. Limitations and Disclaimers.** - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. +a. No trademark or patent rights held by Affirmer are waived, abandoned, +surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or warranties - of any kind concerning the Work, express, implied, statutory or otherwise, - including without limitation warranties of title, merchantability, fitness - for a particular purpose, non infringement, or the absence of latent or - other defects, accuracy, or the present or absence of errors, whether or not - discoverable, all to the greatest extent permissible under applicable law. +b. Affirmer offers the Work as-is and makes no representations or warranties +of any kind concerning the Work, express, implied, statutory or otherwise, +including without limitation warranties of title, merchantability, fitness +for a particular purpose, non infringement, or the absence of latent or +other defects, accuracy, or the present or absence of errors, whether or not +discoverable, all to the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without limitation - any person's Copyright and Related Rights in the Work. Further, Affirmer - disclaims responsibility for obtaining any necessary consents, permissions - or other rights required for any use of the Work. +c. Affirmer disclaims responsibility for clearing rights of other persons +that may apply to the Work or any use thereof, including without limitation +any person's Copyright and Related Rights in the Work. Further, Affirmer +disclaims responsibility for obtaining any necessary consents, permissions +or other rights required for any use of the Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to this - CC0 or use of the Work. +d. Affirmer understands and acknowledges that Creative Commons is not a +party to this document and has no duty or obligation with respect to this +CC0 or use of the Work. For more information, please see diff --git a/bin/road-compiler.js b/bin/road-compiler.js index 493f7f8..1678c94 100755 --- a/bin/road-compiler.js +++ b/bin/road-compiler.js @@ -1,20 +1,22 @@ #!/usr/bin/env node -"use strict"; +'use strict'; -const fs = require("fs"); -const path = require("path"); -const { compiler } = require("../src"); -const { exportNativeRoadPackage } = require("../src/export/native-road-package"); +const fs = require('fs'); +const path = require('path'); +const { compiler } = require('../src'); +const { exportNativeRoadPackage } = require('../src/export/native-road-package'); const args = process.argv.slice(2); -const index = args.indexOf("--input"); -const exportIndex = args.indexOf("--export-zip"); +const index = args.indexOf('--input'); +const exportIndex = args.indexOf('--export-zip'); const expectedLength = exportIndex >= 0 ? 4 : 2; if (index < 0 || !args[index + 1] || (exportIndex >= 0 && !args[exportIndex + 1]) || args.length !== expectedLength) { - throw new Error("Usage: road-compiler --input [--export-zip ]"); + throw new Error('Usage: road-compiler --input [--export-zip ]'); } const inputFile = path.resolve(args[index + 1]); -const input = JSON.parse(fs.readFileSync(inputFile, "utf8")); +const input = JSON.parse(fs.readFileSync(inputFile, 'utf8')); const { result, comparison } = compiler.compileInput(input); if (exportIndex >= 0) exportNativeRoadPackage(input.outDir, path.resolve(args[exportIndex + 1])); -console.log(`NATIVE_ROAD_COMPILE_DONE ${JSON.stringify({ areaId: input.areaId, roads: result.model.roads.length, endpoints: result.model.endpoints.length, diagnostics: result.diagnostics.length, output: input.outDir, comparison })}`); +console.log( + `NATIVE_ROAD_COMPILE_DONE ${JSON.stringify({ areaId: input.areaId, roads: result.model.roads.length, endpoints: result.model.endpoints.length, diagnostics: result.diagnostics.length, output: input.outDir, comparison })}`, +); diff --git a/bin/road-workbench.js b/bin/road-workbench.js index 3d8474b..720c6f6 100755 --- a/bin/road-workbench.js +++ b/bin/road-workbench.js @@ -1,14 +1,14 @@ #!/usr/bin/env node -"use strict"; +'use strict'; -const path = require("path"); -const { startWorkbench } = require("../workbench/server"); -const fs = require("fs"); +const path = require('path'); +const { startWorkbench } = require('../workbench/server'); +const fs = require('fs'); const args = process.argv.slice(2); -const index = args.indexOf("--input"); +const index = args.indexOf('--input'); const inputFile = index >= 0 && args[index + 1] ? path.resolve(args[index + 1]) : null; -const input = inputFile ? JSON.parse(fs.readFileSync(inputFile, "utf8")) : null; -const portIndex = args.indexOf("--port"); +const input = inputFile ? JSON.parse(fs.readFileSync(inputFile, 'utf8')) : null; +const portIndex = args.indexOf('--port'); const port = portIndex >= 0 ? Number(args[portIndex + 1]) : 8787; -startWorkbench({ input, inputFile, repoRoot: path.resolve(__dirname, ".."), port }); +startWorkbench({ input, inputFile, repoRoot: path.resolve(__dirname, '..'), port }); diff --git a/package-lock.json b/package-lock.json index 6bc32cb..0dc6b3a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,10 +9,462 @@ "version": "0.3.0", "dependencies": { "fflate": "0.8.3", - "ol": "10.10.0" + "lucide-react": "^0.468.0", + "ol": "10.10.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" }, "bin": { "road-compiler": "bin/road-compiler.js" + }, + "devDependencies": { + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "prettier": "^3.9.6", + "typescript": "^5.7.3", + "vite": "^6.1.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", + "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/android-arm/-/android-arm-0.24.2.tgz", + "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz", + "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/android-x64/-/android-x64-0.24.2.tgz", + "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz", + "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz", + "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz", + "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz", + "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz", + "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz", + "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz", + "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz", + "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz", + "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz", + "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz", + "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz", + "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz", + "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz", + "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz", + "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz", + "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz", + "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz", + "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz", + "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz", + "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz", + "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "http://172.16.1.86:4873/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" } }, "node_modules/@petamoriken/float16": { @@ -21,12 +473,389 @@ "integrity": "sha512-8awtpHXCx/bNpFt4mt2xdkgtgVvKqty8VbjHI/WWWQuEw+KLzFot3f4+LkQY9YmOtq7A5GdOnqoIC8Pdygjk2g==", "license": "MIT" }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.0.tgz", + "integrity": "sha512-70TeIFezKKy65LgAVyQh+w94/gjWhvPWaLaGGeMEgVrPkQhuj/M5bAYYZzIFUj9Y69oHyTm5Um/R6gcLh4A8JA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.0.tgz", + "integrity": "sha512-YC86tYIHK6M1IV+wbzO+Bxk8RCBr6ZyWYgWxUCzaZD8mc8rrFoIJDNzDrkHBYRc/wKdrsIXmm6/F7NzrAO+OrA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.0.tgz", + "integrity": "sha512-oI+ECtUcli0y0fi4xpW82GdPIXdTkI8G8DSjG2LRuw09fPAGykaWYH/hXxiKuTxiAjiPSTIIuYUqof5Z2hShWw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.0.tgz", + "integrity": "sha512-NwV+1s7TiKrMe4owHyKB/dTLD7ZJD0YEBEhIz+hvav1Cu1GReJjF+rsdNwjzENQeIAbE/CoNiaAc5Vz2h5DPAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.0.tgz", + "integrity": "sha512-tWtHBTu5gOPK4u4Urtk4qAHW3zZ9rQAmbssO8gp7ELvGTGI3aCiq6NqyTQ0PCIg7KbHJF2UkGDDs77YZGxfjCA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.0.tgz", + "integrity": "sha512-2qPoJiwTvtHQ27NnYvTnsgk8laXWYuVmNESG8WFZBcEPKLfZ3I27qBJarjVRQtwGeYyRfq5ZowHXih9lm2BItw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.0.tgz", + "integrity": "sha512-FQwsTRvLNuHoTdICABJQfbPUSEueISGmnpT06tXTMpfprf5NiKLSXKA0A+w45wJnCmZAnzgqBwbt6ARFuyOi5w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.0.tgz", + "integrity": "sha512-BBVTXziw8mY1a4ZbWME9tZyfzqXCDPqaC7Z3heQ29p5dkvXzwL0NwelO8zLa8c3RBKvl3YTuSnBgsBhYBtwjIw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.0.tgz", + "integrity": "sha512-w2Iyy9+RqKwx3d9qWMKsJg0FfRBsY0/pXNv0mCQ3ueRvJI6+QAScfD4nrMlzFLs2HNVW6Ew+mtZfDl9b7Ew5/Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.0.tgz", + "integrity": "sha512-YK++KtrFRHYE0P6/RtYEAy9t8F37znP+K03RrIuLPYOL6SVlObRumf/0OE4V/h63xL9DwkWbNssZfmA9hawuDA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.0.tgz", + "integrity": "sha512-aBfOG6fP7YkkPmTqPwufRJeFyz7WPpECv9XNbnsk9+vg7rxdih0lbtEel7jcRng4LZrrmU3FfitCFyEj4BWDWg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.0.tgz", + "integrity": "sha512-LGaHEOeHNAag9VuS1Crs5DFg4RrU9MPi2nVnNJk9DTePx/B6RRYKVmrIXt2h7YOJlwjaFJ6lwtFDliZxScTLrQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.0.tgz", + "integrity": "sha512-jClvk+J0FC3b7Udvegiw5/4hErbHtmsNsQgENnKXDWtNCJXsJYZH5WURvu7imDOO38xYml24eeh5x3A04ppwCw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.0.tgz", + "integrity": "sha512-0OJlaGK+8+B777Ql5okIpD7ua5Ro9+VB9Ve0OKa28OQJZ1RbuUBVNHK/e3pr4BROqsyPl1JrPO1ZxJseCNffcA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.0.tgz", + "integrity": "sha512-Ygsx+HoNH7afwi1bTIXbnTvVnsO+zurPLSYxybV1hHFVU72OWOCl6v05ql/z0hkpAPx+DK7Kn9Bi7MayCcjLTA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.0.tgz", + "integrity": "sha512-pDQxtMGb+OvG3fLwR2OkZlSd47hW+kWg4BYMG/++sR6RqorQccwPTDsxda5hPwiIeIErAnCF9ma3SAU06bdQtQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.0.tgz", + "integrity": "sha512-0BnUG9mS8I4SSHr3XsxVhuCMEiu+rX61xxZF5vujso4LaiAGFZFxvDjg6Xn6tLPNTUAfuCvQYas4LMQMVsKRSQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.0.tgz", + "integrity": "sha512-Adu/VttB1dpPNW+FEacrZ+xVm9tFty84+RrFzsqlFaPxoJB+9XXyDGtp5dCOoBwGBIEVH0To7lExFXEx0BIF4A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.0.tgz", + "integrity": "sha512-NQ3bDvjUbFKmP23671xUlXtKmqVsUBd6M4PQCvbmNtOy06hnQIdKHy8oG/6S3R/S6He1JgPk6A5VT+prAJMYEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.0.tgz", + "integrity": "sha512-u2eDAl4+0aFvA13GxlGBtTI3SS3sdgwgtV0HyjZ0QaQVCgNE+jqNGey+GtxWiq+wxr/UycAx/OnfJzApCFamvA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.0.tgz", + "integrity": "sha512-XvRb5vfW3wAZQ+ZUG21AnHHDKtNcw99eigzEhjr//NZ3u7SoBaPP0seSc7FgP7p1epAEdAoZckMW9WY/+4w70w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.0.tgz", + "integrity": "sha512-iZPmniy4kNBf5yo2RezbkYNNK5HPbXE9+g+twnbqSng7dtLEJy1SKoxiE/ni4FDacjyuZpEeb9U054N4EoKHYw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.0.tgz", + "integrity": "sha512-mFBBd+LF37fnE8JnYUOH+imj0aPFPK30vpar4ehJkgnLj9sZn8ZxiRENmLtgIwxK7TC8klF6N57fxdNBwQoqOA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.0.tgz", + "integrity": "sha512-ujeqEY3B+zbGn3Z4Q03cUBG/LGWnBJncVT36WER31LcOsQk9+1dmINKKtvmmfChUvRbK1G0R8OhMWFgHgaZtAw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.0.tgz", + "integrity": "sha512-hncn90N4sOky0L2LKE5oESKLbxCPeVo4eLA2LSMoDzM+879ml4WSr+Rr4DWknNIVVvS1Hirkc9hx02W6YxS8rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "http://172.16.1.86:4873/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/rbush": { "version": "4.0.0", "resolved": "http://172.16.1.86:4873/@types/rbush/-/rbush-4.0.0.tgz", "integrity": "sha512-+N+2H39P8X+Hy1I5mC6awlTX54k3FhiUmvt7HWzGJZvF+syUAAxP/stwppS8JE84YHqFgRMv6fCy31202CMFxQ==", "license": "MIT" }, + "node_modules/@types/react": { + "version": "19.0.8", + "resolved": "http://172.16.1.86:4873/@types/react/-/react-19.0.8.tgz", + "integrity": "sha512-9P/o1IGdfmQxrujGbIMDyYaaCykhLKc0NGCtYcECNUr9UAaDe4gwvV9bR6tvd5Br1SG0j+PBpbKr2UYY8CwqSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.0.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.0.3", + "resolved": "http://172.16.1.86:4873/@types/react-dom/-/react-dom-19.0.3.tgz", + "integrity": "sha512-0Knk+HJiMP/qOZgMyNFamlIjw9OFCsyC2ZbigmEEyXXixgre6IQpm/4V+r3qH4GC1JPvRJKInw+on2rV6YZLeA==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.0.0" + } + }, "node_modules/@zarrita/storage": { "version": "0.2.0", "resolved": "http://172.16.1.86:4873/@zarrita/storage/-/storage-0.2.0.tgz", @@ -37,18 +866,81 @@ "unzipit": "2.0.0" } }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "http://172.16.1.86:4873/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, "node_modules/earcut": { "version": "3.2.3", "resolved": "http://172.16.1.86:4873/earcut/-/earcut-3.2.3.tgz", "integrity": "sha512-vnS4AVwp1KHAF13i1vp1/2D5evWy3k5u/iW/B81QVsUZtV8cv2tU0b2VNFlqvh4kYwrFMDdjPCfAmfyJW9y14Q==", "license": "ISC" }, + "node_modules/esbuild": { + "version": "0.24.2", + "resolved": "http://172.16.1.86:4873/esbuild/-/esbuild-0.24.2.tgz", + "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.24.2", + "@esbuild/android-arm": "0.24.2", + "@esbuild/android-arm64": "0.24.2", + "@esbuild/android-x64": "0.24.2", + "@esbuild/darwin-arm64": "0.24.2", + "@esbuild/darwin-x64": "0.24.2", + "@esbuild/freebsd-arm64": "0.24.2", + "@esbuild/freebsd-x64": "0.24.2", + "@esbuild/linux-arm": "0.24.2", + "@esbuild/linux-arm64": "0.24.2", + "@esbuild/linux-ia32": "0.24.2", + "@esbuild/linux-loong64": "0.24.2", + "@esbuild/linux-mips64el": "0.24.2", + "@esbuild/linux-ppc64": "0.24.2", + "@esbuild/linux-riscv64": "0.24.2", + "@esbuild/linux-s390x": "0.24.2", + "@esbuild/linux-x64": "0.24.2", + "@esbuild/netbsd-arm64": "0.24.2", + "@esbuild/netbsd-x64": "0.24.2", + "@esbuild/openbsd-arm64": "0.24.2", + "@esbuild/openbsd-x64": "0.24.2", + "@esbuild/sunos-x64": "0.24.2", + "@esbuild/win32-arm64": "0.24.2", + "@esbuild/win32-ia32": "0.24.2", + "@esbuild/win32-x64": "0.24.2" + } + }, "node_modules/fflate": { "version": "0.8.3", "resolved": "http://172.16.1.86:4873/fflate/-/fflate-0.8.3.tgz", "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "license": "MIT" }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "http://172.16.1.86:4873/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, "node_modules/geotiff": { "version": "3.1.0-beta.0", "resolved": "http://172.16.1.86:4873/geotiff/-/geotiff-3.1.0-beta.0.tgz", @@ -74,6 +966,34 @@ "integrity": "sha512-Rm4J/WaHhRa93nCN2mwWDZFoRVF18G1f47C+kvQWyHGEZxFpTUi73p7lMVSAndyxGt6lJ2/CFbOcf9ra5p8aww==", "license": "Apache-2.0" }, + "node_modules/lucide-react": { + "version": "0.468.0", + "resolved": "http://172.16.1.86:4873/lucide-react/-/lucide-react-0.468.0.tgz", + "integrity": "sha512-6koYRhnM2N0GGZIdXzSeiNwguv1gt/FAjZOiPl76roBi3xKEXa4WmfpxgQwTTL4KipXjefrnf3oV4IsYhi4JFA==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + } + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "http://172.16.1.86:4873/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/numcodecs": { "version": "0.3.2", "resolved": "http://172.16.1.86:4873/numcodecs/-/numcodecs-0.3.2.tgz", @@ -135,6 +1055,58 @@ "pbf": "bin/pbf" } }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "http://172.16.1.86:4873/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "http://172.16.1.86:4873/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prettier": { + "version": "3.9.6", + "resolved": "http://172.16.1.86:4873/prettier/-/prettier-3.9.6.tgz", + "integrity": "sha512-OpN0zzVdiaiAhxpuuj5efpIS4sY9j7bY6uR5mnj5yPzGkdkjNKSJeUThPb60Jw29QuAZgA4o+/iB49kFiaBX6g==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/protocol-buffers-schema": { "version": "3.6.1", "resolved": "http://172.16.1.86:4873/protocol-buffers-schema/-/protocol-buffers-schema-3.6.1.tgz", @@ -168,6 +1140,27 @@ "quickselect": "^3.0.0" } }, + "node_modules/react": { + "version": "19.1.0", + "resolved": "http://172.16.1.86:4873/react/-/react-19.1.0.tgz", + "integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.1.0", + "resolved": "http://172.16.1.86:4873/react-dom/-/react-dom-19.1.0.tgz", + "integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.26.0" + }, + "peerDependencies": { + "react": "^19.1.0" + } + }, "node_modules/reference-spec-reader": { "version": "0.2.0", "resolved": "http://172.16.1.86:4873/reference-spec-reader/-/reference-spec-reader-0.2.0.tgz", @@ -183,6 +1176,82 @@ "protocol-buffers-schema": "^3.3.1" } }, + "node_modules/rollup": { + "version": "4.63.0", + "resolved": "http://172.16.1.86:4873/rollup/-/rollup-4.63.0.tgz", + "integrity": "sha512-T5vnZ2y4QqC3/4P+w2+JO+Q/OVdnPsv4XcSYJYMEn0R9/jjl5AgLwO9LAZMzP2lN71O6pypn91rB7lDstUkfrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.0", + "@rollup/rollup-android-arm64": "4.63.0", + "@rollup/rollup-darwin-arm64": "4.63.0", + "@rollup/rollup-darwin-x64": "4.63.0", + "@rollup/rollup-freebsd-arm64": "4.63.0", + "@rollup/rollup-freebsd-x64": "4.63.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.0", + "@rollup/rollup-linux-arm-musleabihf": "4.63.0", + "@rollup/rollup-linux-arm64-gnu": "4.63.0", + "@rollup/rollup-linux-arm64-musl": "4.63.0", + "@rollup/rollup-linux-loong64-gnu": "4.63.0", + "@rollup/rollup-linux-loong64-musl": "4.63.0", + "@rollup/rollup-linux-ppc64-gnu": "4.63.0", + "@rollup/rollup-linux-ppc64-musl": "4.63.0", + "@rollup/rollup-linux-riscv64-gnu": "4.63.0", + "@rollup/rollup-linux-riscv64-musl": "4.63.0", + "@rollup/rollup-linux-s390x-gnu": "4.63.0", + "@rollup/rollup-linux-x64-gnu": "4.63.0", + "@rollup/rollup-linux-x64-musl": "4.63.0", + "@rollup/rollup-openbsd-x64": "4.63.0", + "@rollup/rollup-openharmony-arm64": "4.63.0", + "@rollup/rollup-win32-arm64-msvc": "4.63.0", + "@rollup/rollup-win32-ia32-msvc": "4.63.0", + "@rollup/rollup-win32-x64-gnu": "4.63.0", + "@rollup/rollup-win32-x64-msvc": "4.63.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/scheduler": { + "version": "0.26.0", + "resolved": "http://172.16.1.86:4873/scheduler/-/scheduler-0.26.0.tgz", + "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "http://172.16.1.86:4873/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/typescript": { + "version": "5.7.3", + "resolved": "http://172.16.1.86:4873/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/unzipit": { "version": "2.0.0", "resolved": "http://172.16.1.86:4873/unzipit/-/unzipit-2.0.0.tgz", @@ -192,6 +1261,78 @@ "node": ">=18" } }, + "node_modules/vite": { + "version": "6.1.0", + "resolved": "http://172.16.1.86:4873/vite/-/vite-6.1.0.tgz", + "integrity": "sha512-RjjMipCKVoR4hVfPY6GQTgveinjNuyLw+qruksLDvA5ktI1150VmcMBKmQaEWJhg/j6Uaf6dNCNA0AfdzUb/hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.24.2", + "postcss": "^8.5.1", + "rollup": "^4.30.1" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, "node_modules/web-worker": { "version": "1.5.0", "resolved": "http://172.16.1.86:4873/web-worker/-/web-worker-1.5.0.tgz", diff --git a/package.json b/package.json index c0b007d..a15955f 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,25 @@ "scripts": { "test": "node test/index.js && node test/fixtures.js", "road:workbench": "node bin/road-workbench.js", - "road:export": "node bin/road-compiler.js" + "road:export": "node bin/road-compiler.js", + "build": "vite build --config workbench/client/vite.config.ts", + "dev": "vite --config workbench/client/vite.config.ts", + "test:client": "tsc --noEmit -p workbench/client/tsconfig.json", + "format": "prettier --write \"{src,bin,test,workbench}/**/*.{js,ts,tsx,css,html}\" \"*.{json,js}\"", + "format:check": "prettier --check \"{src,bin,test,workbench}/**/*.{js,ts,tsx,css,html}\" \"*.{json,js}\"" }, "dependencies": { "fflate": "0.8.3", - "ol": "10.10.0" + "lucide-react": "^0.468.0", + "ol": "10.10.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "prettier": "^3.9.6", + "typescript": "^5.7.3", + "vite": "^6.1.0" } } diff --git a/src/check.js b/src/check.js index bab9cff..35cb427 100644 --- a/src/check.js +++ b/src/check.js @@ -1,27 +1,42 @@ -"use strict"; +'use strict'; -const fs = require("fs"); -const path = require("path"); +const fs = require('fs'); +const path = require('path'); function checkOutput({ areaId, outDir }) { - if (typeof areaId !== "string" || !areaId) throw new Error("RoadCompilerCheckInput.areaId must be a non-empty string"); - if (typeof outDir !== "string" || !outDir) throw new Error("RoadCompilerCheckInput.outDir must be a non-empty string"); - const compiledPath = path.join(outDir, "compiled.json"); + if (typeof areaId !== 'string' || !areaId) + throw new Error('RoadCompilerCheckInput.areaId must be a non-empty string'); + if (typeof outDir !== 'string' || !outDir) + throw new Error('RoadCompilerCheckInput.outDir must be a non-empty string'); + const compiledPath = path.join(outDir, 'compiled.json'); if (!fs.existsSync(compiledPath)) throw new Error(`Native road output is missing: ${compiledPath}`); const compiled = readJson(compiledPath); - const connectors = readJson(path.join(outDir, "layers", "connectors.geojson")); + const connectors = readJson(path.join(outDir, 'layers', 'connectors.geojson')); const published = new Set(connectors.features.map((feature) => feature.properties.movement_id)); const failures = []; for (const movement of compiled.movements || []) { - if (movement.geometryPublished && !published.has(movement.id)) failures.push(`Published movement has no connector: ${movement.id}`); - if (!movement.geometryPublished && published.has(movement.id)) failures.push(`Non-published movement has a connector: ${movement.id}`); + if (movement.geometryPublished && !published.has(movement.id)) + failures.push(`Published movement has no connector: ${movement.id}`); + if (!movement.geometryPublished && published.has(movement.id)) + failures.push(`Non-published movement has a connector: ${movement.id}`); if (!movement.geometryStatus) failures.push(`Movement has no geometry status: ${movement.id}`); } - const errors = (compiled.diagnostics || []).filter((item) => item.severity === "error"); - const warnings = (compiled.diagnostics || []).filter((item) => item.severity === "warning"); - return { schema: "native-road-check/v1", areaId, ok: failures.length === 0 && errors.length === 0, movementCount: (compiled.movements || []).length, connectorCount: connectors.features.length, errors: errors.map((item) => ({ id: item.id, rule: item.rule, message: item.message })), warningCount: warnings.length, failures }; + const errors = (compiled.diagnostics || []).filter((item) => item.severity === 'error'); + const warnings = (compiled.diagnostics || []).filter((item) => item.severity === 'warning'); + return { + schema: 'native-road-check/v1', + areaId, + ok: failures.length === 0 && errors.length === 0, + movementCount: (compiled.movements || []).length, + connectorCount: connectors.features.length, + errors: errors.map((item) => ({ id: item.id, rule: item.rule, message: item.message })), + warningCount: warnings.length, + failures, + }; } -function readJson(file) { return JSON.parse(fs.readFileSync(file, "utf8")); } +function readJson(file) { + return JSON.parse(fs.readFileSync(file, 'utf8')); +} module.exports = { checkOutput }; diff --git a/src/compile/compiler.js b/src/compile/compiler.js index 660d408..7cf562b 100644 --- a/src/compile/compiler.js +++ b/src/compile/compiler.js @@ -1,11 +1,17 @@ #!/usr/bin/env node -"use strict"; +'use strict'; -const fs = require("fs"); -const path = require("path"); -const { compileRoadModel, compileGeometry, loadOverrides, validateOverrides, writeJsonAtomic } = require("./native-road"); -const { LAYER_REGISTRY, manifestForArea, validatePublishedLayers } = require("./layer-manifest"); -const { loadOrGenerate, runtime } = require("../native-traffic-signals"); +const fs = require('fs'); +const path = require('path'); +const { + compileRoadModel, + compileGeometry, + loadOverrides, + validateOverrides, + writeJsonAtomic, +} = require('./native-road'); +const { LAYER_REGISTRY, manifestForArea, validatePublishedLayers } = require('./layer-manifest'); +const { loadOrGenerate, runtime } = require('../native-traffic-signals'); function compileInput(input) { validateInput(input); @@ -22,50 +28,73 @@ function compileInput(input) { }, }; const overrides = loadOverrides(area.outputs.nativeRoadOverrides); - const model = compileRoadModel(fs.readFileSync(area.input, "utf8"), overrides); + const model = compileRoadModel(fs.readFileSync(area.input, 'utf8'), overrides); // Editing the source OSM retires the ids some overrides point at. Those // entries can no longer match anything, so drop them with a diagnostic rather // than aborting the whole compile — otherwise every OSM edit blocks the // pipeline until the file is hand-pruned, one error message at a time. const validated = validateOverrides(overrides, model, { skipStaleTargets: true }); - for (const item of validated.stale) console.warn(`[warning] 忽略失效的 override(目标已不存在):${item.id} -> ${item.target}`); + for (const item of validated.stale) + console.warn(`[warning] 忽略失效的 override(目标已不存在):${item.id} -> ${item.target}`); fs.mkdirSync(area.outputs.pipelineDir, { recursive: true }); - const compiled = compileGeometry(model, overrides, { edgeLines: area.nativeRoad.edgeLines, junctionTemplates: area.nativeRoad.junctionTemplates }); - compiled.diagnostics.push(...validated.stale.map((item) => ({ - id: `diagnostic:stale-override:${item.id}`, - severity: "warning", - subjectId: item.id, - sourceIds: [], - rule: "stale-override-target", - message: `该设置指向的 ${item.kind} 目标 ${item.target} 已不存在(OSM 改动后 id 失效),本次编译已忽略。可在工作台重新设置,或从 native-road-overrides.json 中删除。`, - geometry: null, - }))); - const signalDocument = loadOrGenerate(area.outputs.nativeTrafficSignals, fs.readFileSync(area.input, "utf8"), compiled.vehicleStopLines, compiled.intersectionSurface); + const compiled = compileGeometry(model, overrides, { + edgeLines: area.nativeRoad.edgeLines, + junctionTemplates: area.nativeRoad.junctionTemplates, + }); + compiled.diagnostics.push( + ...validated.stale.map((item) => ({ + id: `diagnostic:stale-override:${item.id}`, + severity: 'warning', + subjectId: item.id, + sourceIds: [], + rule: 'stale-override-target', + message: `该设置指向的 ${item.kind} 目标 ${item.target} 已不存在(OSM 改动后 id 失效),本次编译已忽略。可在工作台重新设置,或从 native-road-overrides.json 中删除。`, + geometry: null, + })), + ); + const signalDocument = loadOrGenerate( + area.outputs.nativeTrafficSignals, + fs.readFileSync(area.input, 'utf8'), + compiled.vehicleStopLines, + compiled.intersectionSurface, + ); const signalRuntime = runtime(signalDocument); // Persist validation normalization, including one-time legacy heading migration. writeJsonAtomic(area.outputs.nativeTrafficSignals, signalDocument); - const staging = fs.mkdtempSync(path.join(area.outputs.pipelineDir, "native-road-")); + const staging = fs.mkdtempSync(path.join(area.outputs.pipelineDir, 'native-road-')); try { const result = { - schema: "native-road-compiled/v1", + schema: 'native-road-compiled/v1', areaId: area.id, - source: { osm: area.input, overrides: area.outputs.nativeRoadOverrides, trafficSignals: area.outputs.nativeTrafficSignals }, + source: { + osm: area.input, + overrides: area.outputs.nativeRoadOverrides, + trafficSignals: area.outputs.nativeTrafficSignals, + }, model: { roads: model.roads, endpoints: model.endpoints, connections: model.connections }, movements: compiled.movements, - trafficSignals: { assemblies: "traffic-signal-assemblies.json", runtime: "traffic-signals.json", count: signalRuntime.signals.length }, + trafficSignals: { + assemblies: 'traffic-signal-assemblies.json', + runtime: 'traffic-signals.json', + count: signalRuntime.signals.length, + }, diagnostics: compiled.diagnostics, layers: Object.fromEntries(LAYER_REGISTRY.map((layer) => [layer.key, `layers/${layer.source}.geojson`])), }; const comparison = compareOsm2Streets(area, result.model, compiled); - writeJsonAtomic(path.join(staging, "compiled.json"), result); - writeJsonAtomic(path.join(staging, "diagnostics.json"), { schema: "native-road-diagnostics/v1", diagnostics: compiled.diagnostics }); - writeJsonAtomic(path.join(staging, "comparison.json"), comparison); - writeJsonAtomic(path.join(staging, "traffic-signal-assemblies.json"), signalDocument.assemblies); - writeJsonAtomic(path.join(staging, "traffic-signals.json"), signalRuntime); - for (const layer of LAYER_REGISTRY) writeJsonAtomic(path.join(staging, "layers", `${layer.source}.geojson`), compiled[layer.key]); + writeJsonAtomic(path.join(staging, 'compiled.json'), result); + writeJsonAtomic(path.join(staging, 'diagnostics.json'), { + schema: 'native-road-diagnostics/v1', + diagnostics: compiled.diagnostics, + }); + writeJsonAtomic(path.join(staging, 'comparison.json'), comparison); + writeJsonAtomic(path.join(staging, 'traffic-signal-assemblies.json'), signalDocument.assemblies); + writeJsonAtomic(path.join(staging, 'traffic-signals.json'), signalRuntime); + for (const layer of LAYER_REGISTRY) + writeJsonAtomic(path.join(staging, 'layers', `${layer.source}.geojson`), compiled[layer.key]); const manifest = manifestForArea(area.id); validatePublishedLayers(staging, manifest); - writeJsonAtomic(path.join(staging, "manifest.json"), manifest); + writeJsonAtomic(path.join(staging, 'manifest.json'), manifest); fs.rmSync(area.outputs.nativeRoadDir, { recursive: true, force: true }); fs.renameSync(staging, area.outputs.nativeRoadDir); return { area, result, comparison }; @@ -76,10 +105,10 @@ function compileInput(input) { } function compareOsm2Streets(area, model, compiled) { - const source = area.outputs.geojsonDir ? path.join(area.outputs.geojsonDir, "road_surface.geojson") : null; + const source = area.outputs.geojsonDir ? path.join(area.outputs.geojsonDir, 'road_surface.geojson') : null; let featureCount = null; if (source && fs.existsSync(source)) { - const collection = JSON.parse(fs.readFileSync(source, "utf8")); + const collection = JSON.parse(fs.readFileSync(source, 'utf8')); featureCount = Array.isArray(collection.features) ? collection.features.length : null; } const diagnosticsBySeverity = {}; @@ -88,18 +117,23 @@ function compareOsm2Streets(area, model, compiled) { diagnosticsBySeverity[item.severity] = (diagnosticsBySeverity[item.severity] || 0) + 1; diagnosticsByRule[item.rule] = (diagnosticsByRule[item.rule] || 0) + 1; } - const dangling = compiled.diagnostics.filter((item) => item.rule === "unconnected-interior-road-end"); + const dangling = compiled.diagnostics.filter((item) => item.rule === 'unconnected-interior-road-end'); const junctions = compiled.intersectionSurface.features; - const fallbackJunctions = junctions.filter((feature) => feature.properties.boundary_mode === "connector-convex-fallback"); + const fallbackJunctions = junctions.filter( + (feature) => feature.properties.boundary_mode === 'connector-convex-fallback', + ); return { - schema: "native-road-comparison/v2", + schema: 'native-road-comparison/v2', nativeRoadCount: model.roads.length, nativeRoadSurfaceFeatures: compiled.roadSurface.features.length, nativeSidewalkSurfaceFeatures: compiled.sidewalkSurface.features.length, nativeJunctionSurfaceFeatures: compiled.intersectionSurface.features.length, nativeApproachEnvelopeJunctions: junctions.length - fallbackJunctions.length, nativeFallbackJunctions: fallbackJunctions.length, - nativeMaxJunctionExpansionRatio: junctions.reduce((maximum, feature) => Math.max(maximum, Number(feature.properties.expansion_ratio) || 0), 0), + nativeMaxJunctionExpansionRatio: junctions.reduce( + (maximum, feature) => Math.max(maximum, Number(feature.properties.expansion_ratio) || 0), + 0, + ), nativeLaneCenterlineFeatures: compiled.laneCenterlines.features.length, nativeLaneSeparatorFeatures: compiled.laneSeparators.features.length, nativeCenterLineFeatures: compiled.centerLines.features.length, @@ -117,18 +151,22 @@ function compareOsm2Streets(area, model, compiled) { diagnosticsByRule, osm2streetsRoadSurfaceFeatures: featureCount, osm2streetsAvailable: featureCount !== null, - note: "Counts are coverage evidence only; geometry quality requires diagnostic and visual review.", + note: 'Counts are coverage evidence only; geometry quality requires diagnostic and visual review.', }; } function validateInput(input) { - if (!input || typeof input !== "object") throw new Error("RoadCompilerInput must be an object"); - for (const key of ["areaId", "osmFile", "outDir", "stagingDir", "overridesFile", "trafficSignalsFile"]) { - if (typeof input[key] !== "string" || input[key].trim() === "") throw new Error(`RoadCompilerInput.${key} must be a non-empty string`); + if (!input || typeof input !== 'object') throw new Error('RoadCompilerInput must be an object'); + for (const key of ['areaId', 'osmFile', 'outDir', 'stagingDir', 'overridesFile', 'trafficSignalsFile']) { + if (typeof input[key] !== 'string' || input[key].trim() === '') + throw new Error(`RoadCompilerInput.${key} must be a non-empty string`); } - if (!input.options || typeof input.options !== "object") throw new Error("RoadCompilerInput.options must be an object"); - if (typeof input.options.edgeLines !== "boolean") throw new Error("RoadCompilerInput.options.edgeLines must be a boolean"); - if (!input.options.junctionTemplates || typeof input.options.junctionTemplates !== "object") throw new Error("RoadCompilerInput.options.junctionTemplates must be an object"); + if (!input.options || typeof input.options !== 'object') + throw new Error('RoadCompilerInput.options must be an object'); + if (typeof input.options.edgeLines !== 'boolean') + throw new Error('RoadCompilerInput.options.edgeLines must be a boolean'); + if (!input.options.junctionTemplates || typeof input.options.junctionTemplates !== 'object') + throw new Error('RoadCompilerInput.options.junctionTemplates must be an object'); } module.exports = { compileInput, validateInput }; diff --git a/src/compile/complex-junction.js b/src/compile/complex-junction.js index 5f41891..8e84af2 100644 --- a/src/compile/complex-junction.js +++ b/src/compile/complex-junction.js @@ -1,7 +1,7 @@ -"use strict"; +'use strict'; -const fs = require("fs"); -const { convertGeoJson, boundsOf } = require("../reference/gaode"); +const fs = require('fs'); +const { convertGeoJson, boundsOf } = require('../reference/gaode'); const metricsCache = new WeakMap(); const CORNER_FILLET_SEGMENTS = 12; // Must match DEFAULT_SIDEWALK_WIDTH_METERS in native-road.js so the corner band @@ -15,8 +15,24 @@ const SIDEWALK_CORNER_OVERRUN_METERS = 6; function buildComplexJunctionGeometry(model, cluster, helpers) { const nodeIds = new Set(cluster.nodeIds.map(String)); const nodes = cluster.nodeIds.map((id) => helpers.junctionPlans.get(String(id))?.node).filter(Boolean); - if (nodes.length < 2) return { features: [], diagnostics: [helpers.diagnostic("warning", `junction-cluster:${cluster.id}`, [...nodeIds], "complex-junction-insufficient-nodes", "复合路口至少需要两个有效节点。", null)] }; - const center = nodes.reduce((sum, point) => [sum[0] + point[0] / nodes.length, sum[1] + point[1] / nodes.length], [0, 0]); + if (nodes.length < 2) + return { + features: [], + diagnostics: [ + helpers.diagnostic( + 'warning', + `junction-cluster:${cluster.id}`, + [...nodeIds], + 'complex-junction-insufficient-nodes', + '复合路口至少需要两个有效节点。', + null, + ), + ], + }; + const center = nodes.reduce( + (sum, point) => [sum[0] + point[0] / nodes.length, sum[1] + point[1] / nodes.length], + [0, 0], + ); const approaches = []; const carriageways = []; for (const [nodeId, plan] of helpers.junctionPlans) { @@ -31,39 +47,92 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { approaches.push({ nodeId, approach, plan, heading, length }); } } - if (approaches.length < 3) return { features: [], diagnostics: [helpers.diagnostic("warning", `junction-cluster:${cluster.id}`, [...nodeIds], "complex-junction-insufficient-approaches", "复合路口无法识别足够的外部进口。", center)] }; + if (approaches.length < 3) + return { + features: [], + diagnostics: [ + helpers.diagnostic( + 'warning', + `junction-cluster:${cluster.id}`, + [...nodeIds], + 'complex-junction-insufficient-approaches', + '复合路口无法识别足够的外部进口。', + center, + ), + ], + }; const { calibration, coreRadius } = complexJunctionMetrics(cluster); const sorted = [...approaches].sort((a, b) => a.heading - b.heading); const arms = sorted.map((representative) => ({ representative, - heading: averageHeading(carriageways.filter((candidate) => Math.abs(normalizeHeading(candidate.heading - representative.heading)) < 20).map((candidate) => candidate.heading)), - members: carriageways.filter((candidate) => Math.abs(normalizeHeading(candidate.heading - representative.heading)) < 20), + heading: averageHeading( + carriageways + .filter((candidate) => Math.abs(normalizeHeading(candidate.heading - representative.heading)) < 20) + .map((candidate) => candidate.heading), + ), + members: carriageways.filter( + (candidate) => Math.abs(normalizeHeading(candidate.heading - representative.heading)) < 20, + ), })); const outerRadius = complexJunctionMetrics(cluster).approachOuterRadius; const boundaryParts = []; const crosswalks = []; const stopLines = []; const islands = []; - const armCrosswalkRadius = coreRadius * .68; + const armCrosswalkRadius = coreRadius * 0.68; for (const item of carriageways) { const outer = pointOnCarriagewayRadius(item, center, outerRadius, helpers); - const inner = pointOnCarriagewayRadius(item, center, coreRadius * .7, helpers); + const inner = pointOnCarriagewayRadius(item, center, coreRadius * 0.7, helpers); const outerHalf = item.approach.widthMeters / 2; const innerHalf = outerHalf; boundaryParts.push({ item, outer, inner, outerHalf, innerHalf }); - const incomingRoad = item.approach.roadIds.map((roadId) => model.roads.find((road) => road.id === roadId)).find((road) => String(road?.sourceNodeIds.at(-1)) === String(item.nodeId)); + const incomingRoad = item.approach.roadIds + .map((roadId) => model.roads.find((road) => road.id === roadId)) + .find((road) => String(road?.sourceNodeIds.at(-1)) === String(item.nodeId)); if (incomingRoad) { // Keep the stop bar just outside the road crosswalk. The previous fixed // core-radius offset placed it nearly ten metres beyond the crossing. const stopCenter = pointOnCarriagewayRadius(item, center, armCrosswalkRadius + 3, helpers); const ring = [ - helpers.offsetCoordinate(helpers.offsetCoordinate(stopCenter, item.heading + 90, -outerHalf), item.heading, -.24), - helpers.offsetCoordinate(helpers.offsetCoordinate(stopCenter, item.heading + 90, outerHalf), item.heading, -.24), - helpers.offsetCoordinate(helpers.offsetCoordinate(stopCenter, item.heading + 90, outerHalf), item.heading, .24), - helpers.offsetCoordinate(helpers.offsetCoordinate(stopCenter, item.heading + 90, -outerHalf), item.heading, .24), - helpers.offsetCoordinate(helpers.offsetCoordinate(stopCenter, item.heading + 90, -outerHalf), item.heading, -.24), + helpers.offsetCoordinate( + helpers.offsetCoordinate(stopCenter, item.heading + 90, -outerHalf), + item.heading, + -0.24, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(stopCenter, item.heading + 90, outerHalf), + item.heading, + -0.24, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(stopCenter, item.heading + 90, outerHalf), + item.heading, + 0.24, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(stopCenter, item.heading + 90, -outerHalf), + item.heading, + 0.24, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(stopCenter, item.heading + 90, -outerHalf), + item.heading, + -0.24, + ), ]; - stopLines.push({ type: "Feature", properties: { native_id: `complex-stop-line:${cluster.id}:${item.approach.segmentId}`, cluster_id: cluster.id, kind: "complex-stop-line", road_id: incomingRoad.id, node_id: item.nodeId, direction: item.heading, provenance: "native-road-complex-junction-stop-line/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }); + stopLines.push({ + type: 'Feature', + properties: { + native_id: `complex-stop-line:${cluster.id}:${item.approach.segmentId}`, + cluster_id: cluster.id, + kind: 'complex-stop-line', + road_id: incomingRoad.id, + node_id: item.nodeId, + direction: item.heading, + provenance: 'native-road-complex-junction-stop-line/v1', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } } // The four support lines provide a common corner frame, but each long @@ -89,15 +158,15 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { const arm = arms[index]; if (!arm.crosswalkFrame) continue; const item = arm.representative; - const roadEdgeInset = .35; - const usableSpan = Math.max(.42, arm.crosswalkFrame.envelopeWidthMeters - roadEdgeInset * 2); + const roadEdgeInset = 0.35; + const usableSpan = Math.max(0.42, arm.crosswalkFrame.envelopeWidthMeters - roadEdgeInset * 2); const endpoints = [ helpers.offsetCoordinate(arm.crosswalkFrame.center, arm.heading + 90, -usableSpan / 2), helpers.offsetCoordinate(arm.crosswalkFrame.center, arm.heading + 90, usableSpan / 2), ]; const groupDepth = arm.crosswalkFrame.groupDepth; - const stripeWidth = .42; - const stripeCount = Math.max(6, Math.floor((usableSpan - stripeWidth) / .82) + 1); + const stripeWidth = 0.42; + const stripeCount = Math.max(6, Math.floor((usableSpan - stripeWidth) / 0.82) + 1); const stripeSpacing = stripeCount > 1 ? (usableSpan - stripeWidth) / (stripeCount - 1) : 0; arm.crosswalkFrame.center = midpoint(...endpoints); arm.crosswalkFrame.endpoints = endpoints; @@ -107,13 +176,54 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { const along = stripeWidth / 2 + stripe * stripeSpacing; const centerPoint = helpers.offsetCoordinate(endpoints[0], bearing(...endpoints), along); const ring = [ - helpers.offsetCoordinate(helpers.offsetCoordinate(centerPoint, arm.heading, -groupDepth / 2), arm.heading + 90, -stripeWidth / 2), - helpers.offsetCoordinate(helpers.offsetCoordinate(centerPoint, arm.heading, groupDepth / 2), arm.heading + 90, -stripeWidth / 2), - helpers.offsetCoordinate(helpers.offsetCoordinate(centerPoint, arm.heading, groupDepth / 2), arm.heading + 90, stripeWidth / 2), - helpers.offsetCoordinate(helpers.offsetCoordinate(centerPoint, arm.heading, -groupDepth / 2), arm.heading + 90, stripeWidth / 2), - helpers.offsetCoordinate(helpers.offsetCoordinate(centerPoint, arm.heading, -groupDepth / 2), arm.heading + 90, -stripeWidth / 2), + helpers.offsetCoordinate( + helpers.offsetCoordinate(centerPoint, arm.heading, -groupDepth / 2), + arm.heading + 90, + -stripeWidth / 2, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(centerPoint, arm.heading, groupDepth / 2), + arm.heading + 90, + -stripeWidth / 2, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(centerPoint, arm.heading, groupDepth / 2), + arm.heading + 90, + stripeWidth / 2, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(centerPoint, arm.heading, -groupDepth / 2), + arm.heading + 90, + stripeWidth / 2, + ), + helpers.offsetCoordinate( + helpers.offsetCoordinate(centerPoint, arm.heading, -groupDepth / 2), + arm.heading + 90, + -stripeWidth / 2, + ), ]; - crosswalks.push({ type: "Feature", properties: { native_id: `complex-crosswalk:${cluster.id}:${item.nodeId}:${item.approach.segmentId}:${stripe + 1}`, cluster_id: cluster.id, kind: "complex-crosswalk", crossing_node_id: item.nodeId, road_id: item.approach.roadIds[0], direction: arm.heading, radial_distance_m: armCrosswalkRadius, span_m: usableSpan, road_envelope_span_m: arm.crosswalkFrame.envelopeWidthMeters, road_edge_inset_m: roadEdgeInset, group_depth_m: groupDepth, stripe_width_m: stripeWidth, stripe_spacing_m: stripeSpacing, frame_center: arm.crosswalkFrame.center, frame_support_heading: arm.crosswalkFrame.supportHeading, provenance: "native-road-complex-junction-crosswalk/v6-road-clipped" }, geometry: { type: "Polygon", coordinates: [ring] } }); + crosswalks.push({ + type: 'Feature', + properties: { + native_id: `complex-crosswalk:${cluster.id}:${item.nodeId}:${item.approach.segmentId}:${stripe + 1}`, + cluster_id: cluster.id, + kind: 'complex-crosswalk', + crossing_node_id: item.nodeId, + road_id: item.approach.roadIds[0], + direction: arm.heading, + radial_distance_m: armCrosswalkRadius, + span_m: usableSpan, + road_envelope_span_m: arm.crosswalkFrame.envelopeWidthMeters, + road_edge_inset_m: roadEdgeInset, + group_depth_m: groupDepth, + stripe_width_m: stripeWidth, + stripe_spacing_m: stripeSpacing, + frame_center: arm.crosswalkFrame.center, + frame_support_heading: arm.crosswalkFrame.supportHeading, + provenance: 'native-road-complex-junction-crosswalk/v6-road-clipped', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } } // The four arm groups are the sides of one pedestrian frame. Each diagonal @@ -128,36 +238,65 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { const bisector = normalizeHeading(first.heading + delta / 2); const frameCorner = frameCorners[index]; if (!frameCorner) continue; - const cornerStripeSpacing = .62; - const cornerStripeWidth = .4; + const cornerStripeSpacing = 0.62; + const cornerStripeWidth = 0.4; const cornerGroupHalfDepth = (5 * cornerStripeSpacing + cornerStripeWidth) / 2; - const endpointForCorner = (arm) => [...arm.crosswalkFrame.endpoints].sort((a, b) => helpers.distanceMeters(a, frameCorner) - helpers.distanceMeters(b, frameCorner))[0]; + const endpointForCorner = (arm) => + [...arm.crosswalkFrame.endpoints].sort( + (a, b) => helpers.distanceMeters(a, frameCorner) - helpers.distanceMeters(b, frameCorner), + )[0]; const outerEdgeAtCorner = (arm) => { const endpoint = endpointForCorner(arm); return [arm.heading, arm.heading + 180] .map((heading) => helpers.offsetCoordinate(endpoint, heading, arm.crosswalkFrame.groupDepth / 2)) - .sort((a, b) => directionalProjectionMeters(center, b, bisector) - directionalProjectionMeters(center, a, bisector))[0]; + .sort( + (a, b) => directionalProjectionMeters(center, b, bisector) - directionalProjectionMeters(center, a, bisector), + )[0]; }; - const islandBaseGap = .05; + const islandBaseGap = 0.05; const islandApexOffset = 1.5; - const islandBase = [outerEdgeAtCorner(first), outerEdgeAtCorner(second)].map((point) => helpers.offsetCoordinate(point, bisector, islandBaseGap)); + const islandBase = [outerEdgeAtCorner(first), outerEdgeAtCorner(second)].map((point) => + helpers.offsetCoordinate(point, bisector, islandBaseGap), + ); const islandApex = helpers.offsetCoordinate(frameCorner, bisector, islandApexOffset); - const islandCrossingClearance = .2; + const islandCrossingClearance = 0.2; const cornerCrossingOffset = islandApexOffset + islandCrossingClearance + cornerGroupHalfDepth; - const islandInnerRadius = Math.min(...islandBase.map((point) => directionalProjectionMeters(center, point, bisector))); + const islandInnerRadius = Math.min( + ...islandBase.map((point) => directionalProjectionMeters(center, point, bisector)), + ); const islandOuterRadius = directionalProjectionMeters(center, islandApex, bisector); const cornerCrossingCenter = helpers.offsetCoordinate(frameCorner, bisector, cornerCrossingOffset); - const islandRing = roundedPolygonRing([islandBase[0], islandApex, islandBase[1]], .24); - islands.push({ type: "Feature", properties: { native_id: `complex-corner-island:${cluster.id}:${index + 1}`, cluster_id: cluster.id, kind: "complex-corner-island", corner_index: index + 1, from_heading: first.heading, to_heading: second.heading, frame_corner: frameCorner, base_points: islandBase, apex_point: islandApex, inner_radius_m: islandInnerRadius, outer_radius_m: islandOuterRadius, base_width_m: helpers.distanceMeters(...islandBase), crossing_clearance_m: islandCrossingClearance, corner_rounding_ratio: .24, provenance: "native-road-complex-junction-corner/v7-road-gap-fill" }, geometry: { type: "Polygon", coordinates: [islandRing] } }); + const islandRing = roundedPolygonRing([islandBase[0], islandApex, islandBase[1]], 0.24); + islands.push({ + type: 'Feature', + properties: { + native_id: `complex-corner-island:${cluster.id}:${index + 1}`, + cluster_id: cluster.id, + kind: 'complex-corner-island', + corner_index: index + 1, + from_heading: first.heading, + to_heading: second.heading, + frame_corner: frameCorner, + base_points: islandBase, + apex_point: islandApex, + inner_radius_m: islandInnerRadius, + outer_radius_m: islandOuterRadius, + base_width_m: helpers.distanceMeters(...islandBase), + crossing_clearance_m: islandCrossingClearance, + corner_rounding_ratio: 0.24, + provenance: 'native-road-complex-junction-corner/v7-road-gap-fill', + }, + geometry: { type: 'Polygon', coordinates: [islandRing] }, + }); - let cornerCrossingHalfSpan = .4; + let cornerCrossingHalfSpan = 0.4; for (let stripe = 0; stripe < 6; stripe += 1) { const stripeOffset = (stripe - 2.5) * cornerStripeSpacing; const stripeCenter = helpers.offsetCoordinate(cornerCrossingCenter, bisector, stripeOffset); const stripeRadius = directionalProjectionMeters(center, stripeCenter, bisector); const curbPair = limitedCornerPair(first, second, stripeRadius, bisector, center, 6.5, helpers); if (!curbPair) continue; - const halfSpan = Math.max(.4, Math.min(6.5, helpers.distanceMeters(...curbPair)) / 2); + const halfSpan = Math.max(0.4, Math.min(6.5, helpers.distanceMeters(...curbPair)) / 2); cornerCrossingHalfSpan = Math.max(cornerCrossingHalfSpan, halfSpan); const stripePair = [ helpers.offsetCoordinate(stripeCenter, bisector - 90, halfSpan), @@ -170,7 +309,22 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { helpers.offsetCoordinate(stripePair[0], bisector, cornerStripeWidth / 2), helpers.offsetCoordinate(stripePair[0], bisector, -cornerStripeWidth / 2), ]; - crosswalks.push({ type: "Feature", properties: { native_id: `complex-corner-crosswalk:${cluster.id}:${index + 1}:${stripe + 1}`, cluster_id: cluster.id, kind: "complex-corner-crosswalk", corner_index: index + 1, direction: bisector, radial_distance_m: stripeRadius, frame_corner: frameCorner, from_heading: first.heading, to_heading: second.heading, provenance: "native-road-complex-junction-corner-crosswalk/v3" }, geometry: { type: "Polygon", coordinates: [ring] } }); + crosswalks.push({ + type: 'Feature', + properties: { + native_id: `complex-corner-crosswalk:${cluster.id}:${index + 1}:${stripe + 1}`, + cluster_id: cluster.id, + kind: 'complex-corner-crosswalk', + corner_index: index + 1, + direction: bisector, + radial_distance_m: stripeRadius, + frame_corner: frameCorner, + from_heading: first.heading, + to_heading: second.heading, + provenance: 'native-road-complex-junction-corner-crosswalk/v3', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } } // Each carriageway ends in its own rectangle, so adjacent arms meet at a @@ -195,17 +349,46 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { // outside that band the two edges are near parallel and any fillet fitted // to them would sweep across the carriageways instead of the corner. if (apexReach === null || apexReach < 1 || apexReach > outerRadius) { - cornerDiagnostics.push(helpers.diagnostic("warning", `junction-cluster:${cluster.id}`, [...nodeIds], "complex-junction-corner-fillet-fallback", "该夹角的道路边缘切线无法安全构造圆角,已保留直角过渡。", center)); + cornerDiagnostics.push( + helpers.diagnostic( + 'warning', + `junction-cluster:${cluster.id}`, + [...nodeIds], + 'complex-junction-corner-fillet-fallback', + '该夹角的道路边缘切线无法安全构造圆角,已保留直角过渡。', + center, + ), + ); continue; } // Tangent distance for a circle of `cornerRadius` inscribed in a wedge of // opening `delta`, clamped so the tangent points stay on the built arms. - const tangentDistance = Math.min(cornerRadius / Math.tan(delta * Math.PI / 360), Math.max(2, outerRadius - apexReach)); + const tangentDistance = Math.min( + cornerRadius / Math.tan((delta * Math.PI) / 360), + Math.max(2, outerRadius - apexReach), + ); const tangents = edges.map((edge) => helpers.offsetCoordinate(apex, edge.heading, tangentDistance)); const curve = quadraticCurve(tangents[0], apex, tangents[1], CORNER_FILLET_SEGMENTS); const ring = [...curve, center, curve[0]]; if (!ring.every((point) => point.every(Number.isFinite))) continue; - cornerFills.push({ type: "Feature", properties: { native_id: `complex-corner-fillet:${cluster.id}:${index + 1}`, cluster_id: cluster.id, kind: "complex-corner-fillet", complex_part: "corner-fillet", corner_index: index + 1, from_heading: first.heading, to_heading: second.heading, bisector_heading: bisector, corner_radius_m: cornerRadius, tangent_distance_m: Math.round(tangentDistance * 100) / 100, apex_reach_m: Math.round(apexReach * 100) / 100, provenance: "native-road-complex-junction-corner-fillet/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }); + cornerFills.push({ + type: 'Feature', + properties: { + native_id: `complex-corner-fillet:${cluster.id}:${index + 1}`, + cluster_id: cluster.id, + kind: 'complex-corner-fillet', + complex_part: 'corner-fillet', + corner_index: index + 1, + from_heading: first.heading, + to_heading: second.heading, + bisector_heading: bisector, + corner_radius_m: cornerRadius, + tangent_distance_m: Math.round(tangentDistance * 100) / 100, + apex_reach_m: Math.round(apexReach * 100) / 100, + provenance: 'native-road-complex-junction-corner-fillet/v1', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); // The straight pedestrian strips are trimmed at the cluster boundary, so // two arms that both carry a footway still meet as two loose ends across @@ -214,24 +397,108 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { // `second`, so each arm must carry the footway on that facing side. if (!armCarriesSidewalk(first, model, true) || !armCarriesSidewalk(second, model, false)) continue; const curb = [ - ...edgeRunToRadius(apex, edges[0], tangentDistance, outerRadius + SIDEWALK_CORNER_OVERRUN_METERS, center, helpers).reverse(), + ...edgeRunToRadius( + apex, + edges[0], + tangentDistance, + outerRadius + SIDEWALK_CORNER_OVERRUN_METERS, + center, + helpers, + ).reverse(), ...curve.slice(1, -1), - ...edgeRunToRadius(apex, edges[1], tangentDistance, outerRadius + SIDEWALK_CORNER_OVERRUN_METERS, center, helpers), + ...edgeRunToRadius( + apex, + edges[1], + tangentDistance, + outerRadius + SIDEWALK_CORNER_OVERRUN_METERS, + center, + helpers, + ), ]; const outerEdge = offsetPolylineAwayFromCenter(curb, center, SIDEWALK_WIDTH_METERS, helpers); const sidewalkRing = [...curb, ...outerEdge.slice().reverse(), curb[0]]; if (!sidewalkRing.every((point) => point.every(Number.isFinite)) || ringSelfIntersects(sidewalkRing)) { - cornerDiagnostics.push(helpers.diagnostic("warning", `junction-cluster:${cluster.id}`, [...nodeIds], "complex-junction-sidewalk-corner-fallback", "该夹角的人行道转角几何自交或无效,已跳过,两侧步行带保持断开。", center)); + cornerDiagnostics.push( + helpers.diagnostic( + 'warning', + `junction-cluster:${cluster.id}`, + [...nodeIds], + 'complex-junction-sidewalk-corner-fallback', + '该夹角的人行道转角几何自交或无效,已跳过,两侧步行带保持断开。', + center, + ), + ); continue; } - sidewalkCorners.push({ type: "Feature", properties: { native_id: `complex-sidewalk-corner:${cluster.id}:${index + 1}`, cluster_id: cluster.id, kind: "complex-sidewalk-corner", corner_index: index + 1, from_heading: first.heading, to_heading: second.heading, bisector_heading: bisector, width_m: SIDEWALK_WIDTH_METERS, overrun_m: SIDEWALK_CORNER_OVERRUN_METERS, provenance: "native-road-complex-junction-sidewalk-corner/v1" }, geometry: { type: "Polygon", coordinates: [sidewalkRing] } }); + sidewalkCorners.push({ + type: 'Feature', + properties: { + native_id: `complex-sidewalk-corner:${cluster.id}:${index + 1}`, + cluster_id: cluster.id, + kind: 'complex-sidewalk-corner', + corner_index: index + 1, + from_heading: first.heading, + to_heading: second.heading, + bisector_heading: bisector, + width_m: SIDEWALK_WIDTH_METERS, + overrun_m: SIDEWALK_CORNER_OVERRUN_METERS, + provenance: 'native-road-complex-junction-sidewalk-corner/v1', + }, + geometry: { type: 'Polygon', coordinates: [sidewalkRing] }, + }); } - const corePoints = boundaryParts.flatMap(({ item, inner, innerHalf }) => [helpers.offsetCoordinate(inner, item.heading + 90, innerHalf), helpers.offsetCoordinate(inner, item.heading - 90, innerHalf)]).sort((first, second) => angleAround(center, first) - angleAround(center, second)); - const coreRing = roundedPolygonRing(corePoints, .16); - const features = [{ type: "Feature", properties: { native_id: `complex-junction:${cluster.id}:core`, cluster_id: cluster.id, kind: "complex-core", complex_part: "core", center, radius_m: coreRadius, configured_radius_m: cluster.coreRadiusMeters, approach_count: approaches.length, carriageway_count: carriageways.length, approach_headings: sorted.map((item) => Math.round(item.heading * 10) / 10), corner_rounding_ratio: .16, provenance: "native-road-complex-junction/v6-rounded-core" }, geometry: { type: "Polygon", coordinates: [coreRing] } }]; + const corePoints = boundaryParts + .flatMap(({ item, inner, innerHalf }) => [ + helpers.offsetCoordinate(inner, item.heading + 90, innerHalf), + helpers.offsetCoordinate(inner, item.heading - 90, innerHalf), + ]) + .sort((first, second) => angleAround(center, first) - angleAround(center, second)); + const coreRing = roundedPolygonRing(corePoints, 0.16); + const features = [ + { + type: 'Feature', + properties: { + native_id: `complex-junction:${cluster.id}:core`, + cluster_id: cluster.id, + kind: 'complex-core', + complex_part: 'core', + center, + radius_m: coreRadius, + configured_radius_m: cluster.coreRadiusMeters, + approach_count: approaches.length, + carriageway_count: carriageways.length, + approach_headings: sorted.map((item) => Math.round(item.heading * 10) / 10), + corner_rounding_ratio: 0.16, + provenance: 'native-road-complex-junction/v6-rounded-core', + }, + geometry: { type: 'Polygon', coordinates: [coreRing] }, + }, + ]; for (const { item, outer, inner, outerHalf, innerHalf } of boundaryParts) { - const ring = [helpers.offsetCoordinate(outer, item.heading + 90, outerHalf), helpers.offsetCoordinate(inner, item.heading + 90, innerHalf), helpers.offsetCoordinate(inner, item.heading - 90, innerHalf), helpers.offsetCoordinate(outer, item.heading - 90, outerHalf), helpers.offsetCoordinate(outer, item.heading + 90, outerHalf)]; - features.push({ type: "Feature", properties: { native_id: `complex-junction:${cluster.id}:carriageway:${item.approach.segmentId}`, cluster_id: cluster.id, kind: "complex-approach", complex_part: "carriageway", heading_deg: item.heading, lane_count: item.approach.roadIds.reduce((sum, roadId) => sum + (model.roads.find((road) => road.id === roadId)?.laneCount || 0), 0), width_m: item.approach.widthMeters, provenance: "native-road-complex-junction/v5" }, geometry: { type: "Polygon", coordinates: [ring] } }); + const ring = [ + helpers.offsetCoordinate(outer, item.heading + 90, outerHalf), + helpers.offsetCoordinate(inner, item.heading + 90, innerHalf), + helpers.offsetCoordinate(inner, item.heading - 90, innerHalf), + helpers.offsetCoordinate(outer, item.heading - 90, outerHalf), + helpers.offsetCoordinate(outer, item.heading + 90, outerHalf), + ]; + features.push({ + type: 'Feature', + properties: { + native_id: `complex-junction:${cluster.id}:carriageway:${item.approach.segmentId}`, + cluster_id: cluster.id, + kind: 'complex-approach', + complex_part: 'carriageway', + heading_deg: item.heading, + lane_count: item.approach.roadIds.reduce( + (sum, roadId) => sum + (model.roads.find((road) => road.id === roadId)?.laneCount || 0), + 0, + ), + width_m: item.approach.widthMeters, + provenance: 'native-road-complex-junction/v5', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } // Corner fills come last so they overlay the rectangular carriageway ends // they are smoothing; they never replace an OSM-derived road surface. @@ -239,20 +506,56 @@ function buildComplexJunctionGeometry(model, cluster, helpers) { // `coreRadiusMeters` is only consulted when there is no reference geometry. // Under calibration the radius comes from the reference span, so a configured // value that silently does nothing has to be reported, not swallowed. - const configuredRadiusIgnored = calibration && Number.isFinite(cluster.coreRadiusMeters) && Math.abs(coreRadius - cluster.coreRadiusMeters) > .5; + const configuredRadiusIgnored = + calibration && Number.isFinite(cluster.coreRadiusMeters) && Math.abs(coreRadius - cluster.coreRadiusMeters) > 0.5; const configurationDiagnostics = configuredRadiusIgnored - ? [helpers.diagnostic("info", `junction-cluster:${cluster.id}`, [...nodeIds], "complex-junction-configured-radius-ignored", `已按参考几何校准核心半径为 ${Math.round(coreRadius * 10) / 10} 米,配置的 coreRadiusMeters=${cluster.coreRadiusMeters} 在有参考文件时不生效。`, center)] + ? [ + helpers.diagnostic( + 'info', + `junction-cluster:${cluster.id}`, + [...nodeIds], + 'complex-junction-configured-radius-ignored', + `已按参考几何校准核心半径为 ${Math.round(coreRadius * 10) / 10} 米,配置的 coreRadiusMeters=${cluster.coreRadiusMeters} 在有参考文件时不生效。`, + center, + ), + ] : []; - return { features, islands: [...islands, ...sidewalkCorners], crosswalks, stopLines, center, approaches, diagnostics: [...cornerDiagnostics, ...configurationDiagnostics, helpers.diagnostic("info", `junction-cluster:${cluster.id}`, [...nodeIds], calibration ? "complex-junction-reference-calibrated" : "complex-junction-generated", calibration ? `已使用参考几何校准参数后,由 OSM/native 重新生成 ${approaches.length} 个进口、道路面、中央分隔带、斑马线和停止线。` : `已独立生成 ${approaches.length} 个进口、道路面、中央分隔带、斑马线和停止线。`, center)] }; + return { + features, + islands: [...islands, ...sidewalkCorners], + crosswalks, + stopLines, + center, + approaches, + diagnostics: [ + ...cornerDiagnostics, + ...configurationDiagnostics, + helpers.diagnostic( + 'info', + `junction-cluster:${cluster.id}`, + [...nodeIds], + calibration ? 'complex-junction-reference-calibrated' : 'complex-junction-generated', + calibration + ? `已使用参考几何校准参数后,由 OSM/native 重新生成 ${approaches.length} 个进口、道路面、中央分隔带、斑马线和停止线。` + : `已独立生成 ${approaches.length} 个进口、道路面、中央分隔带、斑马线和停止线。`, + center, + ), + ], + }; } function readReferenceCalibration(cluster) { if (!cluster.referenceFile || !fs.existsSync(cluster.referenceFile)) return null; try { - const converted = convertGeoJson(JSON.parse(fs.readFileSync(cluster.referenceFile, "utf8"))); - const bounds = boundsOf({ features: converted.features.filter((feature) => [1, 2, 3, 4].includes(Number(feature.properties?.type))) }); - const lonScale = 111320 * Math.cos(((bounds.minLat + bounds.maxLat) / 2) * Math.PI / 180); - return { longSpanMeters: (bounds.maxLon - bounds.minLon) * lonScale, shortSpanMeters: (bounds.maxLat - bounds.minLat) * 111320 }; + const converted = convertGeoJson(JSON.parse(fs.readFileSync(cluster.referenceFile, 'utf8'))); + const bounds = boundsOf({ + features: converted.features.filter((feature) => [1, 2, 3, 4].includes(Number(feature.properties?.type))), + }); + const lonScale = 111320 * Math.cos((((bounds.minLat + bounds.maxLat) / 2) * Math.PI) / 180); + return { + longSpanMeters: (bounds.maxLon - bounds.minLon) * lonScale, + shortSpanMeters: (bounds.maxLat - bounds.minLat) * 111320, + }; } catch (_) { return null; } @@ -268,29 +571,37 @@ function complexJunctionMetrics(cluster) { // — a 24 m dual-carriageway arm was being fitted into a 16.6 m core no matter // what the config asked for. The calibrated branch is unchanged. const coreRadius = calibration - ? Math.max(12, Math.min(24, calibration.shortSpanMeters * .14)) + ? Math.max(12, Math.min(24, calibration.shortSpanMeters * 0.14)) : Math.max(12, Math.min(80, Number(cluster.coreRadiusMeters) || 28)); - const metrics = { calibration, coreRadius, approachOuterRadius: coreRadius + (Number(cluster.outerRadiusExtraMeters) || 18) }; + const metrics = { + calibration, + coreRadius, + approachOuterRadius: coreRadius + (Number(cluster.outerRadiusExtraMeters) || 18), + }; metricsCache.set(cluster, metrics); return metrics; } -function normalizeHeading(value) { return ((value + 180) % 360 + 360) % 360 - 180; } +function normalizeHeading(value) { + return ((((value + 180) % 360) + 360) % 360) - 180; +} // `arm.heading` points outward from the junction, so the corner clockwise from // it sits at heading+90 and the one counter-clockwise at heading-90. A road's // own sidewalk flags are relative to its digitisation direction, so flip them // whenever the arm runs against that direction. function armCarriesSidewalk(arm, model, cornerIsClockwise) { - return arm.members.some((member) => member.approach.roadIds - .map((roadId) => model.roads.find((road) => road.id === roadId)) - .filter(Boolean) - .some((road) => { - const outwardIsForward = String(road.sourceNodeIds[0]) === String(member.nodeId); - const onClockwiseSide = outwardIsForward ? road.sidewalkRight : road.sidewalkLeft; - const onCounterClockwiseSide = outwardIsForward ? road.sidewalkLeft : road.sidewalkRight; - return Boolean(cornerIsClockwise ? onClockwiseSide : onCounterClockwiseSide); - })); + return arm.members.some((member) => + member.approach.roadIds + .map((roadId) => model.roads.find((road) => road.id === roadId)) + .filter(Boolean) + .some((road) => { + const outwardIsForward = String(road.sourceNodeIds[0]) === String(member.nodeId); + const onClockwiseSide = outwardIsForward ? road.sidewalkRight : road.sidewalkLeft; + const onCounterClockwiseSide = outwardIsForward ? road.sidewalkLeft : road.sidewalkRight; + return Boolean(cornerIsClockwise ? onClockwiseSide : onCounterClockwiseSide); + }), + ); } // Walk outward along a wedge edge from its tangent point until the curb reaches @@ -322,8 +633,10 @@ function offsetPolylineAwayFromCenter(points, center, meters, helpers) { function ringSelfIntersects(ring) { const cross = (a, b, c) => (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); const straddles = (p1, p2, p3, p4) => { - const d1 = cross(p3, p4, p1); const d2 = cross(p3, p4, p2); - const d3 = cross(p1, p2, p3); const d4 = cross(p1, p2, p4); + const d1 = cross(p3, p4, p1); + const d2 = cross(p3, p4, p2); + const d3 = cross(p1, p2, p3); + const d4 = cross(p1, p2, p4); return ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0)); }; for (let first = 0; first < ring.length - 1; first += 1) { @@ -335,27 +648,36 @@ function ringSelfIntersects(ring) { return false; } -function angleAround(center, point) { return Math.atan2(point[1] - center[1], point[0] - center[0]); } +function angleAround(center, point) { + return Math.atan2(point[1] - center[1], point[0] - center[0]); +} function signedLateralMeters(origin, point, heading) { - const east = (point[0] - origin[0]) * 111320 * Math.cos(origin[1] * Math.PI / 180); + const east = (point[0] - origin[0]) * 111320 * Math.cos((origin[1] * Math.PI) / 180); const north = (point[1] - origin[1]) * 111320; - const radians = (heading + 90) * Math.PI / 180; + const radians = ((heading + 90) * Math.PI) / 180; return east * Math.sin(radians) + north * Math.cos(radians); } function bearing(first, second) { - const east = (second[0] - first[0]) * Math.cos(first[1] * Math.PI / 180); + const east = (second[0] - first[0]) * Math.cos((first[1] * Math.PI) / 180); const north = second[1] - first[1]; - return Math.atan2(east, north) * 180 / Math.PI; + return (Math.atan2(east, north) * 180) / Math.PI; +} +function midpoint(first, second) { + return [(first[0] + second[0]) / 2, (first[1] + second[1]) / 2]; } -function midpoint(first, second) { return [(first[0] + second[0]) / 2, (first[1] + second[1]) / 2]; } function averageHeading(headings) { - const vector = headings.reduce((sum, heading) => { - const radians = heading * Math.PI / 180; - return [sum[0] + Math.sin(radians), sum[1] + Math.cos(radians)]; - }, [0, 0]); - return Math.atan2(vector[0], vector[1]) * 180 / Math.PI; + const vector = headings.reduce( + (sum, heading) => { + const radians = (heading * Math.PI) / 180; + return [sum[0] + Math.sin(radians), sum[1] + Math.cos(radians)]; + }, + [0, 0], + ); + return (Math.atan2(vector[0], vector[1]) * 180) / Math.PI; +} +function positiveHeadingDelta(first, second) { + return (((second - first) % 360) + 360) % 360; } -function positiveHeadingDelta(first, second) { return ((second - first) % 360 + 360) % 360; } function pointOnCarriagewayRadius(item, center, radius, helpers) { const start = item.approach.line[0]; const startRadius = directionalProjectionMeters(center, start, item.heading); @@ -374,15 +696,18 @@ function armEnvelopeAtRadius(arm, radius, center, helpers) { maximum = Math.max(maximum, lateral + halfWidth); }); if (!Number.isFinite(minimum) || maximum - minimum < 1) return null; - return { center: helpers.offsetCoordinate(reference, arm.heading + 90, (minimum + maximum) / 2), widthMeters: maximum - minimum }; + return { + center: helpers.offsetCoordinate(reference, arm.heading + 90, (minimum + maximum) / 2), + widthMeters: maximum - minimum, + }; } function supportLineIntersection(first, second, origin) { - const lonScale = 111320 * Math.cos(origin[1] * Math.PI / 180); + const lonScale = 111320 * Math.cos((origin[1] * Math.PI) / 180); const toLocal = (point) => [(point[0] - origin[0]) * lonScale, (point[1] - origin[1]) * 111320]; const firstPoint = toLocal(first.center); const secondPoint = toLocal(second.center); const direction = (heading) => { - const radians = heading * Math.PI / 180; + const radians = (heading * Math.PI) / 180; return [Math.sin(radians), Math.cos(radians)]; }; const firstDirection = direction(first.supportHeading); @@ -391,17 +716,26 @@ function supportLineIntersection(first, second, origin) { if (Math.abs(denominator) < 1e-6) return null; const delta = [secondPoint[0] - firstPoint[0], secondPoint[1] - firstPoint[1]]; const distanceAlongFirst = (delta[0] * secondDirection[1] - delta[1] * secondDirection[0]) / denominator; - const intersection = [firstPoint[0] + firstDirection[0] * distanceAlongFirst, firstPoint[1] + firstDirection[1] * distanceAlongFirst]; + const intersection = [ + firstPoint[0] + firstDirection[0] * distanceAlongFirst, + firstPoint[1] + firstDirection[1] * distanceAlongFirst, + ]; return [origin[0] + intersection[0] / lonScale, origin[1] + intersection[1] / 111320]; } function limitedCornerPair(first, second, radius, bisector, center, maxWidth, helpers) { - const pair = [cornerEdgeAtRadius(first, radius, bisector, center, helpers), cornerEdgeAtRadius(second, radius, bisector, center, helpers)]; + const pair = [ + cornerEdgeAtRadius(first, radius, bisector, center, helpers), + cornerEdgeAtRadius(second, radius, bisector, center, helpers), + ]; if (!pair.every(Boolean)) return null; const width = helpers.distanceMeters(pair[0], pair[1]); const middle = midpoint(pair[0], pair[1]); - const halfWidth = Math.max(.4, Math.min(width, maxWidth) / 2); - const acrossHeading = width > .1 ? bearing(pair[0], pair[1]) : bisector + 90; - return [helpers.offsetCoordinate(middle, acrossHeading + 180, halfWidth), helpers.offsetCoordinate(middle, acrossHeading, halfWidth)]; + const halfWidth = Math.max(0.4, Math.min(width, maxWidth) / 2); + const acrossHeading = width > 0.1 ? bearing(pair[0], pair[1]) : bisector + 90; + return [ + helpers.offsetCoordinate(middle, acrossHeading + 180, halfWidth), + helpers.offsetCoordinate(middle, acrossHeading, halfWidth), + ]; } function cornerEdgeAtRadius(arm, radius, bisector, center, helpers) { return cornerEdgeAt(arm, radius, bisector, center, helpers)?.point || null; @@ -410,15 +744,24 @@ function cornerEdgeAt(arm, radius, bisector, center, helpers) { const candidates = arm.members.flatMap((member) => { const point = pointOnCarriagewayRadius(member, center, radius, helpers); const halfWidth = member.approach.widthMeters / 2; - return [90, -90].map((side) => ({ point: helpers.offsetCoordinate(point, member.heading + side, halfWidth), heading: member.heading })); + return [90, -90].map((side) => ({ + point: helpers.offsetCoordinate(point, member.heading + side, halfWidth), + heading: member.heading, + })); }); - return candidates.sort((first, second) => directionalProjectionMeters(center, second.point, bisector) - directionalProjectionMeters(center, first.point, bisector))[0] || null; + return ( + candidates.sort( + (first, second) => + directionalProjectionMeters(center, second.point, bisector) - + directionalProjectionMeters(center, first.point, bisector), + )[0] || null + ); } function rayIntersection(first, second, origin) { - const lonScale = 111320 * Math.cos(origin[1] * Math.PI / 180); + const lonScale = 111320 * Math.cos((origin[1] * Math.PI) / 180); const toLocal = (point) => [(point[0] - origin[0]) * lonScale, (point[1] - origin[1]) * 111320]; const direction = (heading) => { - const radians = heading * Math.PI / 180; + const radians = (heading * Math.PI) / 180; return [Math.sin(radians), Math.cos(radians)]; }; const firstPoint = toLocal(first.point); @@ -429,7 +772,10 @@ function rayIntersection(first, second, origin) { if (Math.abs(denominator) < 1e-4) return null; const delta = [secondPoint[0] - firstPoint[0], secondPoint[1] - firstPoint[1]]; const distanceAlongFirst = (delta[0] * secondDirection[1] - delta[1] * secondDirection[0]) / denominator; - const local = [firstPoint[0] + firstDirection[0] * distanceAlongFirst, firstPoint[1] + firstDirection[1] * distanceAlongFirst]; + const local = [ + firstPoint[0] + firstDirection[0] * distanceAlongFirst, + firstPoint[1] + firstDirection[1] * distanceAlongFirst, + ]; if (!local.every(Number.isFinite)) return null; return [origin[0] + local[0] / lonScale, origin[1] + local[1] / 111320]; } @@ -438,14 +784,17 @@ function quadraticCurve(start, control, end, segments) { for (let index = 0; index <= segments; index += 1) { const t = index / segments; const u = 1 - t; - result.push([u * u * start[0] + 2 * u * t * control[0] + t * t * end[0], u * u * start[1] + 2 * u * t * control[1] + t * t * end[1]]); + result.push([ + u * u * start[0] + 2 * u * t * control[0] + t * t * end[0], + u * u * start[1] + 2 * u * t * control[1] + t * t * end[1], + ]); } return result; } function directionalProjectionMeters(origin, point, heading) { - const east = (point[0] - origin[0]) * 111320 * Math.cos(origin[1] * Math.PI / 180); + const east = (point[0] - origin[0]) * 111320 * Math.cos((origin[1] * Math.PI) / 180); const north = (point[1] - origin[1]) * 111320; - const radians = heading * Math.PI / 180; + const radians = (heading * Math.PI) / 180; return east * Math.sin(radians) + north * Math.cos(radians); } function smoothClosedRing(vertices) { @@ -453,11 +802,18 @@ function smoothClosedRing(vertices) { // carriageway bends slightly. Sort this local corner only around its own // centroid before rounding, avoiding a self-crossing safety island while // keeping the global junction boundary fully OSM-driven. - const centroid = vertices.reduce((sum, point) => [sum[0] + point[0] / vertices.length, sum[1] + point[1] / vertices.length], [0, 0]); - const ordered = [...vertices].sort((first, second) => Math.atan2(first[1] - centroid[1], first[0] - centroid[0]) - Math.atan2(second[1] - centroid[1], second[0] - centroid[0])); + const centroid = vertices.reduce( + (sum, point) => [sum[0] + point[0] / vertices.length, sum[1] + point[1] / vertices.length], + [0, 0], + ); + const ordered = [...vertices].sort( + (first, second) => + Math.atan2(first[1] - centroid[1], first[0] - centroid[0]) - + Math.atan2(second[1] - centroid[1], second[0] - centroid[0]), + ); const points = ordered.flatMap((point, index) => { const next = ordered[(index + 1) % ordered.length]; - return [interpolateCoordinate(point, next, .18), interpolateCoordinate(point, next, .82)]; + return [interpolateCoordinate(point, next, 0.18), interpolateCoordinate(point, next, 0.82)]; }); return [...points, points[0]]; } @@ -469,6 +825,8 @@ function roundedPolygonRing(vertices, ratio) { }); return [...points, points[0]]; } -function interpolateCoordinate(first, second, ratio) { return [first[0] + (second[0] - first[0]) * ratio, first[1] + (second[1] - first[1]) * ratio]; } +function interpolateCoordinate(first, second, ratio) { + return [first[0] + (second[0] - first[0]) * ratio, first[1] + (second[1] - first[1]) * ratio]; +} module.exports = { buildComplexJunctionGeometry, complexJunctionMetrics }; diff --git a/src/compile/layer-manifest.js b/src/compile/layer-manifest.js index a7c977f..a8ae85f 100644 --- a/src/compile/layer-manifest.js +++ b/src/compile/layer-manifest.js @@ -1,26 +1,55 @@ -"use strict"; +'use strict'; // The compiler's layer registry is the single source of truth for published // GeoJSON files and the Blender-facing manifest. Keep rendering details limited // to material slot names; the host owns the actual material definitions. const LAYER_REGISTRY = Object.freeze([ - { key: "roadSurface", source: "road_surface", role: "surface", materialLayer: "road_surface" }, - { key: "edgeLines", source: "edge_lines", role: "marking", materialLayer: "lane_separators" }, - { key: "intersectionSurface", source: "intersection_surface", role: "surface", materialLayer: "intersection_surface" }, - { key: "sidewalkSurface", source: "sidewalk_surface", role: "surface", materialLayer: "sidewalks" }, - { key: "laneSeparators", source: "lane_separators", role: "marking", materialLayer: "lane_separators", splitBy: { prop: "color", cases: [{ match: "yellow", material: "native_lane_separator_yellow" }, { default: true, material: "lane_separators" }] } }, - { key: "centerLines", source: "center_lines", role: "marking", materialLayer: "center_lines", splitBy: { prop: "color", cases: [{ match: "white", material: "native_center_line_white" }, { default: true, material: "center_lines" }] } }, - { key: "directionArrows", source: "direction_arrows", role: "marking", materialLayer: "lane_arrows_webscale" }, - { key: "turnArrows", source: "turn_arrows", role: "marking", materialLayer: "lane_arrows_webscale" }, - { key: "crosswalks", source: "crosswalks", role: "marking", materialLayer: "crosswalks" }, - { key: "vehicleStopLines", source: "vehicle_stop_lines", role: "marking", materialLayer: "vehicle_stop_lines" }, - { key: "laneCenterlines", source: "lane_centerlines", role: "semantic" }, - { key: "connectors", source: "connectors", role: "semantic" }, + { key: 'roadSurface', source: 'road_surface', role: 'surface', materialLayer: 'road_surface' }, + { key: 'edgeLines', source: 'edge_lines', role: 'marking', materialLayer: 'lane_separators' }, + { + key: 'intersectionSurface', + source: 'intersection_surface', + role: 'surface', + materialLayer: 'intersection_surface', + }, + { key: 'sidewalkSurface', source: 'sidewalk_surface', role: 'surface', materialLayer: 'sidewalks' }, + { + key: 'laneSeparators', + source: 'lane_separators', + role: 'marking', + materialLayer: 'lane_separators', + splitBy: { + prop: 'color', + cases: [ + { match: 'yellow', material: 'native_lane_separator_yellow' }, + { default: true, material: 'lane_separators' }, + ], + }, + }, + { + key: 'centerLines', + source: 'center_lines', + role: 'marking', + materialLayer: 'center_lines', + splitBy: { + prop: 'color', + cases: [ + { match: 'white', material: 'native_center_line_white' }, + { default: true, material: 'center_lines' }, + ], + }, + }, + { key: 'directionArrows', source: 'direction_arrows', role: 'marking', materialLayer: 'lane_arrows_webscale' }, + { key: 'turnArrows', source: 'turn_arrows', role: 'marking', materialLayer: 'lane_arrows_webscale' }, + { key: 'crosswalks', source: 'crosswalks', role: 'marking', materialLayer: 'crosswalks' }, + { key: 'vehicleStopLines', source: 'vehicle_stop_lines', role: 'marking', materialLayer: 'vehicle_stop_lines' }, + { key: 'laneCenterlines', source: 'lane_centerlines', role: 'semantic' }, + { key: 'connectors', source: 'connectors', role: 'semantic' }, ]); function manifestForArea(areaId) { return { - contract: "native-road-package/v1.1", + contract: 'native-road-package/v1.1', areaId, layers: LAYER_REGISTRY.map(({ source, role, materialLayer, splitBy }) => ({ source, @@ -34,16 +63,20 @@ function manifestForArea(areaId) { function validatePublishedLayers(directory, manifest) { const declared = new Set(); for (const layer of manifest.layers) { - if (!layer || typeof layer.source !== "string" || declared.has(layer.source)) throw new Error("Manifest has duplicate or invalid source."); + if (!layer || typeof layer.source !== 'string' || declared.has(layer.source)) + throw new Error('Manifest has duplicate or invalid source.'); declared.add(layer.source); - const file = require("path").join(directory, "layers", `${layer.source}.geojson`); - if (!require("fs").existsSync(file)) throw new Error(`Manifest source is missing: ${layer.source}`); + const file = require('path').join(directory, 'layers', `${layer.source}.geojson`); + if (!require('fs').existsSync(file)) throw new Error(`Manifest source is missing: ${layer.source}`); } - const files = require("fs").existsSync(require("path").join(directory, "layers")) - ? require("fs").readdirSync(require("path").join(directory, "layers")).filter((name) => name.endsWith(".geojson")).map((name) => name.slice(0, -8)) + const files = require('fs').existsSync(require('path').join(directory, 'layers')) + ? require('fs') + .readdirSync(require('path').join(directory, 'layers')) + .filter((name) => name.endsWith('.geojson')) + .map((name) => name.slice(0, -8)) : []; const extras = files.filter((source) => !declared.has(source)); - if (extras.length) throw new Error(`Unmanifested GeoJSON source: ${extras.join(", ")}`); + if (extras.length) throw new Error(`Unmanifested GeoJSON source: ${extras.join(', ')}`); } module.exports = { LAYER_REGISTRY, manifestForArea, validatePublishedLayers }; diff --git a/src/compile/native-road.js b/src/compile/native-road.js index 692275a..4c4cba4 100644 --- a/src/compile/native-road.js +++ b/src/compile/native-road.js @@ -1,13 +1,33 @@ -"use strict"; +'use strict'; -const fs = require("fs"); -const path = require("path"); -const { arrowRingsAt, normalizeManeuver } = require("./turn-lane-arrows"); -const { buildComplexJunctionGeometry, complexJunctionMetrics } = require("./complex-junction"); +const fs = require('fs'); +const path = require('path'); +const { arrowRingsAt, normalizeManeuver } = require('./turn-lane-arrows'); +const { buildComplexJunctionGeometry, complexJunctionMetrics } = require('./complex-junction'); -const OVERRIDE_SCHEMA = "native-road-overrides/v1"; -const MOTOR_HIGHWAYS = new Set(["motorway", "trunk", "primary", "secondary", "tertiary", "unclassified", "residential", "living_street", "service"]); -const DEFAULT_WIDTHS = { motorway: 12, trunk: 10, primary: 10, secondary: 8, tertiary: 7, unclassified: 6, residential: 6, living_street: 5, service: 4 }; +const OVERRIDE_SCHEMA = 'native-road-overrides/v1'; +const MOTOR_HIGHWAYS = new Set([ + 'motorway', + 'trunk', + 'primary', + 'secondary', + 'tertiary', + 'unclassified', + 'residential', + 'living_street', + 'service', +]); +const DEFAULT_WIDTHS = { + motorway: 12, + trunk: 10, + primary: 10, + secondary: 8, + tertiary: 7, + unclassified: 6, + residential: 6, + living_street: 5, + service: 4, +}; const DEFAULT_SIDEWALK_WIDTH_METERS = 2; const DIRECTION_ARROW_INTERVAL_METERS = 32; const DIRECTION_ARROW_ENDPOINT_BUFFER_METERS = 14; @@ -17,15 +37,15 @@ const CROSSWALK_JUNCTION_INSET_METERS = 1.5; const CROSSWALK_MAX_JUNCTION_INSET_METERS = 4; const CENTER_LINE_DASH_LENGTH_METERS = 2; const CENTER_LINE_DASH_GAP_METERS = 2; -const CENTER_LINE_WIDTH_METERS = .25; -const CENTER_LINE_SOLID_OVERLAP_METERS = .04; +const CENTER_LINE_WIDTH_METERS = 0.25; +const CENTER_LINE_SOLID_OVERLAP_METERS = 0.04; const CENTER_LINE_CONTROL_CLEARANCE_METERS = 1; -const CENTER_LINE_COLORS = new Set(["yellow", "white"]); -const CENTER_LINE_PATTERNS = new Set(["dashed", "solid"]); -const CONNECTOR_BOUNDARY_TOLERANCE_METERS = .05; +const CENTER_LINE_COLORS = new Set(['yellow', 'white']); +const CENTER_LINE_PATTERNS = new Set(['dashed', 'solid']); +const CONNECTOR_BOUNDARY_TOLERANCE_METERS = 0.05; // A lane centerline is drawn as a hairline, so probe the crossing with a narrow // band. Using the full lane width would clip the line metres early. -const LANE_CENTERLINE_PROBE_WIDTH_METERS = .12; +const LANE_CENTERLINE_PROBE_WIDTH_METERS = 0.12; const JUNCTION_CURVE_SEGMENTS = 8; function parseOsmRoads(xml) { @@ -33,19 +53,21 @@ function parseOsmRoads(xml) { const crossingNodes = []; for (const match of xml.matchAll(/]*?)(?:\/>|>([\s\S]*?)<\/node>)/g)) { const attrs = xmlAttrs(match[1]); - if (attrs.action === "delete" || !attrs.id || attrs.lon === undefined || attrs.lat === undefined) continue; + if (attrs.action === 'delete' || !attrs.id || attrs.lon === undefined || attrs.lat === undefined) continue; const coordinate = [Number(attrs.lon), Number(attrs.lat)]; if (!coordinate.every(Number.isFinite)) continue; - const id = String(attrs.id); const tags = parseTags(match[2] || ""); + const id = String(attrs.id); + const tags = parseTags(match[2] || ''); nodes.set(id, coordinate); - if (tags.highway === "crossing" && !["no", "none", "unmarked"].includes(tags["crossing:markings"])) crossingNodes.push({ id, coordinate, tags }); + if (tags.highway === 'crossing' && !['no', 'none', 'unmarked'].includes(tags['crossing:markings'])) + crossingNodes.push({ id, coordinate, tags }); } const ways = []; for (const match of xml.matchAll(/]*)>([\s\S]*?)<\/way>/g)) { const attrs = xmlAttrs(match[1]); const body = match[2]; const tags = parseTags(body); - if (attrs.action === "delete" || !MOTOR_HIGHWAYS.has(tags.highway || "")) continue; + if (attrs.action === 'delete' || !MOTOR_HIGHWAYS.has(tags.highway || '')) continue; const refs = [...body.matchAll(/]*)\/?\s*>/g)].map((item) => xmlAttrs(item[1]).ref).filter(Boolean); const coords = refs.map((ref) => nodes.get(String(ref))).filter(Boolean); if (coords.length < 2 || coords.length !== refs.length) continue; @@ -61,97 +83,154 @@ function compileRoadModel(xml, overrides) { const endpoints = []; const byNode = new Map(); const sharedNodeWayIds = new Map(); - for (const way of parsed.ways) for (const nodeId of new Set(way.refs)) { - if (!sharedNodeWayIds.has(nodeId)) sharedNodeWayIds.set(nodeId, new Set()); - sharedNodeWayIds.get(nodeId).add(way.id); - } + for (const way of parsed.ways) + for (const nodeId of new Set(way.refs)) { + if (!sharedNodeWayIds.has(nodeId)) sharedNodeWayIds.set(nodeId, new Set()); + sharedNodeWayIds.get(nodeId).add(way.id); + } for (const sourceWay of parsed.ways) { const segments = splitWayAtSharedNodes(sourceWay, sharedNodeWayIds); for (const way of segments) { - const directions = way.tags.oneway === "yes" || way.tags.oneway === "1" || way.tags.junction === "roundabout" ? ["forward"] : ["forward", "backward"]; - for (const direction of directions) { - const base = roadAttributes(way.tags, direction); - const id = `road:way/${way.id}${way.segmentIndex === null ? "" : `:segment/${way.segmentIndex}`}:${direction}`; - const road = { id, osmWayIds: [way.id], segmentId: `segment:way/${way.id}/${way.segmentIndex ?? 0}`, sourceRoadId: `road:way/${way.id}:${direction}`, direction, highway: way.tags.highway, centerline: direction === "forward" ? way.coords : [...way.coords].reverse(), sourceNodeIds: direction === "forward" ? [way.refs[0], way.refs.at(-1)] : [way.refs.at(-1), way.refs[0]], tags: way.tags, ...base, appliedOverrideIds: [], diagnostics: [] }; - applyRoadOverrides(road, overrides, diagnostics); - roads.push(road); - for (const side of ["start", "end"]) { - const nodeId = side === "start" ? road.sourceNodeIds[0] : road.sourceNodeIds[1]; - const endpoint = { id: `endpoint:${road.id}:${side}`, roadId: id, side, nodeId, coordinate: side === "start" ? road.centerline[0] : road.centerline.at(-1), direction }; - endpoints.push(endpoint); - if (!byNode.has(nodeId)) byNode.set(nodeId, []); - byNode.get(nodeId).push(endpoint); + const directions = + way.tags.oneway === 'yes' || way.tags.oneway === '1' || way.tags.junction === 'roundabout' + ? ['forward'] + : ['forward', 'backward']; + for (const direction of directions) { + const base = roadAttributes(way.tags, direction); + const id = `road:way/${way.id}${way.segmentIndex === null ? '' : `:segment/${way.segmentIndex}`}:${direction}`; + const road = { + id, + osmWayIds: [way.id], + segmentId: `segment:way/${way.id}/${way.segmentIndex ?? 0}`, + sourceRoadId: `road:way/${way.id}:${direction}`, + direction, + highway: way.tags.highway, + centerline: direction === 'forward' ? way.coords : [...way.coords].reverse(), + sourceNodeIds: direction === 'forward' ? [way.refs[0], way.refs.at(-1)] : [way.refs.at(-1), way.refs[0]], + tags: way.tags, + ...base, + appliedOverrideIds: [], + diagnostics: [], + }; + applyRoadOverrides(road, overrides, diagnostics); + roads.push(road); + for (const side of ['start', 'end']) { + const nodeId = side === 'start' ? road.sourceNodeIds[0] : road.sourceNodeIds[1]; + const endpoint = { + id: `endpoint:${road.id}:${side}`, + roadId: id, + side, + nodeId, + coordinate: side === 'start' ? road.centerline[0] : road.centerline.at(-1), + direction, + }; + endpoints.push(endpoint); + if (!byNode.has(nodeId)) byNode.set(nodeId, []); + byNode.get(nodeId).push(endpoint); + } } } - } } const connections = resolveConnections(endpoints, byNode, overrides, diagnostics); const extent = roadExtent(roads); for (const [nodeId, items] of byNode) { if (items.length === 1 && distanceToExtentEdgeMeters(items[0].coordinate, extent) > 25) { const endpoint = items[0]; - diagnostics.push({ ...diagnostic("warning", endpoint.roadId, [nodeId], "unconnected-interior-road-end", "道路在区域内部结束,未连接到其他机动车道路。请确认这是实际断头,还是 OSM 节点尚未连接。", endpoint.coordinate), endpointId: endpoint.id, manualCandidates: nearbyManualCandidates(endpoints, endpoint) }); + diagnostics.push({ + ...diagnostic( + 'warning', + endpoint.roadId, + [nodeId], + 'unconnected-interior-road-end', + '道路在区域内部结束,未连接到其他机动车道路。请确认这是实际断头,还是 OSM 节点尚未连接。', + endpoint.coordinate, + ), + endpointId: endpoint.id, + manualCandidates: nearbyManualCandidates(endpoints, endpoint), + }); } } - const crossings = parsed.crossingNodes.map((crossing) => ({ ...crossing, osmWayIds: parsed.ways.filter((way) => way.refs.includes(crossing.id)).map((way) => way.id) })); - return { schema: "native-road-model/v1", roads, endpoints, connections, crossings, diagnostics }; + const crossings = parsed.crossingNodes.map((crossing) => ({ + ...crossing, + osmWayIds: parsed.ways.filter((way) => way.refs.includes(crossing.id)).map((way) => way.id), + })); + return { schema: 'native-road-model/v1', roads, endpoints, connections, crossings, diagnostics }; } function splitWayAtSharedNodes(way, sharedNodeWayIds) { const splitIndexes = [0]; - for (let index = 1; index < way.refs.length - 1; index += 1) if ((sharedNodeWayIds.get(way.refs[index])?.size || 0) > 1) splitIndexes.push(index); + for (let index = 1; index < way.refs.length - 1; index += 1) + if ((sharedNodeWayIds.get(way.refs[index])?.size || 0) > 1) splitIndexes.push(index); splitIndexes.push(way.refs.length - 1); if (splitIndexes.length === 2) return [{ ...way, segmentIndex: null }]; return splitIndexes.slice(1).map((end, index) => { const start = splitIndexes[index]; - return { ...way, refs: way.refs.slice(start, end + 1), coords: way.coords.slice(start, end + 1), segmentIndex: index + 1 }; + return { + ...way, + refs: way.refs.slice(start, end + 1), + coords: way.coords.slice(start, end + 1), + segmentIndex: index + 1, + }; }); } function roadExtent(roads) { const points = roads.flatMap((road) => road.centerline); - return { minLon: Math.min(...points.map((point) => point[0])), maxLon: Math.max(...points.map((point) => point[0])), minLat: Math.min(...points.map((point) => point[1])), maxLat: Math.max(...points.map((point) => point[1])) }; + return { + minLon: Math.min(...points.map((point) => point[0])), + maxLon: Math.max(...points.map((point) => point[0])), + minLat: Math.min(...points.map((point) => point[1])), + maxLat: Math.max(...points.map((point) => point[1])), + }; } function distanceToExtentEdgeMeters(point, extent) { - const lonScale = 111320 * Math.cos(point[1] * Math.PI / 180); - return Math.min((point[0] - extent.minLon) * lonScale, (extent.maxLon - point[0]) * lonScale, (point[1] - extent.minLat) * 111320, (extent.maxLat - point[1]) * 111320); + const lonScale = 111320 * Math.cos((point[1] * Math.PI) / 180); + return Math.min( + (point[0] - extent.minLon) * lonScale, + (extent.maxLon - point[0]) * lonScale, + (point[1] - extent.minLat) * 111320, + (extent.maxLat - point[1]) * 111320, + ); } function roadAttributes(tags, direction) { - const directional = direction === "forward" ? "forward" : "backward"; - const laneTag = tags[`lanes:${directional}`] ?? (tags.oneway === "yes" ? tags.lanes : null); + const directional = direction === 'forward' ? 'forward' : 'backward'; + const laneTag = tags[`lanes:${directional}`] ?? (tags.oneway === 'yes' ? tags.lanes : null); const parsedLanes = positiveInteger(laneTag); const totalLanes = positiveInteger(tags.lanes); - const lanes = parsedLanes || (totalLanes ? Math.max(1, Math.ceil(totalLanes / (tags.oneway === "yes" ? 1 : 2))) : 1); + const lanes = parsedLanes || (totalLanes ? Math.max(1, Math.ceil(totalLanes / (tags.oneway === 'yes' ? 1 : 2))) : 1); const parsedWidth = positiveNumber(tags.width); - const forwardLanes = positiveInteger(tags["lanes:forward"]); - const backwardLanes = positiveInteger(tags["lanes:backward"]); + const forwardLanes = positiveInteger(tags['lanes:forward']); + const backwardLanes = positiveInteger(tags['lanes:backward']); const directionalLaneTotal = forwardLanes && backwardLanes ? forwardLanes + backwardLanes : totalLanes; // `width` describes the whole OSM way. A directional road receives its lane // share; absent width falls back to a realistic per-lane carriageway width. - const width = parsedWidth ? parsedWidth * lanes / (directionalLaneTotal || (tags.oneway === "yes" ? lanes : lanes * 2)) : lanes * 3.25; + const width = parsedWidth + ? (parsedWidth * lanes) / (directionalLaneTotal || (tags.oneway === 'yes' ? lanes : lanes * 2)) + : lanes * 3.25; return { laneCount: lanes, widthMeters: width, - sidewalkLeft: sidewalkState(tags, direction, "left"), - sidewalkRight: sidewalkState(tags, direction, "right"), + sidewalkLeft: sidewalkState(tags, direction, 'left'), + sidewalkRight: sidewalkState(tags, direction, 'right'), provenance: { - laneCount: parsedLanes || totalLanes ? `tag:${parsedLanes ? `lanes:${directional}` : "lanes"}` : "inferred:default-lanes", - widthMeters: parsedWidth ? "tag:width (按方向车道数分配)" : "inferred:3.25m-per-lane", + laneCount: + parsedLanes || totalLanes ? `tag:${parsedLanes ? `lanes:${directional}` : 'lanes'}` : 'inferred:default-lanes', + widthMeters: parsedWidth ? 'tag:width (按方向车道数分配)' : 'inferred:3.25m-per-lane', }, }; } function sidewalkState(tags, direction, side) { - const osmSide = direction === "forward" ? side : side === "left" ? "right" : "left"; + const osmSide = direction === 'forward' ? side : side === 'left' ? 'right' : 'left'; const value = tags[`sidewalk:${osmSide}`] ?? tags.sidewalk; - return value === "both" || value === "yes" || value === osmSide; + return value === 'both' || value === 'yes' || value === osmSide; } function loadOverrides(file) { if (!fs.existsSync(file)) return { schema: OVERRIDE_SCHEMA, overrides: [] }; - return validateOverrides(JSON.parse(fs.readFileSync(file, "utf8"))); + return validateOverrides(JSON.parse(fs.readFileSync(file, 'utf8'))); } // An override points at an id derived from OSM. Editing the source can retire @@ -162,50 +241,117 @@ function loadOverrides(file) { // strict mode: writing a reference that cannot resolve is a real error. function staleOverrideTarget(item, sets) { if (!sets.roadIds) return null; - if (item.kind === "road" && typeof item.roadId === "string" && !sets.roadIds.has(item.roadId)) return item.roadId; - if (item.kind === "lane-separator-style" && typeof item.roadId === "string" && !sets.roadIds.has(item.roadId)) return item.roadId; - if (item.kind === "edge-line-style" && typeof item.roadId === "string" && !sets.directionalRoadIds.has(item.roadId)) return item.roadId; - if (item.kind === "center-line-style" && typeof item.segmentId === "string" && !sets.segmentIds.has(item.segmentId)) return item.segmentId; - if (item.kind === "junction-connection" && typeof item.fromEndpointId === "string" && typeof item.toEndpointId === "string" - && (!sets.endpointIds.has(item.fromEndpointId) || !sets.endpointIds.has(item.toEndpointId))) return `${item.fromEndpointId} → ${item.toEndpointId}`; - if (item.kind === "lane-connection" && typeof item.fromLaneId === "string" && typeof item.toLaneId === "string" - && (!sets.laneIds.has(item.fromLaneId) || !sets.laneIds.has(item.toLaneId))) return `${item.fromLaneId} → ${item.toLaneId}`; + if (item.kind === 'road' && typeof item.roadId === 'string' && !sets.roadIds.has(item.roadId)) return item.roadId; + if (item.kind === 'lane-separator-style' && typeof item.roadId === 'string' && !sets.roadIds.has(item.roadId)) + return item.roadId; + if (item.kind === 'edge-line-style' && typeof item.roadId === 'string' && !sets.directionalRoadIds.has(item.roadId)) + return item.roadId; + if (item.kind === 'center-line-style' && typeof item.segmentId === 'string' && !sets.segmentIds.has(item.segmentId)) + return item.segmentId; + if ( + item.kind === 'junction-connection' && + typeof item.fromEndpointId === 'string' && + typeof item.toEndpointId === 'string' && + (!sets.endpointIds.has(item.fromEndpointId) || !sets.endpointIds.has(item.toEndpointId)) + ) + return `${item.fromEndpointId} → ${item.toEndpointId}`; + if ( + item.kind === 'lane-connection' && + typeof item.fromLaneId === 'string' && + typeof item.toLaneId === 'string' && + (!sets.laneIds.has(item.fromLaneId) || !sets.laneIds.has(item.toLaneId)) + ) + return `${item.fromLaneId} → ${item.toLaneId}`; return null; } function validateOverrides(value, model, options = {}) { - if (!value || value.schema !== OVERRIDE_SCHEMA || !Array.isArray(value.overrides)) throw new Error(`Overrides must use ${OVERRIDE_SCHEMA}.`); + if (!value || value.schema !== OVERRIDE_SCHEMA || !Array.isArray(value.overrides)) + throw new Error(`Overrides must use ${OVERRIDE_SCHEMA}.`); const ids = new Set(); const roadIds = model ? new Set(model.roads.flatMap((road) => [road.id, road.sourceRoadId])) : null; const directionalRoadIds = model ? new Set(model.roads.map((road) => road.id)) : null; const endpointIds = model ? new Set(model.endpoints.map((endpoint) => endpoint.id)) : null; - const laneIds = model ? new Set(model.roads.flatMap((road) => Array.from({ length: road.laneCount }, (_, index) => `lane:${road.id}:${index + 1}`))) : null; + const laneIds = model + ? new Set( + model.roads.flatMap((road) => + Array.from({ length: road.laneCount }, (_, index) => `lane:${road.id}:${index + 1}`), + ), + ) + : null; const segmentIds = model ? new Set(model.roads.map((road) => road.segmentId)) : null; const sets = { roadIds, directionalRoadIds, endpointIds, laneIds, segmentIds }; const kept = []; const stale = []; for (const item of value.overrides) { - if (!item || typeof item.id !== "string" || !item.id || ids.has(item.id)) throw new Error("Each override needs a unique id."); + if (!item || typeof item.id !== 'string' || !item.id || ids.has(item.id)) + throw new Error('Each override needs a unique id.'); ids.add(item.id); if (options.skipStaleTargets) { const target = staleOverrideTarget(item, sets); - if (target) { stale.push({ id: item.id, kind: item.kind, target }); continue; } + if (target) { + stale.push({ id: item.id, kind: item.kind, target }); + continue; + } } - if (item.kind === "road") { - if (typeof item.roadId !== "string" || roadIds && !roadIds.has(item.roadId)) throw new Error(`Unknown road override target: ${item.roadId}`); - for (const key of ["widthMeters", "laneCount"]) if (item[key] !== undefined && (!Number.isFinite(item[key]) || item[key] <= 0 || (key === "laneCount" && !Number.isInteger(item[key])))) throw new Error(`Invalid road override ${key}.`); - for (const key of ["sidewalkLeft", "sidewalkRight"]) if (item[key] !== undefined && typeof item[key] !== "boolean") throw new Error(`Invalid road override ${key}.`); - } else if (item.kind === "junction-connection") { - if (typeof item.fromEndpointId !== "string" || typeof item.toEndpointId !== "string" || typeof item.enabled !== "boolean" || (endpointIds && (!endpointIds.has(item.fromEndpointId) || !endpointIds.has(item.toEndpointId)))) throw new Error("Invalid junction connection override."); - if (model && !connectionEndpointsCompatible(model, item.fromEndpointId, item.toEndpointId)) throw new Error("A manual junction connection must go from a road end to a nearby road start (within 35m)."); - } else if (item.kind === "lane-connection") { - if (typeof item.fromLaneId !== "string" || typeof item.toLaneId !== "string" || typeof item.enabled !== "boolean" || (laneIds && (!laneIds.has(item.fromLaneId) || !laneIds.has(item.toLaneId)))) throw new Error("Invalid lane connection override."); - } else if (item.kind === "center-line-style") { - if (typeof item.segmentId !== "string" || !CENTER_LINE_COLORS.has(item.color) || !CENTER_LINE_PATTERNS.has(item.pattern) || (item.double !== undefined && typeof item.double !== "boolean") || (item.double && (item.color !== "yellow" || item.pattern !== "solid")) || (segmentIds && !segmentIds.has(item.segmentId))) throw new Error("Invalid center line style override."); - } else if (item.kind === "lane-separator-style") { - if (typeof item.roadId !== "string" || (roadIds && !roadIds.has(item.roadId)) || !Number.isInteger(item.leftLaneIndex) || item.rightLaneIndex !== item.leftLaneIndex + 1 || !CENTER_LINE_COLORS.has(item.color) || !CENTER_LINE_PATTERNS.has(item.pattern)) throw new Error("Invalid lane separator style override."); - } else if (item.kind === "edge-line-style") { - if (typeof item.roadId !== "string" || (directionalRoadIds && !directionalRoadIds.has(item.roadId)) || !["left", "right"].includes(item.side) || !CENTER_LINE_COLORS.has(item.color) || !CENTER_LINE_PATTERNS.has(item.pattern)) throw new Error("Invalid edge line style override."); + if (item.kind === 'road') { + if (typeof item.roadId !== 'string' || (roadIds && !roadIds.has(item.roadId))) + throw new Error(`Unknown road override target: ${item.roadId}`); + for (const key of ['widthMeters', 'laneCount']) + if ( + item[key] !== undefined && + (!Number.isFinite(item[key]) || item[key] <= 0 || (key === 'laneCount' && !Number.isInteger(item[key]))) + ) + throw new Error(`Invalid road override ${key}.`); + for (const key of ['sidewalkLeft', 'sidewalkRight']) + if (item[key] !== undefined && typeof item[key] !== 'boolean') throw new Error(`Invalid road override ${key}.`); + } else if (item.kind === 'junction-connection') { + if ( + typeof item.fromEndpointId !== 'string' || + typeof item.toEndpointId !== 'string' || + typeof item.enabled !== 'boolean' || + (endpointIds && (!endpointIds.has(item.fromEndpointId) || !endpointIds.has(item.toEndpointId))) + ) + throw new Error('Invalid junction connection override.'); + if (model && !connectionEndpointsCompatible(model, item.fromEndpointId, item.toEndpointId)) + throw new Error('A manual junction connection must go from a road end to a nearby road start (within 35m).'); + } else if (item.kind === 'lane-connection') { + if ( + typeof item.fromLaneId !== 'string' || + typeof item.toLaneId !== 'string' || + typeof item.enabled !== 'boolean' || + (laneIds && (!laneIds.has(item.fromLaneId) || !laneIds.has(item.toLaneId))) + ) + throw new Error('Invalid lane connection override.'); + } else if (item.kind === 'center-line-style') { + if ( + typeof item.segmentId !== 'string' || + !CENTER_LINE_COLORS.has(item.color) || + !CENTER_LINE_PATTERNS.has(item.pattern) || + (item.double !== undefined && typeof item.double !== 'boolean') || + (item.double && (item.color !== 'yellow' || item.pattern !== 'solid')) || + (segmentIds && !segmentIds.has(item.segmentId)) + ) + throw new Error('Invalid center line style override.'); + } else if (item.kind === 'lane-separator-style') { + if ( + typeof item.roadId !== 'string' || + (roadIds && !roadIds.has(item.roadId)) || + !Number.isInteger(item.leftLaneIndex) || + item.rightLaneIndex !== item.leftLaneIndex + 1 || + !CENTER_LINE_COLORS.has(item.color) || + !CENTER_LINE_PATTERNS.has(item.pattern) + ) + throw new Error('Invalid lane separator style override.'); + } else if (item.kind === 'edge-line-style') { + if ( + typeof item.roadId !== 'string' || + (directionalRoadIds && !directionalRoadIds.has(item.roadId)) || + !['left', 'right'].includes(item.side) || + !CENTER_LINE_COLORS.has(item.color) || + !CENTER_LINE_PATTERNS.has(item.pattern) + ) + throw new Error('Invalid edge line style override.'); } else throw new Error(`Unsupported override kind: ${item.kind}`); kept.push(item); } @@ -213,64 +359,131 @@ function validateOverrides(value, model, options = {}) { } function applyRoadOverrides(road, overrides, diagnostics) { - const matching = overrides.overrides.filter((entry) => entry.kind === "road" && (entry.roadId === road.sourceRoadId || entry.roadId === road.id)); + const matching = overrides.overrides.filter( + (entry) => entry.kind === 'road' && (entry.roadId === road.sourceRoadId || entry.roadId === road.id), + ); // A legacy whole-way edit remains the baseline; a segment-specific edit can // deliberately refine it after the compiler has introduced split segments. matching.sort((first, second) => Number(first.roadId === road.id) - Number(second.roadId === road.id)); for (const item of matching) { - for (const key of ["widthMeters", "laneCount", "sidewalkLeft", "sidewalkRight"]) if (item[key] !== undefined) road[key] = item[key]; + for (const key of ['widthMeters', 'laneCount', 'sidewalkLeft', 'sidewalkRight']) + if (item[key] !== undefined) road[key] = item[key]; road.appliedOverrideIds.push(item.id); - for (const key of ["widthMeters", "laneCount"]) if (item[key] !== undefined) road.provenance[key] = `override:${item.id}`; + for (const key of ['widthMeters', 'laneCount']) + if (item[key] !== undefined) road.provenance[key] = `override:${item.id}`; } - if (road.widthMeters < road.laneCount * 2.4) diagnostics.push(diagnostic("warning", road.id, road.osmWayIds, "narrow-lane-width", "Configured road width is narrow for the selected lane count.", road.centerline[0])); + if (road.widthMeters < road.laneCount * 2.4) + diagnostics.push( + diagnostic( + 'warning', + road.id, + road.osmWayIds, + 'narrow-lane-width', + 'Configured road width is narrow for the selected lane count.', + road.centerline[0], + ), + ); } function resolveConnections(endpoints, byNode, overrides, diagnostics) { const result = []; for (const [nodeId, items] of byNode) { - const arrivals = items.filter((endpoint) => endpoint.side === "end"); - const departures = items.filter((endpoint) => endpoint.side === "start"); - for (const arrival of arrivals) for (const departure of departures) { - if (arrival.roadId === departure.roadId) continue; - const arrivalRoad = endpoints.find((endpoint) => endpoint.id === arrival.id)?.roadId; - const departureRoad = endpoints.find((endpoint) => endpoint.id === departure.id)?.roadId; - if (sameOsmWay(endpoints, arrivalRoad, departureRoad)) continue; - const override = overrides.overrides.find((entry) => entry.kind === "junction-connection" && entry.fromEndpointId === arrival.id && entry.toEndpointId === departure.id); - result.push({ id: `connection:${arrival.id}:${departure.id}`, nodeId, fromEndpointId: arrival.id, toEndpointId: departure.id, enabled: override ? override.enabled : true, provenance: override ? `override:${override.id}` : "osm:shared-node" }); - } - if (items.length > 8) diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "complex-junction", "Junction has more than eight directional endpoints and is not compiled as an ordinary junction.", items[0].coordinate)); + const arrivals = items.filter((endpoint) => endpoint.side === 'end'); + const departures = items.filter((endpoint) => endpoint.side === 'start'); + for (const arrival of arrivals) + for (const departure of departures) { + if (arrival.roadId === departure.roadId) continue; + const arrivalRoad = endpoints.find((endpoint) => endpoint.id === arrival.id)?.roadId; + const departureRoad = endpoints.find((endpoint) => endpoint.id === departure.id)?.roadId; + if (sameOsmWay(endpoints, arrivalRoad, departureRoad)) continue; + const override = overrides.overrides.find( + (entry) => + entry.kind === 'junction-connection' && + entry.fromEndpointId === arrival.id && + entry.toEndpointId === departure.id, + ); + result.push({ + id: `connection:${arrival.id}:${departure.id}`, + nodeId, + fromEndpointId: arrival.id, + toEndpointId: departure.id, + enabled: override ? override.enabled : true, + provenance: override ? `override:${override.id}` : 'osm:shared-node', + }); + } + if (items.length > 8) + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'complex-junction', + 'Junction has more than eight directional endpoints and is not compiled as an ordinary junction.', + items[0].coordinate, + ), + ); } // Overrides can add a deliberate movement omitted by the initial inference. // Keep it only when both endpoints still belong to the same OSM junction. - for (const override of overrides.overrides.filter((item) => item.kind === "junction-connection")) { - const exists = result.some((connection) => connection.fromEndpointId === override.fromEndpointId && connection.toEndpointId === override.toEndpointId); + for (const override of overrides.overrides.filter((item) => item.kind === 'junction-connection')) { + const exists = result.some( + (connection) => + connection.fromEndpointId === override.fromEndpointId && connection.toEndpointId === override.toEndpointId, + ); if (exists) continue; const from = endpoints.find((endpoint) => endpoint.id === override.fromEndpointId); const to = endpoints.find((endpoint) => endpoint.id === override.toEndpointId); if (!from || !to || !connectionEndpointsCompatible({ endpoints }, from.id, to.id)) continue; - result.push({ id: `connection:${from.id}:${to.id}`, nodeId: from.nodeId, fromEndpointId: from.id, toEndpointId: to.id, enabled: override.enabled, provenance: `override:${override.id}` }); + result.push({ + id: `connection:${from.id}:${to.id}`, + nodeId: from.nodeId, + fromEndpointId: from.id, + toEndpointId: to.id, + enabled: override.enabled, + provenance: `override:${override.id}`, + }); } return result; } -function endpointNode(model, endpointId) { return model.endpoints.find((endpoint) => endpoint.id === endpointId)?.nodeId; } +function endpointNode(model, endpointId) { + return model.endpoints.find((endpoint) => endpoint.id === endpointId)?.nodeId; +} function sameOsmWay(endpoints, firstRoadId, secondRoadId) { const roadFor = (roadId) => endpoints.find((endpoint) => endpoint.roadId === roadId)?.roadId; - const segmentId = (roadId) => roadId.replace(/:(forward|backward)$/, ""); + const segmentId = (roadId) => roadId.replace(/:(forward|backward)$/, ''); return segmentId(roadFor(firstRoadId) || firstRoadId) === segmentId(roadFor(secondRoadId) || secondRoadId); } function connectionEndpointsCompatible(model, fromId, toId) { const from = model.endpoints.find((endpoint) => endpoint.id === fromId); const to = model.endpoints.find((endpoint) => endpoint.id === toId); - if (!from || !to || from.roadId === to.roadId || sameOsmWay(model.endpoints, from.roadId, to.roadId) || from.side !== "end" || to.side !== "start") return false; + if ( + !from || + !to || + from.roadId === to.roadId || + sameOsmWay(model.endpoints, from.roadId, to.roadId) || + from.side !== 'end' || + to.side !== 'start' + ) + return false; if (from.nodeId === to.nodeId) return true; - const dx = (from.coordinate[0] - to.coordinate[0]) * 111320 * Math.cos(from.coordinate[1] * Math.PI / 180); + const dx = (from.coordinate[0] - to.coordinate[0]) * 111320 * Math.cos((from.coordinate[1] * Math.PI) / 180); const dy = (from.coordinate[1] - to.coordinate[1]) * 111320; return Math.hypot(dx, dy) <= 35; } function nearbyManualCandidates(endpoints, from) { - return endpoints.filter((to) => to.side === "start" && to.roadId !== from.roadId && !sameOsmWay(endpoints, from.roadId, to.roadId)).map((to) => ({ to, distanceMeters: distanceMeters(from.coordinate, to.coordinate) })).filter((item) => item.distanceMeters <= 35).sort((a, b) => a.distanceMeters - b.distanceMeters).slice(0, 3).map(({ to, distanceMeters: meters }) => ({ toEndpointId: to.id, roadId: to.roadId, distanceMeters: Math.round(meters * 10) / 10 })); + return endpoints + .filter((to) => to.side === 'start' && to.roadId !== from.roadId && !sameOsmWay(endpoints, from.roadId, to.roadId)) + .map((to) => ({ to, distanceMeters: distanceMeters(from.coordinate, to.coordinate) })) + .filter((item) => item.distanceMeters <= 35) + .sort((a, b) => a.distanceMeters - b.distanceMeters) + .slice(0, 3) + .map(({ to, distanceMeters: meters }) => ({ + toEndpointId: to.id, + roadId: to.roadId, + distanceMeters: Math.round(meters * 10) / 10, + })); } // A single physical intersection is often mapped as several nodes joined by @@ -291,27 +504,39 @@ const COMPLEX_CANDIDATE_MIN_NODES = 2; const COMPLEX_CANDIDATE_MAX_DIAMETER_METERS = 45; function detectComplexJunctionCandidates(model, junctionPlans, options, diagnostics) { - const configured = new Set((options.junctionTemplates?.clusters || []).flatMap((cluster) => (cluster.nodeIds || []).map(String))); + const configured = new Set( + (options.junctionTemplates?.clusters || []).flatMap((cluster) => (cluster.nodeIds || []).map(String)), + ); const segmentsByNode = new Map(); for (const endpoint of model.endpoints) { const key = String(endpoint.nodeId); if (!segmentsByNode.has(key)) segmentsByNode.set(key, new Set()); - segmentsByNode.get(key).add(endpoint.roadId.replace(/:(forward|backward)$/, "")); + segmentsByNode.get(key).add(endpoint.roadId.replace(/:(forward|backward)$/, '')); } - const junctionNodes = new Set([...segmentsByNode].filter(([, segments]) => segments.size >= 3).map(([nodeId]) => nodeId)); + const junctionNodes = new Set( + [...segmentsByNode].filter(([, segments]) => segments.size >= 3).map(([nodeId]) => nodeId), + ); const parent = new Map(); const find = (id) => { if (!parent.has(id)) parent.set(id, id); - while (parent.get(id) !== id) { parent.set(id, parent.get(parent.get(id))); id = parent.get(id); } + while (parent.get(id) !== id) { + parent.set(id, parent.get(parent.get(id))); + id = parent.get(id); + } return id; }; - const union = (first, second) => { const a = find(first); const b = find(second); if (a !== b) parent.set(a, b); }; + const union = (first, second) => { + const a = find(first); + const b = find(second); + if (a !== b) parent.set(a, b); + }; const links = new Map(); const seenSegments = new Set(); for (const road of model.roads) { if (seenSegments.has(road.segmentId)) continue; seenSegments.add(road.segmentId); - const start = String(road.sourceNodeIds[0]); const end = String(road.sourceNodeIds.at(-1)); + const start = String(road.sourceNodeIds[0]); + const end = String(road.sourceNodeIds.at(-1)); if (start === end || !junctionNodes.has(start) || !junctionNodes.has(end)) continue; const length = lineLengthMeters(road.centerline); if (length > COMPLEX_CANDIDATE_MAX_LINK_METERS) continue; @@ -329,25 +554,36 @@ function detectComplexJunctionCandidates(model, junctionPlans, options, diagnost if (nodeIds.some((nodeId) => configured.has(nodeId))) continue; const points = nodeIds.map((nodeId) => junctionPlans.get(nodeId)?.node).filter(Boolean); if (points.length !== nodeIds.length) continue; - const center = points.reduce((sum, point) => [sum[0] + point[0] / points.length, sum[1] + point[1] / points.length], [0, 0]); + const center = points.reduce( + (sum, point) => [sum[0] + point[0] / points.length, sum[1] + point[1] / points.length], + [0, 0], + ); const spreadMeters = Math.max(...points.map((point) => distanceMeters(center, point))); let diameterMeters = 0; for (let first = 0; first < points.length; first += 1) { - for (let second = first + 1; second < points.length; second += 1) diameterMeters = Math.max(diameterMeters, distanceMeters(points[first], points[second])); + for (let second = first + 1; second < points.length; second += 1) + diameterMeters = Math.max(diameterMeters, distanceMeters(points[first], points[second])); } if (diameterMeters > COMPLEX_CANDIDATE_MAX_DIAMETER_METERS) continue; const inner = [...links.values()].filter((link) => nodeIds.includes(link.start) && nodeIds.includes(link.end)); - const widths = nodeIds.flatMap((nodeId) => (junctionPlans.get(nodeId)?.approaches || []).map((approach) => approach.widthMeters)); + const widths = nodeIds.flatMap((nodeId) => + (junctionPlans.get(nodeId)?.approaches || []).map((approach) => approach.widthMeters), + ); const widestApproach = widths.length ? Math.max(...widths) : 0; // Enough core to cover every member node plus the widest approach's half // width, with a little slack. A starting point for tuning, not a result. const suggestedCoreRadius = Math.min(80, Math.max(12, Math.round(spreadMeters + widestApproach / 2 + 4))); diagnostics.push({ - ...diagnostic("info", `junction-cluster-candidate:${nodeIds.slice().sort().join("+")}`, nodeIds, "complex-junction-candidate", + ...diagnostic( + 'info', + `junction-cluster-candidate:${nodeIds.slice().sort().join('+')}`, + nodeIds, + 'complex-junction-candidate', `检测到 ${nodeIds.length} 个路口节点由 ${inner.length} 条短路段(最长 ${Math.round(Math.max(...inner.map((link) => link.length)) * 10) / 10} 米)相连,可能是同一个物理路口。当前按独立路口编译;如需合并请在 nativeRoad.junctionTemplates.clusters 中配置。`, - center), + center, + ), suggestedCluster: { - template: "complex-junction-v1", + template: 'complex-junction-v1', nodeIds: nodeIds.slice().sort(), nodeCount: nodeIds.length, spreadMeters: Math.round(spreadMeters * 10) / 10, @@ -365,13 +601,24 @@ function compileGeometry(model, overrides = { overrides: [] }, options = {}) { const junctionPlans = compileJunctionPlans(model, options, diagnostics); detectComplexJunctionCandidates(model, junctionPlans, options, diagnostics); const features = []; - const activeClusters = options.junctionTemplates?.enabled ? (options.junctionTemplates.clusters || []) : []; - const clusterByNode = new Map(activeClusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster]))); - const complexClusterCenters = new Map(activeClusters.filter((cluster) => cluster.template === "complex-junction-v1").map((cluster) => { - const points = cluster.nodeIds.map((nodeId) => junctionPlans.get(String(nodeId))?.node).filter(Boolean); - const center = points.length ? points.reduce((sum, point) => [sum[0] + point[0] / points.length, sum[1] + point[1] / points.length], [0, 0]) : null; - return [cluster.id, center]; - })); + const activeClusters = options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []; + const clusterByNode = new Map( + activeClusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster])), + ); + const complexClusterCenters = new Map( + activeClusters + .filter((cluster) => cluster.template === 'complex-junction-v1') + .map((cluster) => { + const points = cluster.nodeIds.map((nodeId) => junctionPlans.get(String(nodeId))?.node).filter(Boolean); + const center = points.length + ? points.reduce( + (sum, point) => [sum[0] + point[0] / points.length, sum[1] + point[1] / points.length], + [0, 0], + ) + : null; + return [cluster.id, center]; + }), + ); const emittedSegments = new Set(); const generatedComplexSidewalks = []; const generatedComplexCrosswalks = []; @@ -383,26 +630,69 @@ function compileGeometry(model, overrides = { overrides: [] }, options = {}) { const directions = model.roads.filter((item) => item.segmentId === segmentKey); const startCluster = clusterByNode.get(String(road.sourceNodeIds[0])); const endCluster = clusterByNode.get(String(road.sourceNodeIds.at(-1))); - if (startCluster?.template === "complex-junction-v1" && endCluster?.template === "complex-junction-v1" && startCluster.id === endCluster.id) continue; + if ( + startCluster?.template === 'complex-junction-v1' && + endCluster?.template === 'complex-junction-v1' && + startCluster.id === endCluster.id + ) + continue; const totalWidth = directions.reduce((sum, item) => sum + item.widthMeters, 0); // The approach surface stops at the junction cutback. The junction layer // owns the intervening rounded corners; leaving approaches untrimmed // would cover that outline with rectangular road ends in Blender/Cesium. - const cluster = clusterByNode.get(String(road.sourceNodeIds[0])) || clusterByNode.get(String(road.sourceNodeIds.at(-1))); - if (cluster?.template === "complex-junction-v1") { + const cluster = + clusterByNode.get(String(road.sourceNodeIds[0])) || clusterByNode.get(String(road.sourceNodeIds.at(-1))); + if (cluster?.template === 'complex-junction-v1') { const center = complexClusterCenters.get(cluster.id); const length = lineLengthMeters(road.centerline); - const farEndpoint = cluster.nodeIds.map(String).includes(String(road.sourceNodeIds[0])) ? road.centerline.at(-1) : road.centerline[0]; + const farEndpoint = cluster.nodeIds.map(String).includes(String(road.sourceNodeIds[0])) + ? road.centerline.at(-1) + : road.centerline[0]; const outerRadius = complexJunctionMetrics(cluster).approachOuterRadius; if (center && length < outerRadius + 8 && distanceMeters(farEndpoint, center) < outerRadius) continue; } - const line = cluster?.template === "complex-junction-v1" - ? trimLineAtComplexCluster(road.centerline, road.sourceNodeIds, junctionPlans, cluster, complexClusterCenters.get(cluster.id)) - : trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans); + const line = + cluster?.template === 'complex-junction-v1' + ? trimLineAtComplexCluster( + road.centerline, + road.sourceNodeIds, + junctionPlans, + cluster, + complexClusterCenters.get(cluster.id), + ) + : trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans); const ring = roadRing(line, totalWidth); - if (!ring) { diagnostics.push(diagnostic("error", road.id, road.osmWayIds, "unclosed-road-surface", "Could not construct a valid road polygon from this centerline.", road.centerline[0])); continue; } - const surfaceId = road.segmentId.endsWith("/0") ? `surface:way/${road.osmWayIds.join(",")}` : `surface:${segmentKey}`; - features.push({ type: "Feature", properties: { native_id: surfaceId, cluster_id: cluster?.template === "complex-junction-v1" ? cluster.id : null, directional_road_ids: directions.map((item) => item.id).join(","), osm_way_ids: road.osmWayIds.join(","), source_road_id: road.sourceRoadId, width_m: totalWidth, lane_count: directions.reduce((sum, item) => item.laneCount + sum, 0), provenance: JSON.stringify(directions.map((item) => item.provenance)), override_ids: directions.flatMap((item) => item.appliedOverrideIds).join(",") }, geometry: { type: "Polygon", coordinates: [ring] } }); + if (!ring) { + diagnostics.push( + diagnostic( + 'error', + road.id, + road.osmWayIds, + 'unclosed-road-surface', + 'Could not construct a valid road polygon from this centerline.', + road.centerline[0], + ), + ); + continue; + } + const surfaceId = road.segmentId.endsWith('/0') + ? `surface:way/${road.osmWayIds.join(',')}` + : `surface:${segmentKey}`; + features.push({ + type: 'Feature', + properties: { + native_id: surfaceId, + cluster_id: cluster?.template === 'complex-junction-v1' ? cluster.id : null, + directional_road_ids: directions.map((item) => item.id).join(','), + osm_way_ids: road.osmWayIds.join(','), + source_road_id: road.sourceRoadId, + width_m: totalWidth, + lane_count: directions.reduce((sum, item) => item.laneCount + sum, 0), + provenance: JSON.stringify(directions.map((item) => item.provenance)), + override_ids: directions.flatMap((item) => item.appliedOverrideIds).join(','), + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } for (const [nodeId, plan] of junctionPlans) { if (plan.clusterId && activeComplexCluster(options, plan.clusterId)) continue; @@ -410,43 +700,112 @@ function compileGeometry(model, overrides = { overrides: [] }, options = {}) { for (const approach of plan.approaches) { const transition = templateApproachRing(approach, plan); if (!transition) { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-template-approach-fallback", "进口道路长度不足以生成规整过渡面,已保留该进口的 native 直筒道路。", plan.node)); + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-approach-fallback', + '进口道路长度不足以生成规整过渡面,已保留该进口的 native 直筒道路。', + plan.node, + ), + ); continue; } - features.push({ type: "Feature", properties: { native_id: `junction-approach:${plan.template}:node/${nodeId}:${approach.segmentId}`, osm_node_id: nodeId, segment_id: approach.segmentId, directional_road_ids: approach.roadIds.join(","), width_m: approach.widthMeters, approach_width_m: Math.round(approach.widthMeters * plan.approachWidthMultiplier * 10) / 10, approach_length_m: Math.round(transition.lengthMeters * 10) / 10, template: plan.template, provenance: "native-road-junction-approach-template/v1" }, geometry: { type: "Polygon", coordinates: [transition.ring] } }); + features.push({ + type: 'Feature', + properties: { + native_id: `junction-approach:${plan.template}:node/${nodeId}:${approach.segmentId}`, + osm_node_id: nodeId, + segment_id: approach.segmentId, + directional_road_ids: approach.roadIds.join(','), + width_m: approach.widthMeters, + approach_width_m: Math.round(approach.widthMeters * plan.approachWidthMultiplier * 10) / 10, + approach_length_m: Math.round(transition.lengthMeters * 10) / 10, + template: plan.template, + provenance: 'native-road-junction-approach-template/v1', + }, + geometry: { type: 'Polygon', coordinates: [transition.ring] }, + }); } } for (const cluster of activeClusters) { - if (cluster.template !== "complex-junction-v1") continue; - const generated = buildComplexJunctionGeometry(model, cluster, { junctionPlans, diagnostic, distanceMeters, lineLengthMeters, pointAlongLine, offsetCoordinate, headingAtEndpoint, headingVector, circleRing }); + if (cluster.template !== 'complex-junction-v1') continue; + const generated = buildComplexJunctionGeometry(model, cluster, { + junctionPlans, + diagnostic, + distanceMeters, + lineLengthMeters, + pointAlongLine, + offsetCoordinate, + headingAtEndpoint, + headingVector, + circleRing, + }); features.push(...generated.features); if (generated.crosswalks) generatedComplexCrosswalks.push(...generated.crosswalks); if (generated.stopLines) generatedComplexStopLines.push(...generated.stopLines); if (generated.islands) generatedComplexSidewalks.push(...generated.islands); diagnostics.push(...generated.diagnostics); } - const lanes = compileLaneCenterlines(model, diagnostics, junctionPlans, options, { crosswalks: generatedComplexCrosswalks, stopLines: generatedComplexStopLines }); - const edgeLines = options.edgeLines === false ? [] : compileEdgeLines(model, overrides, junctionPlans, options, features); + const lanes = compileLaneCenterlines(model, diagnostics, junctionPlans, options, { + crosswalks: generatedComplexCrosswalks, + stopLines: generatedComplexStopLines, + }); + const edgeLines = + options.edgeLines === false ? [] : compileEdgeLines(model, overrides, junctionPlans, options, features); const controls = compileControlMarkings(model, lanes, diagnostics, junctionPlans); - const allControls = { crosswalks: [...controls.crosswalks, ...generatedComplexCrosswalks], stopLines: [...controls.stopLines, ...generatedComplexStopLines] }; + const allControls = { + crosswalks: [...controls.crosswalks, ...generatedComplexCrosswalks], + stopLines: [...controls.stopLines, ...generatedComplexStopLines], + }; const centerLines = compileCenterLines(model, overrides, junctionPlans, allControls, diagnostics, options); const markings = compileLaneMarkings(model, overrides, lanes, diagnostics, junctionPlans, allControls, options); - markings.separators.push(...compileComplexLaneSeparators(lanes.features, [...allControls.crosswalks, ...allControls.stopLines])); + markings.separators.push( + ...compileComplexLaneSeparators(lanes.features, [...allControls.crosswalks, ...allControls.stopLines]), + ); markings.directionArrows.push(...compileComplexPreviewArrows(lanes.features, generatedComplexStopLines)); const sidewalks = compileSidewalkSurfaces(model, diagnostics, junctionPlans, options); // Complex crosswalks are generated from native approach tangents; do not // synthesize side strips that can be mistaken for crosswalks. sidewalks.push(...generatedComplexSidewalks); const connectorResult = compileConnectors(model, lanes, diagnostics, overrides, junctionPlans); - const junctionFeatures = compileJunctionSurfaces(model, junctionPlans, connectorResult.features, connectorResult.movements, diagnostics, options); + const junctionFeatures = compileJunctionSurfaces( + model, + junctionPlans, + connectorResult.features, + connectorResult.movements, + diagnostics, + options, + ); validateConnectorContainment(connectorResult.features, junctionFeatures, diagnostics); - return { roadSurface: { type: "FeatureCollection", features }, edgeLines: { type: "FeatureCollection", features: edgeLines }, sidewalkSurface: { type: "FeatureCollection", features: sidewalks }, intersectionSurface: { type: "FeatureCollection", features: junctionFeatures }, laneCenterlines: { type: "FeatureCollection", features: lanes.features }, laneSeparators: { type: "FeatureCollection", features: markings.separators }, centerLines: { type: "FeatureCollection", features: centerLines }, directionArrows: { type: "FeatureCollection", features: markings.directionArrows }, turnArrows: { type: "FeatureCollection", features: markings.turnArrows }, crosswalks: { type: "FeatureCollection", features: [...controls.crosswalks, ...generatedComplexCrosswalks] }, vehicleStopLines: { type: "FeatureCollection", features: [...controls.stopLines, ...generatedComplexStopLines] }, connectors: { type: "FeatureCollection", features: connectorResult.features }, movements: connectorResult.movements, diagnostics }; + return { + roadSurface: { type: 'FeatureCollection', features }, + edgeLines: { type: 'FeatureCollection', features: edgeLines }, + sidewalkSurface: { type: 'FeatureCollection', features: sidewalks }, + intersectionSurface: { type: 'FeatureCollection', features: junctionFeatures }, + laneCenterlines: { type: 'FeatureCollection', features: lanes.features }, + laneSeparators: { type: 'FeatureCollection', features: markings.separators }, + centerLines: { type: 'FeatureCollection', features: centerLines }, + directionArrows: { type: 'FeatureCollection', features: markings.directionArrows }, + turnArrows: { type: 'FeatureCollection', features: markings.turnArrows }, + crosswalks: { type: 'FeatureCollection', features: [...controls.crosswalks, ...generatedComplexCrosswalks] }, + vehicleStopLines: { type: 'FeatureCollection', features: [...controls.stopLines, ...generatedComplexStopLines] }, + connectors: { type: 'FeatureCollection', features: connectorResult.features }, + movements: connectorResult.movements, + diagnostics, + }; } function compileComplexPreviewArrows(features, stopLines) { const result = []; for (const feature of features) { - if (!feature.properties?.cluster_preview || !feature.properties.incoming || feature.properties.maneuver === "outbound") continue; + if ( + !feature.properties?.cluster_preview || + !feature.properties.incoming || + feature.properties.maneuver === 'outbound' + ) + continue; const line = feature.geometry?.coordinates || []; if (line.length < 2) continue; const stopLine = stopLines.find((candidate) => candidate.properties?.road_id === feature.properties.road_id); @@ -454,16 +813,30 @@ function compileComplexPreviewArrows(features, stopLines) { const stopRing = stopLine.geometry?.coordinates?.[0]; if (!stopRing || stopRing.length < 4) continue; const stopPoints = stopRing.slice(0, -1); - const stopCenter = [stopPoints.reduce((sum, point) => sum + point[0], 0) / stopPoints.length, stopPoints.reduce((sum, point) => sum + point[1], 0) / stopPoints.length]; + const stopCenter = [ + stopPoints.reduce((sum, point) => sum + point[0], 0) / stopPoints.length, + stopPoints.reduce((sum, point) => sum + point[1], 0) / stopPoints.length, + ]; const placement = distanceMeters(line.at(-1), stopCenter) + 8; const placementInfo = pointAndAxisAlongLine(line, Math.max(0, lineLengthMeters(line) - placement)); if (!placementInfo) continue; const rings = arrowRingsAt(feature.properties.maneuver, placementInfo.point, placementInfo.axis); - for (let part = 0; part < rings.length; part += 1) result.push({ - type: "Feature", - properties: { native_id: `${feature.properties.native_id}:arrow:${part}`, road_id: feature.properties.road_id, lane_id: feature.properties.native_id, cluster_id: feature.properties.cluster_id, cluster_preview: true, maneuver: feature.properties.maneuver, travel_heading_deg: headingDegrees(line[0], line.at(-1)), placement_distance_from_stop_meters: 8, provenance: "native-road-complex-preview-arrow/v2-stop-anchored" }, - geometry: { type: "Polygon", coordinates: [rings[part]] }, - }); + for (let part = 0; part < rings.length; part += 1) + result.push({ + type: 'Feature', + properties: { + native_id: `${feature.properties.native_id}:arrow:${part}`, + road_id: feature.properties.road_id, + lane_id: feature.properties.native_id, + cluster_id: feature.properties.cluster_id, + cluster_preview: true, + maneuver: feature.properties.maneuver, + travel_heading_deg: headingDegrees(line[0], line.at(-1)), + placement_distance_from_stop_meters: 8, + provenance: 'native-road-complex-preview-arrow/v2-stop-anchored', + }, + geometry: { type: 'Polygon', coordinates: [rings[part]] }, + }); } return result; } @@ -479,13 +852,31 @@ function compileComplexLaneSeparators(features, controls = []) { for (const [roadId, lanes] of groups) { lanes.sort((first, second) => first.properties.lane_index - second.properties.lane_index); for (let index = 1; index < lanes.length; index += 1) { - const left = lanes[index - 1].geometry.coordinates; const right = lanes[index].geometry.coordinates; + const left = lanes[index - 1].geometry.coordinates; + const right = lanes[index].geometry.coordinates; if (left.length !== right.length) continue; - const line = left.map((point, pointIndex) => [(point[0] + right[pointIndex][0]) / 2, (point[1] + right[pointIndex][1]) / 2]); - const visibleLine = trimLineBeforeFirstControl(line, controls, .12); - const ring = visibleLine ? roadRing(visibleLine, .12) : null; + const line = left.map((point, pointIndex) => [ + (point[0] + right[pointIndex][0]) / 2, + (point[1] + right[pointIndex][1]) / 2, + ]); + const visibleLine = trimLineBeforeFirstControl(line, controls, 0.12); + const ring = visibleLine ? roadRing(visibleLine, 0.12) : null; if (!ring) continue; - result.push({ type: "Feature", properties: { native_id: `complex-lane-separator:${roadId}:${index}-${index + 1}`, road_id: roadId, cluster_id: lanes[0].properties.cluster_id, left_lane_index: index, right_lane_index: index + 1, color: "white", pattern: "solid", effective_style: "white-solid", provenance: "native-road-complex-lane-separator/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }); + result.push({ + type: 'Feature', + properties: { + native_id: `complex-lane-separator:${roadId}:${index}-${index + 1}`, + road_id: roadId, + cluster_id: lanes[0].properties.cluster_id, + left_lane_index: index, + right_lane_index: index + 1, + color: 'white', + pattern: 'solid', + effective_style: 'white-solid', + provenance: 'native-road-complex-lane-separator/v1', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } } return result; @@ -493,17 +884,17 @@ function compileComplexLaneSeparators(features, controls = []) { function trimLineBeforeFirstControl(line, controls, width) { const total = lineLengthMeters(line); - const step = .25; + const step = 0.25; for (let distance = step; distance <= total; distance += step) { const placement = pointAndAxisAlongLine(line, Math.min(total, distance - step / 2)); if (!placement) continue; const ring = rectangleAt(placement.point, placement.axis, [-placement.axis[1], placement.axis[0]], step, width, 0); if (!ringsOverlapControl([ring], controls)) continue; - let cutoff = Math.max(0, distance - step - .12); - while (cutoff > .5) { + let cutoff = Math.max(0, distance - step - 0.12); + while (cutoff > 0.5) { const candidate = roadRing([line[0], pointAlongLine(line, cutoff)], width); if (candidate && !ringsOverlapControl([candidate], controls)) return [line[0], pointAlongLine(line, cutoff)]; - cutoff -= .25; + cutoff -= 0.25; } return null; } @@ -527,18 +918,24 @@ function trimLaneOutsideControls(line, controls, width, center) { // Reference identity is not reliable here: the reversed path rebuilds the array // even when nothing was cut. Compare travelled length instead. function laneWasClipped(original, visible) { - return Boolean(visible) && lineLengthMeters(visible) < lineLengthMeters(original) - .01; + return Boolean(visible) && lineLengthMeters(visible) < lineLengthMeters(original) - 0.01; } function compileEdgeLines(model, overrides, junctionPlans, options = {}, roadSurfaces = []) { const features = []; - const clusters = options.junctionTemplates?.enabled ? (options.junctionTemplates.clusters || []) : []; - const clusterByNode = new Map(clusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster]))); + const clusters = options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []; + const clusterByNode = new Map( + clusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster])), + ); const clusterCenters = new Map(clusters.map((cluster) => [cluster.id, clusterCenter(cluster, junctionPlans)])); // Roads swallowed by a complex cluster never get a surface. Deriving their // edge from the centerline anyway paints a curb across bare ground, so take // the surfaces actually emitted as the authority on what can be outlined. - const surfaced = new Set(roadSurfaces.flatMap((feature) => String(feature.properties?.directional_road_ids || "").split(",")).filter(Boolean)); + const surfaced = new Set( + roadSurfaces + .flatMap((feature) => String(feature.properties?.directional_road_ids || '').split(',')) + .filter(Boolean), + ); for (const road of model.roads) { if (surfaced.size && !surfaced.has(road.id)) continue; // The road surface is one polygon per segment, centred on this centerline @@ -550,28 +947,44 @@ function compileEdgeLines(model, overrides, junctionPlans, options = {}, roadSur // Match the surface's trim exactly. A cluster road is cut at the cluster // boundary, not at the ordinary junction cutback; using the cutback here // runs the edge line out past the asphalt it is supposed to outline. - const cluster = clusterByNode.get(String(road.sourceNodeIds[0])) || clusterByNode.get(String(road.sourceNodeIds.at(-1))); - const line = cluster?.template === "complex-junction-v1" - ? trimLineAtComplexCluster(road.centerline, road.sourceNodeIds, junctionPlans, cluster, clusterCenters.get(cluster.id)) - : trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans); + const cluster = + clusterByNode.get(String(road.sourceNodeIds[0])) || clusterByNode.get(String(road.sourceNodeIds.at(-1))); + const line = + cluster?.template === 'complex-junction-v1' + ? trimLineAtComplexCluster( + road.centerline, + road.sourceNodeIds, + junctionPlans, + cluster, + clusterCenters.get(cluster.id), + ) + : trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans); // On a two-way segment, the inner edge is the road centre boundary and is // owned by center_lines. Emit only each directional carriageway's outer // edge; emitting both sides makes the layer look like a second centreline. const offsets = bidirectional ? [-1] : [-1, 1]; for (const offset of offsets) { - const side = offset < 0 ? "right" : "left"; + const side = offset < 0 ? 'right' : 'left'; const style = edgeLineStyle(overrides, road.id, side); - const centerline = offsetLine(line, offset * totalWidth / 2); + const centerline = offsetLine(line, (offset * totalWidth) / 2); if (!centerline) continue; - if (style.pattern === "solid") { - const ring = roadRing(centerline, .12); + if (style.pattern === 'solid') { + const ring = roadRing(centerline, 0.12); if (ring) features.push(edgeLineFeature(road, side, style, ring)); continue; } for (let distance = 1, part = 1; distance + 1 <= lineLengthMeters(centerline); distance += 4, part += 1) { const placement = pointAndAxisAlongLine(centerline, distance); if (!placement) continue; - features.push(edgeLineFeature(road, side, style, rectangleAt(placement.point, placement.axis, [-placement.axis[1], placement.axis[0]], 2, .12, 0), part)); + features.push( + edgeLineFeature( + road, + side, + style, + rectangleAt(placement.point, placement.axis, [-placement.axis[1], placement.axis[0]], 2, 0.12, 0), + part, + ), + ); } } } @@ -579,7 +992,20 @@ function compileEdgeLines(model, overrides, junctionPlans, options = {}, roadSur } function edgeLineFeature(road, side, style, ring, part = null) { - return { type: "Feature", properties: { native_id: `edge-line:${road.id}:${side}${part ? `:${part}` : ""}`, road_id: road.id, side, osm_way_ids: road.osmWayIds.join(","), color: style.color, pattern: style.pattern, effective_style: `${style.color}-${style.pattern}`, provenance: "native-road-edge-line/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }; + return { + type: 'Feature', + properties: { + native_id: `edge-line:${road.id}:${side}${part ? `:${part}` : ''}`, + road_id: road.id, + side, + osm_way_ids: road.osmWayIds.join(','), + color: style.color, + pattern: style.pattern, + effective_style: `${style.color}-${style.pattern}`, + provenance: 'native-road-edge-line/v1', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }; } function compileCenterLines(model, overrides, junctionPlans, controls, diagnostics, options = {}) { @@ -591,62 +1017,198 @@ function compileCenterLines(model, overrides, junctionPlans, controls, diagnosti segments.get(road.segmentId).push(road); } for (const [segmentId, roads] of segments) { - const forward = roads.find((road) => road.direction === "forward"); - const backward = roads.find((road) => road.direction === "backward"); - if (roads.length !== 2 || !forward || !backward || forward.highway === "service" || backward.highway === "service") continue; + const forward = roads.find((road) => road.direction === 'forward'); + const backward = roads.find((road) => road.direction === 'backward'); + if (roads.length !== 2 || !forward || !backward || forward.highway === 'service' || backward.highway === 'service') + continue; const cluster = clusterForRoad(forward, options) || clusterForRoad(backward, options); const internalCluster = cluster && roadInternalToCluster(forward, cluster); const line = cluster - ? trimLineAtComplexCluster(forward.centerline, forward.sourceNodeIds, junctionPlans, cluster, clusterCenter(cluster, junctionPlans)) + ? trimLineAtComplexCluster( + forward.centerline, + forward.sourceNodeIds, + junctionPlans, + cluster, + clusterCenter(cluster, junctionPlans), + ) : trimLineAtJunctions(forward.centerline, forward.sourceNodeIds, junctionPlans); const length = lineLengthMeters(line); - if (line.length < 2 || !Number.isFinite(length)) { diagnostics.push(diagnostic("warning", segmentId, forward.osmWayIds, "invalid-center-line", "双向道路无法生成有效道路中心虚线。", forward.centerline[0])); continue; } + if (line.length < 2 || !Number.isFinite(length)) { + diagnostics.push( + diagnostic( + 'warning', + segmentId, + forward.osmWayIds, + 'invalid-center-line', + '双向道路无法生成有效道路中心虚线。', + forward.centerline[0], + ), + ); + continue; + } const style = centerLineStyle(overrides, segmentId); const visibleLine = trimLineBeforeFirstControl(line, controlFeatures, CENTER_LINE_WIDTH_METERS); const visibleLength = visibleLine ? lineLengthMeters(visibleLine) : 0; if (!visibleLine || visibleLength < CENTER_LINE_DASH_LENGTH_METERS) continue; - const gap = style.pattern === "solid" ? 0 : CENTER_LINE_DASH_GAP_METERS; - const markLength = CENTER_LINE_DASH_LENGTH_METERS + (style.pattern === "solid" ? CENTER_LINE_SOLID_OVERLAP_METERS : 0); - for (let start = 0, dashIndex = 1; start + markLength <= visibleLength; start += CENTER_LINE_DASH_LENGTH_METERS + gap, dashIndex += 1) { + const gap = style.pattern === 'solid' ? 0 : CENTER_LINE_DASH_GAP_METERS; + const markLength = + CENTER_LINE_DASH_LENGTH_METERS + (style.pattern === 'solid' ? CENTER_LINE_SOLID_OVERLAP_METERS : 0); + for ( + let start = 0, dashIndex = 1; + start + markLength <= visibleLength; + start += CENTER_LINE_DASH_LENGTH_METERS + gap, dashIndex += 1 + ) { const placement = pointAndAxisAlongLine(visibleLine, start + markLength / 2); if (!placement) continue; - const clearanceRing = rectangleAt(placement.point, placement.axis, [-placement.axis[1], placement.axis[0]], markLength + CENTER_LINE_CONTROL_CLEARANCE_METERS * 2, CENTER_LINE_WIDTH_METERS + CENTER_LINE_CONTROL_CLEARANCE_METERS * 2, 0); - for (const offset of style.double ? [-.16, .16] : [0]) { const ring = rectangleAt(placement.point, placement.axis, [-placement.axis[1], placement.axis[0]], markLength, CENTER_LINE_WIDTH_METERS, offset); features.push({ type: "Feature", properties: { native_id: `center-line:${segmentId}:${dashIndex}:${offset}`, segment_id: segmentId, road_id: forward.id, cluster_id: cluster?.id || null, cluster_internal: Boolean(internalCluster), cluster_preview_hidden: Boolean(internalCluster), directional_road_ids: roads.map((road) => road.id).join(","), osm_way_ids: forward.osmWayIds.join(","), dash_index: dashIndex, dash_length_m: markLength, dash_gap_m: gap, color: style.color, pattern: style.pattern, double: Boolean(style.double), effective_style: `${style.double ? "double-" : ""}${style.color}-${style.pattern}`, placement_rule: "native-bidirectional-centerline/v1", provenance: "native-road-center-line/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }); } + const clearanceRing = rectangleAt( + placement.point, + placement.axis, + [-placement.axis[1], placement.axis[0]], + markLength + CENTER_LINE_CONTROL_CLEARANCE_METERS * 2, + CENTER_LINE_WIDTH_METERS + CENTER_LINE_CONTROL_CLEARANCE_METERS * 2, + 0, + ); + for (const offset of style.double ? [-0.16, 0.16] : [0]) { + const ring = rectangleAt( + placement.point, + placement.axis, + [-placement.axis[1], placement.axis[0]], + markLength, + CENTER_LINE_WIDTH_METERS, + offset, + ); + features.push({ + type: 'Feature', + properties: { + native_id: `center-line:${segmentId}:${dashIndex}:${offset}`, + segment_id: segmentId, + road_id: forward.id, + cluster_id: cluster?.id || null, + cluster_internal: Boolean(internalCluster), + cluster_preview_hidden: Boolean(internalCluster), + directional_road_ids: roads.map((road) => road.id).join(','), + osm_way_ids: forward.osmWayIds.join(','), + dash_index: dashIndex, + dash_length_m: markLength, + dash_gap_m: gap, + color: style.color, + pattern: style.pattern, + double: Boolean(style.double), + effective_style: `${style.double ? 'double-' : ''}${style.color}-${style.pattern}`, + placement_rule: 'native-bidirectional-centerline/v1', + provenance: 'native-road-center-line/v1', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); + } } } return features; } function centerLineStyle(overrides, segmentId) { - const override = overrides.overrides.find((item) => item.kind === "center-line-style" && item.segmentId === segmentId); - return override ? { color: override.color, pattern: override.pattern, double: Boolean(override.double) } : { color: "yellow", pattern: "dashed", double: false }; + const override = overrides.overrides.find( + (item) => item.kind === 'center-line-style' && item.segmentId === segmentId, + ); + return override + ? { color: override.color, pattern: override.pattern, double: Boolean(override.double) } + : { color: 'yellow', pattern: 'dashed', double: false }; } function edgeLineStyle(overrides, roadId, side) { - const value = overrides.overrides.find((item) => item.kind === "edge-line-style" && item.roadId === roadId && item.side === side); - return value ? { color: value.color, pattern: value.pattern } : { color: "white", pattern: "solid" }; + const value = overrides.overrides.find( + (item) => item.kind === 'edge-line-style' && item.roadId === roadId && item.side === side, + ); + return value ? { color: value.color, pattern: value.pattern } : { color: 'white', pattern: 'solid' }; } function compileControlMarkings(model, lanes, diagnostics, junctionPlans = new Map()) { - const crosswalks = []; const stopLines = []; - const arrivalEndpointIds = new Set(model.connections.filter((connection) => connection.enabled).map((connection) => connection.fromEndpointId)); + const crosswalks = []; + const stopLines = []; + const arrivalEndpointIds = new Set( + model.connections.filter((connection) => connection.enabled).map((connection) => connection.fromEndpointId), + ); for (const crossing of model.crossings || []) { - const candidates = model.roads.filter((road) => crossing.osmWayIds.includes(road.osmWayIds[0])).flatMap((road) => (lanes.byRoadId.get(road.id) || []).map((lane) => ({ road, lane, placement: nearestLanePlacement(lane.coordinates, crossing.coordinate), junctionDistanceMeters: distanceMeters(crossing.coordinate, road.centerline.at(-1)) })).filter((item) => item.placement)); + const candidates = model.roads + .filter((road) => crossing.osmWayIds.includes(road.osmWayIds[0])) + .flatMap((road) => + (lanes.byRoadId.get(road.id) || []) + .map((lane) => ({ + road, + lane, + placement: nearestLanePlacement(lane.coordinates, crossing.coordinate), + junctionDistanceMeters: distanceMeters(crossing.coordinate, road.centerline.at(-1)), + })) + .filter((item) => item.placement), + ); const candidate = candidates.sort((a, b) => a.placement.distance - b.placement.distance)[0]; - if (!candidate || candidate.placement.distance > 12) { diagnostics.push(diagnostic("warning", `crossing:node/${crossing.id}`, [crossing.id], "crossing-no-native-lane", "人行横道无法匹配到安全的原生车道,未生成标线。", crossing.coordinate)); continue; } - const approach = candidates.filter((item) => arrivalEndpointIds.has(`endpoint:${item.road.id}:end`) && item.junctionDistanceMeters > STOP_LINE_OFFSET_METERS && item.junctionDistanceMeters <= STOP_LINE_MAX_APPROACH_DISTANCE_METERS).sort((a, b) => a.junctionDistanceMeters - b.junctionDistanceMeters || a.placement.distance - b.placement.distance)[0]; + if (!candidate || candidate.placement.distance > 12) { + diagnostics.push( + diagnostic( + 'warning', + `crossing:node/${crossing.id}`, + [crossing.id], + 'crossing-no-native-lane', + '人行横道无法匹配到安全的原生车道,未生成标线。', + crossing.coordinate, + ), + ); + continue; + } + const approach = candidates + .filter( + (item) => + arrivalEndpointIds.has(`endpoint:${item.road.id}:end`) && + item.junctionDistanceMeters > STOP_LINE_OFFSET_METERS && + item.junctionDistanceMeters <= STOP_LINE_MAX_APPROACH_DISTANCE_METERS, + ) + .sort( + (a, b) => a.junctionDistanceMeters - b.junctionDistanceMeters || a.placement.distance - b.placement.distance, + )[0]; const crosswalkCandidate = approach || candidate; const junctionInsetMeters = approach ? crossingJunctionInset(approach, junctionPlans) : 0; const controlCenter = offsetByMeters(crossing.coordinate, crosswalkCandidate.placement.axis, junctionInsetMeters); - const { axis } = crosswalkCandidate.placement; const across = [-axis[1], axis[0]]; - for (let index = 0; index < 6; index += 1) crosswalks.push(controlFeature("crosswalk", crossing, crosswalkCandidate, index + 1, rectangleAt(controlCenter, axis, across, 3, .45, -2.25 + index * .9), { junctionInsetMeters })); - if (!approach) { diagnostics.push(diagnostic("info", `crossing:node/${crossing.id}`, [crossing.id], "crossing-no-safe-stop-line", "人行横道没有可确认的路口进口车道,保留斑马线但未生成停止线。", crossing.coordinate)); continue; } + const { axis } = crosswalkCandidate.placement; + const across = [-axis[1], axis[0]]; + for (let index = 0; index < 6; index += 1) + crosswalks.push( + controlFeature( + 'crosswalk', + crossing, + crosswalkCandidate, + index + 1, + rectangleAt(controlCenter, axis, across, 3, 0.45, -2.25 + index * 0.9), + { junctionInsetMeters }, + ), + ); + if (!approach) { + diagnostics.push( + diagnostic( + 'info', + `crossing:node/${crossing.id}`, + [crossing.id], + 'crossing-no-safe-stop-line', + '人行横道没有可确认的路口进口车道,保留斑马线但未生成停止线。', + crossing.coordinate, + ), + ); + continue; + } const rawRoadPlacement = nearestLanePlacement(approach.road.centerline, controlCenter); const laneOffset = rawRoadPlacement ? project(approach.placement.point, rawRoadPlacement.point) : [0, 0]; const lateralOffset = laneOffset[0] * across[0] + laneOffset[1] * across[1]; const laneCenterAtCrossing = offsetByMeters(controlCenter, across, lateralOffset); const stopCenter = offsetByMeters(laneCenterAtCrossing, approach.placement.axis, -STOP_LINE_OFFSET_METERS); - stopLines.push(controlFeature("stop-line", crossing, approach, 1, rectangleAt(stopCenter, across, approach.placement.axis, approach.road.widthMeters, .45, 0), { junctionInsetMeters })); + stopLines.push( + controlFeature( + 'stop-line', + crossing, + approach, + 1, + rectangleAt(stopCenter, across, approach.placement.axis, approach.road.widthMeters, 0.45, 0), + { junctionInsetMeters }, + ), + ); } return { crosswalks, stopLines }; } @@ -659,58 +1221,236 @@ function crossingJunctionInset(candidate, junctionPlans) { return Math.min(CROSSWALK_MAX_JUNCTION_INSET_METERS, Math.max(0, candidate.junctionDistanceMeters - targetDistance)); } -function nearestLanePlacement(line, target) { let best = null; let traversedMeters = 0; for (let index = 1; index < line.length; index += 1) { const a = line[index - 1]; const b = line[index]; const vector = project(b, a); const length = Math.hypot(...vector); if (!length) continue; const relative = project(target, a); const ratio = Math.max(0, Math.min(1, (relative[0] * vector[0] + relative[1] * vector[1]) / (length * length))); const point = interpolate(a, b, ratio); const distance = distanceMeters(point, target); if (!best || distance < best.distance) best = { point, axis: [vector[0] / length, vector[1] / length], distance, distanceToEndMeters: lineLengthMeters(line) - traversedMeters - length * ratio }; traversedMeters += length; } return best; } -function offsetByMeters(point, axis, meters) { return unproject([axis[0] * meters, axis[1] * meters], point); } -function rectangleAt(center, axis, across, length, width, offset) { const shifted = offsetByMeters(center, across, offset); const corners = [[-length / 2, -width / 2], [length / 2, -width / 2], [length / 2, width / 2], [-length / 2, width / 2]].map(([forward, side]) => unproject([axis[0] * forward + across[0] * side, axis[1] * forward + across[1] * side], shifted)); return [...corners, corners[0]]; } -function controlFeature(kind, crossing, candidate, part, ring, placement = {}) { const stop = kind === "stop-line"; return { type: "Feature", properties: { native_id: `${kind}:node/${crossing.id}:${part}`, crossing_node_id: crossing.id, road_id: candidate.road.id, lane_id: candidate.lane.id, osm_way_ids: candidate.road.osmWayIds.join(","), direction: candidate.road.direction, placement_method: "native-lane-nearest-point/v1", junction_inset_m: Math.round((placement.junctionInsetMeters || 0) * 100) / 100, provenance: stop ? "native-road-stop-line/v1" : "native-road-crosswalk/v1" }, geometry: { type: "Polygon", coordinates: [ring] } }; } +function nearestLanePlacement(line, target) { + let best = null; + let traversedMeters = 0; + for (let index = 1; index < line.length; index += 1) { + const a = line[index - 1]; + const b = line[index]; + const vector = project(b, a); + const length = Math.hypot(...vector); + if (!length) continue; + const relative = project(target, a); + const ratio = Math.max(0, Math.min(1, (relative[0] * vector[0] + relative[1] * vector[1]) / (length * length))); + const point = interpolate(a, b, ratio); + const distance = distanceMeters(point, target); + if (!best || distance < best.distance) + best = { + point, + axis: [vector[0] / length, vector[1] / length], + distance, + distanceToEndMeters: lineLengthMeters(line) - traversedMeters - length * ratio, + }; + traversedMeters += length; + } + return best; +} +function offsetByMeters(point, axis, meters) { + return unproject([axis[0] * meters, axis[1] * meters], point); +} +function rectangleAt(center, axis, across, length, width, offset) { + const shifted = offsetByMeters(center, across, offset); + const corners = [ + [-length / 2, -width / 2], + [length / 2, -width / 2], + [length / 2, width / 2], + [-length / 2, width / 2], + ].map(([forward, side]) => + unproject([axis[0] * forward + across[0] * side, axis[1] * forward + across[1] * side], shifted), + ); + return [...corners, corners[0]]; +} +function controlFeature(kind, crossing, candidate, part, ring, placement = {}) { + const stop = kind === 'stop-line'; + return { + type: 'Feature', + properties: { + native_id: `${kind}:node/${crossing.id}:${part}`, + crossing_node_id: crossing.id, + road_id: candidate.road.id, + lane_id: candidate.lane.id, + osm_way_ids: candidate.road.osmWayIds.join(','), + direction: candidate.road.direction, + placement_method: 'native-lane-nearest-point/v1', + junction_inset_m: Math.round((placement.junctionInsetMeters || 0) * 100) / 100, + provenance: stop ? 'native-road-stop-line/v1' : 'native-road-crosswalk/v1', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }; +} function compileLaneMarkings(model, overrides, lanes, diagnostics, junctionPlans, controls, options = {}) { - const separators = []; const directionArrows = []; const turnArrows = []; + const separators = []; + const directionArrows = []; + const turnArrows = []; const controlFeatures = [...controls.crosswalks, ...controls.stopLines]; for (const road of model.roads) { const cluster = clusterForRoad(road, options); const internalCluster = cluster && roadInternalToCluster(road, cluster); const roadLanes = (lanes.markingByRoadId || lanes.byRoadId).get(road.id) || []; for (let index = 1; index < roadLanes.length; index += 1) { - const left = roadLanes[index - 1].coordinates; const right = roadLanes[index].coordinates; + const left = roadLanes[index - 1].coordinates; + const right = roadLanes[index].coordinates; if (left.length !== right.length) continue; - const centerline = left.map((point, pointIndex) => [(point[0] + right[pointIndex][0]) / 2, (point[1] + right[pointIndex][1]) / 2]); + const centerline = left.map((point, pointIndex) => [ + (point[0] + right[pointIndex][0]) / 2, + (point[1] + right[pointIndex][1]) / 2, + ]); const style = laneSeparatorStyle(overrides, road.id, index, index + 1); - const properties = { road_id: road.id, left_lane_index: index, right_lane_index: index + 1, cluster_id: cluster?.id || null, cluster_internal: Boolean(internalCluster), cluster_preview_hidden: Boolean(internalCluster), osm_way_ids: road.osmWayIds.join(","), color: style.color, pattern: style.pattern, effective_style: `${style.color}-${style.pattern}`, provenance: "native-road-lane-separator/v1" }; - if (style.pattern === "solid") { - const visibleLine = trimLineBeforeFirstControl(centerline, controlFeatures, .12); - const ring = visibleLine ? roadRing(visibleLine, .12) : null; - if (ring) separators.push({ type: "Feature", properties: { native_id: `lane-separator:${road.id}:${index}-${index + 1}`, ...properties }, geometry: { type: "Polygon", coordinates: [ring] } }); + const properties = { + road_id: road.id, + left_lane_index: index, + right_lane_index: index + 1, + cluster_id: cluster?.id || null, + cluster_internal: Boolean(internalCluster), + cluster_preview_hidden: Boolean(internalCluster), + osm_way_ids: road.osmWayIds.join(','), + color: style.color, + pattern: style.pattern, + effective_style: `${style.color}-${style.pattern}`, + provenance: 'native-road-lane-separator/v1', + }; + if (style.pattern === 'solid') { + const visibleLine = trimLineBeforeFirstControl(centerline, controlFeatures, 0.12); + const ring = visibleLine ? roadRing(visibleLine, 0.12) : null; + if (ring) + separators.push({ + type: 'Feature', + properties: { native_id: `lane-separator:${road.id}:${index}-${index + 1}`, ...properties }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } else { - const visibleLine = trimLineBeforeFirstControl(centerline, controlFeatures, .12); + const visibleLine = trimLineBeforeFirstControl(centerline, controlFeatures, 0.12); const visibleLength = visibleLine ? lineLengthMeters(visibleLine) : 0; for (let distance = 1, part = 1; visibleLine && distance + 1 <= visibleLength; distance += 4, part += 1) { - const placement = pointAndAxisAlongLine(visibleLine, distance); - if (!placement) continue; - const ring = rectangleAt(placement.point, placement.axis, [-placement.axis[1], placement.axis[0]], 2, .12, 0); - separators.push({ type: "Feature", properties: { native_id: `lane-separator:${road.id}:${index}-${index + 1}:${part}`, ...properties }, geometry: { type: "Polygon", coordinates: [ring] } }); + const placement = pointAndAxisAlongLine(visibleLine, distance); + if (!placement) continue; + const ring = rectangleAt( + placement.point, + placement.axis, + [-placement.axis[1], placement.axis[0]], + 2, + 0.12, + 0, + ); + separators.push({ + type: 'Feature', + properties: { native_id: `lane-separator:${road.id}:${index}-${index + 1}:${part}`, ...properties }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } } } - for (const lane of roadLanes) directionArrows.push(...directionArrowFeatures(road, lane, controlFeatures, diagnostics).map((feature) => ({ ...feature, properties: { ...feature.properties, cluster_id: cluster?.id || null, cluster_internal: Boolean(internalCluster), cluster_preview_hidden: Boolean(internalCluster) } }))); - const turns = road.tags[`turn:lanes:${road.direction}`] ?? road.tags["turn:lanes"]; - const maneuvers = turns ? String(turns).split("|") : []; + for (const lane of roadLanes) + directionArrows.push( + ...directionArrowFeatures(road, lane, controlFeatures, diagnostics).map((feature) => ({ + ...feature, + properties: { + ...feature.properties, + cluster_id: cluster?.id || null, + cluster_internal: Boolean(internalCluster), + cluster_preview_hidden: Boolean(internalCluster), + }, + })), + ); + const turns = road.tags[`turn:lanes:${road.direction}`] ?? road.tags['turn:lanes']; + const maneuvers = turns ? String(turns).split('|') : []; for (let index = 0; index < roadLanes.length; index += 1) { - const lane = roadLanes[index]; const explicitManeuver = maneuvers[index]; + const lane = roadLanes[index]; + const explicitManeuver = maneuvers[index]; if (!explicitManeuver) continue; const maneuver = normalizeManeuver(explicitManeuver); - if (!lane) { diagnostics.push(diagnostic("warning", road.id, road.osmWayIds, "turn-arrow-lane-missing", "转向标签引用了不存在的车道,未生成箭头。", road.centerline.at(-1))); continue; } - if (!arrowRingsAt(maneuver, lane.coordinates.at(-1), [0, 1]).length) { diagnostics.push(diagnostic("info", lane.id, road.osmWayIds, "turn-arrow-unsupported", "转向标签不在当前已测试的箭头集合中,未生成箭头。", lane.coordinates.at(-1))); continue; } - if (lineLengthMeters(lane.coordinates) < 8) { diagnostics.push(diagnostic("warning", lane.id, road.osmWayIds, "turn-arrow-no-safe-placement", "驶入路口前的车道过短,未生成转向箭头。", lane.coordinates.at(-1))); continue; } - const previous = lane.coordinates.at(-2); const end = lane.coordinates.at(-1); - const meters = project(end, end); const vector = project(previous, end); const length = Math.hypot(-vector[0], -vector[1]); + if (!lane) { + diagnostics.push( + diagnostic( + 'warning', + road.id, + road.osmWayIds, + 'turn-arrow-lane-missing', + '转向标签引用了不存在的车道,未生成箭头。', + road.centerline.at(-1), + ), + ); + continue; + } + if (!arrowRingsAt(maneuver, lane.coordinates.at(-1), [0, 1]).length) { + diagnostics.push( + diagnostic( + 'info', + lane.id, + road.osmWayIds, + 'turn-arrow-unsupported', + '转向标签不在当前已测试的箭头集合中,未生成箭头。', + lane.coordinates.at(-1), + ), + ); + continue; + } + if (lineLengthMeters(lane.coordinates) < 8) { + diagnostics.push( + diagnostic( + 'warning', + lane.id, + road.osmWayIds, + 'turn-arrow-no-safe-placement', + '驶入路口前的车道过短,未生成转向箭头。', + lane.coordinates.at(-1), + ), + ); + continue; + } + const previous = lane.coordinates.at(-2); + const end = lane.coordinates.at(-1); + const meters = project(end, end); + const vector = project(previous, end); + const length = Math.hypot(-vector[0], -vector[1]); const axis = length ? [-vector[0] / length, -vector[1] / length] : null; - const placement = axis ? [6, 10, 14, 18, 22].find((distance) => distance < lineLengthMeters(lane.coordinates) - 2 && !ringsOverlapControl(arrowRingsAt(maneuver, pointAlongLine([...lane.coordinates].reverse(), distance), axis), controlFeatures)) : null; - if (!placement) { diagnostics.push(diagnostic("info", lane.id, road.osmWayIds, "turn-arrow-control-conflict", "转向箭头会压住斑马线或停止线,未生成该箭头。", lane.coordinates.at(-1))); continue; } + const placement = axis + ? [6, 10, 14, 18, 22].find( + (distance) => + distance < lineLengthMeters(lane.coordinates) - 2 && + !ringsOverlapControl( + arrowRingsAt(maneuver, pointAlongLine([...lane.coordinates].reverse(), distance), axis), + controlFeatures, + ), + ) + : null; + if (!placement) { + diagnostics.push( + diagnostic( + 'info', + lane.id, + road.osmWayIds, + 'turn-arrow-control-conflict', + '转向箭头会压住斑马线或停止线,未生成该箭头。', + lane.coordinates.at(-1), + ), + ); + continue; + } const center = pointAlongLine([...lane.coordinates].reverse(), placement); const rings = arrowRingsAt(maneuver, center, axis); if (!rings.length) continue; - for (let part = 0; part < rings.length; part += 1) turnArrows.push({ type: "Feature", properties: { native_id: `turn-arrow:${lane.id}:${maneuver}:${part}`, road_id: road.id, lane_id: lane.id, cluster_id: cluster?.id || null, cluster_internal: Boolean(internalCluster), cluster_preview_hidden: Boolean(internalCluster), osm_way_ids: road.osmWayIds.join(","), direction: road.direction, lane_index: lane.index, maneuver, arrow_part: part, placement_distance_meters: placement, provenance: "native-road-turn-arrow/v1" }, geometry: { type: "Polygon", coordinates: [rings[part]] } }); + for (let part = 0; part < rings.length; part += 1) + turnArrows.push({ + type: 'Feature', + properties: { + native_id: `turn-arrow:${lane.id}:${maneuver}:${part}`, + road_id: road.id, + lane_id: lane.id, + cluster_id: cluster?.id || null, + cluster_internal: Boolean(internalCluster), + cluster_preview_hidden: Boolean(internalCluster), + osm_way_ids: road.osmWayIds.join(','), + direction: road.direction, + lane_index: lane.index, + maneuver, + arrow_part: part, + placement_distance_meters: placement, + provenance: 'native-road-turn-arrow/v1', + }, + geometry: { type: 'Polygon', coordinates: [rings[part]] }, + }); } } return { separators, directionArrows, turnArrows }; @@ -718,7 +1458,13 @@ function compileLaneMarkings(model, overrides, lanes, diagnostics, junctionPlans function clusterForRoad(road, options) { const clusters = options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []; - return clusters.find((cluster) => cluster.template === "complex-junction-v1" && road.sourceNodeIds.some((nodeId) => cluster.nodeIds.map(String).includes(String(nodeId)))) || null; + return ( + clusters.find( + (cluster) => + cluster.template === 'complex-junction-v1' && + road.sourceNodeIds.some((nodeId) => cluster.nodeIds.map(String).includes(String(nodeId))), + ) || null + ); } function roadInternalToCluster(road, cluster) { @@ -728,24 +1474,68 @@ function roadInternalToCluster(road, cluster) { function clusterCenter(cluster, junctionPlans) { const points = cluster.nodeIds.map((nodeId) => junctionPlans.get(String(nodeId))?.node).filter(Boolean); - return points.length ? points.reduce((sum, point) => [sum[0] + point[0] / points.length, sum[1] + point[1] / points.length], [0, 0]) : null; + return points.length + ? points.reduce((sum, point) => [sum[0] + point[0] / points.length, sum[1] + point[1] / points.length], [0, 0]) + : null; } function angularDistance(first, second) { return Math.abs(((first - second + 180) % 360) - 180); } -function laneSeparatorStyle(overrides, roadId, leftLaneIndex, rightLaneIndex) { const value = overrides.overrides.find((item) => item.kind === "lane-separator-style" && item.roadId === roadId && item.leftLaneIndex === leftLaneIndex && item.rightLaneIndex === rightLaneIndex); return value ? { color: value.color, pattern: value.pattern } : { color: "white", pattern: "dashed" }; } +function laneSeparatorStyle(overrides, roadId, leftLaneIndex, rightLaneIndex) { + const value = overrides.overrides.find( + (item) => + item.kind === 'lane-separator-style' && + item.roadId === roadId && + item.leftLaneIndex === leftLaneIndex && + item.rightLaneIndex === rightLaneIndex, + ); + return value ? { color: value.color, pattern: value.pattern } : { color: 'white', pattern: 'dashed' }; +} function directionArrowFeatures(road, lane, controlFeatures, diagnostics) { const length = lineLengthMeters(lane.coordinates); const features = []; - for (let distance = DIRECTION_ARROW_ENDPOINT_BUFFER_METERS, sequence = 1; distance <= length - DIRECTION_ARROW_ENDPOINT_BUFFER_METERS; distance += DIRECTION_ARROW_INTERVAL_METERS, sequence += 1) { + for ( + let distance = DIRECTION_ARROW_ENDPOINT_BUFFER_METERS, sequence = 1; + distance <= length - DIRECTION_ARROW_ENDPOINT_BUFFER_METERS; + distance += DIRECTION_ARROW_INTERVAL_METERS, sequence += 1 + ) { const placement = pointAndAxisAlongLine(lane.coordinates, distance); if (!placement) continue; - const rings = arrowRingsAt("through", placement.point, placement.axis); - if (ringsOverlapControl(rings, controlFeatures)) { diagnostics.push(diagnostic("info", lane.id, road.osmWayIds, "direction-arrow-control-conflict", "默认直行箭头会压住斑马线或停止线,已跳过该位置。", placement.point)); continue; } - for (let part = 0; part < rings.length; part += 1) features.push({ type: "Feature", properties: { native_id: `direction-arrow:${lane.id}:${sequence}:${part}`, road_id: road.id, lane_id: lane.id, osm_way_ids: road.osmWayIds.join(","), direction: road.direction, lane_index: lane.index, maneuver: "through", sequence, distance_along_lane_meters: Math.round(distance * 10) / 10, placement_interval_meters: DIRECTION_ARROW_INTERVAL_METERS, provenance: "native-road-direction-arrow/v1" }, geometry: { type: "Polygon", coordinates: [rings[part]] } }); + const rings = arrowRingsAt('through', placement.point, placement.axis); + if (ringsOverlapControl(rings, controlFeatures)) { + diagnostics.push( + diagnostic( + 'info', + lane.id, + road.osmWayIds, + 'direction-arrow-control-conflict', + '默认直行箭头会压住斑马线或停止线,已跳过该位置。', + placement.point, + ), + ); + continue; + } + for (let part = 0; part < rings.length; part += 1) + features.push({ + type: 'Feature', + properties: { + native_id: `direction-arrow:${lane.id}:${sequence}:${part}`, + road_id: road.id, + lane_id: lane.id, + osm_way_ids: road.osmWayIds.join(','), + direction: road.direction, + lane_index: lane.index, + maneuver: 'through', + sequence, + distance_along_lane_meters: Math.round(distance * 10) / 10, + placement_interval_meters: DIRECTION_ARROW_INTERVAL_METERS, + provenance: 'native-road-direction-arrow/v1', + }, + geometry: { type: 'Polygon', coordinates: [rings[part]] }, + }); } return features; } @@ -754,11 +1544,22 @@ function ringsOverlapControl(rings, controls) { return rings.some((ring) => controls.some((feature) => ringsOverlap(ring, feature.geometry.coordinates[0]))); } function ringsOverlap(first, second) { - const bounds = (ring) => [Math.min(...ring.map((point) => point[0])), Math.min(...ring.map((point) => point[1])), Math.max(...ring.map((point) => point[0])), Math.max(...ring.map((point) => point[1]))]; - const a = bounds(first); const b = bounds(second); + const bounds = (ring) => [ + Math.min(...ring.map((point) => point[0])), + Math.min(...ring.map((point) => point[1])), + Math.max(...ring.map((point) => point[0])), + Math.max(...ring.map((point) => point[1])), + ]; + const a = bounds(first); + const b = bounds(second); if (a[0] > b[2] || a[2] < b[0] || a[1] > b[3] || a[3] < b[1]) return false; - if (first.some((point) => pointInPolygon(point, second)) || second.some((point) => pointInPolygon(point, first))) return true; - return first.slice(1).some((point, index) => second.slice(1).some((other, otherIndex) => segmentsIntersect(first[index], point, second[otherIndex], other))); + if (first.some((point) => pointInPolygon(point, second)) || second.some((point) => pointInPolygon(point, first))) + return true; + return first + .slice(1) + .some((point, index) => + second.slice(1).some((other, otherIndex) => segmentsIntersect(first[index], point, second[otherIndex], other)), + ); } function compileSidewalkSurfaces(model, diagnostics, junctionPlans, options = {}) { @@ -770,12 +1571,12 @@ function compileSidewalkSurfaces(model, diagnostics, junctionPlans, options = {} byWay.get(key).push(road); } for (const [wayKey, directions] of byWay) { - const forward = directions.find((road) => road.direction === "forward") || directions[0]; + const forward = directions.find((road) => road.direction === 'forward') || directions[0]; const backward = directions.find((road) => road.id !== forward.id); const totalWidth = directions.reduce((sum, road) => sum + road.widthMeters, 0); const sides = [ - ["left", forward.sidewalkLeft || Boolean(backward?.sidewalkRight)], - ["right", forward.sidewalkRight || Boolean(backward?.sidewalkLeft)], + ['left', forward.sidewalkLeft || Boolean(backward?.sidewalkRight)], + ['right', forward.sidewalkRight || Boolean(backward?.sidewalkLeft)], ]; const cluster = clusterForRoad(forward, options) || (backward ? clusterForRoad(backward, options) : null); const center = cluster ? clusterCenter(cluster, junctionPlans) : null; @@ -784,10 +1585,43 @@ function compileSidewalkSurfaces(model, diagnostics, junctionPlans, options = {} const centerline = cluster ? trimLineAtComplexCluster(forward.centerline, forward.sourceNodeIds, junctionPlans, cluster, center) : trimLineAtJunctions(forward.centerline, forward.sourceNodeIds, junctionPlans); - const ring = sidewalkRing(centerline, totalWidth / 2, totalWidth / 2 + DEFAULT_SIDEWALK_WIDTH_METERS, side === "left" ? 1 : -1); - if (!ring) { diagnostics.push(diagnostic("warning", forward.id, forward.osmWayIds, "invalid-sidewalk-surface", "无法为该道路生成连续人行道面。", forward.centerline[0])); continue; } - const sidewalkId = forward.segmentId.endsWith("/0") ? `sidewalk:way/${forward.osmWayIds.join(",")}:${side}` : `sidewalk:${wayKey}:${side}`; - features.push({ type: "Feature", properties: { native_id: sidewalkId, cluster_id: cluster?.id || null, osm_way_ids: forward.osmWayIds.join(","), source_road_id: forward.sourceRoadId, side, width_m: DEFAULT_SIDEWALK_WIDTH_METERS, directional_road_ids: directions.map((road) => road.id).join(","), provenance: "native-road-sidewalk/v1", override_ids: directions.flatMap((road) => road.appliedOverrideIds).join(",") }, geometry: { type: "Polygon", coordinates: [ring] } }); + const ring = sidewalkRing( + centerline, + totalWidth / 2, + totalWidth / 2 + DEFAULT_SIDEWALK_WIDTH_METERS, + side === 'left' ? 1 : -1, + ); + if (!ring) { + diagnostics.push( + diagnostic( + 'warning', + forward.id, + forward.osmWayIds, + 'invalid-sidewalk-surface', + '无法为该道路生成连续人行道面。', + forward.centerline[0], + ), + ); + continue; + } + const sidewalkId = forward.segmentId.endsWith('/0') + ? `sidewalk:way/${forward.osmWayIds.join(',')}:${side}` + : `sidewalk:${wayKey}:${side}`; + features.push({ + type: 'Feature', + properties: { + native_id: sidewalkId, + cluster_id: cluster?.id || null, + osm_way_ids: forward.osmWayIds.join(','), + source_road_id: forward.sourceRoadId, + side, + width_m: DEFAULT_SIDEWALK_WIDTH_METERS, + directional_road_ids: directions.map((road) => road.id).join(','), + provenance: 'native-road-sidewalk/v1', + override_ids: directions.flatMap((road) => road.appliedOverrideIds).join(','), + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); } } features.push(...compileSidewalkCorners(model, junctionPlans, options)); @@ -801,7 +1635,7 @@ function compileSidewalkCorners(model, junctionPlans, options = {}) { const candidates = []; for (const approach of plan.approaches) { const directions = model.roads.filter((road) => road.segmentId === approach.segmentId); - const forward = directions.find((road) => road.direction === "forward") || directions[0]; + const forward = directions.find((road) => road.direction === 'forward') || directions[0]; if (!forward) continue; const outwardIsForward = forward.sourceNodeIds[0] === nodeId; const sideStates = outwardIsForward @@ -815,10 +1649,10 @@ function compileSidewalkCorners(model, junctionPlans, options = {}) { if (!enabled) continue; // offsetLine's positive normal is driver's left, which is heading -90 // in this north-based heading convention. - const sideHeading = heading + (side === "left" ? -90 : 90); + const sideHeading = heading + (side === 'left' ? -90 : 90); candidates.push({ wayKey: approach.segmentId, - sourceWayKey: forward.osmWayIds.join(","), + sourceWayKey: forward.osmWayIds.join(','), side, outwardHeading: heading, normalDegrees: sideHeading, @@ -843,15 +1677,15 @@ function compileSidewalkCorners(model, junctionPlans, options = {}) { if (hasSelfIntersection(ring)) continue; if (continuation && cornerFallsIntoOtherApproach(ring, first.sourceWayKey, plan.approaches)) continue; result.push({ - type: "Feature", + type: 'Feature', properties: { native_id: `sidewalk-corner:node/${nodeId}:${first.wayKey}:${first.side}->${second.wayKey}:${second.side}`, osm_node_id: nodeId, - kind: continuation ? "continuation" : "corner", + kind: continuation ? 'continuation' : 'corner', width_m: DEFAULT_SIDEWALK_WIDTH_METERS, - provenance: continuation ? "native-road-sidewalk-continuation/v1" : "native-road-sidewalk-corner/v1", + provenance: continuation ? 'native-road-sidewalk-continuation/v1' : 'native-road-sidewalk-corner/v1', }, - geometry: { type: "Polygon", coordinates: [ring] }, + geometry: { type: 'Polygon', coordinates: [ring] }, }); } } @@ -862,12 +1696,16 @@ function roundedSidewalkCorner(node, first, second) { // Keep the established vehicle curb geometry, then derive the outer edge // from it. Independent Bezier curves drift apart and leave asphalt exposed // between the junction and pedestrian layers. - const curbForward = roundedCorner(node, first.curb, second.curb, first.outwardHeading, second.outwardHeading) || [first.curb, second.curb]; + const curbForward = roundedCorner(node, first.curb, second.curb, first.outwardHeading, second.outwardHeading) || [ + first.curb, + second.curb, + ]; // Construct the outside edge from the same tangent-support rule. A linear // point-by-point offset changes the curvature and makes the two boundaries // visibly disagree at the middle of the corner. - const outerForward = roundedCorner(node, first.outer, second.outer, first.outwardHeading, second.outwardHeading) - || offsetCornerArc(curbForward, first.curb, first.outer, second.curb, second.outer); + const outerForward = + roundedCorner(node, first.outer, second.outer, first.outwardHeading, second.outwardHeading) || + offsetCornerArc(curbForward, first.curb, first.outer, second.curb, second.outer); const curbArc = [...curbForward].reverse(); return [ first.curb, @@ -885,72 +1723,105 @@ function offsetCornerArc(curbArc, firstCurb, firstOuter, secondCurb, secondOuter const ratio = curbArc.length === 1 ? 0 : index / (curbArc.length - 1); const firstOffset = [firstOuter[0] - firstCurb[0], firstOuter[1] - firstCurb[1]]; const secondOffset = [secondOuter[0] - secondCurb[0], secondOuter[1] - secondCurb[1]]; - return [point[0] + firstOffset[0] + (secondOffset[0] - firstOffset[0]) * ratio, point[1] + firstOffset[1] + (secondOffset[1] - firstOffset[1]) * ratio]; + return [ + point[0] + firstOffset[0] + (secondOffset[0] - firstOffset[0]) * ratio, + point[1] + firstOffset[1] + (secondOffset[1] - firstOffset[1]) * ratio, + ]; }); } function samePhysicalSide(first, second) { - const radians = (first.normalDegrees - second.normalDegrees) * Math.PI / 180; + const radians = ((first.normalDegrees - second.normalDegrees) * Math.PI) / 180; return Math.cos(radians) >= 0.98; } function isStraightSidewalkContinuation(first, second) { if (first.sourceWayKey !== second.sourceWayKey || !samePhysicalSide(first, second)) return false; - const radians = (first.outwardHeading - second.outwardHeading) * Math.PI / 180; + const radians = ((first.outwardHeading - second.outwardHeading) * Math.PI) / 180; return Math.cos(radians) <= -0.98; } function cornerFallsIntoOtherApproach(ring, sourceWayKey, approaches) { const vertices = ring.slice(0, -1); - const center = vertices.reduce((sum, point) => [sum[0] + point[0] / vertices.length, sum[1] + point[1] / vertices.length], [0, 0]); - return approaches.filter((approach) => approach.sourceWayKey !== sourceWayKey).some((approach) => { - const carriageway = roadRing(approach.line, approach.widthMeters); - return carriageway && pointInPolygon(center, carriageway); - }); + const center = vertices.reduce( + (sum, point) => [sum[0] + point[0] / vertices.length, sum[1] + point[1] / vertices.length], + [0, 0], + ); + return approaches + .filter((approach) => approach.sourceWayKey !== sourceWayKey) + .some((approach) => { + const carriageway = roadRing(approach.line, approach.widthMeters); + return carriageway && pointInPolygon(center, carriageway); + }); } function validateConnectorContainment(connectors, junctionFeatures, diagnostics) { const junctionByNode = new Map(); for (const feature of junctionFeatures) { - if (feature.properties.osm_node_ids) for (const nodeId of String(feature.properties.osm_node_ids).split(",")) junctionByNode.set(nodeId, feature); + if (feature.properties.osm_node_ids) + for (const nodeId of String(feature.properties.osm_node_ids).split(',')) junctionByNode.set(nodeId, feature); else if (feature.properties.osm_node_id) junctionByNode.set(feature.properties.osm_node_id, feature); } for (const connector of connectors) { const junction = junctionByNode.get(connector.properties.node_id); if (!junction) continue; - if (junction.properties.kind === "cluster") continue; + if (junction.properties.kind === 'cluster') continue; const ring = junction.geometry.coordinates[0]; - if (!connector.geometry.coordinates.every((point) => pointInOrNearPolygon(point, ring, CONNECTOR_BOUNDARY_TOLERANCE_METERS))) { - diagnostics.push(diagnostic("warning", connector.properties.connection_id, [connector.properties.node_id], "connector-outside-junction", "转向路径有部分落在路口面外,请检查道路截面或转向连接。", connector.geometry.coordinates[0])); + if ( + !connector.geometry.coordinates.every((point) => + pointInOrNearPolygon(point, ring, CONNECTOR_BOUNDARY_TOLERANCE_METERS), + ) + ) { + diagnostics.push( + diagnostic( + 'warning', + connector.properties.connection_id, + [connector.properties.node_id], + 'connector-outside-junction', + '转向路径有部分落在路口面外,请检查道路截面或转向连接。', + connector.geometry.coordinates[0], + ), + ); } } } function pointInPolygon(point, ring) { - for (let index = 1; index < ring.length; index += 1) if (pointOnSegment(point, ring[index - 1], ring[index])) return true; + for (let index = 1; index < ring.length; index += 1) + if (pointOnSegment(point, ring[index - 1], ring[index])) return true; let inside = false; for (let index = 0, previous = ring.length - 1; index < ring.length; previous = index++) { - const a = ring[index]; const b = ring[previous]; - const intersect = a[1] > point[1] !== b[1] > point[1] && point[0] < (b[0] - a[0]) * (point[1] - a[1]) / (b[1] - a[1]) + a[0]; + const a = ring[index]; + const b = ring[previous]; + const intersect = + a[1] > point[1] !== b[1] > point[1] && point[0] < ((b[0] - a[0]) * (point[1] - a[1])) / (b[1] - a[1]) + a[0]; if (intersect) inside = !inside; } return inside; } function pointInOrNearPolygon(point, ring, toleranceMeters) { - return pointInPolygon(point, ring) || ring.slice(1).some((end, index) => distancePointToSegmentMeters(point, ring[index], end) <= toleranceMeters); + return ( + pointInPolygon(point, ring) || + ring.slice(1).some((end, index) => distancePointToSegmentMeters(point, ring[index], end) <= toleranceMeters) + ); } function distancePointToSegmentMeters(point, start, end) { const localPoint = project(point, start); const localEnd = project(end, start); const lengthSquared = localEnd[0] ** 2 + localEnd[1] ** 2; - if (lengthSquared < .0001) return Math.hypot(...localPoint); + if (lengthSquared < 0.0001) return Math.hypot(...localPoint); const ratio = Math.max(0, Math.min(1, (localPoint[0] * localEnd[0] + localPoint[1] * localEnd[1]) / lengthSquared)); return Math.hypot(localPoint[0] - localEnd[0] * ratio, localPoint[1] - localEnd[1] * ratio); } function pointOnSegment(point, a, b) { const cross = (point[0] - a[0]) * (b[1] - a[1]) - (point[1] - a[1]) * (b[0] - a[0]); if (Math.abs(cross) > 1e-12) return false; - return point[0] >= Math.min(a[0], b[0]) - 1e-12 && point[0] <= Math.max(a[0], b[0]) + 1e-12 && point[1] >= Math.min(a[1], b[1]) - 1e-12 && point[1] <= Math.max(a[1], b[1]) + 1e-12; + return ( + point[0] >= Math.min(a[0], b[0]) - 1e-12 && + point[0] <= Math.max(a[0], b[0]) + 1e-12 && + point[1] >= Math.min(a[1], b[1]) - 1e-12 && + point[1] <= Math.max(a[1], b[1]) + 1e-12 + ); } // `complexControls` carries the crosswalks and stop bars the complex-junction @@ -962,8 +1833,10 @@ function compileLaneCenterlines(model, diagnostics, junctionPlans, options = {}, const controlFeatures = [...(complexControls.crosswalks || []), ...(complexControls.stopLines || [])]; const byRoadId = new Map(); const markingByRoadId = new Map(); - const clusters = options.junctionTemplates?.enabled ? (options.junctionTemplates.clusters || []) : []; - const clusterByNode = new Map(clusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster]))); + const clusters = options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []; + const clusterByNode = new Map( + clusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster])), + ); const clusterCenters = new Map(clusters.map((cluster) => [cluster.id, clusterCenter(cluster, junctionPlans)])); for (const road of model.roads) { const lanes = []; @@ -971,35 +1844,90 @@ function compileLaneCenterlines(model, diagnostics, junctionPlans, options = {}, const laneWidth = road.widthMeters / road.laneCount; const siblings = model.roads.filter((item) => item.segmentId === road.segmentId); const opposite = siblings.find((item) => item.id !== road.id); - const boundaryCluster = clusterByNode.get(String(road.sourceNodeIds[0])) || clusterByNode.get(String(road.sourceNodeIds.at(-1))); + const boundaryCluster = + clusterByNode.get(String(road.sourceNodeIds[0])) || clusterByNode.get(String(road.sourceNodeIds.at(-1))); const internalCluster = boundaryCluster && roadInternalToCluster(road, boundaryCluster); - const clippedRoadLine = boundaryCluster?.template === "complex-junction-v1" - ? trimLineAtComplexCluster(road.centerline, road.sourceNodeIds, junctionPlans, boundaryCluster, clusterCenters.get(boundaryCluster.id)) - : trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans); + const clippedRoadLine = + boundaryCluster?.template === 'complex-junction-v1' + ? trimLineAtComplexCluster( + road.centerline, + road.sourceNodeIds, + junctionPlans, + boundaryCluster, + clusterCenters.get(boundaryCluster.id), + ) + : trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans); // OSM centerline is the shared carriageway center. On a two-way road, // offset each directed carriageway to its own side before placing lanes. - const carriagewayOffset = opposite ? (road.direction === "forward" ? -opposite.widthMeters / 2 : -road.widthMeters / 2) : 0; + const carriagewayOffset = opposite + ? road.direction === 'forward' + ? -opposite.widthMeters / 2 + : -road.widthMeters / 2 + : 0; for (let index = 0; index < road.laneCount; index += 1) { // OSM `turn:lanes` is ordered from left to right. Keep lane 1 on the // driver's left so tag positions and generated lane IDs have one meaning. const offset = carriagewayOffset + (road.widthMeters / 2 - laneWidth * (index + 0.5)); const coordinates = offsetLine(trimLineAtJunctions(road.centerline, road.sourceNodeIds, junctionPlans), offset); const publishedCoordinates = offsetLine(clippedRoadLine, offset); - if (!coordinates || !publishedCoordinates) { diagnostics.push(diagnostic("error", road.id, road.osmWayIds, "invalid-lane-centerline", "无法为该道路生成车道中心线。", road.centerline[0])); continue; } + if (!coordinates || !publishedCoordinates) { + diagnostics.push( + diagnostic( + 'error', + road.id, + road.osmWayIds, + 'invalid-lane-centerline', + '无法为该道路生成车道中心线。', + road.centerline[0], + ), + ); + continue; + } const lane = { id: `lane:${road.id}:${index + 1}`, roadId: road.id, index: index + 1, coordinates }; lanes.push(lane); // Only the published geometry stops at the crossing. `coordinates` stays // whole because connectors are derived from it; a lane that ends at the // stop bar would otherwise break every turn path through the junction. - const visibleCoordinates = boundaryCluster?.template === "complex-junction-v1" - ? trimLaneOutsideControls(publishedCoordinates, controlFeatures, LANE_CENTERLINE_PROBE_WIDTH_METERS, clusterCenters.get(boundaryCluster.id)) - : publishedCoordinates; - if (!visibleCoordinates) { diagnostics.push(diagnostic("warning", road.id, road.osmWayIds, "lane-centerline-fully-inside-control", "该车道中心线整体落在斑马线或停止线内,已按未裁剪几何发布。", publishedCoordinates[0])); } + const visibleCoordinates = + boundaryCluster?.template === 'complex-junction-v1' + ? trimLaneOutsideControls( + publishedCoordinates, + controlFeatures, + LANE_CENTERLINE_PROBE_WIDTH_METERS, + clusterCenters.get(boundaryCluster.id), + ) + : publishedCoordinates; + if (!visibleCoordinates) { + diagnostics.push( + diagnostic( + 'warning', + road.id, + road.osmWayIds, + 'lane-centerline-fully-inside-control', + '该车道中心线整体落在斑马线或停止线内,已按未裁剪几何发布。', + publishedCoordinates[0], + ), + ); + } // Lane markings are laid out along this line. Feeding it the clipped // geometry is what keeps separators and arrows from being painted *past* // a crossing: control avoidance only stops them landing *on* one. markingLanes.push({ ...lane, coordinates: visibleCoordinates || publishedCoordinates }); - features.push({ type: "Feature", properties: { native_id: lane.id, road_id: road.id, lane_index: lane.index, cluster_id: boundaryCluster?.id || null, cluster_internal: Boolean(internalCluster), cluster_preview_hidden: Boolean(internalCluster), cluster_boundary_clipped: Boolean(boundaryCluster && !internalCluster), control_clipped: laneWasClipped(publishedCoordinates, visibleCoordinates), source: "native-road-lane-centerline/v3-control-clipped" }, geometry: { type: "LineString", coordinates: visibleCoordinates || publishedCoordinates } }); + features.push({ + type: 'Feature', + properties: { + native_id: lane.id, + road_id: road.id, + lane_index: lane.index, + cluster_id: boundaryCluster?.id || null, + cluster_internal: Boolean(internalCluster), + cluster_preview_hidden: Boolean(internalCluster), + cluster_boundary_clipped: Boolean(boundaryCluster && !internalCluster), + control_clipped: laneWasClipped(publishedCoordinates, visibleCoordinates), + source: 'native-road-lane-centerline/v3-control-clipped', + }, + geometry: { type: 'LineString', coordinates: visibleCoordinates || publishedCoordinates }, + }); } byRoadId.set(road.id, lanes); markingByRoadId.set(road.id, markingLanes); @@ -1008,41 +1936,90 @@ function compileLaneCenterlines(model, diagnostics, junctionPlans, options = {}, const clusterNodes = new Set(cluster.nodeIds.map(String)); const clusterCoordinates = [...clusterNodes].map((nodeId) => junctionPlans.get(nodeId)?.node).filter(Boolean); const compositeCenter = clusterCoordinates.length - ? clusterCoordinates.reduce((sum, point) => [sum[0] + point[0] / clusterCoordinates.length, sum[1] + point[1] / clusterCoordinates.length], [0, 0]) + ? clusterCoordinates.reduce( + (sum, point) => [ + sum[0] + point[0] / clusterCoordinates.length, + sum[1] + point[1] / clusterCoordinates.length, + ], + [0, 0], + ) : null; const corridors = []; for (const [nodeId, plan] of junctionPlans) { if (!clusterNodes.has(String(nodeId))) continue; for (const approach of plan.approaches) { const end = approach.line.at(-1); - if ([...clusterNodes].some((candidate) => candidate !== String(nodeId) && distanceMeters(end, junctionPlans.get(candidate)?.node || [Infinity, Infinity]) < 3)) continue; + if ( + [...clusterNodes].some( + (candidate) => + candidate !== String(nodeId) && + distanceMeters(end, junctionPlans.get(candidate)?.node || [Infinity, Infinity]) < 3, + ) + ) + continue; const heading = ((headingAtEndpoint(approach.line) + 180) % 360) - 180; corridors.push({ nodeId, heading, approach, plan }); } } for (const corridor of corridors) { - const approach = corridor.approach; const plan = corridor.plan; + const approach = corridor.approach; + const plan = corridor.plan; const outerRadius = complexJunctionMetrics(cluster).approachOuterRadius; - const length = Math.min(distanceAlongLineToRadius(approach.line, compositeCenter, outerRadius), lineLengthMeters(approach.line)); + const length = Math.min( + distanceAlongLineToRadius(approach.line, compositeCenter, outerRadius), + lineLengthMeters(approach.line), + ); if (length < 12) continue; - const line = approach.line; const outer = pointAlongLine(line, Math.max(0, length)); const inner = pointAlongLine(line, Math.min(Math.max(3, Number(cluster.coreRadiusMeters || 28) * .14), Math.max(3, length - 8))); - const corridorRoads = approach.roadIds.map((roadId) => model.roads.find((road) => road.id === roadId)).filter(Boolean); + const line = approach.line; + const outer = pointAlongLine(line, Math.max(0, length)); + const inner = pointAlongLine( + line, + Math.min(Math.max(3, Number(cluster.coreRadiusMeters || 28) * 0.14), Math.max(3, length - 8)), + ); + const corridorRoads = approach.roadIds + .map((roadId) => model.roads.find((road) => road.id === roadId)) + .filter(Boolean); const incoming = corridorRoads.some((road) => String(road.sourceNodeIds.at(-1)) === String(corridor.nodeId)); - const count = Math.max(1, corridorRoads.reduce((sum, road) => sum + road.laneCount, 0)); + const count = Math.max( + 1, + corridorRoads.reduce((sum, road) => sum + road.laneCount, 0), + ); const laneWidth = approach.widthMeters / count; - const axis = project(inner, outer); const total = Math.hypot(...axis); if (!total) continue; - const normalized = [axis[0] / total, axis[1] / total]; const across = [-normalized[1], normalized[0]]; + const axis = project(inner, outer); + const total = Math.hypot(...axis); + if (!total) continue; + const normalized = [axis[0] / total, axis[1] / total]; + const across = [-normalized[1], normalized[0]]; for (let index = 0; index < count; index += 1) { - const offset = approach.widthMeters / 2 - laneWidth * (index + .5); + const offset = approach.widthMeters / 2 - laneWidth * (index + 0.5); const start = unproject([across[0] * offset, across[1] * offset], outer); const end = unproject([across[0] * offset, across[1] * offset], inner); - const maneuver = incoming ? index === 0 ? "left" : index === count - 1 ? "right" : "through" : "outbound"; + const maneuver = incoming ? (index === 0 ? 'left' : index === count - 1 ? 'right' : 'through') : 'outbound'; // Preview lanes are laid out radially from the outer radius inwards, so // an untrimmed one runs straight over the arm crossing. Stop it at the // first control: incoming lanes land on the stop bar, outgoing lanes on // the far edge of the crossing. - const visible = trimLaneOutsideControls([start, end], controlFeatures, LANE_CENTERLINE_PROBE_WIDTH_METERS, compositeCenter); - features.push({ type: "Feature", properties: { native_id: `cluster-approach-lane:${cluster.id}:${approach.segmentId}:${index + 1}`, road_id: corridorRoads[0]?.id || null, cluster_id: cluster.id, cluster_preview: true, incoming, lane_index: index + 1, maneuver, control_clipped: laneWasClipped([start, end], visible), source: "native-road-junction-cluster-lane/v4-control-clipped" }, geometry: { type: "LineString", coordinates: visible || [start, end] } }); + const visible = trimLaneOutsideControls( + [start, end], + controlFeatures, + LANE_CENTERLINE_PROBE_WIDTH_METERS, + compositeCenter, + ); + features.push({ + type: 'Feature', + properties: { + native_id: `cluster-approach-lane:${cluster.id}:${approach.segmentId}:${index + 1}`, + road_id: corridorRoads[0]?.id || null, + cluster_id: cluster.id, + cluster_preview: true, + incoming, + lane_index: index + 1, + maneuver, + control_clipped: laneWasClipped([start, end], visible), + source: 'native-road-junction-cluster-lane/v4-control-clipped', + }, + geometry: { type: 'LineString', coordinates: visible || [start, end] }, + }); } } } @@ -1050,12 +2027,13 @@ function compileLaneCenterlines(model, diagnostics, junctionPlans, options = {}, } function cubicTurnCurve(start, end, startHeading, endHeading, radius, turn, center) { - const reach = turn === "right" ? Math.max(5, radius * .75) : Math.max(9, radius * 1.35); + const reach = turn === 'right' ? Math.max(5, radius * 0.75) : Math.max(9, radius * 1.35); const first = offsetCoordinate(start, startHeading, reach); const second = offsetCoordinate(end, endHeading, reach); const points = []; for (let index = 0; index <= 18; index += 1) { - const t = index / 18; const inverse = 1 - t; + const t = index / 18; + const inverse = 1 - t; points.push([ inverse ** 3 * start[0] + 3 * inverse ** 2 * t * first[0] + 3 * inverse * t ** 2 * second[0] + t ** 3 * end[0], inverse ** 3 * start[1] + 3 * inverse ** 2 * t * first[1] + 3 * inverse * t ** 2 * second[1] + t ** 3 * end[1], @@ -1072,75 +2050,160 @@ function compileConnectors(model, lanes, diagnostics, overrides, junctionPlans) const toRoad = model.roads.find((road) => road.id === endpointRoadId(model, connection.toEndpointId)); const fromLanes = lanes.byRoadId.get(fromRoad?.id) || []; const toLanes = lanes.byRoadId.get(endpointRoadId(model, connection.toEndpointId)) || []; - if (!fromLanes.length || !toLanes.length) { diagnostics.push(diagnostic("warning", connection.id, [connection.nodeId], "connector-missing-lane", "转向连接缺少可用车道中心线。", endpointCoordinate(model, connection.fromEndpointId))); continue; } + if (!fromLanes.length || !toLanes.length) { + diagnostics.push( + diagnostic( + 'warning', + connection.id, + [connection.nodeId], + 'connector-missing-lane', + '转向连接缺少可用车道中心线。', + endpointCoordinate(model, connection.fromEndpointId), + ), + ); + continue; + } for (let index = 0; index < fromLanes.length; index += 1) { const turn = connectionTurn(fromRoad, toRoad); const defaultTargetIndex = targetLaneIndex(turn, index, fromLanes.length, toLanes.length); - const defaultFromLane = fromLanes[index]; const defaultToLane = toLanes[defaultTargetIndex]; + const defaultFromLane = fromLanes[index]; + const defaultToLane = toLanes[defaultTargetIndex]; const override = laneOverride(overrides, defaultFromLane.id, defaultToLane.id); - if ((!laneAllowsTurn(fromRoad, index, turn) && override?.enabled !== true) || override?.enabled === false) continue; - const from = defaultFromLane.coordinates.at(-1); const to = defaultToLane.coordinates[0]; + if ((!laneAllowsTurn(fromRoad, index, turn) && override?.enabled !== true) || override?.enabled === false) + continue; + const from = defaultFromLane.coordinates.at(-1); + const to = defaultToLane.coordinates[0]; const plan = junctionPlans.get(connection.nodeId); // Cross intersections retain the earlier center-node curve while T junctions // use lane tangents so their through movement does not bow toward the stem. - const coordinates = plan?.segmentIds.size === 4 - ? quadraticCurve(from, endpointCoordinate(model, connection.fromEndpointId), to, 12) - : connectorCurve(defaultFromLane.coordinates, defaultToLane.coordinates, turn); + const coordinates = + plan?.segmentIds.size === 4 + ? quadraticCurve(from, endpointCoordinate(model, connection.fromEndpointId), to, 12) + : connectorCurve(defaultFromLane.coordinates, defaultToLane.coordinates, turn); const length = lineLengthMeters(coordinates); const id = `movement:${connection.id}:${defaultFromLane.id}->${defaultToLane.id}`; const provenance = override ? `override:${override.id}` : connection.provenance; const connectorId = `connector:${id}`; - const geometryStatus = length < .4 ? "continuous" : length > 80 ? "deferred-too-long" : "connector"; - const movement = { id, connectorId, connectionId: connection.id, nodeId: connection.nodeId, fromRoadId: fromRoad.id, toRoadId: defaultToLane.roadId, fromLaneId: defaultFromLane.id, toLaneId: defaultToLane.id, turn, provenance, appliedOverrideIds: override ? [override.id] : [], geometryPublished: geometryStatus === "connector", geometryStatus }; - if (length < .4) { movements.push(movement); continue; } - if (length > 80) { diagnostics.push(diagnostic("warning", connection.id, [connection.nodeId], "connector-too-long", "转向路径超过 80 米,未发布几何;请检查路口拓扑或人工连接。", from)); movements.push(movement); continue; } + const geometryStatus = length < 0.4 ? 'continuous' : length > 80 ? 'deferred-too-long' : 'connector'; + const movement = { + id, + connectorId, + connectionId: connection.id, + nodeId: connection.nodeId, + fromRoadId: fromRoad.id, + toRoadId: defaultToLane.roadId, + fromLaneId: defaultFromLane.id, + toLaneId: defaultToLane.id, + turn, + provenance, + appliedOverrideIds: override ? [override.id] : [], + geometryPublished: geometryStatus === 'connector', + geometryStatus, + }; + if (length < 0.4) { + movements.push(movement); + continue; + } + if (length > 80) { + diagnostics.push( + diagnostic( + 'warning', + connection.id, + [connection.nodeId], + 'connector-too-long', + '转向路径超过 80 米,未发布几何;请检查路口拓扑或人工连接。', + from, + ), + ); + movements.push(movement); + continue; + } const cluster = plan?.clusterId || null; - features.push({ type: "Feature", properties: { native_id: connectorId, movement_id: id, connection_id: connection.id, node_id: connection.nodeId, cluster_id: cluster, cluster_internal: Boolean(cluster), from_lane_id: defaultFromLane.id, to_lane_id: defaultToLane.id, turn, provenance }, geometry: { type: "LineString", coordinates } }); + features.push({ + type: 'Feature', + properties: { + native_id: connectorId, + movement_id: id, + connection_id: connection.id, + node_id: connection.nodeId, + cluster_id: cluster, + cluster_internal: Boolean(cluster), + from_lane_id: defaultFromLane.id, + to_lane_id: defaultToLane.id, + turn, + provenance, + }, + geometry: { type: 'LineString', coordinates }, + }); movements.push(movement); } } return { features, movements }; } -function laneOverride(overrides, fromLaneId, toLaneId) { return overrides.overrides.find((item) => item.kind === "lane-connection" && item.fromLaneId === fromLaneId && item.toLaneId === toLaneId); } +function laneOverride(overrides, fromLaneId, toLaneId) { + return overrides.overrides.find( + (item) => item.kind === 'lane-connection' && item.fromLaneId === fromLaneId && item.toLaneId === toLaneId, + ); +} -function endpointRoadId(model, endpointId) { return model.endpoints.find((endpoint) => endpoint.id === endpointId)?.roadId; } -function endpointCoordinate(model, endpointId) { return model.endpoints.find((endpoint) => endpoint.id === endpointId)?.coordinate; } +function endpointRoadId(model, endpointId) { + return model.endpoints.find((endpoint) => endpoint.id === endpointId)?.roadId; +} +function endpointCoordinate(model, endpointId) { + return model.endpoints.find((endpoint) => endpoint.id === endpointId)?.coordinate; +} function connectionTurn(fromRoad, toRoad) { - if (!fromRoad || !toRoad) return "unknown"; + if (!fromRoad || !toRoad) return 'unknown'; const incoming = headingDegrees(fromRoad.centerline.at(-2), fromRoad.centerline.at(-1)); const outgoing = headingDegrees(toRoad.centerline[0], toRoad.centerline[1]); const delta = ((outgoing - incoming + 540) % 360) - 180; - if (Math.abs(delta) >= 150) return "uturn"; - if (Math.abs(delta) <= 30) return "through"; - return delta > 0 ? "right" : "left"; + if (Math.abs(delta) >= 150) return 'uturn'; + if (Math.abs(delta) <= 30) return 'through'; + return delta > 0 ? 'right' : 'left'; } function laneAllowsTurn(road, zeroIndex, turn) { if (!road) return true; - const tag = road.tags[`turn:lanes:${road.direction}`] ?? road.tags["turn:lanes"]; + const tag = road.tags[`turn:lanes:${road.direction}`] ?? road.tags['turn:lanes']; if (!tag) return true; - const lanes = String(tag).split("|").map((lane) => lane.split(";").map((value) => value.trim().replace("slight_", "")).filter(Boolean)); + const lanes = String(tag) + .split('|') + .map((lane) => + lane + .split(';') + .map((value) => value.trim().replace('slight_', '')) + .filter(Boolean), + ); const allowed = lanes[zeroIndex]; - return !allowed || allowed.includes(turn) || turn === "uturn" && allowed.includes("reverse"); + return !allowed || allowed.includes(turn) || (turn === 'uturn' && allowed.includes('reverse')); } function targetLaneIndex(turn, sourceIndex, sourceCount, targetCount) { - if (turn === "left") return 0; - if (turn === "right") return targetCount - 1; - if (turn === "uturn") return 0; - return Math.min(targetCount - 1, Math.round(sourceIndex / Math.max(1, sourceCount - 1) * Math.max(0, targetCount - 1))); + if (turn === 'left') return 0; + if (turn === 'right') return targetCount - 1; + if (turn === 'uturn') return 0; + return Math.min( + targetCount - 1, + Math.round((sourceIndex / Math.max(1, sourceCount - 1)) * Math.max(0, targetCount - 1)), + ); } function connectorCurve(incoming, outgoing, turn) { const start = incoming.at(-1); const end = outgoing[0]; - if (turn === "through") return lineCurve(start, end, 12); + if (turn === 'through') return lineCurve(start, end, 12); const incomingHeading = headingDegrees(incoming.at(-2), start); const outgoingHeading = headingDegrees(end, outgoing[1]); const chord = distanceMeters(start, end); const incomingSpan = distanceMeters(incoming.at(-2), start); const outgoingSpan = distanceMeters(end, outgoing[1]); const tangentIntersection = intersectTangentRays(start, end, incomingHeading, outgoingHeading); - const fallbackDistance = Math.min(8, Math.max(.75, Math.min(chord * .42, incomingSpan * .8, outgoingSpan * .8))); - const firstDistance = tangentIntersection && tangentIntersection.incoming >= 0 ? Math.min(tangentIntersection.incoming, Math.min(8, Math.max(.75, incomingSpan * 2.4))) / 3 : fallbackDistance; - const secondDistance = tangentIntersection && tangentIntersection.outgoing >= 0 ? Math.min(tangentIntersection.outgoing, Math.min(8, Math.max(.75, outgoingSpan * 2.4))) / 3 : fallbackDistance; + const fallbackDistance = Math.min(8, Math.max(0.75, Math.min(chord * 0.42, incomingSpan * 0.8, outgoingSpan * 0.8))); + const firstDistance = + tangentIntersection && tangentIntersection.incoming >= 0 + ? Math.min(tangentIntersection.incoming, Math.min(8, Math.max(0.75, incomingSpan * 2.4))) / 3 + : fallbackDistance; + const secondDistance = + tangentIntersection && tangentIntersection.outgoing >= 0 + ? Math.min(tangentIntersection.outgoing, Math.min(8, Math.max(0.75, outgoingSpan * 2.4))) / 3 + : fallbackDistance; const firstControl = offsetCoordinate(start, incomingHeading, firstDistance); const secondControl = offsetCoordinate(end, outgoingHeading + 180, secondDistance); return cubicBezier(start, firstControl, secondControl, end, 12); @@ -1165,8 +2228,12 @@ function lineCurve(start, end, segments) { function cubicBezier(a, firstControl, secondControl, b, segments) { const result = []; for (let index = 0; index <= segments; index += 1) { - const t = index / segments; const u = 1 - t; - result.push([u ** 3 * a[0] + 3 * u * u * t * firstControl[0] + 3 * u * t * t * secondControl[0] + t ** 3 * b[0], u ** 3 * a[1] + 3 * u * u * t * firstControl[1] + 3 * u * t * t * secondControl[1] + t ** 3 * b[1]]); + const t = index / segments; + const u = 1 - t; + result.push([ + u ** 3 * a[0] + 3 * u * u * t * firstControl[0] + 3 * u * t * t * secondControl[0] + t ** 3 * b[0], + u ** 3 * a[1] + 3 * u * u * t * firstControl[1] + 3 * u * t * t * secondControl[1] + t ** 3 * b[1], + ]); } return result; } @@ -1174,25 +2241,46 @@ function cubicBezier(a, firstControl, secondControl, b, segments) { function quadraticCurve(a, control, b, segments) { const result = []; for (let index = 0; index <= segments; index += 1) { - const t = index / segments; const u = 1 - t; - result.push([u * u * a[0] + 2 * u * t * control[0] + t * t * b[0], u * u * a[1] + 2 * u * t * control[1] + t * t * b[1]]); + const t = index / segments; + const u = 1 - t; + result.push([ + u * u * a[0] + 2 * u * t * control[0] + t * t * b[0], + u * u * a[1] + 2 * u * t * control[1] + t * t * b[1], + ]); } return result; } function offsetLine(line, offsetMeters) { if (line.length < 2) return null; - const origin = line[0]; const points = line.map((point) => project(point, origin)); const result = []; + const origin = line[0]; + const points = line.map((point) => project(point, origin)); + const result = []; for (let index = 0; index < points.length; index += 1) { - const previous = points[Math.max(0, index - 1)]; const next = points[Math.min(points.length - 1, index + 1)]; - const dx = next[0] - previous[0]; const dy = next[1] - previous[1]; const length = Math.hypot(dx, dy); + const previous = points[Math.max(0, index - 1)]; + const next = points[Math.min(points.length - 1, index + 1)]; + const dx = next[0] - previous[0]; + const dy = next[1] - previous[1]; + const length = Math.hypot(dx, dy); if (length < 0.01) return null; - result.push(unproject([points[index][0] - dy / length * offsetMeters, points[index][1] + dx / length * offsetMeters], origin)); + result.push( + unproject( + [points[index][0] - (dy / length) * offsetMeters, points[index][1] + (dx / length) * offsetMeters], + origin, + ), + ); } return result; } -function lineLengthMeters(line) { return line.slice(1).reduce((sum, point, index) => { const previous = line[index]; const dx = (point[0] - previous[0]) * 111320 * Math.cos(point[1] * Math.PI / 180); const dy = (point[1] - previous[1]) * 111320; return sum + Math.hypot(dx, dy); }, 0); } +function lineLengthMeters(line) { + return line.slice(1).reduce((sum, point, index) => { + const previous = line[index]; + const dx = (point[0] - previous[0]) * 111320 * Math.cos((point[1] * Math.PI) / 180); + const dy = (point[1] - previous[1]) * 111320; + return sum + Math.hypot(dx, dy); + }, 0); +} function polygonAreaMeters(ring) { if (ring.length < 3) return 0; const origin = ring[0]; @@ -1207,51 +2295,146 @@ function polygonAreaMeters(ring) { function compileJunctionSurfaces(model, junctionPlans, connectors, movements, diagnostics, options = {}) { const result = []; - const complexClusters = new Set((options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []).filter((cluster) => cluster.template === "complex-junction-v1").map((cluster) => cluster.id)); + const complexClusters = new Set( + (options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []) + .filter((cluster) => cluster.template === 'complex-junction-v1') + .map((cluster) => cluster.id), + ); for (const [nodeId, plan] of junctionPlans) { if (plan.clusterId && complexClusters.has(plan.clusterId)) continue; const { segmentIds, node, approaches, cutbackMeters, boundary } = plan; const junctionConnectors = connectors.filter((feature) => feature.properties.node_id === nodeId); const junctionMovements = movements.filter((movement) => movement.nodeId === nodeId); if (boundary.length < 3 || !junctionMovements.length) { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-surface-deferred", "路口缺少足够的截面或转向路径,暂不生成路口面。", node)); + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-surface-deferred', + '路口缺少足够的截面或转向路径,暂不生成路口面。', + node, + ), + ); continue; } const approachAreaMeters = polygonAreaMeters(boundary); let ring = [...boundary, boundary[0]]; - let boundaryMode = plan.boundaryMode || "approach-envelope"; - if (hasSelfIntersection(ring) || !junctionConnectors.every((feature) => feature.geometry.coordinates.every((point) => pointInOrNearPolygon(point, ring, CONNECTOR_BOUNDARY_TOLERANCE_METERS)))) { - const envelope = convexHull([...boundary, ...junctionConnectors.flatMap((feature) => feature.geometry.coordinates)]); + let boundaryMode = plan.boundaryMode || 'approach-envelope'; + if ( + hasSelfIntersection(ring) || + !junctionConnectors.every((feature) => + feature.geometry.coordinates.every((point) => + pointInOrNearPolygon(point, ring, CONNECTOR_BOUNDARY_TOLERANCE_METERS), + ), + ) + ) { + const envelope = convexHull([ + ...boundary, + ...junctionConnectors.flatMap((feature) => feature.geometry.coordinates), + ]); ring = [...envelope, envelope[0]]; - boundaryMode = "connector-convex-fallback"; + boundaryMode = 'connector-convex-fallback'; } if (hasSelfIntersection(ring)) { - diagnostics.push(diagnostic("error", `junction:node/${nodeId}`, [nodeId], "invalid-junction-surface", "路口截面边界发生自相交,未发布路口面。请检查道路方向或路口拓扑。", node)); + diagnostics.push( + diagnostic( + 'error', + `junction:node/${nodeId}`, + [nodeId], + 'invalid-junction-surface', + '路口截面边界发生自相交,未发布路口面。请检查道路方向或路口拓扑。', + node, + ), + ); continue; } const surfaceAreaMeters = polygonAreaMeters(ring); const expansionRatio = approachAreaMeters > 0 ? surfaceAreaMeters / approachAreaMeters : null; - result.push({ type: "Feature", properties: { native_id: `junction:node/${nodeId}`, osm_node_id: nodeId, kind: segmentIds.size === 3 ? "t" : "cross", source_road_ids: approaches.flatMap((approach) => approach.roadIds).join(","), cutback_m: cutbackMeters, movement_count: junctionMovements.length, connector_count: junctionConnectors.length, boundary_mode: boundaryMode, template: plan.template || null, template_reference: plan.templateReference || null, approach_area_m2: Math.round(approachAreaMeters * 10) / 10, surface_area_m2: Math.round(surfaceAreaMeters * 10) / 10, expansion_ratio: expansionRatio === null ? null : Math.round(expansionRatio * 100) / 100, rule: plan.template ? "junction-cross-template/v1" : "junction-shared-cutback/v4-shared-node-split" }, geometry: { type: "Polygon", coordinates: [ring] } }); - if (boundaryMode === "connector-convex-fallback") diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-connector-envelope-fallback", "路口面需要按转向路径的凸包兜底生成;请检查外缘和路缘与步行带是否符合实际。", node)); - if (plan.boundaryFallbacks) diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-rounded-corner-fallback", "部分路口圆角无法按道路边缘切线安全构造,已对该角使用确定性的直线回退。", node)); - if (!plan.clusterId) diagnostics.push(diagnostic("info", `junction:node/${nodeId}`, [nodeId], "ordinary-junction-surface", "已按道路截面与转向路径生成普通路口面。", node)); + result.push({ + type: 'Feature', + properties: { + native_id: `junction:node/${nodeId}`, + osm_node_id: nodeId, + kind: segmentIds.size === 3 ? 't' : 'cross', + source_road_ids: approaches.flatMap((approach) => approach.roadIds).join(','), + cutback_m: cutbackMeters, + movement_count: junctionMovements.length, + connector_count: junctionConnectors.length, + boundary_mode: boundaryMode, + template: plan.template || null, + template_reference: plan.templateReference || null, + approach_area_m2: Math.round(approachAreaMeters * 10) / 10, + surface_area_m2: Math.round(surfaceAreaMeters * 10) / 10, + expansion_ratio: expansionRatio === null ? null : Math.round(expansionRatio * 100) / 100, + rule: plan.template ? 'junction-cross-template/v1' : 'junction-shared-cutback/v4-shared-node-split', + }, + geometry: { type: 'Polygon', coordinates: [ring] }, + }); + if (boundaryMode === 'connector-convex-fallback') + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-connector-envelope-fallback', + '路口面需要按转向路径的凸包兜底生成;请检查外缘和路缘与步行带是否符合实际。', + node, + ), + ); + if (plan.boundaryFallbacks) + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-rounded-corner-fallback', + '部分路口圆角无法按道路边缘切线安全构造,已对该角使用确定性的直线回退。', + node, + ), + ); + if (!plan.clusterId) + diagnostics.push( + diagnostic( + 'info', + `junction:node/${nodeId}`, + [nodeId], + 'ordinary-junction-surface', + '已按道路截面与转向路径生成普通路口面。', + node, + ), + ); } for (const cluster of options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []) { - if (cluster.template === "complex-junction-v1") continue; + if (cluster.template === 'complex-junction-v1') continue; const clusterNodes = new Set(cluster.nodeIds.map(String)); const members = result.filter((feature) => clusterNodes.has(String(feature.properties.osm_node_id))); if (members.length < 2) continue; const points = []; const clusterCoordinates = [...clusterNodes].map((nodeId) => junctionPlans.get(nodeId)?.node).filter(Boolean); - const clusterCenter = clusterCoordinates.reduce((sum, point) => [sum[0] + point[0] / clusterCoordinates.length, sum[1] + point[1] / clusterCoordinates.length], [0, 0]); + const clusterCenter = clusterCoordinates.reduce( + (sum, point) => [sum[0] + point[0] / clusterCoordinates.length, sum[1] + point[1] / clusterCoordinates.length], + [0, 0], + ); for (let index = 0; index < 8; index += 1) points.push(offsetCoordinate(clusterCenter, index * 45, 12)); for (const [nodeId, plan] of junctionPlans) { if (!clusterNodes.has(String(nodeId))) continue; for (const approach of plan.approaches) { const end = approach.line.at(-1); - if ([...clusterNodes].some((candidate) => candidate !== String(nodeId) && distanceMeters(end, junctionPlans.get(candidate)?.node || [Infinity, Infinity]) < 3)) continue; - const cutback = pointAlongLine(approach.line, Math.min(plan.cutbackMeters, Math.max(12, cluster.approachLengthMeters * .5))); - const heading = headingAtEndpoint(approach.line); const half = approach.widthMeters / 2; + if ( + [...clusterNodes].some( + (candidate) => + candidate !== String(nodeId) && + distanceMeters(end, junctionPlans.get(candidate)?.node || [Infinity, Infinity]) < 3, + ) + ) + continue; + const cutback = pointAlongLine( + approach.line, + Math.min(plan.cutbackMeters, Math.max(12, cluster.approachLengthMeters * 0.5)), + ); + const heading = headingAtEndpoint(approach.line); + const half = approach.widthMeters / 2; points.push(offsetCoordinate(cutback, heading + 90, half), offsetCoordinate(cutback, heading - 90, half)); } const node = plan.node; @@ -1261,15 +2444,46 @@ function compileJunctionSurfaces(model, junctionPlans, connectors, movements, di if (hull.length < 3) continue; const ring = roundedHull(hull, 0.22); const memberIds = new Set(members.map((feature) => feature.properties.native_id)); - for (let index = result.length - 1; index >= 0; index -= 1) if (memberIds.has(result[index].properties.native_id)) result.splice(index, 1); - result.push({ type: "Feature", properties: { native_id: `junction-cluster:${cluster.id}`, osm_node_ids: [...clusterNodes].join(","), kind: "cluster", template: cluster.template, boundary_mode: "cluster-import-core", center: clusterCenter, member_count: members.length, movement_count: movements.filter((movement) => clusterNodes.has(String(movement.nodeId))).length, connector_count: connectors.filter((feature) => clusterNodes.has(String(feature.properties.node_id))).length, surface_area_m2: Math.round(polygonAreaMeters(ring) * 10) / 10, rule: "junction-cluster-template/v2" }, geometry: { type: "Polygon", coordinates: [[...ring, ring[0]]] } }); - diagnostics.push(diagnostic("info", `junction-cluster:${cluster.id}`, [...clusterNodes], "junction-cluster-core-applied", "已按外部进口截面和簇节点核心生成受限复合路口面。", ring[0])); + for (let index = result.length - 1; index >= 0; index -= 1) + if (memberIds.has(result[index].properties.native_id)) result.splice(index, 1); + result.push({ + type: 'Feature', + properties: { + native_id: `junction-cluster:${cluster.id}`, + osm_node_ids: [...clusterNodes].join(','), + kind: 'cluster', + template: cluster.template, + boundary_mode: 'cluster-import-core', + center: clusterCenter, + member_count: members.length, + movement_count: movements.filter((movement) => clusterNodes.has(String(movement.nodeId))).length, + connector_count: connectors.filter((feature) => clusterNodes.has(String(feature.properties.node_id))).length, + surface_area_m2: Math.round(polygonAreaMeters(ring) * 10) / 10, + rule: 'junction-cluster-template/v2', + }, + geometry: { type: 'Polygon', coordinates: [[...ring, ring[0]]] }, + }); + diagnostics.push( + diagnostic( + 'info', + `junction-cluster:${cluster.id}`, + [...clusterNodes], + 'junction-cluster-core-applied', + '已按外部进口截面和簇节点核心生成受限复合路口面。', + ring[0], + ), + ); } return result; } function activeComplexCluster(options, clusterId) { - return Boolean(clusterId && (options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []).some((cluster) => cluster.id === clusterId && cluster.template === "complex-junction-v1")); + return Boolean( + clusterId && + (options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []).some( + (cluster) => cluster.id === clusterId && cluster.template === 'complex-junction-v1', + ), + ); } function compileJunctionPlans(model, options = {}, diagnostics = []) { @@ -1279,10 +2493,12 @@ function compileJunctionPlans(model, options = {}, diagnostics = []) { byNode.get(endpoint.nodeId).push(endpoint); } const plans = new Map(); - const clusters = options.junctionTemplates?.enabled ? (options.junctionTemplates.clusters || []) : []; - const clusterByNode = new Map(clusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster]))); + const clusters = options.junctionTemplates?.enabled ? options.junctionTemplates.clusters || [] : []; + const clusterByNode = new Map( + clusters.flatMap((cluster) => cluster.nodeIds.map((nodeId) => [String(nodeId), cluster])), + ); for (const [nodeId, endpoints] of byNode) { - const segmentIds = new Set(endpoints.map((endpoint) => endpoint.roadId.replace(/:(forward|backward)$/, ""))); + const segmentIds = new Set(endpoints.map((endpoint) => endpoint.roadId.replace(/:(forward|backward)$/, ''))); if (segmentIds.size < 3 || segmentIds.size > 4) continue; const approaches = junctionApproaches(model, endpoints); if (approaches.length !== segmentIds.size) continue; @@ -1292,10 +2508,29 @@ function compileJunctionPlans(model, options = {}, diagnostics = []) { const node = endpoints[0].coordinate; const template = junctionTemplateFor(nodeId, segmentIds, options.junctionTemplates, diagnostics, node); const cutbackMeters = baseCutbackMeters * (template?.cutbackMultiplier || 1); - const boundary = junctionBoundary(approaches, node, cutbackMeters, template?.cornerRadiusMultiplier || 1, template?.approachWidthMultiplier || 1); + const boundary = junctionBoundary( + approaches, + node, + cutbackMeters, + template?.cornerRadiusMultiplier || 1, + template?.approachWidthMultiplier || 1, + ); if (boundary.points.length < 3) continue; const cluster = clusterByNode.get(String(nodeId)); - plans.set(nodeId, { node, segmentIds, approaches, cutbackMeters, boundary: boundary.points, boundaryMode: boundary.mode, boundaryFallbacks: boundary.fallbacks, template: template?.template || null, templateReference: template?.referenceFile || null, approachWidthMultiplier: template?.approachWidthMultiplier || 1, approachLengthMeters: template?.approachLengthMeters || 0, clusterId: cluster?.id || null }); + plans.set(nodeId, { + node, + segmentIds, + approaches, + cutbackMeters, + boundary: boundary.points, + boundaryMode: boundary.mode, + boundaryFallbacks: boundary.fallbacks, + template: template?.template || null, + templateReference: template?.referenceFile || null, + approachWidthMultiplier: template?.approachWidthMultiplier || 1, + approachLengthMeters: template?.approachLengthMeters || 0, + clusterId: cluster?.id || null, + }); } return plans; } @@ -1311,7 +2546,13 @@ function junctionApproaches(model, endpoints) { } return [...groups.values()].map((directions) => { const { endpoint, road } = directions[0]; - return { segmentId: road.segmentId, sourceWayKey: road.osmWayIds.join(","), line: endpoint.side === "end" ? [...road.centerline].reverse() : road.centerline, roadIds: directions.map((item) => item.road.id), widthMeters: directions.reduce((sum, item) => sum + item.road.widthMeters, 0) }; + return { + segmentId: road.segmentId, + sourceWayKey: road.osmWayIds.join(','), + line: endpoint.side === 'end' ? [...road.centerline].reverse() : road.centerline, + roadIds: directions.map((item) => item.road.id), + widthMeters: directions.reduce((sum, item) => sum + item.road.widthMeters, 0), + }; }); } @@ -1340,13 +2581,20 @@ function headingAtCutback(line, cutbackMeters) { // into one face and keep only its outermost edges. const PARALLEL_APPROACH_DEGREES = 25; -function signedHeadingDelta(value) { return ((value + 180) % 360 + 360) % 360 - 180; } +function signedHeadingDelta(value) { + return ((((value + 180) % 360) + 360) % 360) - 180; +} function mergeParallelApproachPoints(points, node) { const groups = []; for (const item of points) { - const group = groups.find((candidate) => Math.abs(signedHeadingDelta(candidate.heading - item.outwardHeading)) < PARALLEL_APPROACH_DEGREES); - if (group) { group.points.push(item); continue; } + const group = groups.find( + (candidate) => Math.abs(signedHeadingDelta(candidate.heading - item.outwardHeading)) < PARALLEL_APPROACH_DEGREES, + ); + if (group) { + group.points.push(item); + continue; + } groups.push({ heading: item.outwardHeading, points: [item] }); } return groups.flatMap((group) => { @@ -1354,9 +2602,12 @@ function mergeParallelApproachPoints(points, node) { if (segments.length < 2) return group.points; // Order across the face by bearing measured from the group's own heading, // so the comparison never straddles the +/-180 discontinuity. - const sorted = [...group.points].sort((first, second) => - signedHeadingDelta(headingDegrees(node, first.point) - group.heading) - signedHeadingDelta(headingDegrees(node, second.point) - group.heading)); - const merged = segments.sort().join("+"); + const sorted = [...group.points].sort( + (first, second) => + signedHeadingDelta(headingDegrees(node, first.point) - group.heading) - + signedHeadingDelta(headingDegrees(node, second.point) - group.heading), + ); + const merged = segments.sort().join('+'); return [sorted[0], sorted.at(-1)].map((item) => ({ ...item, segmentId: merged, sourceWayKey: merged })); }); } @@ -1367,42 +2618,63 @@ function junctionBoundary(approaches, node, cutbackMeters, cornerRadiusMultiplie const cutback = pointAlongLine(approach.line, cutbackMeters); if (!cutback) continue; const heading = headingAtCutback(approach.line, cutbackMeters) ?? headingAtEndpoint(approach.line); - const half = approach.widthMeters * approachWidthMultiplier / 2; - points.push({ point: offsetCoordinate(cutback, heading + 90, half), segmentId: approach.segmentId, sourceWayKey: approach.sourceWayKey, outwardHeading: heading }); - points.push({ point: offsetCoordinate(cutback, heading - 90, half), segmentId: approach.segmentId, sourceWayKey: approach.sourceWayKey, outwardHeading: heading }); + const half = (approach.widthMeters * approachWidthMultiplier) / 2; + points.push({ + point: offsetCoordinate(cutback, heading + 90, half), + segmentId: approach.segmentId, + sourceWayKey: approach.sourceWayKey, + outwardHeading: heading, + }); + points.push({ + point: offsetCoordinate(cutback, heading - 90, half), + segmentId: approach.segmentId, + sourceWayKey: approach.sourceWayKey, + outwardHeading: heading, + }); } const faces = mergeParallelApproachPoints(points, node); const ordered = faces.sort((a, b) => angleAround(node, a.point) - angleAround(node, b.point)); - if (ordered.length < 3) return { points: [], mode: "approach-envelope" }; + if (ordered.length < 3) return { points: [], mode: 'approach-envelope' }; const boundary = []; let rounded = 0; let fallbacks = 0; for (let index = 0; index < ordered.length; index += 1) { - const first = ordered[index]; const second = ordered[(index + 1) % ordered.length]; + const first = ordered[index]; + const second = ordered[(index + 1) % ordered.length]; boundary.push(first.point); // One physical OSM way is often split at an intersection node. Its two // opposite approaches share a continuous road edge; rounding that edge // bends the far side of a T junction and exposes junction asphalt beyond // the pedestrian strip. if (first.segmentId === second.segmentId || isStraightJunctionEdge(first, second)) continue; - const curve = roundedCorner(node, first.point, second.point, first.outwardHeading, second.outwardHeading, cornerRadiusMultiplier); - if (!curve) { fallbacks += 1; continue; } + const curve = roundedCorner( + node, + first.point, + second.point, + first.outwardHeading, + second.outwardHeading, + cornerRadiusMultiplier, + ); + if (!curve) { + fallbacks += 1; + continue; + } boundary.push(...curve.slice(1, -1)); rounded += 1; } - return { points: boundary, mode: rounded ? "rounded-approach-envelope" : "approach-envelope", fallbacks }; + return { points: boundary, mode: rounded ? 'rounded-approach-envelope' : 'approach-envelope', fallbacks }; } function templateApproachRing(approach, plan) { const innerDistance = plan.cutbackMeters; - const availableLength = lineLengthMeters(approach.line) - innerDistance - .5; + const availableLength = lineLengthMeters(approach.line) - innerDistance - 0.5; const lengthMeters = Math.min(plan.approachLengthMeters, availableLength); if (lengthMeters < 10) return null; const outerDistance = innerDistance + lengthMeters; const inner = pointAlongLine(approach.line, innerDistance); const outer = pointAlongLine(approach.line, outerDistance); const heading = headingAtEndpoint(approach.line); - const innerHalf = approach.widthMeters * plan.approachWidthMultiplier / 2; + const innerHalf = (approach.widthMeters * plan.approachWidthMultiplier) / 2; const outerHalf = approach.widthMeters / 2; const ring = [ offsetCoordinate(outer, heading + 90, outerHalf), @@ -1432,15 +2704,16 @@ function roundedHull(hull, factor) { function isStraightJunctionEdge(first, second) { if (first.sourceWayKey !== second.sourceWayKey) return false; - const radians = (first.outwardHeading - second.outwardHeading) * Math.PI / 180; + const radians = ((first.outwardHeading - second.outwardHeading) * Math.PI) / 180; return Math.cos(radians) <= -0.98; } function roundedCorner(node, first, second, firstHeading, secondHeading, radiusMultiplier = 1) { const origin = node; - const a = project(first, origin); const b = project(second, origin); + const a = project(first, origin); + const b = project(second, origin); const chord = Math.hypot(a[0] - b[0], a[1] - b[1]); - if (chord < .5 || !Number.isFinite(firstHeading) || !Number.isFinite(secondHeading)) return null; + if (chord < 0.5 || !Number.isFinite(firstHeading) || !Number.isFinite(secondHeading)) return null; const firstDirection = headingVector(firstHeading); const secondDirection = headingVector(secondHeading); const intersection = lineIntersection(a, firstDirection, b, secondDirection); @@ -1450,7 +2723,7 @@ function roundedCorner(node, first, second, firstHeading, secondHeading, radiusM // Adjacent approach edge tangents should meet in the corner between the // node and the cutback. Reject near-parallel or remote intersections rather // than publishing a huge/self-crossing curve. - if (controlDistance < .01 || controlDistance > endpointDistance * 1.5 || controlDistance > 80) return null; + if (controlDistance < 0.01 || controlDistance > endpointDistance * 1.5 || controlDistance > 80) return null; const scaledIntersection = [intersection[0] * radiusMultiplier, intersection[1] * radiusMultiplier]; const control = unproject(scaledIntersection, origin); return quadraticCurve(first, control, second, JUNCTION_CURVE_SEGMENTS); @@ -1461,43 +2734,122 @@ function junctionTemplateFor(nodeId, segmentIds, configured, diagnostics, node) const entry = (configured.references || []).find((item) => String(item.nodeId) === String(nodeId)); if (!entry) return null; if (segmentIds.size !== 4) { - diagnostics.push(diagnostic("info", `junction:node/${nodeId}`, [nodeId], "junction-template-topology-skip", "cross 模板只应用于四臂路口,当前路口保留 native 几何。", node)); + diagnostics.push( + diagnostic( + 'info', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-topology-skip', + 'cross 模板只应用于四臂路口,当前路口保留 native 几何。', + node, + ), + ); return null; } - if (entry.template !== "cross-v1") { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-template-unsupported", "路口模板名称不受支持,已回退 native 几何。", node)); + if (entry.template !== 'cross-v1') { + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-unsupported', + '路口模板名称不受支持,已回退 native 几何。', + node, + ), + ); return null; } const multiplier = Number(entry.cornerRadiusMultiplier ?? 1); if (!Number.isFinite(multiplier) || multiplier < 0.75 || multiplier > 1.25) { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-template-invalid-parameter", "cross 模板圆角参数必须在 0.75 到 1.25 之间,已回退 native 几何。", node)); + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-invalid-parameter', + 'cross 模板圆角参数必须在 0.75 到 1.25 之间,已回退 native 几何。', + node, + ), + ); return null; } const cutbackMultiplier = Number(entry.cutbackMultiplier ?? 1); if (!Number.isFinite(cutbackMultiplier) || cutbackMultiplier < 1 || cutbackMultiplier > 1.35) { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-template-invalid-parameter", "cross 模板进口过渡参数必须在 1 到 1.35 之间,已回退 native 几何。", node)); + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-invalid-parameter', + 'cross 模板进口过渡参数必须在 1 到 1.35 之间,已回退 native 几何。', + node, + ), + ); return null; } const approachWidthMultiplier = Number(entry.approachWidthMultiplier ?? 1); if (!Number.isFinite(approachWidthMultiplier) || approachWidthMultiplier < 1 || approachWidthMultiplier > 1.8) { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-template-invalid-parameter", "cross 模板进口宽度参数必须在 1 到 1.8 之间,已回退 native 几何。", node)); + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-invalid-parameter', + 'cross 模板进口宽度参数必须在 1 到 1.8 之间,已回退 native 几何。', + node, + ), + ); return null; } const approachLengthMeters = Number(entry.approachLengthMeters ?? 24); if (!Number.isFinite(approachLengthMeters) || approachLengthMeters < 10 || approachLengthMeters > 50) { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-template-invalid-parameter", "cross 模板进口过渡长度必须在 10 到 50 米之间,已回退 native 几何。", node)); + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-invalid-parameter', + 'cross 模板进口过渡长度必须在 10 到 50 米之间,已回退 native 几何。', + node, + ), + ); return null; } if (entry.referenceFile && !fs.existsSync(entry.referenceFile)) { - diagnostics.push(diagnostic("warning", `junction:node/${nodeId}`, [nodeId], "junction-template-reference-missing", "路口参考文件不存在,已回退 native 几何。", node)); + diagnostics.push( + diagnostic( + 'warning', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-reference-missing', + '路口参考文件不存在,已回退 native 几何。', + node, + ), + ); return null; } - diagnostics.push(diagnostic("info", `junction:node/${nodeId}`, [nodeId], "junction-template-applied", "已按 cross-v1 模板规整路口面;道路、车道、连接器和控制设施保持 native 结果。", node)); - return { template: entry.template, referenceFile: entry.referenceFile || null, cornerRadiusMultiplier: multiplier, cutbackMultiplier, approachWidthMultiplier, approachLengthMeters }; + diagnostics.push( + diagnostic( + 'info', + `junction:node/${nodeId}`, + [nodeId], + 'junction-template-applied', + '已按 cross-v1 模板规整路口面;道路、车道、连接器和控制设施保持 native 结果。', + node, + ), + ); + return { + template: entry.template, + referenceFile: entry.referenceFile || null, + cornerRadiusMultiplier: multiplier, + cutbackMultiplier, + approachWidthMultiplier, + approachLengthMeters, + }; } function headingVector(degrees) { - const radians = degrees * Math.PI / 180; + const radians = (degrees * Math.PI) / 180; return [Math.sin(radians), Math.cos(radians)]; } @@ -1522,7 +2874,8 @@ function pointAlongLine(line, meters) { function pointAndAxisAlongLine(line, meters) { let remaining = meters; for (let index = 1; index < line.length; index += 1) { - const start = line[index - 1]; const end = line[index]; + const start = line[index - 1]; + const end = line[index]; const length = distanceMeters(start, end); if (length < 0.01) continue; if (length >= remaining) { @@ -1573,14 +2926,14 @@ function distanceAlongLineToRadius(line, center, radius) { if (!center || line.length < 2) return 0; const heading = headingAtEndpoint(line); const vector = project(line[0], center); - const radians = heading * Math.PI / 180; + const radians = (heading * Math.PI) / 180; const startRadius = vector[0] * Math.sin(radians) + vector[1] * Math.cos(radians); return Math.max(0, radius - startRadius); } function trimLineRange(line, startCutback, endCutback) { const total = lineLengthMeters(line); - if (startCutback + endCutback >= total - .5) return line; + if (startCutback + endCutback >= total - 0.5) return line; const result = [pointAlongLine(line, startCutback)]; let traversed = 0; for (let index = 1; index < line.length - 1; index += 1) { @@ -1591,39 +2944,75 @@ function trimLineRange(line, startCutback, endCutback) { return result; } -function headingAtEndpoint(line) { return headingDegrees(line[0], line[1]); } -function headingDegrees(a, b) { return Math.atan2((b[0] - a[0]) * Math.cos(a[1] * Math.PI / 180), b[1] - a[1]) * 180 / Math.PI; } -function offsetCoordinate(point, degrees, meters) { const radians = degrees * Math.PI / 180; return [point[0] + Math.sin(radians) * meters / (111320 * Math.cos(point[1] * Math.PI / 180)), point[1] + Math.cos(radians) * meters / 111320]; } -function angleAround(center, point) { return Math.atan2(point[1] - center[1], point[0] - center[0]); } -function sortAround(center, points) { return points.sort((a, b) => Math.atan2(a[1] - center[1], a[0] - center[0]) - Math.atan2(b[1] - center[1], b[0] - center[0])); } +function headingAtEndpoint(line) { + return headingDegrees(line[0], line[1]); +} +function headingDegrees(a, b) { + return (Math.atan2((b[0] - a[0]) * Math.cos((a[1] * Math.PI) / 180), b[1] - a[1]) * 180) / Math.PI; +} +function offsetCoordinate(point, degrees, meters) { + const radians = (degrees * Math.PI) / 180; + return [ + point[0] + (Math.sin(radians) * meters) / (111320 * Math.cos((point[1] * Math.PI) / 180)), + point[1] + (Math.cos(radians) * meters) / 111320, + ]; +} +function angleAround(center, point) { + return Math.atan2(point[1] - center[1], point[0] - center[0]); +} +function sortAround(center, points) { + return points.sort( + (a, b) => Math.atan2(a[1] - center[1], a[0] - center[0]) - Math.atan2(b[1] - center[1], b[0] - center[0]), + ); +} function convexHull(points) { - const unique = [...new Map(points.map((point) => [`${point[0]},${point[1]}`, point])).values()].sort((a, b) => a[0] - b[0] || a[1] - b[1]); + const unique = [...new Map(points.map((point) => [`${point[0]},${point[1]}`, point])).values()].sort( + (a, b) => a[0] - b[0] || a[1] - b[1], + ); if (unique.length < 3) return unique; const cross = (a, b, c) => (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]); - const lower = []; for (const point of unique) { while (lower.length >= 2 && cross(lower.at(-2), lower.at(-1), point) <= 0) lower.pop(); lower.push(point); } - const upper = []; for (const point of [...unique].reverse()) { while (upper.length >= 2 && cross(upper.at(-2), upper.at(-1), point) <= 0) upper.pop(); upper.push(point); } + const lower = []; + for (const point of unique) { + while (lower.length >= 2 && cross(lower.at(-2), lower.at(-1), point) <= 0) lower.pop(); + lower.push(point); + } + const upper = []; + for (const point of [...unique].reverse()) { + while (upper.length >= 2 && cross(upper.at(-2), upper.at(-1), point) <= 0) upper.pop(); + upper.push(point); + } return [...lower.slice(0, -1), ...upper.slice(0, -1)]; } -function interpolate(a, b, ratio) { return [a[0] + (b[0] - a[0]) * ratio, a[1] + (b[1] - a[1]) * ratio]; } -function distanceMeters(a, b) { const dx = (b[0] - a[0]) * 111320 * Math.cos(a[1] * Math.PI / 180); const dy = (b[1] - a[1]) * 111320; return Math.hypot(dx, dy); } +function interpolate(a, b, ratio) { + return [a[0] + (b[0] - a[0]) * ratio, a[1] + (b[1] - a[1]) * ratio]; +} +function distanceMeters(a, b) { + const dx = (b[0] - a[0]) * 111320 * Math.cos((a[1] * Math.PI) / 180); + const dy = (b[1] - a[1]) * 111320; + return Math.hypot(dx, dy); +} function hasSelfIntersection(ring) { - for (let first = 0; first < ring.length - 1; first += 1) for (let second = first + 1; second < ring.length - 1; second += 1) { - if (Math.abs(first - second) <= 1 || first === 0 && second === ring.length - 2) continue; - if (segmentsIntersect(ring[first], ring[first + 1], ring[second], ring[second + 1])) return true; - } + for (let first = 0; first < ring.length - 1; first += 1) + for (let second = first + 1; second < ring.length - 1; second += 1) { + if (Math.abs(first - second) <= 1 || (first === 0 && second === ring.length - 2)) continue; + if (segmentsIntersect(ring[first], ring[first + 1], ring[second], ring[second + 1])) return true; + } return false; } function segmentsIntersect(a, b, c, d) { const cross = (p, q, r) => (q[0] - p[0]) * (r[1] - p[1]) - (q[1] - p[1]) * (r[0] - p[0]); - const abC = cross(a, b, c); const abD = cross(a, b, d); const cdA = cross(c, d, a); const cdB = cross(c, d, b); - return (abC > 0 && abD < 0 || abC < 0 && abD > 0) && (cdA > 0 && cdB < 0 || cdA < 0 && cdB > 0); + const abC = cross(a, b, c); + const abD = cross(a, b, d); + const cdA = cross(c, d, a); + const cdB = cross(c, d, b); + return ((abC > 0 && abD < 0) || (abC < 0 && abD > 0)) && ((cdA > 0 && cdB < 0) || (cdA < 0 && cdB > 0)); } function circleRing(center, radius, segments) { const origin = center; const ring = []; for (let index = 0; index <= segments; index += 1) { - const angle = index / segments * Math.PI * 2; + const angle = (index / segments) * Math.PI * 2; ring.push(unproject([Math.cos(angle) * radius, Math.sin(angle) * radius], origin)); } return ring; @@ -1640,8 +3029,12 @@ function removeOffsetFolds(offset, centerline) { for (let guard = 0; guard < offset.length && kept.length > 2; guard += 1) { let removed = false; for (let position = 0; position < kept.length - 1; position += 1) { - const from = kept[position]; const to = kept[position + 1]; - const alongCenter = [centerline[to.index][0] - centerline[from.index][0], centerline[to.index][1] - centerline[from.index][1]]; + const from = kept[position]; + const to = kept[position + 1]; + const alongCenter = [ + centerline[to.index][0] - centerline[from.index][0], + centerline[to.index][1] - centerline[from.index][1], + ]; const alongOffset = [to.point[0] - from.point[0], to.point[1] - from.point[1]]; if (alongCenter[0] * alongOffset[0] + alongCenter[1] * alongOffset[1] >= 0) continue; // Keep both termini: they are where the surface meets its junctions. @@ -1658,13 +3051,18 @@ function roadRing(line, width) { if (line.length < 2 || !Number.isFinite(width)) return null; const origin = line[0]; const points = line.map((point) => project(point, origin)); - const left = []; const right = []; + const left = []; + const right = []; const half = width / 2; for (let i = 0; i < points.length; i += 1) { - const prior = points[Math.max(0, i - 1)]; const next = points[Math.min(points.length - 1, i + 1)]; - const dx = next[0] - prior[0]; const dy = next[1] - prior[1]; const length = Math.hypot(dx, dy); + const prior = points[Math.max(0, i - 1)]; + const next = points[Math.min(points.length - 1, i + 1)]; + const dx = next[0] - prior[0]; + const dy = next[1] - prior[1]; + const length = Math.hypot(dx, dy); if (length < 0.01) return null; - const nx = -dy / length * half; const ny = dx / length * half; + const nx = (-dy / length) * half; + const ny = (dx / length) * half; left.push([points[i][0] + nx, points[i][1] + ny]); right.push([points[i][0] - nx, points[i][1] - ny]); } @@ -1683,13 +3081,59 @@ function sidewalkRing(line, innerOffset, outerOffset, side) { return ring.every((point) => point.every(Number.isFinite)) ? ring : null; } -function project(point, origin) { const scale = 111320; return [(point[0] - origin[0]) * scale * Math.cos(origin[1] * Math.PI / 180), (point[1] - origin[1]) * scale]; } -function unproject(point, origin) { const scale = 111320; return [point[0] / (scale * Math.cos(origin[1] * Math.PI / 180)) + origin[0], point[1] / scale + origin[1]]; } -function diagnostic(severity, subjectId, sourceIds, rule, message, coordinate) { return { id: `diagnostic:${rule}:${subjectId}`, severity, subjectId, sourceIds, rule, message, geometry: coordinate ? { type: "Point", coordinates: coordinate } : null }; } -function xmlAttrs(text) { const attrs = {}; for (const match of text.matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) attrs[match[1]] = match[2] ?? match[3]; return attrs; } -function parseTags(body) { const tags = {}; for (const match of body.matchAll(/]*)\/?\s*>/g)) { const attrs = xmlAttrs(match[1]); if (attrs.k) tags[attrs.k] = attrs.v || ""; } return tags; } -function positiveInteger(value) { const number = Number(value); return Number.isInteger(number) && number > 0 ? number : null; } -function positiveNumber(value) { const match = String(value ?? "").match(/^\s*(\d+(?:\.\d+)?)/); const number = match ? Number(match[1]) : null; return Number.isFinite(number) && number > 0 ? number : null; } -function writeJsonAtomic(file, data) { fs.mkdirSync(path.dirname(file), { recursive: true }); const temporary = `${file}.${process.pid}.tmp`; fs.writeFileSync(temporary, `${JSON.stringify(data, null, 2)}\n`); fs.renameSync(temporary, file); } +function project(point, origin) { + const scale = 111320; + return [(point[0] - origin[0]) * scale * Math.cos((origin[1] * Math.PI) / 180), (point[1] - origin[1]) * scale]; +} +function unproject(point, origin) { + const scale = 111320; + return [point[0] / (scale * Math.cos((origin[1] * Math.PI) / 180)) + origin[0], point[1] / scale + origin[1]]; +} +function diagnostic(severity, subjectId, sourceIds, rule, message, coordinate) { + return { + id: `diagnostic:${rule}:${subjectId}`, + severity, + subjectId, + sourceIds, + rule, + message, + geometry: coordinate ? { type: 'Point', coordinates: coordinate } : null, + }; +} +function xmlAttrs(text) { + const attrs = {}; + for (const match of text.matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) attrs[match[1]] = match[2] ?? match[3]; + return attrs; +} +function parseTags(body) { + const tags = {}; + for (const match of body.matchAll(/]*)\/?\s*>/g)) { + const attrs = xmlAttrs(match[1]); + if (attrs.k) tags[attrs.k] = attrs.v || ''; + } + return tags; +} +function positiveInteger(value) { + const number = Number(value); + return Number.isInteger(number) && number > 0 ? number : null; +} +function positiveNumber(value) { + const match = String(value ?? '').match(/^\s*(\d+(?:\.\d+)?)/); + const number = match ? Number(match[1]) : null; + return Number.isFinite(number) && number > 0 ? number : null; +} +function writeJsonAtomic(file, data) { + fs.mkdirSync(path.dirname(file), { recursive: true }); + const temporary = `${file}.${process.pid}.tmp`; + fs.writeFileSync(temporary, `${JSON.stringify(data, null, 2)}\n`); + fs.renameSync(temporary, file); +} -module.exports = { OVERRIDE_SCHEMA, compileRoadModel, compileGeometry, loadOverrides, validateOverrides, writeJsonAtomic }; +module.exports = { + OVERRIDE_SCHEMA, + compileRoadModel, + compileGeometry, + loadOverrides, + validateOverrides, + writeJsonAtomic, +}; diff --git a/src/compile/turn-lane-arrows.js b/src/compile/turn-lane-arrows.js index d6f1a3a..ab021f5 100644 --- a/src/compile/turn-lane-arrows.js +++ b/src/compile/turn-lane-arrows.js @@ -1,10 +1,10 @@ -"use strict"; +'use strict'; -const fs = require("fs"); -const path = require("path"); -const { laneCenterline } = require("../geometry/lane-geometry"); +const fs = require('fs'); +const path = require('path'); +const { laneCenterline } = require('../geometry/lane-geometry'); -const ASSET_MANIFEST = path.resolve(__dirname, "..", "..", "assets", "lane-icons", "manifest.json"); +const ASSET_MANIFEST = path.resolve(__dirname, '..', '..', 'assets', 'lane-icons', 'manifest.json'); const LANE_WIDTH_METERS = 3.2; const PLACEMENT_DISTANCE_METERS = 9; const SPATIAL_MATCH_MAX_DISTANCE_METERS = 18; @@ -12,69 +12,98 @@ const SPATIAL_MATCH_MIN_ALIGNMENT = Math.cos(Math.PI / 6); // Existing osm2streets lane arrows are approximately 1.4 m across. Keep the // 25-unit upstream icon at the same on-road scale rather than at screen scale. -const SVG_METERS_PER_UNIT = 0.10; +const SVG_METERS_PER_UNIT = 0.1; function loadManifest(file = ASSET_MANIFEST) { - const manifest = JSON.parse(fs.readFileSync(file, "utf8")); - if (!Array.isArray(manifest.assets)) throw new Error("turn-lane asset manifest has no assets array"); + const manifest = JSON.parse(fs.readFileSync(file, 'utf8')); + if (!Array.isArray(manifest.assets)) throw new Error('turn-lane asset manifest has no assets array'); return manifest; } function supportedAssets(manifest = loadManifest()) { - return new Map(manifest.assets - .filter((asset) => asset.supported === true && asset.tested === true) - .map((asset) => [asset.id, asset])); + return new Map( + manifest.assets + .filter((asset) => asset.supported === true && asset.tested === true) + .map((asset) => [asset.id, asset]), + ); } function buildCustomTurnLaneArrows(osm, options = {}) { const enabled = options.enabled === true; const diagnostics = []; - if (!enabled) return { features: [], diagnostics: [{ reason: "disabled" }] }; + if (!enabled) return { features: [], diagnostics: [{ reason: 'disabled' }] }; const assets = supportedAssets(options.manifest); const endpointRoadCounts = roadCountsByNode(osm); - const networkIntersectionNodes = new Set((options.network?.intersections || []) - .flatMap(([, intersection]) => intersection.osm_ids || []).map(Number)); + const networkIntersectionNodes = new Set( + (options.network?.intersections || []).flatMap(([, intersection]) => intersection.osm_ids || []).map(Number), + ); const features = []; const ways = [...osm.ways.values()].sort((a, b) => a.id - b.id); for (const way of ways) { - for (const direction of ["forward", "backward"]) { + for (const direction of ['forward', 'backward']) { const tag = way.tags[`turn:lanes:${direction}`]; if (!tag) continue; const laneCount = directionalLaneCount(way, direction); if (!laneCount) { - diagnostics.push(skip(way, direction, "missing_lane_count")); + diagnostics.push(skip(way, direction, 'missing_lane_count')); continue; } const endpoint = endpointGeometry(osm, way, direction, endpointRoadCounts, networkIntersectionNodes); if (!endpoint) { - diagnostics.push(skip(way, direction, "indeterminate_intersection_endpoint")); + diagnostics.push(skip(way, direction, 'indeterminate_intersection_endpoint')); continue; } - const maneuvers = String(tag).split("|").map((value) => normalizeManeuver(value)); + const maneuvers = String(tag) + .split('|') + .map((value) => normalizeManeuver(value)); for (let laneIndex = 0; laneIndex < maneuvers.length; laneIndex += 1) { const maneuver = maneuvers[laneIndex]; const asset = assets.get(maneuver); if (!asset) { - diagnostics.push(skip(way, direction, "unsupported_or_untested_maneuver", { lane_index: laneIndex, maneuver })); + diagnostics.push( + skip(way, direction, 'unsupported_or_untested_maneuver', { lane_index: laneIndex, maneuver }), + ); continue; } if (laneIndex >= laneCount) { - diagnostics.push(skip(way, direction, "lane_index_exceeds_lane_count", { lane_index: laneIndex, maneuver })); + diagnostics.push(skip(way, direction, 'lane_index_exceeds_lane_count', { lane_index: laneIndex, maneuver })); continue; } - const resolvedPlacement = lanePlacement(way, direction, laneIndex, endpoint, options.lanePolygons, options.crosswalkStripes, options.stopLines); + const resolvedPlacement = lanePlacement( + way, + direction, + laneIndex, + endpoint, + options.lanePolygons, + options.crosswalkStripes, + options.stopLines, + ); if (resolvedPlacement?.blocked) { - diagnostics.push(skip(way, direction, "no_safe_turn_arrow_position", { lane_index: laneIndex, maneuver })); + diagnostics.push(skip(way, direction, 'no_safe_turn_arrow_position', { lane_index: laneIndex, maneuver })); continue; } - const placement = resolvedPlacement || fallbackLanePlacement(endpoint, direction, laneIndex, options.crosswalkStripes, options.stopLines); + const placement = + resolvedPlacement || + fallbackLanePlacement(endpoint, direction, laneIndex, options.crosswalkStripes, options.stopLines); if (!placement) { - diagnostics.push(skip(way, direction, "no_safe_turn_arrow_position", { lane_index: laneIndex, maneuver })); + diagnostics.push(skip(way, direction, 'no_safe_turn_arrow_position', { lane_index: laneIndex, maneuver })); continue; } const parts = templateFor(asset.id, options.manifest); for (let partIndex = 0; partIndex < parts.length; partIndex += 1) { - features.push(makeFeature(way, direction, laneIndex, maneuver, asset, partIndex, parts[partIndex], placement.center, placement)); + features.push( + makeFeature( + way, + direction, + laneIndex, + maneuver, + asset, + partIndex, + parts[partIndex], + placement.center, + placement, + ), + ); } } } @@ -83,13 +112,20 @@ function buildCustomTurnLaneArrows(osm, options = {}) { } function normalizeManeuver(value) { - const parts = String(value || "").split(";").map((part) => part.trim()).filter(Boolean).sort(); + const parts = String(value || '') + .split(';') + .map((part) => part.trim()) + .filter(Boolean) + .sort(); const supported = new Map([ - ["through", "through"], ["left", "left"], ["right", "right"], - ["left;through", "through;left"], ["right;through", "through;right"], - ["left;right;through", "through;left;right"], + ['through', 'through'], + ['left', 'left'], + ['right', 'right'], + ['left;through', 'through;left'], + ['right;through', 'through;right'], + ['left;right;through', 'through;left;right'], ]); - return supported.get(parts.join(";")) || parts.join(";"); + return supported.get(parts.join(';')) || parts.join(';'); } function directionalLaneCount(way, direction) { @@ -104,20 +140,21 @@ function directionalLaneCount(way, direction) { function roadCountsByNode(osm) { const out = new Map(); for (const way of osm.ways.values()) { - if (!way.tags.highway || way.tags.highway === "service") continue; + if (!way.tags.highway || way.tags.highway === 'service') continue; for (const ref of new Set(way.refs)) out.set(ref, (out.get(ref) || 0) + 1); } return out; } function endpointGeometry(osm, way, direction, roadCounts, networkIntersectionNodes) { - const forward = direction === "forward"; + const forward = direction === 'forward'; const endpointIndex = forward ? way.refs.length - 1 : 0; const neighborIndex = forward ? endpointIndex - 1 : 1; const node = osm.nodes.get(way.refs[endpointIndex]); const neighbor = osm.nodes.get(way.refs[neighborIndex]); if (!node || !neighbor) return null; - const networkSaysIntersection = networkIntersectionNodes && networkIntersectionNodes.size > 0 && networkIntersectionNodes.has(node.id); + const networkSaysIntersection = + networkIntersectionNodes && networkIntersectionNodes.size > 0 && networkIntersectionNodes.has(node.id); if (!networkSaysIntersection && (roadCounts.get(node.id) || 0) < 3) return null; const meters = metersForLat(node.lat); // For both directions, point from the adjacent road node to the endpoint. @@ -134,15 +171,36 @@ function laneCenter(endpoint, direction, laneIndex, meters) { const lateral = (laneIndex + 0.5) * LANE_WIDTH_METERS; // The local axis always follows travel, so moving back from either endpoint // places the marking on its approach lane before the intersection. - return addMeters([endpoint.node.lon, endpoint.node.lat], endpoint.axis, -PLACEMENT_DISTANCE_METERS, endpoint.right, lateral, meters); + return addMeters( + [endpoint.node.lon, endpoint.node.lat], + endpoint.axis, + -PLACEMENT_DISTANCE_METERS, + endpoint.right, + lateral, + meters, + ); } function fallbackLanePlacement(endpoint, direction, laneIndex, crosswalkStripes, stopLines) { const lateral = (laneIndex + 0.5) * LANE_WIDTH_METERS; for (const distance of [PLACEMENT_DISTANCE_METERS, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42]) { - const center = addMeters([endpoint.node.lon, endpoint.node.lat], endpoint.axis, -distance, endpoint.right, lateral, endpoint.meters); + const center = addMeters( + [endpoint.node.lon, endpoint.node.lat], + endpoint.axis, + -distance, + endpoint.right, + lateral, + endpoint.meters, + ); if (!nearIntersectionMarking(center, endpoint.axis, crosswalkStripes, stopLines, endpoint.meters)) { - return { center, axis: endpoint.axis, right: endpoint.right, meters: endpoint.meters, placementDistance: distance, placementSource: "osm_way_fallback" }; + return { + center, + axis: endpoint.axis, + right: endpoint.right, + meters: endpoint.meters, + placementDistance: distance, + placementSource: 'osm_way_fallback', + }; } } return null; @@ -150,28 +208,35 @@ function fallbackLanePlacement(endpoint, direction, laneIndex, crosswalkStripes, function lanePlacement(way, direction, laneIndex, endpoint, lanePolygons, crosswalkStripes, stopLines) { if (!Array.isArray(lanePolygons)) return null; - const expectedDirection = direction === "forward" ? "Fwd" : "Back"; - const directionalCandidates = lanePolygons.filter((feature) => - feature.properties?.type === "Driving" && - feature.properties.direction === expectedDirection + const expectedDirection = direction === 'forward' ? 'Fwd' : 'Back'; + const directionalCandidates = lanePolygons.filter( + (feature) => feature.properties?.type === 'Driving' && feature.properties.direction === expectedDirection, ); let candidates = directionalCandidates.filter((feature) => - (feature.properties.osm_way_ids || []).map(Number).includes(way.id) + (feature.properties.osm_way_ids || []).map(Number).includes(way.id), ); - let placementSource = "driving_lane_centerline"; + let placementSource = 'driving_lane_centerline'; let spatialAnchors = null; if (!candidates.length) { const ranked = directionalCandidates .map((feature) => ({ feature, anchor: spatialLaneAnchor(feature, endpoint) })) .filter(({ anchor }) => anchor) - .filter(({ anchor }) => anchor.alignment >= SPATIAL_MATCH_MIN_ALIGNMENT && anchor.distance <= SPATIAL_MATCH_MAX_DISTANCE_METERS) - .sort((a, b) => a.anchor.distance - b.anchor.distance || a.anchor.lateral - b.anchor.lateral || Number(a.feature.properties.index) - Number(b.feature.properties.index)); + .filter( + ({ anchor }) => + anchor.alignment >= SPATIAL_MATCH_MIN_ALIGNMENT && anchor.distance <= SPATIAL_MATCH_MAX_DISTANCE_METERS, + ) + .sort( + (a, b) => + a.anchor.distance - b.anchor.distance || + a.anchor.lateral - b.anchor.lateral || + Number(a.feature.properties.index) - Number(b.feature.properties.index), + ); if (ranked.length) { // JOSM may split a tagged OSM way into temporary negative IDs. Those IDs // are absent from osm2streets' rendered polygons, so associate the full // physical approach by endpoint proximity and road-axis alignment. candidates = ranked.map(({ feature }) => feature); - placementSource = "spatial_driving_lane_centerline"; + placementSource = 'spatial_driving_lane_centerline'; spatialAnchors = new Map(ranked.map(({ feature, anchor }) => [feature, anchor])); } } @@ -184,24 +249,53 @@ function lanePlacement(way, direction, laneIndex, endpoint, lanePolygons, crossw const lane = candidates[laneIndex]; const spatialAnchor = spatialAnchors?.get(lane); if (spatialAnchor) { - const sampled = placementDistances().map((distance) => ({ - center: sampleCenterlineAwayFromEndpoint(spatialAnchor, distance, endpoint.meters), - distance, - })).find(({ center }) => center && !nearIntersectionMarking(center, spatialAnchor.axis, crosswalkStripes, stopLines, endpoint.meters)); + const sampled = placementDistances() + .map((distance) => ({ + center: sampleCenterlineAwayFromEndpoint(spatialAnchor, distance, endpoint.meters), + distance, + })) + .find( + ({ center }) => + center && !nearIntersectionMarking(center, spatialAnchor.axis, crosswalkStripes, stopLines, endpoint.meters), + ); if (!sampled) return { blocked: true }; - return { center: sampled.center, axis: spatialAnchor.axis, right: [spatialAnchor.axis[1], -spatialAnchor.axis[0]], meters: endpoint.meters, placementDistance: sampled.distance, placementSource }; + return { + center: sampled.center, + axis: spatialAnchor.axis, + right: [spatialAnchor.axis[1], -spatialAnchor.axis[0]], + meters: endpoint.meters, + placementDistance: sampled.distance, + placementSource, + }; } const centerline = laneCenterline(lane); if (!centerline) return null; - const startsAtEndpoint = direction === "backward"; + const startsAtEndpoint = direction === 'backward'; const ordered = startsAtEndpoint ? centerline : [...centerline].reverse(); const sampled = [PLACEMENT_DISTANCE_METERS, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42] .map((distance) => ({ center: samplePolyline(ordered, distance, endpoint.meters), distance })) - .find(({ center }) => center && !nearIntersectionMarking(center, axisForLane(ordered, endpoint.meters), crosswalkStripes, stopLines, endpoint.meters)); + .find( + ({ center }) => + center && + !nearIntersectionMarking( + center, + axisForLane(ordered, endpoint.meters), + crosswalkStripes, + stopLines, + endpoint.meters, + ), + ); if (!sampled) return { blocked: true }; const axis = axisForLane(ordered, endpoint.meters); if (!axis) return null; - return { center: sampled.center, axis, right: [axis[1], -axis[0]], meters: endpoint.meters, placementDistance: sampled.distance, placementSource }; + return { + center: sampled.center, + axis, + right: [axis[1], -axis[0]], + meters: endpoint.meters, + placementDistance: sampled.distance, + placementSource, + }; } function spatialLaneAnchor(lane, endpoint) { @@ -212,7 +306,10 @@ function spatialLaneAnchor(lane, endpoint) { const start = centerline[index]; const end = centerline[index + 1]; const point = closestPointOnSegment([endpoint.node.lon, endpoint.node.lat], start, end, endpoint.meters); - const distance = Math.hypot((point[0] - endpoint.node.lon) * endpoint.meters.lon, (point[1] - endpoint.node.lat) * endpoint.meters.lat); + const distance = Math.hypot( + (point[0] - endpoint.node.lon) * endpoint.meters.lon, + (point[1] - endpoint.node.lat) * endpoint.meters.lat, + ); const tangent = normalizeMetersVector(subtractPoint(end, start), endpoint.meters); if (!tangent || (best && distance >= best.distance)) continue; const dot = tangent[0] * endpoint.axis[0] + tangent[1] * endpoint.axis[1]; @@ -223,7 +320,8 @@ function spatialLaneAnchor(lane, endpoint) { distance, axis, alignment: Math.abs(dot), - lateral: offset[0] * endpoint.right[0] * endpoint.meters.lon + offset[1] * endpoint.right[1] * endpoint.meters.lat, + lateral: + offset[0] * endpoint.right[0] * endpoint.meters.lon + offset[1] * endpoint.right[1] * endpoint.meters.lat, centerline, segmentIndex: index, }; @@ -276,11 +374,13 @@ function nearIntersectionMarking(center, axis, stripes, stopLines, meters) { samples.push(addMeters(center, axis, forward, right, lateral, meters)); } } - return [...(stripes || []), ...(stopLines || [])].some((feature) => samples.some((point) => nearFeature(point, feature, meters))); + return [...(stripes || []), ...(stopLines || [])].some((feature) => + samples.some((point) => nearFeature(point, feature, meters)), + ); } function nearFeature(point, feature, meters) { - const ring = feature.geometry?.type === "Polygon" ? feature.geometry.coordinates?.[0] : null; + const ring = feature.geometry?.type === 'Polygon' ? feature.geometry.coordinates?.[0] : null; if (!ring?.length) return false; const xs = ring.map((coordinate) => coordinate[0]); const ys = ring.map((coordinate) => coordinate[1]); @@ -309,12 +409,14 @@ function samplePolyline(points, distanceMeters, meters) { } function makeFeature(way, direction, laneIndex, maneuver, asset, partIndex, template, center, endpoint) { - const ring = template.map(([rightMeters, forwardMeters]) => addMeters(center, endpoint.axis, forwardMeters, endpoint.right, rightMeters, endpoint.meters)); + const ring = template.map(([rightMeters, forwardMeters]) => + addMeters(center, endpoint.axis, forwardMeters, endpoint.right, rightMeters, endpoint.meters), + ); return { - type: "Feature", + type: 'Feature', properties: { - type: "lane arrow", - source: "osm_turn_lanes", + type: 'lane arrow', + source: 'osm_turn_lanes', osm_way_id: way.id, direction, lane_index: laneIndex, @@ -326,18 +428,18 @@ function makeFeature(way, direction, laneIndex, maneuver, asset, partIndex, temp // This stable key lets the QGIS normalizer restore one rendered arrow. custom_arrow_id: `${way.id}:${direction}:${laneIndex}:${maneuver}`, placement_distance_meters: endpoint.placementDistance ?? PLACEMENT_DISTANCE_METERS, - placement_source: endpoint.placementSource ?? "osm_way_fallback", + placement_source: endpoint.placementSource ?? 'osm_way_fallback', }, - geometry: { type: "Polygon", coordinates: [ring] }, + geometry: { type: 'Polygon', coordinates: [ring] }, }; } function skip(way, direction, reason, extra = {}) { - return { source: "osm_turn_lanes", osm_way_id: way.id, direction, reason, ...extra }; + return { source: 'osm_turn_lanes', osm_way_id: way.id, direction, reason, ...extra }; } function isOneway(way) { - return ["yes", "true", "1"].includes(String(way.tags.oneway || "").toLowerCase()); + return ['yes', 'true', '1'].includes(String(way.tags.oneway || '').toLowerCase()); } function metersForLat(lat) { @@ -366,8 +468,11 @@ function arrowRingsAt(maneuver, center, axis, manifest = loadManifest()) { if (!Number.isFinite(length) || length < 0.001) return []; const forward = [axis[0] / length, axis[1] / length]; const right = [forward[1], -forward[0]]; - return templateFor(normalized, manifest).map((template) => template.map(([rightMeters, forwardMeters]) => - addMeters(center, forward, forwardMeters, right, rightMeters, meters))); + return templateFor(normalized, manifest).map((template) => + template.map(([rightMeters, forwardMeters]) => + addMeters(center, forward, forwardMeters, right, rightMeters, meters), + ), + ); } function templateFor(assetId, manifest = loadManifest()) { @@ -377,67 +482,101 @@ function templateFor(assetId, manifest = loadManifest()) { } function angularTemplate(assetId) { - const shaftWidth = 0.30; + const shaftWidth = 0.3; const shaftHalf = shaftWidth / 2; const straightBase = 1.18; const straightTip = 1.92; const rectangle = (minX, minY, maxX, maxY) => [ - [minX, minY], [maxX, minY], [maxX, maxY], [minX, maxY], [minX, minY], + [minX, minY], + [maxX, minY], + [maxX, maxY], + [minX, maxY], + [minX, minY], + ]; + const throughHead = () => [ + [0, straightTip], + [-0.42, straightBase], + [-shaftHalf, straightBase], + [-shaftHalf, 0], + [shaftHalf, 0], + [shaftHalf, straightBase], + [0.42, straightBase], + [0, straightTip], ]; - const throughHead = () => [[0, straightTip], [-0.42, straightBase], [-shaftHalf, straightBase], [-shaftHalf, 0], [shaftHalf, 0], [shaftHalf, straightBase], [0.42, straightBase], [0, straightTip]]; const diagonalShaft = (side) => { const start = [0, 0.56]; const end = [side * 0.72, 0.96]; const length = Math.hypot(end[0] - start[0], end[1] - start[1]); - const normal = [-(end[1] - start[1]) / length * shaftHalf, (end[0] - start[0]) / length * shaftHalf]; - return [[start[0] + normal[0], start[1] + normal[1]], [end[0] + normal[0], end[1] + normal[1]], [end[0] - normal[0], end[1] - normal[1]], [start[0] - normal[0], start[1] - normal[1]], [start[0] + normal[0], start[1] + normal[1]]]; + const normal = [(-(end[1] - start[1]) / length) * shaftHalf, ((end[0] - start[0]) / length) * shaftHalf]; + return [ + [start[0] + normal[0], start[1] + normal[1]], + [end[0] + normal[0], end[1] + normal[1]], + [end[0] - normal[0], end[1] - normal[1]], + [start[0] - normal[0], start[1] - normal[1]], + [start[0] + normal[0], start[1] + normal[1]], + ]; }; const diagonalHead = (side) => { - const base = [side * 0.60, 0.89]; + const base = [side * 0.6, 0.89]; const tip = [side * 1.22, 1.24]; const length = Math.hypot(tip[0] - base[0], tip[1] - base[1]); - const normal = [-(tip[1] - base[1]) / length * 0.36, (tip[0] - base[0]) / length * 0.36]; + const normal = [(-(tip[1] - base[1]) / length) * 0.36, ((tip[0] - base[0]) / length) * 0.36]; return [tip, [base[0] + normal[0], base[1] + normal[1]], [base[0] - normal[0], base[1] - normal[1]], tip]; }; const turnStem = (side) => { const cutMidpoint = 0.73; const cutRise = side * 0.084; return [ - [-shaftHalf, 0], [shaftHalf, 0], - [shaftHalf, cutMidpoint + cutRise], [-shaftHalf, cutMidpoint - cutRise], + [-shaftHalf, 0], + [shaftHalf, 0], + [shaftHalf, cutMidpoint + cutRise], + [-shaftHalf, cutMidpoint - cutRise], [-shaftHalf, 0], ]; }; - if (assetId === "through") return [throughHead()]; - if (assetId === "right") return [turnStem(1), diagonalShaft(1), diagonalHead(1)]; - if (assetId === "left") return [turnStem(-1), diagonalShaft(-1), diagonalHead(-1)]; - if (assetId === "through;right") return [throughHead(), diagonalShaft(1), diagonalHead(1)]; - if (assetId === "through;left") return [throughHead(), diagonalShaft(-1), diagonalHead(-1)]; - if (assetId === "through;left;right") return [throughHead(), diagonalShaft(-1), diagonalHead(-1), diagonalShaft(1), diagonalHead(1)]; + if (assetId === 'through') return [throughHead()]; + if (assetId === 'right') return [turnStem(1), diagonalShaft(1), diagonalHead(1)]; + if (assetId === 'left') return [turnStem(-1), diagonalShaft(-1), diagonalHead(-1)]; + if (assetId === 'through;right') return [throughHead(), diagonalShaft(1), diagonalHead(1)]; + if (assetId === 'through;left') return [throughHead(), diagonalShaft(-1), diagonalHead(-1)]; + if (assetId === 'through;left;right') + return [throughHead(), diagonalShaft(-1), diagonalHead(-1), diagonalShaft(1), diagonalHead(1)]; throw new Error(`No angular turn-lane template: ${assetId}`); } function sourceSvgTemplateFor(asset, assetId) { - const source = fs.readFileSync(path.resolve(__dirname, "..", "..", "assets", "lane-icons", asset.source), "utf8"); + const source = fs.readFileSync(path.resolve(__dirname, '..', '..', 'assets', 'lane-icons', asset.source), 'utf8'); const mirrorX = asset.mirror_x === true; const anchorX = Number(asset.anchor_x); if (!Number.isFinite(anchorX)) throw new Error(`turn-lane asset has invalid anchor_x: ${assetId}`); const shapes = []; for (const match of source.matchAll(/]*)\/>|]*)\/>/g)) { const attrs = parseSvgAttrs(match[1] || match[2]); - const strokeWidth = Number(attrs["stroke-width"] || 0); + const strokeWidth = Number(attrs['stroke-width'] || 0); if (match[1]) { - shapes.push(strokePolygon([[Number(attrs.x1), Number(attrs.y1)], [Number(attrs.x2), Number(attrs.y2)]], strokeWidth)); + shapes.push( + strokePolygon( + [ + [Number(attrs.x1), Number(attrs.y1)], + [Number(attrs.x2), Number(attrs.y2)], + ], + strokeWidth, + ), + ); } else { - const points = parseSvgPath(attrs.d || ""); - if (attrs.fill !== "none") shapes.push(points); + const points = parseSvgPath(attrs.d || ''); + if (attrs.fill !== 'none') shapes.push(points); if (strokeWidth > 0) shapes.push(strokePolygon(points, strokeWidth)); } } - return shapes.filter((ring) => ring.length >= 4).map((ring) => ring.map(([x, y]) => [ - (mirrorX ? anchorX - x : x - anchorX) * SVG_METERS_PER_UNIT, - (23 - y) * SVG_METERS_PER_UNIT, - ])); + return shapes + .filter((ring) => ring.length >= 4) + .map((ring) => + ring.map(([x, y]) => [ + (mirrorX ? anchorX - x : x - anchorX) * SVG_METERS_PER_UNIT, + (23 - y) * SVG_METERS_PER_UNIT, + ]), + ); } function parseSvgAttrs(text) { @@ -449,36 +588,69 @@ function parseSvgAttrs(text) { function parseSvgPath(value) { const tokens = value.match(/[a-zA-Z]|[-+]?(?:\d*\.\d+|\d+\.?)(?:[eE][-+]?\d+)?/g) || []; let index = 0; - let command = ""; + let command = ''; let point = [0, 0]; let start = null; const points = []; const number = () => Number(tokens[index++]); - const lineTo = (x, y) => { point = [x, y]; points.push(point); }; + const lineTo = (x, y) => { + point = [x, y]; + points.push(point); + }; while (index < tokens.length) { if (/^[a-zA-Z]$/.test(tokens[index])) command = tokens[index++]; const relative = command === command.toLowerCase(); const op = command.toUpperCase(); - if (op === "Z") { if (start) points.push(start); command = ""; continue; } - if (op === "M" || op === "L") { - const x = number(); const y = number(); - const next = relative ? [point[0] + x, point[1] + y] : [x, y]; - if (op === "M" && !start) { start = next; point = next; points.push(point); command = relative ? "l" : "L"; } else lineTo(...next); + if (op === 'Z') { + if (start) points.push(start); + command = ''; continue; } - if (op === "H") { lineTo(relative ? point[0] + number() : number(), point[1]); continue; } - if (op === "V") { lineTo(point[0], relative ? point[1] + number() : number()); continue; } - if (op === "C") { + if (op === 'M' || op === 'L') { + const x = number(); + const y = number(); + const next = relative ? [point[0] + x, point[1] + y] : [x, y]; + if (op === 'M' && !start) { + start = next; + point = next; + points.push(point); + command = relative ? 'l' : 'L'; + } else lineTo(...next); + continue; + } + if (op === 'H') { + lineTo(relative ? point[0] + number() : number(), point[1]); + continue; + } + if (op === 'V') { + lineTo(point[0], relative ? point[1] + number() : number()); + continue; + } + if (op === 'C') { const values = [number(), number(), number(), number(), number(), number()]; const controls = relative ? values.map((n, i) => n + point[i % 2]) : values; const origin = point; for (let step = 1; step <= 8; step += 1) { - const t = step / 8; const u = 1 - t; - lineTo(u ** 3 * origin[0] + 3 * u ** 2 * t * controls[0] + 3 * u * t ** 2 * controls[2] + t ** 3 * controls[4], u ** 3 * origin[1] + 3 * u ** 2 * t * controls[1] + 3 * u * t ** 2 * controls[3] + t ** 3 * controls[5]); + const t = step / 8; + const u = 1 - t; + lineTo( + u ** 3 * origin[0] + 3 * u ** 2 * t * controls[0] + 3 * u * t ** 2 * controls[2] + t ** 3 * controls[4], + u ** 3 * origin[1] + 3 * u ** 2 * t * controls[1] + 3 * u * t ** 2 * controls[3] + t ** 3 * controls[5], + ); } continue; } - if (op === "A") { number(); number(); number(); number(); number(); const x = number(); const y = number(); lineTo(relative ? point[0] + x : x, relative ? point[1] + y : y); continue; } + if (op === 'A') { + number(); + number(); + number(); + number(); + number(); + const x = number(); + const y = number(); + lineTo(relative ? point[0] + x : x, relative ? point[1] + y : y); + continue; + } throw new Error(`Unsupported SVG path command: ${command}`); } return points; @@ -487,16 +659,27 @@ function parseSvgPath(value) { function strokePolygon(points, width) { if (points.length < 2) return []; const half = width / 2; - const left = []; const right = []; + const left = []; + const right = []; for (let index = 0; index < points.length; index += 1) { const prev = points[Math.max(0, index - 1)]; const next = points[Math.min(points.length - 1, index + 1)]; - const dx = next[0] - prev[0]; const dy = next[1] - prev[1]; const length = Math.hypot(dx, dy) || 1; - const nx = -dy / length * half; const ny = dx / length * half; + const dx = next[0] - prev[0]; + const dy = next[1] - prev[1]; + const length = Math.hypot(dx, dy) || 1; + const nx = (-dy / length) * half; + const ny = (dx / length) * half; left.push([points[index][0] + nx, points[index][1] + ny]); right.unshift([points[index][0] - nx, points[index][1] - ny]); } return [...left, ...right, left[0]]; } -module.exports = { arrowRingsAt, buildCustomTurnLaneArrows, loadManifest, normalizeManeuver, supportedAssets, templateFor }; +module.exports = { + arrowRingsAt, + buildCustomTurnLaneArrows, + loadManifest, + normalizeManeuver, + supportedAssets, + templateFor, +}; diff --git a/src/export/native-road-package.js b/src/export/native-road-package.js index 0a6582b..17aea49 100644 --- a/src/export/native-road-package.js +++ b/src/export/native-road-package.js @@ -1,49 +1,54 @@ -"use strict"; +'use strict'; -const fs = require("fs"); -const path = require("path"); -const { zipSync } = require("fflate"); -const { LAYER_REGISTRY, validatePublishedLayers } = require("../compile/layer-manifest"); +const fs = require('fs'); +const path = require('path'); +const { zipSync } = require('fflate'); +const { LAYER_REGISTRY, validatePublishedLayers } = require('../compile/layer-manifest'); const ROOT_FILES = [ - "manifest.json", - "compiled.json", - "diagnostics.json", - "comparison.json", - "traffic-signal-assemblies.json", - "traffic-signals.json", + 'manifest.json', + 'compiled.json', + 'diagnostics.json', + 'comparison.json', + 'traffic-signal-assemblies.json', + 'traffic-signals.json', ]; -const GENERATOR = Object.freeze({ name: "road-compiler", version: "0.3.0" }); +const GENERATOR = Object.freeze({ name: 'road-compiler', version: '0.3.0' }); function packageEntries(directory) { - const manifestPath = path.join(directory, "manifest.json"); + const manifestPath = path.join(directory, 'manifest.json'); if (!fs.existsSync(manifestPath)) throw new Error(`Native road package manifest is missing: ${manifestPath}`); - const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8")); - if (manifest.contract !== "native-road-package/v1.1") throw new Error("Native road package requires native-road-package/v1.1."); - if (!manifest.areaId || typeof manifest.areaId !== "string") throw new Error("Native road package manifest areaId is required."); + const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')); + if (manifest.contract !== 'native-road-package/v1.1') + throw new Error('Native road package requires native-road-package/v1.1.'); + if (!manifest.areaId || typeof manifest.areaId !== 'string') + throw new Error('Native road package manifest areaId is required.'); validatePublishedLayers(directory, manifest); const files = []; for (const relative of ROOT_FILES) files.push(relative); for (const layer of LAYER_REGISTRY) files.push(`layers/${layer.source}.geojson`); for (const relative of files) { const file = path.join(directory, relative); - if (!fs.existsSync(file) || !fs.statSync(file).isFile()) throw new Error(`Native road package file is missing: ${relative}`); + if (!fs.existsSync(file) || !fs.statSync(file).isFile()) + throw new Error(`Native road package file is missing: ${relative}`); } return { manifest, files: [...new Set(files)].sort() }; } function exportNativeRoadPackage(directory, destination = null) { const { manifest, files } = packageEntries(directory); - const entries = Object.fromEntries(files.map((relative) => [relative, fs.readFileSync(path.join(directory, relative))])); + const entries = Object.fromEntries( + files.map((relative) => [relative, fs.readFileSync(path.join(directory, relative))]), + ); // The model/movements remain needed by host preview generation, but absolute // compiler-workspace paths are not part of the exported package contract. - const compiled = JSON.parse(entries["compiled.json"].toString("utf8")); + const compiled = JSON.parse(entries['compiled.json'].toString('utf8')); delete compiled.source; - entries["compiled.json"] = Buffer.from(`${JSON.stringify(compiled, null, 2)}\n`); - entries["manifest.json"] = Buffer.from(`${JSON.stringify({ ...manifest, generator: GENERATOR }, null, 2)}\n`); + entries['compiled.json'] = Buffer.from(`${JSON.stringify(compiled, null, 2)}\n`); + entries['manifest.json'] = Buffer.from(`${JSON.stringify({ ...manifest, generator: GENERATOR }, null, 2)}\n`); // ZIP timestamps start at 1980; a fixed value keeps repeated exports byte-stable. - const bytes = zipSync(entries, { level: 6, mtime: new Date("1980-01-01T00:00:00Z") }); + const bytes = zipSync(entries, { level: 6, mtime: new Date('1980-01-01T00:00:00Z') }); if (destination) { fs.mkdirSync(path.dirname(destination), { recursive: true }); const temporary = `${destination}.tmp-${process.pid}`; diff --git a/src/geometry/lane-geometry.js b/src/geometry/lane-geometry.js index 20d36bc..4e72dd8 100644 --- a/src/geometry/lane-geometry.js +++ b/src/geometry/lane-geometry.js @@ -1,18 +1,20 @@ -"use strict"; +'use strict'; const EARTH_RADIUS_METERS = 6371008.8; function laneCenterline(lane) { - const ring = lane?.geometry?.type === "Polygon" ? lane.geometry.coordinates?.[0] : null; + const ring = lane?.geometry?.type === 'Polygon' ? lane.geometry.coordinates?.[0] : null; if (!Array.isArray(ring) || ring.length < 5 || !sameCoordinate(ring[0], ring.at(-1))) return null; const vertices = ring.slice(0, -1); if (!vertices.every(validCoordinate)) return null; const half = vertices.length / 2; if (!Number.isInteger(half) || half < 2) return null; - const centerline = vertices.slice(0, half).map((point, index) => [ - (point[0] + vertices[vertices.length - 1 - index][0]) / 2, - (point[1] + vertices[vertices.length - 1 - index][1]) / 2, - ]); + const centerline = vertices + .slice(0, half) + .map((point, index) => [ + (point[0] + vertices[vertices.length - 1 - index][0]) / 2, + (point[1] + vertices[vertices.length - 1 - index][1]) / 2, + ]); return polylineLength(centerline) > 0.01 ? centerline : null; } @@ -82,7 +84,7 @@ function lateralOffsetFrom(polyline, point) { const offsetY = py - dy * ratio; const distance = Math.hypot(offsetX, offsetY); if (!best || distance < best.distance) { - best = { distance, lateral: offsetX * dy / length - offsetY * dx / length }; + best = { distance, lateral: (offsetX * dy) / length - (offsetY * dx) / length }; } } return best; @@ -145,7 +147,7 @@ function metersAt(latitude) { } function degreesToRadians(value) { - return value * Math.PI / 180; + return (value * Math.PI) / 180; } module.exports = { diff --git a/src/index.js b/src/index.js index 3f66f11..70df7f2 100644 --- a/src/index.js +++ b/src/index.js @@ -1,16 +1,16 @@ -"use strict"; +'use strict'; module.exports = { - laneGeometry: require("./geometry/lane-geometry"), - gaodeReference: require("./reference/gaode"), - turnLaneArrows: require("./compile/turn-lane-arrows"), - complexJunction: require("./compile/complex-junction"), - osm: require("./osm"), - trafficSignals: require("./traffic-signals"), - nativeTrafficSignals: require("./native-traffic-signals"), - nativeRoad: require("./compile/native-road"), - layerManifest: require("./compile/layer-manifest"), - nativeRoadPackage: require("./export/native-road-package"), - compiler: require("./compile/compiler"), - check: require("./check"), + laneGeometry: require('./geometry/lane-geometry'), + gaodeReference: require('./reference/gaode'), + turnLaneArrows: require('./compile/turn-lane-arrows'), + complexJunction: require('./compile/complex-junction'), + osm: require('./osm'), + trafficSignals: require('./traffic-signals'), + nativeTrafficSignals: require('./native-traffic-signals'), + nativeRoad: require('./compile/native-road'), + layerManifest: require('./compile/layer-manifest'), + nativeRoadPackage: require('./export/native-road-package'), + compiler: require('./compile/compiler'), + check: require('./check'), }; diff --git a/src/native-traffic-signals.js b/src/native-traffic-signals.js index 58069f5..8cc5872 100644 --- a/src/native-traffic-signals.js +++ b/src/native-traffic-signals.js @@ -1,25 +1,25 @@ -"use strict"; +'use strict'; -const fs = require("fs"); -const { parseOsm } = require("./osm"); +const fs = require('fs'); +const { parseOsm } = require('./osm'); const { buildTrafficSignalFeatures, buildTrafficSignalsFromFeatures, validateTrafficSignalFeatures, validateTrafficSignalSourceReferences, -} = require("./traffic-signals"); +} = require('./traffic-signals'); -const SCHEMA = "native-traffic-signals/v1"; +const SCHEMA = 'native-traffic-signals/v1'; function loadOrGenerate(file, osmText, stopLines, intersections) { if (fs.existsSync(file)) { - const document = JSON.parse(fs.readFileSync(file, "utf8")); + const document = JSON.parse(fs.readFileSync(file, 'utf8')); try { return validateDocument(document, osmText); } catch (error) { // OSM edits can invalidate the stable identities in a document that was // itself generated from OSM. User-authored documents must remain strict. - if (document?.provenance === "generated:osm-controls" && isStaleSourceReferenceError(error)) { + if (document?.provenance === 'generated:osm-controls' && isStaleSourceReferenceError(error)) { return generate(osmText, stopLines, intersections); } throw error; @@ -29,21 +29,32 @@ function loadOrGenerate(file, osmText, stopLines, intersections) { } function isStaleSourceReferenceError(error) { - return error instanceof Error && /^traffic signal feature \d+: (approach_id .* is not present on OSM control|control_id .* is not present in the current OSM)/.test(error.message); + return ( + error instanceof Error && + /^traffic signal feature \d+: (approach_id .* is not present on OSM control|control_id .* is not present in the current OSM)/.test( + error.message, + ) + ); } function generate(osmText, stopLines, intersections) { const controls = parseOsm(osmText).trafficSignalControls; - return { schema: SCHEMA, provenance: "generated:osm-controls", assemblies: buildTrafficSignalFeatures(stopLines, intersections, controls) }; + return { + schema: SCHEMA, + provenance: 'generated:osm-controls', + assemblies: buildTrafficSignalFeatures(stopLines, intersections, controls), + }; } function validateDocument(value, osmText) { if (value?.schema !== SCHEMA) throw new Error(`Expected ${SCHEMA} signal document`); const assemblies = validateTrafficSignalFeatures(value.assemblies); if (osmText) validateTrafficSignalSourceReferences(assemblies, parseOsm(osmText).trafficSignalControls); - return { schema: SCHEMA, provenance: value.provenance || "native", assemblies }; + return { schema: SCHEMA, provenance: value.provenance || 'native', assemblies }; } -function runtime(document) { return buildTrafficSignalsFromFeatures(document.assemblies); } +function runtime(document) { + return buildTrafficSignalsFromFeatures(document.assemblies); +} module.exports = { SCHEMA, generate, loadOrGenerate, validateDocument, runtime }; diff --git a/src/osm.js b/src/osm.js index 03e8f75..b5fa4e7 100644 --- a/src/osm.js +++ b/src/osm.js @@ -1,11 +1,13 @@ -"use strict"; +'use strict'; function parseOsm(xml) { const boundsMatch = xml.match(/]*)\/?\s*>/); const boundsAttrs = boundsMatch ? xmlAttrs(boundsMatch[1]) : {}; const candidateBounds = { - minLon: Number(boundsAttrs.minlon), minLat: Number(boundsAttrs.minlat), - maxLon: Number(boundsAttrs.maxlon), maxLat: Number(boundsAttrs.maxlat), + minLon: Number(boundsAttrs.minlon), + minLat: Number(boundsAttrs.minlat), + maxLon: Number(boundsAttrs.maxlon), + maxLat: Number(boundsAttrs.maxlat), }; const bounds = Object.values(candidateBounds).every(Number.isFinite) ? candidateBounds : null; const nodes = new Map(); @@ -13,19 +15,19 @@ function parseOsm(xml) { const nodePattern = /]*?)(?:\/>|>([\s\S]*?)<\/node>)/g; for (const match of xml.matchAll(nodePattern)) { const attrs = xmlAttrs(match[1]); - if (attrs.action === "delete" || !attrs.id || attrs.lon === undefined || attrs.lat === undefined) continue; + if (attrs.action === 'delete' || !attrs.id || attrs.lon === undefined || attrs.lat === undefined) continue; const coordinate = [Number(attrs.lon), Number(attrs.lat)]; if (!coordinate.every(Number.isFinite)) continue; nodes.set(attrs.id, coordinate); - const tags = parseTags(match[2] || ""); - if (tags.highway === "traffic_signals") { + const tags = parseTags(match[2] || ''); + if (tags.highway === 'traffic_signals') { trafficSignalControls.push({ id: attrs.id, longitude: coordinate[0], latitude: coordinate[1], tags }); } } const ways = []; for (const match of xml.matchAll(/]*)>([\s\S]*?)<\/way>/g)) { const attrs = xmlAttrs(match[1]); - if (attrs.action === "delete") continue; + if (attrs.action === 'delete') continue; const body = match[2]; const refs = []; for (const ndMatch of body.matchAll(/]*)\/?\s*>/g)) { @@ -53,22 +55,36 @@ function parseOsm(xml) { } } control.arms = dedupeHeadings(arms); - control.junctionType = control.arms.length === 3 ? "T" : control.arms.length === 4 ? "cross" : "other"; + control.junctionType = control.arms.length === 3 ? 'T' : control.arms.length === 4 ? 'cross' : 'other'; } return { bounds, nodes, ways, trafficSignalControls }; } function isMotorRoad(tags) { - const highway = tags.highway || ""; - return highway && tags.area !== "yes" && !new Set([ - "footway", "path", "pedestrian", "steps", "cycleway", "service", "track", - "bridleway", "corridor", "elevator", "platform", "construction", - ]).has(highway); + const highway = tags.highway || ''; + return ( + highway && + tags.area !== 'yes' && + !new Set([ + 'footway', + 'path', + 'pedestrian', + 'steps', + 'cycleway', + 'service', + 'track', + 'bridleway', + 'corridor', + 'elevator', + 'platform', + 'construction', + ]).has(highway) + ); } function headingBetween(from, to) { - const latitude = (from.latitude + to[1]) / 2 * Math.PI / 180; - return Math.atan2((to[0] - from.longitude) * Math.cos(latitude), to[1] - from.latitude) * 180 / Math.PI; + const latitude = (((from.latitude + to[1]) / 2) * Math.PI) / 180; + return (Math.atan2((to[0] - from.longitude) * Math.cos(latitude), to[1] - from.latitude) * 180) / Math.PI; } function dedupeHeadings(arms) { @@ -94,7 +110,7 @@ function parseTags(body) { const tags = {}; for (const match of body.matchAll(/]*)\/?\s*>/g)) { const tag = xmlAttrs(match[1]); - if (tag.k) tags[tag.k] = tag.v || ""; + if (tag.k) tags[tag.k] = tag.v || ''; } return tags; } diff --git a/src/reference/gaode.js b/src/reference/gaode.js index 7d8eac1..a131253 100644 --- a/src/reference/gaode.js +++ b/src/reference/gaode.js @@ -1,6 +1,6 @@ -"use strict"; +'use strict'; -const fs = require("fs"); +const fs = require('fs'); const PI = Math.PI; const EARTH_A = 6378245.0; @@ -8,17 +8,17 @@ const EARTH_EE = 0.00669342162296594323; function transformLat(x, y) { let value = -100 + 2 * x + 3 * y + 0.2 * y * y + 0.1 * x * y + 0.2 * Math.sqrt(Math.abs(x)); - value += (20 * Math.sin(6 * x * PI) + 20 * Math.sin(2 * x * PI)) * 2 / 3; - value += (20 * Math.sin(y * PI) + 40 * Math.sin(y / 3 * PI)) * 2 / 3; - value += (160 * Math.sin(y / 12 * PI) + 320 * Math.sin(y * PI / 30)) * 2 / 3; + value += ((20 * Math.sin(6 * x * PI) + 20 * Math.sin(2 * x * PI)) * 2) / 3; + value += ((20 * Math.sin(y * PI) + 40 * Math.sin((y / 3) * PI)) * 2) / 3; + value += ((160 * Math.sin((y / 12) * PI) + 320 * Math.sin((y * PI) / 30)) * 2) / 3; return value; } function transformLon(x, y) { let value = 300 + x + 2 * y + 0.1 * x * x + 0.1 * x * y + 0.1 * Math.sqrt(Math.abs(x)); - value += (20 * Math.sin(6 * x * PI) + 20 * Math.sin(2 * x * PI)) * 2 / 3; - value += (20 * Math.sin(x * PI) + 40 * Math.sin(x / 3 * PI)) * 2 / 3; - value += (150 * Math.sin(x / 12 * PI) + 300 * Math.sin(x / 30 * PI)) * 2 / 3; + value += ((20 * Math.sin(6 * x * PI) + 20 * Math.sin(2 * x * PI)) * 2) / 3; + value += ((20 * Math.sin(x * PI) + 40 * Math.sin((x / 3) * PI)) * 2) / 3; + value += ((150 * Math.sin((x / 12) * PI) + 300 * Math.sin((x / 30) * PI)) * 2) / 3; return value; } @@ -27,33 +27,37 @@ function transformLon(x, y) { // source coordinates remain WGS84. function gcj02ToWgs84(coordinate) { const [longitude, latitude] = coordinate; - if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) throw new Error("Reference coordinate must be finite"); + if (!Number.isFinite(longitude) || !Number.isFinite(latitude)) throw new Error('Reference coordinate must be finite'); const dLat = transformLat(longitude - 105, latitude - 35); const dLon = transformLon(longitude - 105, latitude - 35); - const radLat = latitude / 180 * PI; + const radLat = (latitude / 180) * PI; const magic = 1 - EARTH_EE * Math.sin(radLat) ** 2; const sqrtMagic = Math.sqrt(magic); return [ - longitude - dLon * 180 / (EARTH_A / sqrtMagic * Math.cos(radLat) * PI), - latitude - dLat * 180 / (EARTH_A * (1 - EARTH_EE) / (magic * sqrtMagic) * PI), + longitude - (dLon * 180) / ((EARTH_A / sqrtMagic) * Math.cos(radLat) * PI), + latitude - (dLat * 180) / (((EARTH_A * (1 - EARTH_EE)) / (magic * sqrtMagic)) * PI), ]; } function mapCoordinates(coordinates, mapper) { - if (typeof coordinates[0] === "number") return mapper(coordinates); + if (typeof coordinates[0] === 'number') return mapper(coordinates); return coordinates.map((value) => mapCoordinates(value, mapper)); } function convertGeoJson(document) { - if (!document || document.type !== "FeatureCollection" || !Array.isArray(document.features)) { - throw new Error("Reference must be a GeoJSON FeatureCollection"); + if (!document || document.type !== 'FeatureCollection' || !Array.isArray(document.features)) { + throw new Error('Reference must be a GeoJSON FeatureCollection'); } return { ...document, crs: undefined, features: document.features.map((feature) => { - if (!feature || !feature.geometry || !feature.geometry.coordinates) throw new Error("Reference feature is missing geometry"); - return { ...feature, geometry: { ...feature.geometry, coordinates: mapCoordinates(feature.geometry.coordinates, gcj02ToWgs84) } }; + if (!feature || !feature.geometry || !feature.geometry.coordinates) + throw new Error('Reference feature is missing geometry'); + return { + ...feature, + geometry: { ...feature.geometry, coordinates: mapCoordinates(feature.geometry.coordinates, gcj02ToWgs84) }, + }; }), }; } @@ -66,7 +70,7 @@ function coordinatesOf(document) { function walkCoordinates(value, points) { if (!Array.isArray(value) || !value.length) return; - if (typeof value[0] === "number") { + if (typeof value[0] === 'number') { points.push(value); return; } @@ -75,7 +79,7 @@ function walkCoordinates(value, points) { function boundsOf(document) { const points = coordinatesOf(document); - if (!points.length) throw new Error("Reference contains no coordinates"); + if (!points.length) throw new Error('Reference contains no coordinates'); return { minLon: Math.min(...points.map((point) => point[0])), minLat: Math.min(...points.map((point) => point[1])), @@ -89,7 +93,7 @@ function centerOf(bounds) { } function distanceMeters(first, second) { - const lonScale = 111320 * Math.cos(first[1] * PI / 180); + const lonScale = 111320 * Math.cos((first[1] * PI) / 180); return Math.hypot((second[0] - first[0]) * lonScale, (second[1] - first[1]) * 111320); } @@ -97,13 +101,15 @@ function parseOsmNodes(xml) { const nodes = []; for (const match of xml.matchAll(/]*?)(?:\/>|>([\s\S]*?)<\/node>)/g)) { const attrs = {}; - for (const item of match[1].matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) attrs[item[1]] = item[2] ?? item[3]; + for (const item of match[1].matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) + attrs[item[1]] = item[2] ?? item[3]; if (!attrs.id || !Number.isFinite(Number(attrs.lon)) || !Number.isFinite(Number(attrs.lat))) continue; const tags = {}; - for (const item of (match[2] || "").matchAll(/]*)\/?\s*>/g)) { + for (const item of (match[2] || '').matchAll(/]*)\/?\s*>/g)) { const tag = {}; - for (const attr of item[1].matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) tag[attr[1]] = attr[2] ?? attr[3]; - if (tag.k) tags[tag.k] = tag.v || ""; + for (const attr of item[1].matchAll(/([:\w-]+)\s*=\s*(?:"([^"]*)"|'([^']*)')/g)) + tag[attr[1]] = attr[2] ?? attr[3]; + if (tag.k) tags[tag.k] = tag.v || ''; } nodes.push({ id: String(attrs.id), coordinate: [Number(attrs.lon), Number(attrs.lat)], tags }); } @@ -114,12 +120,12 @@ function nearestNode(nodes, coordinate, nodeId) { if (nodeId) { const exact = nodes.find((node) => node.id === String(nodeId)); if (!exact) throw new Error(`OSM node not found: ${nodeId}`); - return { ...exact, distanceMeters: distanceMeters(exact.coordinate, coordinate), match: "node-id" }; + return { ...exact, distanceMeters: distanceMeters(exact.coordinate, coordinate), match: 'node-id' }; } const candidates = nodes.map((node) => ({ ...node, distanceMeters: distanceMeters(node.coordinate, coordinate) })); candidates.sort((first, second) => first.distanceMeters - second.distanceMeters); - if (!candidates[0]) throw new Error("OSM contains no usable nodes"); - return { ...candidates[0], match: "nearest-node" }; + if (!candidates[0]) throw new Error('OSM contains no usable nodes'); + return { ...candidates[0], match: 'nearest-node' }; } function bboxIntersectionRatio(first, second) { @@ -136,47 +142,73 @@ function bboxIntersectionRatio(first, second) { // Match it by cluster id, or by whichever cluster core sits nearest the node. function complexClusterSurface(nativeRoadSurfaceFile, node, clusterId) { if (!nativeRoadSurfaceFile || !fs.existsSync(nativeRoadSurfaceFile)) return null; - const surface = JSON.parse(fs.readFileSync(nativeRoadSurfaceFile, "utf8")); + const surface = JSON.parse(fs.readFileSync(nativeRoadSurfaceFile, 'utf8')); const parts = (surface.features || []).filter((item) => item.properties?.cluster_id && item.properties?.complex_part); - const cores = parts.filter((item) => item.properties.complex_part === "core" && Array.isArray(item.properties.center)); + const cores = parts.filter( + (item) => item.properties.complex_part === 'core' && Array.isArray(item.properties.center), + ); if (!cores.length) return null; const core = clusterId ? cores.find((item) => String(item.properties.cluster_id) === String(clusterId)) - : [...cores].sort((first, second) => distanceMeters(first.properties.center, node.coordinate) - distanceMeters(second.properties.center, node.coordinate))[0]; + : [...cores].sort( + (first, second) => + distanceMeters(first.properties.center, node.coordinate) - + distanceMeters(second.properties.center, node.coordinate), + )[0]; if (!core) return null; const features = parts.filter((item) => item.properties.cluster_id === core.properties.cluster_id); return { clusterId: core.properties.cluster_id, core, features }; } -function inspectReference({ referenceFile, osmFile, nativeIntersectionFile, nativeRoadSurfaceFile, nodeId, clusterId }) { - const source = JSON.parse(fs.readFileSync(referenceFile, "utf8")); +function inspectReference({ + referenceFile, + osmFile, + nativeIntersectionFile, + nativeRoadSurfaceFile, + nodeId, + clusterId, +}) { + const source = JSON.parse(fs.readFileSync(referenceFile, 'utf8')); const converted = convertGeoJson(source); const referenceBounds = boundsOf(converted); const referenceCenter = centerOf(referenceBounds); - const nodes = parseOsmNodes(fs.readFileSync(osmFile, "utf8")); + const nodes = parseOsmNodes(fs.readFileSync(osmFile, 'utf8')); const matchedNode = nearestNode(nodes, referenceCenter, nodeId); - const native = JSON.parse(fs.readFileSync(nativeIntersectionFile, "utf8")); + const native = JSON.parse(fs.readFileSync(nativeIntersectionFile, 'utf8')); const feature = (native.features || []).find((item) => item.properties?.osm_node_id === matchedNode.id); const cluster = feature ? null : complexClusterSurface(nativeRoadSurfaceFile, matchedNode, clusterId); const matchedFeatures = feature ? [feature] : cluster?.features || null; const nativeBounds = matchedFeatures ? boundsOf({ features: matchedFeatures }) : null; const diagnostics = []; - if (!matchedFeatures) diagnostics.push(nativeRoadSurfaceFile ? "No native intersection surface or complex cluster matched the OSM node" : "No native intersection surface matched the OSM node; pass --native-road-surface to also search complex junction clusters"); + if (!matchedFeatures) + diagnostics.push( + nativeRoadSurfaceFile + ? 'No native intersection surface or complex cluster matched the OSM node' + : 'No native intersection surface matched the OSM node; pass --native-road-surface to also search complex junction clusters', + ); return { - schema: "gaode-junction-reference-comparison/v2", - source: { file: referenceFile, coordinateSystem: "GCJ-02", featureCount: converted.features.length }, - conversion: { target: "WGS84", method: "gcj02-inverse-approximation" }, + schema: 'gaode-junction-reference-comparison/v2', + source: { file: referenceFile, coordinateSystem: 'GCJ-02', featureCount: converted.features.length }, + conversion: { target: 'WGS84', method: 'gcj02-inverse-approximation' }, reference: { bounds: referenceBounds, center: referenceCenter }, - matchedOsmNode: { id: matchedNode.id, coordinate: matchedNode.coordinate, tags: matchedNode.tags, match: matchedNode.match, centerDistanceMeters: matchedNode.distanceMeters }, - nativeIntersection: nativeBounds ? { - kind: feature ? "junction-node" : "complex-cluster", - clusterId: cluster?.clusterId || null, - featureCount: matchedFeatures.length, - bounds: nativeBounds, - bboxIoU: bboxIntersectionRatio(referenceBounds, nativeBounds), - centerOffsetMeters: distanceMeters(referenceCenter, centerOf(nativeBounds)), - featureProperties: feature ? feature.properties : cluster.core.properties, - } : null, + matchedOsmNode: { + id: matchedNode.id, + coordinate: matchedNode.coordinate, + tags: matchedNode.tags, + match: matchedNode.match, + centerDistanceMeters: matchedNode.distanceMeters, + }, + nativeIntersection: nativeBounds + ? { + kind: feature ? 'junction-node' : 'complex-cluster', + clusterId: cluster?.clusterId || null, + featureCount: matchedFeatures.length, + bounds: nativeBounds, + bboxIoU: bboxIntersectionRatio(referenceBounds, nativeBounds), + centerOffsetMeters: distanceMeters(referenceCenter, centerOf(nativeBounds)), + featureProperties: feature ? feature.properties : cluster.core.properties, + } + : null, diagnostics, converted, matchedFeatures, @@ -186,10 +218,10 @@ function inspectReference({ referenceFile, osmFile, nativeIntersectionFile, nati function localReferenceSvg({ converted, nativeIntersection, center, radiusMeters = 180 }) { const width = 1000; const height = 1000; - const lonScale = 111320 * Math.cos(center[1] * PI / 180); + const lonScale = 111320 * Math.cos((center[1] * PI) / 180); const project = (point) => [ - width / 2 + (point[0] - center[0]) * lonScale * width / (radiusMeters * 2), - height / 2 - (point[1] - center[1]) * 111320 * height / (radiusMeters * 2), + width / 2 + ((point[0] - center[0]) * lonScale * width) / (radiusMeters * 2), + height / 2 - ((point[1] - center[1]) * 111320 * height) / (radiusMeters * 2), ]; const pathFor = (coordinates) => { const parts = []; @@ -201,23 +233,30 @@ function localReferenceSvg({ converted, nativeIntersection, center, radiusMeters const [x, y] = project(point); parts.push(`L ${x.toFixed(1)} ${y.toFixed(1)}`); } - if (close) parts.push("Z"); + if (close) parts.push('Z'); }; const visit = (value) => { if (!Array.isArray(value) || !value.length) return; - if (typeof value[0] === "number") return; - if (typeof value[0][0] === "number") appendLine(value, value.length > 2); + if (typeof value[0] === 'number') return; + if (typeof value[0][0] === 'number') appendLine(value, value.length > 2); else value.forEach(visit); }; visit(coordinates); - return parts.join(" "); + return parts.join(' '); }; - const color = { 1: "#2563eb", 2: "#0f766e", 3: "#7c3aed", 4: "#ea580c", 5: "#64748b" }; - const references = converted.features.map((feature) => { - const type = feature.properties?.type || "unknown"; - return ``; - }).join("\n"); - const nativePaths = (nativeIntersection?.features || []).map((feature) => ``).join("\n"); + const color = { 1: '#2563eb', 2: '#0f766e', 3: '#7c3aed', 4: '#ea580c', 5: '#64748b' }; + const references = converted.features + .map((feature) => { + const type = feature.properties?.type || 'unknown'; + return ``; + }) + .join('\n'); + const nativePaths = (nativeIntersection?.features || []) + .map( + (feature) => + ``, + ) + .join('\n'); return ` @@ -228,4 +267,12 @@ function localReferenceSvg({ converted, nativeIntersection, center, radiusMeters `; } -module.exports = { gcj02ToWgs84, convertGeoJson, boundsOf, parseOsmNodes, nearestNode, inspectReference, localReferenceSvg }; +module.exports = { + gcj02ToWgs84, + convertGeoJson, + boundsOf, + parseOsmNodes, + nearestNode, + inspectReference, + localReferenceSvg, +}; diff --git a/src/traffic-signals.js b/src/traffic-signals.js index e3f8d11..aa4691e 100644 --- a/src/traffic-signals.js +++ b/src/traffic-signals.js @@ -1,28 +1,40 @@ -"use strict"; +'use strict'; -const fs = require("fs"); -const crypto = require("crypto"); -const { parseOsm } = require("./osm"); +const fs = require('fs'); +const crypto = require('crypto'); +const { parseOsm } = require('./osm'); const EARTH_RADIUS = 6371008.8; const CURB_OFFSET_METERS = 5.2; const MAST_REACH_METERS = 4.5; const SIGNAL_LAYOUT = Object.freeze({ - poleHeightMeters: 6.7, poleRadiusMeters: 0.13, armWidthMeters: 0.21, - mastHeightMeters: 6.25, headCenterHeightMeters: 6.25, - headWidthMeters: 0.68, headDepthMeters: 0.30, headBodyHeightMeters: 1.62, - lensRadiusMeters: 0.22, lensDepthMeters: 0.07, lensFaceOffsetMeters: 0.18, + poleHeightMeters: 6.7, + poleRadiusMeters: 0.13, + armWidthMeters: 0.21, + mastHeightMeters: 6.25, + headCenterHeightMeters: 6.25, + headWidthMeters: 0.68, + headDepthMeters: 0.3, + headBodyHeightMeters: 1.62, + lensRadiusMeters: 0.22, + lensDepthMeters: 0.07, + lensFaceOffsetMeters: 0.18, lensVerticalOffsetsMeters: [0.49, -0.01, -0.51], - countdownLateralMeters: 1.15, countdownFaceOffsetMeters: 0.05, - countdownWidthMeters: 0.82, countdownDepthMeters: 0.14, - countdownHeightMeters: 0.56, countdownVerticalOffsetMeters: 0.0, + countdownLateralMeters: 1.15, + countdownFaceOffsetMeters: 0.05, + countdownWidthMeters: 0.82, + countdownDepthMeters: 0.14, + countdownHeightMeters: 0.56, + countdownVerticalOffsetMeters: 0.0, }); function buildTrafficSignalFeatures(stopLines, intersections, controls = []) { - const centers = (intersections.features || []).map((feature, index) => { - const point = polygonCenter(feature.geometry); - return { id: `intersection-${index + 1}`, point, radius: polygonRadius(feature.geometry, point) }; - }).filter((entry) => entry.point); + const centers = (intersections.features || []) + .map((feature, index) => { + const point = polygonCenter(feature.geometry); + return { id: `intersection-${index + 1}`, point, radius: polygonRadius(feature.geometry, point) }; + }) + .filter((entry) => entry.point); const clusteredStops = new Map(); for (const feature of stopLines.features || []) { const clusterId = feature.properties?.cluster_id; @@ -33,27 +45,39 @@ function buildTrafficSignalFeatures(stopLines, intersections, controls = []) { } for (const [clusterId, points] of clusteredStops) { if (points.length < 3) continue; - const point = points.reduce((sum, item) => [sum[0] + item[0] / points.length, sum[1] + item[1] / points.length], [0, 0]); - centers.push({ id: `cluster-${clusterId}`, clusterId, point, radius: Math.max(...points.map((item) => metersBetween(point, item))) }); + const point = points.reduce( + (sum, item) => [sum[0] + item[0] / points.length, sum[1] + item[1] / points.length], + [0, 0], + ); + centers.push({ + id: `cluster-${clusterId}`, + clusterId, + point, + radius: Math.max(...points.map((item) => metersBetween(point, item))), + }); } const candidates = []; for (const feature of stopLines.features || []) { const center = polygonCenter(feature.geometry); if (!center) continue; const clusterId = feature.properties?.cluster_id; - const intersection = clusterId ? centers.find((entry) => entry.clusterId === clusterId) : nearestCenter(center, centers); + const intersection = clusterId + ? centers.find((entry) => entry.clusterId === clusterId) + : nearestCenter(center, centers); if (!intersection || metersBetween(center, intersection.point) > 32) continue; const axis = roadAxis(feature.geometry, center, intersection.point); if (!axis) continue; const right = [axis[1], -axis[0]]; candidates.push({ - intersectionId: intersection.id, center, axis, + intersectionId: intersection.id, + center, + axis, point: intersection.clusterId ? moveMeters(center, right, CURB_OFFSET_METERS) : moveMeters(moveMeters(intersection.point, axis, intersection.radius + 3.2), right, CURB_OFFSET_METERS), - headingDegrees: normalizeDegrees(Math.atan2(axis[0], axis[1]) * 180 / Math.PI), + headingDegrees: normalizeDegrees((Math.atan2(axis[0], axis[1]) * 180) / Math.PI), matchHeadingDegrees: intersection.clusterId - ? normalizeDegrees(Math.atan2(-axis[0], -axis[1]) * 180 / Math.PI) + ? normalizeDegrees((Math.atan2(-axis[0], -axis[1]) * 180) / Math.PI) : null, }); } @@ -63,158 +87,212 @@ function buildTrafficSignalFeatures(stopLines, intersections, controls = []) { if (!controlPoint.every(Number.isFinite) || !Array.isArray(control.arms) || control.arms.length < 3) continue; const intersection = nearestCenter(controlPoint, centers); if (!intersection || metersBetween(controlPoint, intersection.point) > 32) continue; - const arms = matchOsmArms(candidates.filter((item) => item.intersectionId === intersection.id), controlPoint, control.arms); + const arms = matchOsmArms( + candidates.filter((item) => item.intersectionId === intersection.id), + controlPoint, + control.arms, + ); const groups = phaseGroups(arms); arms.forEach((candidate, index) => { const fallbackArmId = `heading-${Math.round(normalizeDegrees(candidate.osmArm?.headingDegrees || 0) * 1000)}`; - const sourceWayId = String(candidate.osmArm?.wayId || "legacy"); + const sourceWayId = String(candidate.osmArm?.wayId || 'legacy'); const neighborNodeId = String(candidate.osmArm?.neighborNodeId || fallbackArmId); const approachId = `${sourceWayId}:${neighborNodeId}`; const signalUid = `osm-${String(control.id)}-${sourceWayId}-${neighborNodeId}`; features.push({ - type: "Feature", - geometry: { type: "Point", coordinates: candidate.point.slice() }, + type: 'Feature', + geometry: { type: 'Point', coordinates: candidate.point.slice() }, properties: { - signal_uid: signalUid, display_id: signalUid, control_id: String(control.id), - approach_id: approachId, source_way_id: sourceWayId, + signal_uid: signalUid, + display_id: signalUid, + control_id: String(control.id), + approach_id: approachId, + source_way_id: sourceWayId, // These are independent assembly controls. heading_deg remains a // migration hint for older native documents only. mast_heading_deg: normalizeDegrees(candidate.headingDegrees - 90), face_heading_deg: normalizeDegrees(candidate.headingDegrees + 180), phase_group: groups[index], mast_reach_m: MAST_REACH_METERS, - stop_lon: candidate.center[0], stop_lat: candidate.center[1], - enabled: true, z_offset_m: 0, + stop_lon: candidate.center[0], + stop_lat: candidate.center[1], + enabled: true, + z_offset_m: 0, }, }); }); } - return validateTrafficSignalFeatures({ type: "FeatureCollection", features }); + return validateTrafficSignalFeatures({ type: 'FeatureCollection', features }); } function validateTrafficSignalFeatures(collection) { - if (collection?.type !== "FeatureCollection" || !Array.isArray(collection.features)) { - throw new Error("Traffic signal assemblies must be a FeatureCollection"); + if (collection?.type !== 'FeatureCollection' || !Array.isArray(collection.features)) { + throw new Error('Traffic signal assemblies must be a FeatureCollection'); } const uids = new Set(); const displayIds = new Set(); const features = collection.features.map((feature, index) => { const label = `traffic signal feature ${index + 1}`; - if (feature?.geometry?.type !== "Point" || !Array.isArray(feature.geometry.coordinates) || - feature.geometry.coordinates.length < 2 || !feature.geometry.coordinates.slice(0, 2).every(Number.isFinite)) { + if ( + feature?.geometry?.type !== 'Point' || + !Array.isArray(feature.geometry.coordinates) || + feature.geometry.coordinates.length < 2 || + !feature.geometry.coordinates.slice(0, 2).every(Number.isFinite) + ) { throw new Error(`${label}: geometry must be a finite Point`); } const input = feature.properties || {}; const text = (key, required = true) => { - const value = input[key] == null ? "" : String(input[key]).trim(); + const value = input[key] == null ? '' : String(input[key]).trim(); if (required && !value) throw new Error(`${label}: missing ${key}`); return value; }; const number = (key, options = {}) => { - if (input[key] === null || input[key] === undefined || input[key] === "") { + if (input[key] === null || input[key] === undefined || input[key] === '') { throw new Error(`${label}: missing ${key}`); } const value = Number(input[key]); - if (!Number.isFinite(value) || (options.min != null && value < options.min) || (options.max != null && value > options.max)) { + if ( + !Number.isFinite(value) || + (options.min != null && value < options.min) || + (options.max != null && value > options.max) + ) { throw new Error(`${label}: invalid ${key} '${input[key]}'`); } return value; }; - const signalUid = text("signal_uid"); + const signalUid = text('signal_uid'); if (!/^osm-[A-Za-z0-9_.:-]+$/.test(signalUid)) throw new Error(`${label}: invalid signal_uid '${signalUid}'`); if (uids.has(signalUid)) throw new Error(`Duplicate signal_uid '${signalUid}'`); uids.add(signalUid); - const displayId = text("display_id", false); + const displayId = text('display_id', false); if (displayId && displayIds.has(displayId)) throw new Error(`Duplicate display_id '${displayId}'`); if (displayId) displayIds.add(displayId); - const phaseGroup = number("phase_group", { min: 0, max: 1 }); + const phaseGroup = number('phase_group', { min: 0, max: 1 }); if (!Number.isInteger(phaseGroup)) throw new Error(`${label}: phase_group must be 0 or 1`); const enabled = normalizeBoolean(input.enabled, label); - const controlId = text("control_id"); - const approachId = text("approach_id"); - const sourceWayId = text("source_way_id"); - if (!approachId.startsWith(`${sourceWayId}:`)) throw new Error(`${label}: approach_id does not match source_way_id`); - const expectedUid = `osm-${controlId}-${approachId.replace(":", "-")}`; - if (signalUid !== expectedUid) throw new Error(`${label}: signal_uid does not match source identity (expected '${expectedUid}')`); - const legacyHeading = input.heading_deg == null || input.heading_deg === "" ? null : normalizeDegrees(number("heading_deg")); - if (legacyHeading == null && (input.mast_heading_deg == null || input.mast_heading_deg === "")) { + const controlId = text('control_id'); + const approachId = text('approach_id'); + const sourceWayId = text('source_way_id'); + if (!approachId.startsWith(`${sourceWayId}:`)) + throw new Error(`${label}: approach_id does not match source_way_id`); + const expectedUid = `osm-${controlId}-${approachId.replace(':', '-')}`; + if (signalUid !== expectedUid) + throw new Error(`${label}: signal_uid does not match source identity (expected '${expectedUid}')`); + const legacyHeading = + input.heading_deg == null || input.heading_deg === '' ? null : normalizeDegrees(number('heading_deg')); + if (legacyHeading == null && (input.mast_heading_deg == null || input.mast_heading_deg === '')) { throw new Error(`${label}: missing mast_heading_deg`); } - if (legacyHeading == null && (input.face_heading_deg == null || input.face_heading_deg === "")) { + if (legacyHeading == null && (input.face_heading_deg == null || input.face_heading_deg === '')) { throw new Error(`${label}: missing face_heading_deg`); } - const mastHeading = input.mast_heading_deg == null || input.mast_heading_deg === "" - ? normalizeDegrees((legacyHeading == null ? 0 : legacyHeading) - 90) - : normalizeDegrees(number("mast_heading_deg")); - const faceHeading = input.face_heading_deg == null || input.face_heading_deg === "" - ? normalizeDegrees((legacyHeading == null ? 0 : legacyHeading) + 180) - : normalizeDegrees(number("face_heading_deg")); + const mastHeading = + input.mast_heading_deg == null || input.mast_heading_deg === '' + ? normalizeDegrees((legacyHeading == null ? 0 : legacyHeading) - 90) + : normalizeDegrees(number('mast_heading_deg')); + const faceHeading = + input.face_heading_deg == null || input.face_heading_deg === '' + ? normalizeDegrees((legacyHeading == null ? 0 : legacyHeading) + 180) + : normalizeDegrees(number('face_heading_deg')); return { - type: "Feature", - geometry: { type: "Point", coordinates: feature.geometry.coordinates.slice(0, 2).map(Number) }, + type: 'Feature', + geometry: { type: 'Point', coordinates: feature.geometry.coordinates.slice(0, 2).map(Number) }, properties: { - ...input, signal_uid: signalUid, display_id: displayId, - control_id: controlId, approach_id: approachId, + ...input, + signal_uid: signalUid, + display_id: displayId, + control_id: controlId, + approach_id: approachId, source_way_id: sourceWayId, // Retain the legacy value only for migration compatibility. Runtime // geometry is entirely defined by mast_heading_deg and face_heading_deg. heading_deg: legacyHeading, - mast_heading_deg: mastHeading, face_heading_deg: faceHeading, - phase_group: phaseGroup, mast_reach_m: number("mast_reach_m", { min: 0.1, max: 30 }), - stop_lon: number("stop_lon", { min: -180, max: 180 }), - stop_lat: number("stop_lat", { min: -90, max: 90 }), - enabled, z_offset_m: number("z_offset_m", { min: -20, max: 100 }), + mast_heading_deg: mastHeading, + face_heading_deg: faceHeading, + phase_group: phaseGroup, + mast_reach_m: number('mast_reach_m', { min: 0.1, max: 30 }), + stop_lon: number('stop_lon', { min: -180, max: 180 }), + stop_lat: number('stop_lat', { min: -90, max: 90 }), + enabled, + z_offset_m: number('z_offset_m', { min: -20, max: 100 }), }, }; }); - return { type: "FeatureCollection", features }; + return { type: 'FeatureCollection', features }; } function buildTrafficSignalsFromFeatures(collection) { const normalized = validateTrafficSignalFeatures(collection); - const signals = normalized.features.filter((feature) => feature.properties.enabled).map((feature) => { - const p = feature.properties; - const point = feature.geometry.coordinates; - const mastAxis = headingVector(p.mast_heading_deg); - return { - id: p.signal_uid, signalUid: p.signal_uid, displayId: p.display_id, - nodeKey: signalNodeKey(p.signal_uid), - controlId: p.control_id, approachId: p.approach_id, sourceWayId: p.source_way_id, - phaseGroup: p.phase_group, longitude: point[0], latitude: point[1], - stopLongitude: p.stop_lon, stopLatitude: p.stop_lat, - // Existing Blender readers require headingDegrees. It is a compatibility - // alias only; the independent mast/face fields below define all geometry. - headingDegrees: p.heading_deg == null ? p.mast_heading_deg : p.heading_deg, - mastHeadingDegrees: p.mast_heading_deg, - faceHeadingDegrees: p.face_heading_deg, mastReachMeters: p.mast_reach_m, - zOffsetMeters: p.z_offset_m, - pose: buildSignalPose(point, mastAxis, p.face_heading_deg, p.mast_reach_m, p.z_offset_m), - }; - }); + const signals = normalized.features + .filter((feature) => feature.properties.enabled) + .map((feature) => { + const p = feature.properties; + const point = feature.geometry.coordinates; + const mastAxis = headingVector(p.mast_heading_deg); + return { + id: p.signal_uid, + signalUid: p.signal_uid, + displayId: p.display_id, + nodeKey: signalNodeKey(p.signal_uid), + controlId: p.control_id, + approachId: p.approach_id, + sourceWayId: p.source_way_id, + phaseGroup: p.phase_group, + longitude: point[0], + latitude: point[1], + stopLongitude: p.stop_lon, + stopLatitude: p.stop_lat, + // Existing Blender readers require headingDegrees. It is a compatibility + // alias only; the independent mast/face fields below define all geometry. + headingDegrees: p.heading_deg == null ? p.mast_heading_deg : p.heading_deg, + mastHeadingDegrees: p.mast_heading_deg, + faceHeadingDegrees: p.face_heading_deg, + mastReachMeters: p.mast_reach_m, + zOffsetMeters: p.z_offset_m, + pose: buildSignalPose(point, mastAxis, p.face_heading_deg, p.mast_reach_m, p.z_offset_m), + }; + }); return { version: 3, layout: SIGNAL_LAYOUT, signals }; } function signalNodeKey(signalUid) { - return `ts_${crypto.createHash("sha256").update(signalUid).digest("hex").slice(0, 16)}`; + return `ts_${crypto.createHash('sha256').update(signalUid).digest('hex').slice(0, 16)}`; } function reconcileTrafficSignalSourceReferences(collection, controls) { const normalized = validateTrafficSignalFeatures(collection); - const approachesByControl = new Map((controls || []).map((control) => [ - String(control.id), - new Set((control.arms || []).map((arm) => `${String(arm.wayId)}:${String(arm.neighborNodeId)}`)), - ])); + const approachesByControl = new Map( + (controls || []).map((control) => [ + String(control.id), + new Set((control.arms || []).map((arm) => `${String(arm.wayId)}:${String(arm.neighborNodeId)}`)), + ]), + ); const kept = []; const dropped = []; for (const [index, feature] of normalized.features.entries()) { const { control_id: controlId, approach_id: approachId, signal_uid: signalUid } = feature.properties; const approaches = approachesByControl.get(controlId); if (!approaches) { - dropped.push({ index: index + 1, signalUid, controlId, approachId, reason: "missing-control", message: `control_id '${controlId}' is not present in the current OSM` }); + dropped.push({ + index: index + 1, + signalUid, + controlId, + approachId, + reason: 'missing-control', + message: `control_id '${controlId}' is not present in the current OSM`, + }); continue; } if (!approaches.has(approachId)) { - dropped.push({ index: index + 1, signalUid, controlId, approachId, reason: "missing-approach", message: `approach_id '${approachId}' is not present on OSM control '${controlId}'` }); + dropped.push({ + index: index + 1, + signalUid, + controlId, + approachId, + reason: 'missing-approach', + message: `approach_id '${approachId}' is not present on OSM control '${controlId}'`, + }); continue; } kept.push(feature); @@ -233,67 +311,119 @@ function buildTrafficSignals(stopLines, intersections, controls = []) { } function readTrafficSignalFeatures(stopLinePath, intersectionPath, osmPath) { - const controls = parseOsm(fs.readFileSync(osmPath, "utf8")).trafficSignalControls; + const controls = parseOsm(fs.readFileSync(osmPath, 'utf8')).trafficSignalControls; return buildTrafficSignalFeatures( - JSON.parse(fs.readFileSync(stopLinePath, "utf8")), - JSON.parse(fs.readFileSync(intersectionPath, "utf8")), controls, + JSON.parse(fs.readFileSync(stopLinePath, 'utf8')), + JSON.parse(fs.readFileSync(intersectionPath, 'utf8')), + controls, ); } function readTrafficSignals(editablePath, osmPath = null) { - const collection = JSON.parse(fs.readFileSync(editablePath, "utf8")); + const collection = JSON.parse(fs.readFileSync(editablePath, 'utf8')); if (osmPath) { - const controls = parseOsm(fs.readFileSync(osmPath, "utf8")).trafficSignalControls; + const controls = parseOsm(fs.readFileSync(osmPath, 'utf8')).trafficSignalControls; validateTrafficSignalSourceReferences(collection, controls); } return buildTrafficSignalsFromFeatures(collection); } function normalizeBoolean(value, label) { - if (value === true || value === 1 || value === "1" || String(value).toLowerCase() === "true" || String(value).toLowerCase() === "yes") return true; - if (value === false || value === 0 || value === "0" || String(value).toLowerCase() === "false" || String(value).toLowerCase() === "no") return false; + if ( + value === true || + value === 1 || + value === '1' || + String(value).toLowerCase() === 'true' || + String(value).toLowerCase() === 'yes' + ) + return true; + if ( + value === false || + value === 0 || + value === '0' || + String(value).toLowerCase() === 'false' || + String(value).toLowerCase() === 'no' + ) + return false; throw new Error(`${label}: invalid enabled '${value}'`); } function uniqueApproachArms(candidates, controlPoint) { - const sorted = candidates.map((candidate) => ({ ...candidate, armHeading: normalizeDegrees(headingBetween(controlPoint, candidate.center)), controlDistance: metersBetween(controlPoint, candidate.center) })) + const sorted = candidates + .map((candidate) => ({ + ...candidate, + armHeading: normalizeDegrees(headingBetween(controlPoint, candidate.center)), + controlDistance: metersBetween(controlPoint, candidate.center), + })) .sort((a, b) => a.armHeading - b.armHeading || a.controlDistance - b.controlDistance); const arms = []; - for (const candidate of sorted) if (!arms.some((arm) => angularDistance(arm.armHeading, candidate.armHeading) <= 25)) arms.push(candidate); + for (const candidate of sorted) + if (!arms.some((arm) => angularDistance(arm.armHeading, candidate.armHeading) <= 25)) arms.push(candidate); return arms; } function matchOsmArms(candidates, controlPoint, osmArms) { - const remaining = candidates.map((candidate) => ({ ...candidate, armHeading: candidate.matchHeadingDegrees ?? normalizeDegrees(headingBetween(controlPoint, candidate.center)) })); + const remaining = candidates.map((candidate) => ({ + ...candidate, + armHeading: candidate.matchHeadingDegrees ?? normalizeDegrees(headingBetween(controlPoint, candidate.center)), + })); if (!osmArms.length) return uniqueApproachArms(remaining, controlPoint); return osmArms.map((osmArm) => { - let bestIndex = -1; let bestDistance = Infinity; + let bestIndex = -1; + let bestDistance = Infinity; remaining.forEach((item, index) => { const directedDistance = angularDistance(item.armHeading, osmArm.headingDegrees); - const distance = item.matchHeadingDegrees == null - ? directedDistance - : Math.min( - angularDistance(item.matchHeadingDegrees, osmArm.headingDegrees), - angularDistance(item.matchHeadingDegrees + 180, osmArm.headingDegrees), - ); - if (distance < bestDistance) { bestDistance = distance; bestIndex = index; } + const distance = + item.matchHeadingDegrees == null + ? directedDistance + : Math.min( + angularDistance(item.matchHeadingDegrees, osmArm.headingDegrees), + angularDistance(item.matchHeadingDegrees + 180, osmArm.headingDegrees), + ); + if (distance < bestDistance) { + bestDistance = distance; + bestIndex = index; + } }); - const candidate = bestIndex >= 0 && bestDistance <= 45 ? remaining.splice(bestIndex, 1)[0] : fallbackCandidate(controlPoint, osmArm); + const candidate = + bestIndex >= 0 && bestDistance <= 45 + ? remaining.splice(bestIndex, 1)[0] + : fallbackCandidate(controlPoint, osmArm); return { ...candidate, osmArm }; }); } function fallbackCandidate(controlPoint, osmArm) { - const outward = headingVector(osmArm.headingDegrees); const axis = [-outward[0], -outward[1]]; - const center = moveMeters(controlPoint, outward, 8); const farSide = moveMeters(controlPoint, axis, 3.2); - return { center, axis, point: moveMeters(farSide, [axis[1], -axis[0]], CURB_OFFSET_METERS), armHeading: normalizeDegrees(osmArm.headingDegrees), headingDegrees: normalizeDegrees(Math.atan2(axis[0], axis[1]) * 180 / Math.PI), fallback: true }; + const outward = headingVector(osmArm.headingDegrees); + const axis = [-outward[0], -outward[1]]; + const center = moveMeters(controlPoint, outward, 8); + const farSide = moveMeters(controlPoint, axis, 3.2); + return { + center, + axis, + point: moveMeters(farSide, [axis[1], -axis[0]], CURB_OFFSET_METERS), + armHeading: normalizeDegrees(osmArm.headingDegrees), + headingDegrees: normalizeDegrees((Math.atan2(axis[0], axis[1]) * 180) / Math.PI), + fallback: true, + }; } function phaseGroups(arms) { - const groups = Array(arms.length).fill(1); if (arms.length < 2) return groups; - let main = [0, 1]; let best = -1; - for (let a = 0; a < arms.length; a += 1) for (let b = a + 1; b < arms.length; b += 1) { const opposition = angularDistance(arms[a].armHeading, arms[b].armHeading); if (opposition > best) { best = opposition; main = [a, b]; } } - groups[main[0]] = 0; groups[main[1]] = 0; return groups; + const groups = Array(arms.length).fill(1); + if (arms.length < 2) return groups; + let main = [0, 1]; + let best = -1; + for (let a = 0; a < arms.length; a += 1) + for (let b = a + 1; b < arms.length; b += 1) { + const opposition = angularDistance(arms[a].armHeading, arms[b].armHeading); + if (opposition > best) { + best = opposition; + main = [a, b]; + } + } + groups[main[0]] = 0; + groups[main[1]] = 0; + return groups; } function buildSignalPose(pole, mastAxis, faceHeadingDegrees, mastReach, zOffset = 0) { @@ -302,20 +432,81 @@ function buildSignalPose(pole, mastAxis, faceHeadingDegrees, mastReach, zOffset const position = (point, height) => ({ longitude: point[0], latitude: point[1], height: height + zOffset }); const lensPoint = moveMeters(head, face, SIGNAL_LAYOUT.lensFaceOffsetMeters); const faceRight = [-face[1], face[0]]; - const board = moveMeters(moveMeters(head, faceRight, SIGNAL_LAYOUT.countdownLateralMeters), face, SIGNAL_LAYOUT.countdownFaceOffsetMeters); - return { pole: position(pole, 0), arm: { from: position(pole, SIGNAL_LAYOUT.mastHeightMeters), to: position(head, SIGNAL_LAYOUT.mastHeightMeters) }, head: { ...position(head, SIGNAL_LAYOUT.headCenterHeightMeters), faceHeadingDegrees }, lenses: ["red", "yellow", "green"].map((state, index) => ({ state, ...position(lensPoint, SIGNAL_LAYOUT.headCenterHeightMeters + SIGNAL_LAYOUT.lensVerticalOffsetsMeters[index]) })), countdown: { ...position(board, SIGNAL_LAYOUT.mastHeightMeters), faceHeadingDegrees } }; + const board = moveMeters( + moveMeters(head, faceRight, SIGNAL_LAYOUT.countdownLateralMeters), + face, + SIGNAL_LAYOUT.countdownFaceOffsetMeters, + ); + return { + pole: position(pole, 0), + arm: { from: position(pole, SIGNAL_LAYOUT.mastHeightMeters), to: position(head, SIGNAL_LAYOUT.mastHeightMeters) }, + head: { ...position(head, SIGNAL_LAYOUT.headCenterHeightMeters), faceHeadingDegrees }, + lenses: ['red', 'yellow', 'green'].map((state, index) => ({ + state, + ...position(lensPoint, SIGNAL_LAYOUT.headCenterHeightMeters + SIGNAL_LAYOUT.lensVerticalOffsetsMeters[index]), + })), + countdown: { ...position(board, SIGNAL_LAYOUT.mastHeightMeters), faceHeadingDegrees }, + }; } -function polygonCenter(geometry) { const ring = geometry?.type === "Polygon" ? geometry.coordinates?.[0] : null; if (!ring || ring.length < 4) return null; const points = ring.slice(0, -1); return [points.reduce((s, p) => s + p[0], 0) / points.length, points.reduce((s, p) => s + p[1], 0) / points.length]; } -function polygonRadius(geometry, center) { const ring = geometry?.type === "Polygon" ? geometry.coordinates?.[0] : null; return ring && center ? Math.max(...ring.slice(0, -1).map((point) => metersBetween(center, point)), 0) : 0; } -function roadAxis(geometry, center, target) { const ring = geometry?.coordinates?.[0]; if (!ring || ring.length < 3) return null; let longest; for (let i = 0; i < ring.length - 1; i += 1) { const dx = (ring[i + 1][0] - ring[i][0]) * Math.cos(center[1] * Math.PI / 180); const dy = ring[i + 1][1] - ring[i][1]; const length = Math.hypot(dx, dy); if (!longest || length > longest.length) longest = { dx, dy, length }; } if (!longest?.length) return null; let axis = [-longest.dy / longest.length, longest.dx / longest.length]; const toward = [(target[0] - center[0]) * Math.cos(center[1] * Math.PI / 180), target[1] - center[1]]; if (axis[0] * toward[0] + axis[1] * toward[1] < 0) axis = [-axis[0], -axis[1]]; return axis; } -function nearestCenter(point, centers) { return centers.map((entry) => ({ ...entry, distance: metersBetween(point, entry.point) })).sort((a, b) => a.distance - b.distance)[0] || null; } -function metersBetween(a, b) { const lat = (a[1] + b[1]) / 2 * Math.PI / 180; return Math.hypot((a[0] - b[0]) * Math.cos(lat), a[1] - b[1]) * Math.PI / 180 * EARTH_RADIUS; } -function moveMeters(point, vector, meters) { const scale = 180 / Math.PI / EARTH_RADIUS; return [point[0] + vector[0] * meters * scale / Math.cos(point[1] * Math.PI / 180), point[1] + vector[1] * meters * scale]; } -function headingBetween(from, to) { const latitude = (from[1] + to[1]) / 2 * Math.PI / 180; return Math.atan2((to[0] - from[0]) * Math.cos(latitude), to[1] - from[1]) * 180 / Math.PI; } -function headingVector(degrees) { const radians = degrees * Math.PI / 180; return [Math.sin(radians), Math.cos(radians)]; } -function normalizeDegrees(value) { return ((value % 360) + 360) % 360; } -function angularDistance(a, b) { return Math.abs(((a - b + 540) % 360) - 180); } +function polygonCenter(geometry) { + const ring = geometry?.type === 'Polygon' ? geometry.coordinates?.[0] : null; + if (!ring || ring.length < 4) return null; + const points = ring.slice(0, -1); + return [points.reduce((s, p) => s + p[0], 0) / points.length, points.reduce((s, p) => s + p[1], 0) / points.length]; +} +function polygonRadius(geometry, center) { + const ring = geometry?.type === 'Polygon' ? geometry.coordinates?.[0] : null; + return ring && center ? Math.max(...ring.slice(0, -1).map((point) => metersBetween(center, point)), 0) : 0; +} +function roadAxis(geometry, center, target) { + const ring = geometry?.coordinates?.[0]; + if (!ring || ring.length < 3) return null; + let longest; + for (let i = 0; i < ring.length - 1; i += 1) { + const dx = (ring[i + 1][0] - ring[i][0]) * Math.cos((center[1] * Math.PI) / 180); + const dy = ring[i + 1][1] - ring[i][1]; + const length = Math.hypot(dx, dy); + if (!longest || length > longest.length) longest = { dx, dy, length }; + } + if (!longest?.length) return null; + let axis = [-longest.dy / longest.length, longest.dx / longest.length]; + const toward = [(target[0] - center[0]) * Math.cos((center[1] * Math.PI) / 180), target[1] - center[1]]; + if (axis[0] * toward[0] + axis[1] * toward[1] < 0) axis = [-axis[0], -axis[1]]; + return axis; +} +function nearestCenter(point, centers) { + return ( + centers + .map((entry) => ({ ...entry, distance: metersBetween(point, entry.point) })) + .sort((a, b) => a.distance - b.distance)[0] || null + ); +} +function metersBetween(a, b) { + const lat = (((a[1] + b[1]) / 2) * Math.PI) / 180; + return ((Math.hypot((a[0] - b[0]) * Math.cos(lat), a[1] - b[1]) * Math.PI) / 180) * EARTH_RADIUS; +} +function moveMeters(point, vector, meters) { + const scale = 180 / Math.PI / EARTH_RADIUS; + return [ + point[0] + (vector[0] * meters * scale) / Math.cos((point[1] * Math.PI) / 180), + point[1] + vector[1] * meters * scale, + ]; +} +function headingBetween(from, to) { + const latitude = (((from[1] + to[1]) / 2) * Math.PI) / 180; + return (Math.atan2((to[0] - from[0]) * Math.cos(latitude), to[1] - from[1]) * 180) / Math.PI; +} +function headingVector(degrees) { + const radians = (degrees * Math.PI) / 180; + return [Math.sin(radians), Math.cos(radians)]; +} +function normalizeDegrees(value) { + return ((value % 360) + 360) % 360; +} +function angularDistance(a, b) { + return Math.abs(((a - b + 540) % 360) - 180); +} module.exports = { SIGNAL_LAYOUT, diff --git a/test/baseline/fengshu-er-road.json b/test/baseline/fengshu-er-road.json index 1d10646..760c23e 100644 --- a/test/baseline/fengshu-er-road.json +++ b/test/baseline/fengshu-er-road.json @@ -101,8 +101,5 @@ "bytes": 15491 } }, - "volatileExcluded": [ - "absolute paths -> or ", - "native-road-* staging directory -> " - ] + "volatileExcluded": ["absolute paths -> or ", "native-road-* staging directory -> "] } diff --git a/test/baseline/nantaizi-lake-innovation-valley.json b/test/baseline/nantaizi-lake-innovation-valley.json index 554b0e5..bb4b6c4 100644 --- a/test/baseline/nantaizi-lake-innovation-valley.json +++ b/test/baseline/nantaizi-lake-innovation-valley.json @@ -101,8 +101,5 @@ "bytes": 74151 } }, - "volatileExcluded": [ - "absolute paths -> or ", - "native-road-* staging directory -> " - ] + "volatileExcluded": ["absolute paths -> or ", "native-road-* staging directory -> "] } diff --git a/test/fixtures.js b/test/fixtures.js index acc2794..0d4a436 100644 --- a/test/fixtures.js +++ b/test/fixtures.js @@ -1,49 +1,61 @@ -"use strict"; +'use strict'; -const assert = require("assert/strict"); -const crypto = require("crypto"); -const { execFileSync } = require("child_process"); -const fs = require("fs"); -const path = require("path"); -const { unzipSync, strFromU8 } = require("fflate"); -const { compiler } = require("../src"); -const { exportNativeRoadPackage } = require("../src/export/native-road-package"); +const assert = require('assert/strict'); +const crypto = require('crypto'); +const { execFileSync } = require('child_process'); +const fs = require('fs'); +const path = require('path'); +const { unzipSync, strFromU8 } = require('fflate'); +const { compiler } = require('../src'); +const { exportNativeRoadPackage } = require('../src/export/native-road-package'); -const root = path.resolve(__dirname, ".."); -for (const areaId of ["fengshu-er-road", "nantaizi-lake-innovation-valley"]) { - const isFengshu = areaId === "fengshu-er-road"; - const outputRoot = path.join(root, "outputs", areaId); +const root = path.resolve(__dirname, '..'); +for (const areaId of ['fengshu-er-road', 'nantaizi-lake-innovation-valley']) { + const isFengshu = areaId === 'fengshu-er-road'; + const outputRoot = path.join(root, 'outputs', areaId); const input = { areaId, - osmFile: path.join(root, "inputs", "osm", isFengshu ? "枫树二路.osm" : "南台子湖创新谷OSM.osm"), - outDir: path.join(outputRoot, "native-road"), - stagingDir: path.join(outputRoot, "_pipeline"), - overridesFile: path.join(outputRoot, "native-road-overrides.json"), - trafficSignalsFile: path.join(outputRoot, "native-traffic-signals.json"), - comparisonDir: path.join(outputRoot, "osm2streets_web_out"), + osmFile: path.join(root, 'inputs', 'osm', isFengshu ? '枫树二路.osm' : '南台子湖创新谷OSM.osm'), + outDir: path.join(outputRoot, 'native-road'), + stagingDir: path.join(outputRoot, '_pipeline'), + overridesFile: path.join(outputRoot, 'native-road-overrides.json'), + trafficSignalsFile: path.join(outputRoot, 'native-traffic-signals.json'), + comparisonDir: path.join(outputRoot, 'osm2streets_web_out'), options: { edgeLines: false, junctionTemplates: { enabled: false, references: [] } }, }; - assert.ok(fs.existsSync(path.join(root, "test", "baseline", `${areaId}.json`))); + assert.ok(fs.existsSync(path.join(root, 'test', 'baseline', `${areaId}.json`))); const { result } = compiler.compileInput(input); assert.equal(result.areaId, areaId); - assert.ok(fs.existsSync(path.join(input.outDir, "compiled.json"))); + assert.ok(fs.existsSync(path.join(input.outDir, 'compiled.json'))); const first = exportNativeRoadPackage(input.outDir); const second = exportNativeRoadPackage(input.outDir); - assert.equal(crypto.createHash("sha256").update(first.bytes).digest("hex"), crypto.createHash("sha256").update(second.bytes).digest("hex")); + assert.equal( + crypto.createHash('sha256').update(first.bytes).digest('hex'), + crypto.createHash('sha256').update(second.bytes).digest('hex'), + ); const entries = unzipSync(first.bytes); - const manifest = JSON.parse(strFromU8(entries["manifest.json"])); + const manifest = JSON.parse(strFromU8(entries['manifest.json'])); assert.equal(manifest.areaId, areaId); - assert.equal(manifest.contract, "native-road-package/v1.1"); - assert.deepEqual(manifest.generator, { name: "road-compiler", version: "0.3.0" }); - assert.equal("source" in JSON.parse(strFromU8(entries["compiled.json"])), false); - assert.equal(Object.keys(entries).some((entry) => entry.includes("override") || entry.endsWith(".osm")), false); + assert.equal(manifest.contract, 'native-road-package/v1.1'); + assert.deepEqual(manifest.generator, { name: 'road-compiler', version: '0.3.0' }); + assert.equal('source' in JSON.parse(strFromU8(entries['compiled.json'])), false); + assert.equal( + Object.keys(entries).some((entry) => entry.includes('override') || entry.endsWith('.osm')), + false, + ); assert.equal(Object.keys(entries).length, 18); if (isFengshu) { - const inputFile = path.join(outputRoot, "road-compiler-input.json"); - const archive = path.join(outputRoot, "export.zip"); + const inputFile = path.join(outputRoot, 'road-compiler-input.json'); + const archive = path.join(outputRoot, 'export.zip'); fs.writeFileSync(inputFile, `${JSON.stringify(input)}\n`); - execFileSync(process.execPath, [path.join(root, "bin", "road-compiler.js"), "--input", inputFile, "--export-zip", archive]); + execFileSync(process.execPath, [ + path.join(root, 'bin', 'road-compiler.js'), + '--input', + inputFile, + '--export-zip', + archive, + ]); assert.ok(fs.existsSync(archive)); } } -console.log("road compiler fixture tests passed"); +console.log('road compiler fixture tests passed'); diff --git a/test/fixtures/fengshu-junction-reference.geojson b/test/fixtures/fengshu-junction-reference.geojson index f5d5069..007e4d1 100644 --- a/test/fixtures/fengshu-junction-reference.geojson +++ b/test/fixtures/fengshu-junction-reference.geojson @@ -1 +1,8769 @@ -{"type":"FeatureCollection","features":[{"type":"Feature","id":"10","properties":{"type":1},"geometry":{"type":"MultiPolygon","coordinates":[[[[114.12670642509887,30.459719866954526],[114.12672480418713,30.459728657730796],[114.12674493919597,30.459737997306732],[114.1267833123607,30.459756158076946],[114.12680436982367,30.45976593212108],[114.12685397313732,30.459789508849415],[114.12690012817846,30.45981125331352],[114.12696581650293,30.459842297256657],[114.12703599666129,30.459875724524046],[114.1270498805918,30.45988229362388],[114.12705936578135,30.459887101893713],[114.12713738017123,30.459929772755643],[114.1272371446353,30.459984098991324],[114.12732032068887,30.460029332274345],[114.12756802667649,30.460156811375963],[114.1276272681774,30.46018476753933],[114.12767330666927,30.460206334960937],[114.12774798860086,30.460241752793856],[114.12781876709889,30.460274913778548],[114.12793737942023,30.46033070051492],[114.12802476152558,30.460371550079984],[114.12803377753897,30.460375985604387],[114.12804110941299,30.460380028660342],[114.12805101374497,30.460386354257707],[114.12806883527543,30.460398025244622],[114.12808654794983,30.46041118828605],[114.12809933710143,30.46042164630667],[114.1281073950421,30.460428573291047],[114.12811395128384,30.460434620557336],[114.12811938213294,30.460440066689017],[114.12812926407065,30.460450347349173],[114.12813473454605,30.46045657462467],[114.12814164327082,30.460464424462735],[114.12814730374363,30.460471255615666],[114.12815294371121,30.460478077312562],[114.12815879253623,30.460486283596325],[114.1281645602278,30.460494776825477],[114.12816883816927,30.460501272024807],[114.12817349886402,30.460509245696944],[114.1281771352045,30.460517071410813],[114.12817986770692,30.460522406328934],[114.12818358105403,30.460530441147434],[114.12818686670175,30.46053817825591],[114.12819018776598,30.460545819342492],[114.12819418910176,30.46055564186251],[114.12820380410353,30.460582194804786],[114.12820842032188,30.46060375471472],[114.12821202983478,30.46062954010985],[114.1282137381741,30.46064526447438],[114.12821400817043,30.460653439918755],[114.12821393731382,30.46067277098964],[114.12821257637805,30.46069672891237],[114.12821098332131,30.460710299619304],[114.12820721720577,30.460726258836115],[114.12819314233184,30.460756750044617],[114.12817805241743,30.46078387834311],[114.1281664689422,30.460803920591136],[114.12815723705758,30.46081876175518],[114.12813556102246,30.46085392543641],[114.12786187058398,30.461286782141404],[114.12767310863411,30.461588040608625],[114.12749605673096,30.46186848121499],[114.12771556983981,30.46197223706339],[114.12829921553568,30.46103318454979],[114.12834745214138,30.460953266211526],[114.12837228716977,30.460914859036752],[114.1283903573218,30.460889225666122],[114.12840962380116,30.46086537779413],[114.12842658919257,30.46084461385105],[114.12844027647458,30.460830720633155],[114.12845700359827,30.460817290594896],[114.12846927807756,30.460807952031754],[114.1284793259837,30.46080120044445],[114.12848732126338,30.46079615416839],[114.12849824982679,30.460790108078196],[114.12851735673152,30.46078057113085],[114.12853428994015,30.460773234382117],[114.12854767478771,30.460768204092016],[114.12856867125925,30.460760493145365],[114.12858706934223,30.460754937340415],[114.12861834861083,30.46074820545063],[114.12862756243905,30.46074661029612],[114.12864869683986,30.46074212232996],[114.12865920934019,30.460740441110147],[114.12866868493343,30.460739396487128],[114.12868036248904,30.460738380348605],[114.12868990874968,30.460737761184348],[114.12870180719327,30.460737363112344],[114.12871667998598,30.460737392256405],[114.1287292771692,30.46073780823301],[114.1287499886682,30.46073912084974],[114.12876640653077,30.460741578573987],[114.12877789632182,30.460743427010925],[114.12878992723334,30.460745602599992],[114.12880068089633,30.46074775119988],[114.12881210760563,30.460750689795557],[114.12882252563773,30.460753300372676],[114.12882949358202,30.460755215632997],[114.12884206581988,30.460759486418695],[114.12884823128418,30.460761483949376],[114.12890089866964,30.460782505183808],[114.12891485544512,30.460788816797255],[114.12895540785316,30.460808048501978],[114.12902501127174,30.46084044145126],[114.12908466293443,30.460868698056956],[114.12910546198879,30.46087847390208],[114.1291300006723,30.460890007409656],[114.12917162969823,30.4609095887503],[114.12927120714293,30.460956547967346],[114.12935900625274,30.460997903663344],[114.12943766593234,30.461035023980024],[114.12945728539037,30.461044170004904],[114.12947426624748,30.461052231980972],[114.12948015380047,30.46105484591913],[114.12948639545796,30.4610574312437],[114.1294967439802,30.461061699880304],[114.12951325767341,30.461068326698438],[114.12953869523535,30.4610785199137],[114.12956431897612,30.461088970193117],[114.12959304786149,30.461100566489673],[114.1296316067205,30.461116134315017],[114.12965073525396,30.461123943783882],[114.1296767364358,30.461134433008322],[114.12971617913543,30.461150365527534],[114.1297826382227,30.461177241498255],[114.12984084737079,30.46120092141013],[114.12985385983599,30.46120636036464],[114.12988107460019,30.46121346938985],[114.12988717793624,30.461215056315822],[114.1299011234562,30.461221666468255],[114.12991177953043,30.461226717093556],[114.12993382466588,30.46123713451079],[114.12998083593384,30.461259449163787],[114.13001973787617,30.46127751673334],[114.13006243727204,30.461297540583438],[114.13013580529461,30.461331867997014],[114.13020225528513,30.461363289096944],[114.13026373880149,30.461392632679807],[114.13029120386813,30.46140550178206],[114.13040786031041,30.461460431954237],[114.13046438304347,30.461487017104876],[114.13058639783064,30.46127753388595],[114.1305697754666,30.461269607588438],[114.13044610731077,30.461211548873724],[114.13036451796869,30.461173104230493],[114.13028045912006,30.46113356386891],[114.13023878831747,30.46111384483735],[114.1301812215938,30.46108665817515],[114.1301369597027,30.461066069392857],[114.13012052111407,30.461058307966308],[114.13010307916063,30.461050038266155],[114.13009311705432,30.461045354054807],[114.12976904001516,30.460871316765],[114.12973565501949,30.460853218474796],[114.12971961636799,30.460845148883948],[114.1295129460683,30.46074116541183],[114.12945722337031,30.46071487608635],[114.12926460885387,30.460624157320336],[114.12907783250328,30.460536187270748],[114.12906810320003,30.46053099337933],[114.12905584211939,30.460524970242773],[114.12902430272453,30.460508788612035],[114.12900645526051,30.460499046548748],[114.12899922449976,30.46049483639869],[114.12899068600481,30.460489375426576],[114.12897983929717,30.460481324246036],[114.12896495996615,30.460469828796573],[114.1289488405392,30.460456110632084],[114.12893156889058,30.460438092299785],[114.12889747265079,30.460394122293796],[114.12889453203489,30.46039033011819],[114.12888995839599,30.46038307458886],[114.12888289382553,30.46037047324551],[114.12887002948217,30.46033996405978],[114.12886289038934,30.460319750928605],[114.12885395471388,30.46029112420495],[114.1288521680376,30.46028171852131],[114.12884964067962,30.46026239223364],[114.1288483923799,30.46025102886467],[114.12884796986727,30.460232582397943],[114.12884875193501,30.460206802769104],[114.1288503392283,30.46019201466402],[114.12885801135928,30.460157905337283],[114.12886084427603,30.46014882845719],[114.12886636717148,30.46013113267193],[114.12887584793602,30.460110139555457],[114.1288838291907,30.460092466765246],[114.12890164662322,30.460061561716298],[114.12891494258247,30.460039083877408],[114.12892546862423,30.46002213124867],[114.12897421219743,30.459943575764843],[114.1289842110455,30.45992736538217],[114.12899391148486,30.459911638771093],[114.12900365647637,30.459895839915685],[114.1290135248707,30.459879840980385],[114.12902702305738,30.45985795729204],[114.1290760801111,30.459778424191825],[114.12928780195848,30.45945907207147],[114.1293908264265,30.459296790906148],[114.12951420054556,30.45909081702124],[114.12929465307654,30.45898788033217],[114.12887225771087,30.459683803494816],[114.12882348281829,30.45976346737858],[114.12881370466052,30.459779839929716],[114.12881212257278,30.45978237574835],[114.12880649093621,30.459791362993084],[114.12879568258535,30.459808611481332],[114.1287862339858,30.45982368999736],[114.12877733346733,30.459837893847396],[114.1287684006543,30.45985214922154],[114.12874671778104,30.459886751646973],[114.12870820465953,30.459948212292552],[114.12869629062735,30.459967225092058],[114.1286909504444,30.459975584332216],[114.1286733227205,30.460001654020463],[114.12866499322047,30.46001094922957],[114.1286409124706,30.460037821819622],[114.12862056779049,30.460055362448653],[114.12859942445834,30.460073591613387],[114.12859892982115,30.460074018074582],[114.12858006237614,30.46008735465943],[114.1285703977271,30.460094186172874],[114.12856173865131,30.46009965466516],[114.12855854817336,30.460101651529794],[114.12854977053016,30.460106600493948],[114.1285407570152,30.460111128749116],[114.1285349971284,30.460113638283904],[114.12853144757541,30.460115080735324],[114.12851676295261,30.460120842817474],[114.12849833445783,30.46012695982037],[114.12849320364326,30.460128659370767],[114.12848915726917,30.46012975301531],[114.12847990318281,30.46013225418443],[114.12846243300255,30.460136775580118],[114.12844856827508,30.460140200201295],[114.12842993780764,30.46014382686593],[114.12841310640472,30.46014658507335],[114.12839158428022,30.46014837514409],[114.12838418126694,30.460149173943524],[114.12837031195335,30.4601498098311],[114.12834724265122,30.460149881331255],[114.12832457028,30.460148815571962],[114.12830033627363,30.460146787633487],[114.12828606564942,30.460144447170077],[114.12826758884803,30.460140971343122],[114.1282376466944,30.460134095768744],[114.12822807094831,30.460131457652615],[114.12821968332197,30.46012905213806],[114.12821317234342,30.460126785540226],[114.12820518502357,30.460123838223822],[114.12819414681434,30.460119689764355],[114.12818404012039,30.460115828881552],[114.12818024400646,30.460114010332596],[114.12816840687336,30.460108401648185],[114.12810279127679,30.460077521889644],[114.12801392613177,30.460035481897506],[114.127875067572,30.45996989516379],[114.1277868310944,30.459928396957878],[114.12776774120755,30.459919418827678],[114.12765252636181,30.45986512851318],[114.12763346721754,30.45985561035686],[114.1274294888824,30.459774464597885],[114.12729689668743,30.459721334876495],[114.12725367350123,30.459704157756786],[114.12720564883506,30.459685197896864],[114.12713351632975,30.459651534720905],[114.12711081207827,30.459641142294075],[114.12704721321815,30.459610978372535],[114.12699816961752,30.45958834667066],[114.12693500878125,30.459558363528384],[114.12687186487535,30.459528720775257],[114.12682601995878,30.459507090823386],[114.12670642509887,30.459719866954526]]]]}},{"type":"Feature","id":"11","properties":{"type":2},"geometry":{"type":"MultiPolygon","coordinates":[[[[114.12667851705592,30.459769519300867],[114.12678669733096,30.459820404831465],[114.12696098364239,30.459902229147097],[114.12706246005538,30.459954887442564],[114.12713863456968,30.45999580532362],[114.1272445047874,30.460052759625377],[114.12729073755453,30.460077508855534],[114.1275086759357,30.460190139691004],[114.12793638723153,30.460393536847192],[114.1280091529264,30.460429436483583],[114.12807692852476,30.460488494764924],[114.12809608381721,30.46050926878318],[114.12812869945513,30.460564335635073],[114.12814437677969,30.460598855787218],[114.12815471974382,30.460634219292533],[114.12815795586218,30.46065232227354],[114.12816084802566,30.460666226523696],[114.12816297177484,30.460678205130506],[114.12816330989769,30.460697061140195],[114.1281615490448,30.460711335366184],[114.12815967333917,30.460725154080695],[114.12815412047559,30.460745801236786],[114.12813685164059,30.460778711292647],[114.12812526814679,30.46079875352665],[114.12811603624742,30.46081359467971],[114.1279496458915,30.46108040507848],[114.12746442959613,30.46185587039544],[114.12748164436566,30.461863076578037],[114.12797226529804,30.461083207482826],[114.12808936576536,30.460894645704666],[114.12813972501878,30.46081439412671],[114.1281489569059,30.46079955296438],[114.12816054038488,30.46077951071853],[114.1281755585996,30.46075295372288],[114.12818963347885,30.46072246251689],[114.12819258724711,30.460709382623207],[114.12819434809374,30.460695108394308],[114.12819591291681,30.46067465433775],[114.1281960248573,30.46066411627798],[114.12819561473438,30.460648068731086],[114.12819466169702,30.46063554207201],[114.12819279494028,30.460620418411153],[114.12818940492706,30.460607712318566],[114.12818563805288,30.460597393544138],[114.1281819505387,30.460586594127783],[114.12817989670708,30.460577862964488],[114.12817924467245,30.460573839455144],[114.12817949504986,30.46057124649343],[114.12818053476947,30.460570151389053],[114.12818221818578,30.46056899362691],[114.12818652570962,30.46056741402056],[114.12819690363179,30.46056423973758],[114.12819339565145,30.460554513371267],[114.12818938424755,30.460544357421085],[114.12818682298207,30.46053814209666],[114.12818353733651,30.460530404990013],[114.12817982399187,30.460522370173596],[114.12817709149125,30.460517035257006],[114.1281748449635,30.4605134072006],[114.12815999976104,30.460516411050083],[114.1281569844651,30.460516108596025],[114.12815427533317,30.46051473858637],[114.1281504066004,30.46051071863724],[114.12813888688173,30.460497459445044],[114.12813622851498,30.46049402946341],[114.12812933351096,30.460485710839954],[114.12812094472461,30.460476242046507],[114.12811173169072,30.460466178487792],[114.12809933858425,30.460453694910942],[114.12808964467422,30.460444747095103],[114.1280722884732,30.460430668424696],[114.12806373666007,30.460423929440257],[114.12805096285751,30.4604148188883],[114.12803869437997,30.460406548926965],[114.12802808197053,30.46039991968587],[114.12802370738075,30.46039743663877],[114.12801513967143,30.460393015625648],[114.12799381210561,30.46038291219492],[114.12797508311029,30.460374198797272],[114.12791771802227,30.460347195819843],[114.12767435823383,30.46023264012388],[114.12762175159291,30.460208054352417],[114.12750768942868,30.460153778331048],[114.12750968523949,30.46015305383419],[114.12751615320019,30.460151116056814],[114.12752101174054,30.460149847807664],[114.1275245010255,30.460149209070295],[114.12753385950025,30.46014791885199],[114.12753905352344,30.460147613899547],[114.12754442532368,30.460147980243853],[114.12732052000571,30.460030682280813],[114.12732096353244,30.460033522969436],[114.1273209437513,30.46003526354767],[114.12732042193159,30.460039600474627],[114.12731982187725,30.460044125178978],[114.12731900199067,30.46004906017859],[114.12731813378313,30.460053516056735],[114.12731760854854,30.460055567040012],[114.12731527476217,30.46005429975084],[114.12725782565802,30.460023057477574],[114.1271690692801,30.459974725730937],[114.12702132571069,30.459895863096776],[114.1270118405141,30.45989105481675],[114.12699795657305,30.459884485702027],[114.126927776362,30.45985105836026],[114.1268620879885,30.459820014347677],[114.1268159329123,30.459798269834298],[114.12676616873911,30.459774841761565],[114.12674527208262,30.4597649189869],[114.12672905649116,30.459757399324875],[114.12669446854811,30.459741139345454],[114.12667851705592,30.459769519300867]]],[[[114.12683688366135,30.459487762763597],[114.12694685034681,30.45953947097963],[114.12702821410709,30.459577593679693],[114.1271414291467,30.459631188953093],[114.12719322411404,30.459655676670067],[114.12726367595704,30.459686168438914],[114.1273874043263,30.45973747924787],[114.12745538294098,30.459766253026842],[114.12746249171516,30.459769261998932],[114.12746847497262,30.459771794566112],[114.12746506444269,30.45977297871145],[114.12745847670172,30.45977469440682],[114.12745466519337,30.459775504406007],[114.12744835030661,30.459775950289142],[114.12744153549609,30.459776037784387],[114.12743665669757,30.459775368827874],[114.12743317980241,30.459774519397744],[114.12763742270677,30.45985630983784],[114.1276294917997,30.45985222493772],[114.12762504577312,30.45984893496641],[114.12761871568127,30.45984280134446],[114.1276187147639,30.45984276277992],[114.12761871065264,30.45984272395687],[114.12761870683686,30.459842685110296],[114.1276187033168,30.45984264624262],[114.12761870009267,30.459842607355274],[114.1276186971644,30.45984256845031],[114.12761869453227,30.459842529528967],[114.12761869219653,30.45984249059319],[114.12761869062959,30.45984246138299],[114.12761868924792,30.45984243216579],[114.12761868801458,30.459842402943504],[114.12761868696664,30.459842373715418],[114.12761868606704,30.459842344483548],[114.12761868535262,30.459842315247634],[114.1276186846472,30.459842276262652],[114.12761868423841,30.4598422372737],[114.12761868411735,30.459842208031027],[114.12761868418148,30.459842178787778],[114.12761868439428,30.45984214954512],[114.12761868479193,30.459842120303886],[114.12761868533845,30.459842091064104],[114.12761868607039,30.459842061827327],[114.12761868729305,30.459842022849955],[114.12761868881246,30.459841983880256],[114.12761869013697,30.459841954658607],[114.12761869164726,30.459841925443666],[114.12761869330551,30.459841896234774],[114.1276186951485,30.459841867033727],[114.12761869785317,30.459841828111287],[114.12761870085355,30.459841789205232],[114.12761870415021,30.459841750316855],[114.12761870680785,30.45984172116234],[114.12761870964978,30.459841692021122],[114.1276187126398,30.459841662890913],[114.12761871581431,30.459841633775135],[114.12761871913659,30.45984160467167],[114.1276187226427,30.45984157558435],[114.12761872756414,30.45984153682401],[114.12761873143927,30.459841507771703],[114.1276187354987,30.459841478738042],[114.12761873970517,30.459841449720273],[114.1276187440955,30.459841420722743],[114.1276187501946,30.459841382087735],[114.12761875658737,30.459841343488495],[114.1276187615654,30.459841314561874],[114.12761876672647,30.459841285659866],[114.12761877385242,30.45984124715648],[114.12761878127093,30.459841208694883],[114.12761878898186,30.459841170276164],[114.1276187949478,30.459841141490177],[114.12761880109602,30.459841112732796],[114.12761880953707,30.459841074429427],[114.12761881826873,30.459841036174858],[114.12761882499953,30.459841007515504],[114.12761883191142,30.45984097888851],[114.12761883896847,30.459840950288267],[114.12761884620718,30.45984092172191],[114.12761885359049,30.45984089318333],[114.12761886115479,30.45984086468046],[114.12761887148058,30.45984082672498],[114.12761888209575,30.459840788829606],[114.12761889299816,30.45984075099531],[114.12761890418855,30.459840713224306],[114.12761891276041,30.459840684936363],[114.1276189215118,30.459840656689476],[114.12761893341859,30.45984061908359],[114.12761894252766,30.459840590921647],[114.12761895181512,30.459840562803826],[114.12761896443511,30.45984052537278],[114.12761897734006,30.459840488014866],[114.12761899052893,30.45984045073134],[114.12761900059769,30.45984042281616],[114.12761901084313,30.459840394949538],[114.12761902122963,30.459840367122013],[114.12761903179226,30.45984033934458],[114.12761904611081,30.459840302375543],[114.12761906070975,30.45984026548902],[114.12761907558946,30.45984022868712],[114.12761908692329,30.45984020113895],[114.12761909843222,30.45984017364542],[114.12761911008049,30.459840146196054],[114.12761912190228,30.459840118802354],[114.12761913386308,30.459840091454122],[114.12761914599669,30.459840064163327],[114.12761915826864,30.459840036918962],[114.12761917071323,30.45984000973357],[114.12761918753536,30.459839973566115],[114.12761920463223,30.459839937495637],[114.12761922200302,30.459839901523655],[114.12761923964709,30.459839865651997],[114.12761925305021,30.459839838811785],[114.12761926662367,30.459839812035433],[114.1276192849469,30.45983977642092],[114.12761930354125,30.459839740911907],[114.12761932237923,30.459839705557602],[114.12761934140767,30.4598396704538],[114.12761936070153,30.45983963545903],[114.12761937533706,30.45983960928203],[114.12761939013714,30.4598395831747],[114.12761940506908,30.459839557123757],[114.12761942016478,30.459839531144077],[114.12761943539132,30.459839505221865],[114.12761945078094,30.459839479372334],[114.12761947151769,30.459839445004132],[114.12761949251376,30.459839410754853],[114.12761951376811,30.459839376625645],[114.12761953528096,30.459839342618263],[114.1276195570496,30.459839308734132],[114.12761957907493,30.459839274974502],[114.12761960135458,30.459839241341257],[114.1276196238882,30.459839207835074],[114.12761964667455,30.45983917445846],[114.12761966971314,30.459839141211692],[114.12761969300254,30.45983910809728],[114.12761971062592,30.45983908334458],[114.12761972840475,30.459839058676106],[114.1276197523174,30.459839025897416],[114.12761977040617,30.45983900139877],[114.12761978864927,30.459838976986457],[114.12761980701526,30.459838952643675],[114.12761982553445,30.45983892838848],[114.12761985042985,30.459838896165554],[114.1276198755689,30.459838864085224],[114.12761990094943,30.4598388321488],[114.12761992013613,30.45983880828728],[114.12761993947205,30.459838784516933],[114.12761996545335,30.45983875294645],[114.12761999167229,30.45983872152413],[114.1276200181291,30.459838690251917],[114.1276200381189,30.459838666892264],[114.12762005825522,30.459838643627762],[114.12762007850922,30.459838620440074],[114.12762009890882,30.45983859734891],[114.12762012630185,30.459838566690046],[114.12762015392593,30.459838536188006],[114.12762018178003,30.45983850584421],[114.1276202098635,30.459838475659282],[114.12762023817392,30.45983844563511],[114.12762026671132,30.459838415772875],[114.12762028825482,30.459838393478226],[114.12762030993893,30.45983837128579],[114.12762033903648,30.459838341834047],[114.12762036835612,30.459838312548637],[114.12762039048407,30.45983829068925],[114.12762041279098,30.459838268894966],[114.12762044271615,30.459838239977927],[114.12762047285979,30.45983821123222],[114.12762050322027,30.45983818265933],[114.12762053379673,30.459838154260165],[114.1276205568633,30.459838133070182],[114.12762058006322,30.45983811199049],[114.12762061117319,30.45983808403135],[114.1276206424942,30.459838056250543],[114.12762067402507,30.45983802864933],[114.12762070576474,30.459838001228317],[114.12762073771073,30.459837973989174],[114.12762076986259,30.459837946933085],[114.12762079410406,30.459837926755934],[114.12762081847264,30.459837906694375],[114.12762085113233,30.459837880100626],[114.12762088399268,30.459837853693976],[114.12762091705243,30.459837827475287],[114.12762095030996,30.459837801446138],[114.12762098376406,30.45983777560717],[114.12762101741312,30.45983774995985],[114.12762105125513,30.45983772450498],[114.1276210767572,30.459837705535097],[114.12762110237843,30.45983768668677],[114.12762112809455,30.459837667936096],[114.12762115392856,30.45983764930772],[114.12762117985655,30.459837630778082],[114.12762120590098,30.459837612372162],[114.12762123203778,30.45983759406543],[114.12762125829046,30.459837575883395],[114.12762129344584,30.459837551807507],[114.12762131992537,30.459837533876286],[114.12762134651851,30.459837516071463],[114.127621382125,30.459837492500615],[114.12762140894068,30.45983747494968],[114.12762143586701,30.45983745752674],[114.12762147191441,30.45983743446725],[114.1276215081359,30.459837411614217],[114.12762153540983,30.45983739460385],[114.12762156279013,30.45983737772347],[114.12762159943968,30.459837355390285],[114.1276216270315,30.459837338771592],[114.12762165472797,30.459837322284315],[114.12762169179473,30.45983730047752],[114.12762172902579,30.459837278882418],[114.12762175705105,30.45983726281962],[114.12762178517671,30.45983724688988],[114.12762182281158,30.459837225829375],[114.12762186060465,30.459837204984552],[114.12762189844541,30.45983718441425],[114.12762193611204,30.45983716423502],[114.12762196445577,30.459837149233792],[114.12762199289281,30.459837134366264],[114.12762203093145,30.45983711472131],[114.1276220691167,30.45983709529137],[114.12762209784599,30.459837080854093],[114.12762212666482,30.459837066552055],[114.12762215555456,30.459837052358374],[114.12762218453251,30.45983703830107],[114.12762222328578,30.459837019739837],[114.12762225243738,30.459837005955855],[114.1276222816744,30.459836992309274],[114.12762231097932,30.45983697877282],[114.12762234036788,30.459836965374453],[114.12762237966366,30.4598369476944],[114.12762241909073,30.45983693023659],[114.12762245864755,30.459836913002057],[114.12762249833237,30.45983689599091],[114.12762253814398,30.45983687920406],[114.12762256808047,30.459836866754415],[114.12762259809404,30.459836854446106],[114.12762262816855,30.459836842250773],[114.12762265831932,30.45983683019728],[114.12762269861896,30.459836814314745],[114.12762273903685,30.459836798660557],[114.12762276942279,30.459836787062446],[114.12762279988081,30.459836775607666],[114.12762284058573,30.45983676052571],[114.1276228815797,30.459836745610463],[114.12762292274388,30.459836730906748],[114.12762296401716,30.45983671643723],[114.1276230053987,30.459836702202328],[114.12762304688614,30.45983668820277],[114.12762308847797,30.459836674439074],[114.12762313017268,30.45983666091164],[114.1276231719677,30.459836647621437],[114.12762321386248,30.459836634568866],[114.12762324534398,30.459836624928133],[114.12762327688561,30.45983661543659],[114.12762330847417,30.45983660606458],[114.12762334012137,30.459836596842106],[114.12762337181421,30.459836587739506],[114.12762340356346,30.45983657878724],[114.12762344597049,30.45983656705118],[114.1276234778297,30.459836558399925],[114.1276235097433,30.459836549899297],[114.12762355236475,30.45983653876698],[114.12762358438253,30.459836530569017],[114.12762361645154,30.459836522522764],[114.12762365927591,30.459836511996816],[114.12762370217928,30.45983650171443],[114.12762373440403,30.459836494154903],[114.12762376667597,30.459836486747943],[114.12762379898444,30.459836479463302],[114.12762383133824,30.45983647233185],[114.12762387453527,30.459836463027482],[114.12762391780132,30.45983645396862],[114.127623950293,30.459836447328122],[114.12762398282547,30.45983644084116],[114.1276240262554,30.459836432397626],[114.12762406974804,30.459836424201143],[114.12762411330148,30.459836416251473],[114.12762414600274,30.45983641044426],[114.12762417874067,30.459836404791663],[114.12762422243705,30.459836397461554],[114.12762426647649,30.45983639033405],[114.12762431066506,30.45983638344345],[114.12762435490482,30.459836376806383],[114.12762439919386,30.4598363704228],[114.12762444353012,30.459836364293214],[114.12762448791192,30.459836358417853],[114.12762453233674,30.459836352796604],[114.12762457680334,30.459836347430212],[114.12762462130945,30.45983634231879],[114.1276246547125,30.459836338644507],[114.12762468813864,30.45983633512997],[114.12762472158133,30.45983633174301],[114.12762475504547,30.45983632851574],[114.12762479969035,30.45983632442604],[114.12762483319266,30.45983632151856],[114.127624866713,30.45983631877129],[114.1276249114282,30.459836315321517],[114.12762494498104,30.459836312894442],[114.12762497854808,30.459836310627463],[114.12762502332265,30.45983630781871],[114.12762506811713,30.45983630526654],[114.12762511292976,30.459836302971063],[114.12762515775864,30.459836300932515],[114.12762520260196,30.459836299150833],[114.12762523624184,30.45983629797529],[114.12762526988877,30.459836296960308],[114.12762531475859,30.459836295821326],[114.12762535963614,30.459836294939453],[114.12762540451882,30.459836294314968],[114.1276254494054,30.45983629394731],[114.12762549429334,30.459836293837213],[114.12762552795887,30.4598362939152],[114.12762556162362,30.459836294154258],[114.12762559528632,30.459836294521654],[114.12762562880437,30.459836295046962],[114.12762567311198,30.459836295951487],[114.12762570633784,30.45983629678661],[114.12762573955806,30.45983629777844],[114.12762577277238,30.45983629889571],[114.127625805979,30.45983630016923],[114.127625850243,30.459836302076255],[114.1276258944916,30.459836304233814],[114.12762593872274,30.459836306641275],[114.1276259829345,30.459836309299394],[114.12762601608061,30.459836311449266],[114.12762604921208,30.459836313755456],[114.12762609336727,30.45983631703821],[114.12762613749685,30.459836320571167],[114.12762618159863,30.45983632435347],[114.12762622567084,30.45983632838541],[114.12762626971127,30.459836332666814],[114.1276263137185,30.459836337197064],[114.12762635769045,30.459836341976207],[114.12762640162542,30.459836347004373],[114.12762643455244,30.4598363509307],[114.12762646745382,30.45983635501222],[114.12762650033469,30.459836359217938],[114.12762653318843,30.459836363578727],[114.12762657695646,30.459836369599568],[114.12762662067755,30.459836375867443],[114.12762665343853,30.459836380723218],[114.12762668616885,30.459836385733446],[114.12762672976561,30.45983639261954],[114.12762677368023,30.45983639981385],[114.12762681766263,30.45983640727993],[114.1276268615865,30.45983641499749],[114.1276269054507,30.45983642296648],[114.1276269492532,30.459836431186268],[114.12762699299216,30.459836439656467],[114.12762702575566,30.459836446165387],[114.12762705847707,30.459836452830707],[114.1276271020486,30.45983646192548],[114.12762713468335,30.45983646890252],[114.12762716727306,30.45983647603511],[114.12762721066561,30.45983648575225],[114.12762724316296,30.459836493195315],[114.12762727561211,30.459836500793024],[114.12762731881337,30.45983651112993],[114.12762736193498,30.459836521713676],[114.12762740497458,30.45983653254432],[114.12762743720252,30.45983654082061],[114.12762746937713,30.459836549250873],[114.12762751220447,30.45983656069578],[114.1276275442704,30.459836569432436],[114.12762757628029,30.459836578321926],[114.1276276188839,30.459836590377932],[114.12762766139517,30.459836602677782],[114.12762770381161,30.459836615220507],[114.12762774613181,30.459836628005814],[114.12762778835355,30.459836641033036],[114.12762783047522,30.459836654301775],[114.12762787249478,30.45983666781129],[114.12762791441045,30.45983668156107],[114.12762795622102,30.459836695550614],[114.12762799792417,30.459836709778944],[114.12762802913298,30.459836720599355],[114.12762806027291,30.459836731568572],[114.12762810169936,30.459836746391844],[114.1276281430122,30.459836761451868],[114.12762818420902,30.459836776748535],[114.12762821503378,30.459836788367827],[114.12762824578444,30.45983680013411],[114.12762828668501,30.459836816018285],[114.12762832746405,30.459836832135693],[114.12762836811932,30.459836848486344],[114.12762840864899,30.45983686506971],[114.12763036571418,30.45983774033913],[114.12767012739805,30.45985553030195],[114.12777511998902,30.45990500231548],[114.12779109555976,30.459912477348286],[114.12791443773256,30.45997081696305],[114.12804831801135,30.460034140516466],[114.12812695665549,30.46007133529421],[114.12817294740331,30.46009199515834],[114.12820544743163,30.460104998116936],[114.12822395736802,30.460111308702093],[114.12825057198464,30.460118745368774],[114.12828752276148,30.460126209644613],[114.12830984742014,30.46012992768818],[114.12832069121107,30.46013139808863],[114.12833517285914,30.460132563528205],[114.1283542702872,30.46013382655505],[114.12836998873179,30.46013406342985],[114.12837846217042,30.460133860706673],[114.12838442976265,30.46013334116843],[114.12839168437102,30.46009575927462],[114.12834998644269,30.460093641808722],[114.12828941210206,30.460081463918776],[114.12824893713191,30.460074122081945],[114.128216315597,30.46006719885561],[114.12780441489443,30.459881181622045],[114.12769332402713,30.45982907167044],[114.12765769404636,30.45981272951338],[114.12742849592884,30.459716222464763],[114.1272364659745,30.459626098477848],[114.12718111350233,30.45960016361023],[114.12709825116885,30.459561251063718],[114.12699977087117,30.45951488076466],[114.12691108838536,30.45947279517776],[114.12685896736635,30.45944847273632],[114.12683688366135,30.459487762763597]]],[[[114.13061898402532,30.46122158770834],[114.13050093394426,30.4611659455433],[114.13011032314509,30.460982830355253],[114.12948509785771,30.460676729562238],[114.12915080419201,30.46052159776799],[114.12910905022575,30.460499808444705],[114.12908108043383,30.460480458154805],[114.12906609730352,30.46046825584629],[114.12904116432249,30.460440950193757],[114.1290302547096,30.46042482415912],[114.12902839379315,30.46042077514532],[114.12885361666702,30.460284356806863],[114.12885456445991,30.460289597977344],[114.12885699706965,30.460299440816254],[114.12886055715941,30.460311722664137],[114.1288644893895,30.460324983381874],[114.12886998531407,30.460339927505565],[114.12887792622648,30.46035876025066],[114.12888132456803,30.460365733766483],[114.12888470366988,30.46037169567034],[114.12888717307217,30.460376077774853],[114.12889053410137,30.460381787712556],[114.12889339870573,30.460386617190853],[114.12889489628813,30.460389215954844],[114.12889742846474,30.460394085724353],[114.12893152468206,30.460438055711222],[114.1289487963196,30.4604560740338],[114.12895919300571,30.46046399981868],[114.12896128462303,30.460460423906998],[114.1289652252416,30.46045728503223],[114.1289692683392,30.460455341689837],[114.12897714389003,30.460453956628445],[114.12898001822931,30.460454375339527],[114.12898246998583,30.460455143136826],[114.12898620474245,30.46045904078643],[114.12900059349039,30.460471954477043],[114.12901977307614,30.46048624243595],[114.12903720594392,30.460497358755884],[114.12904224205833,30.460499925867214],[114.12906283040171,30.460510409674747],[114.1290906202127,30.460524959988184],[114.1294632289959,30.460700150112054],[114.12947592210453,30.460706120207483],[114.12947208127396,30.46072074536206],[114.12951290148226,30.460741128494877],[114.12971957164879,30.460845111851004],[114.12973316140021,30.460850578955355],[114.12973317110324,30.46085054094337],[114.12973318087785,30.460850502945007],[114.12973319072499,30.460850464960792],[114.12973320064394,30.460850426990543],[114.12973321063416,30.460850389034093],[114.12973322069686,30.460850351092297],[114.12973323083084,30.460850313164645],[114.12973324103676,30.460850275251637],[114.12973325131463,30.460850237352837],[114.12973326166396,30.460850199468855],[114.12973327208536,30.4608501615997],[114.12973328257823,30.460850123745423],[114.12973329314272,30.460850085906138],[114.12973330377868,30.460850048081735],[114.12973331448659,30.460850010272384],[114.12973332526539,30.460849972478595],[114.12973333611637,30.46084993469997],[114.12973334703862,30.460849896937077],[114.12973335803225,30.460849859189462],[114.12973336909747,30.46084982145753],[114.12973338023383,30.46084978374155],[114.12973339144177,30.460849746041255],[114.12973340272111,30.46084970835714],[114.12973341407171,30.460849670689047],[114.12973342549365,30.460849633037082],[114.12973343698654,30.46084959540154],[114.12973344855115,30.460849557782296],[114.12973346018664,30.460849520179355],[114.12973347189309,30.46084948259334],[114.12973348367112,30.460849445023626],[114.12973349552028,30.4608494074709],[114.12973350743991,30.46084936993516],[114.12973351943081,30.4608493324164],[114.12973353149319,30.460849294914627],[114.12973354362605,30.46084925742984],[114.12973355582992,30.460849219962604],[114.12973356810481,30.460849182512636],[114.12973358045053,30.460849145080452],[114.12973359286681,30.46084910766571],[114.12973360535426,30.460849070268512],[114.12973361791227,30.46084903288939],[114.12973363054132,30.4608489955281],[114.12973364324083,30.4608489581847],[114.1297336560108,30.460848920859487],[114.12973366885136,30.460848883552394],[114.12973368176294,30.460848846263648],[114.12973369474476,30.460848808993372],[114.12973370779727,30.46084877174144],[114.12973372092013,30.460848734508314],[114.12973373411369,30.460848697294107],[114.12973374737749,30.460848660098307],[114.12973376071149,30.460848622921482],[114.12973377411633,30.460848585763742],[114.12973378759119,30.460848548625098],[114.12973380113627,30.460848511505596],[114.12973381475169,30.46084847440558],[114.12973382843747,30.460848437324884],[114.1297338421928,30.460848400263618],[114.12973385601907,30.460848363222006],[114.12973386991467,30.460848326200225],[114.12973388388117,30.460848289198104],[114.12973389791702,30.460848252215982],[114.12973391202287,30.460848215253858],[114.12973392619884,30.460848178311903],[114.1297339404446,30.460848141390173],[114.12973395476035,30.460848104488615],[114.12973396914592,30.460848067607564],[114.12973398360111,30.460848030747087],[114.12973399812635,30.460847993907294],[114.12973401272126,30.46084795708806],[114.12973402738538,30.460847920289403],[114.12973404211985,30.460847883512105],[114.12973405692318,30.460847846755435],[114.1297340717962,30.460847810019956],[114.12973408673935,30.460847773305897],[114.12973410175135,30.460847736613093],[114.1297341168327,30.460847699941535],[114.12973413198372,30.460847663291453],[114.12973414720406,30.460847626663142],[114.12973416249373,30.46084759005659],[114.12973417785264,30.46084755347163],[114.12973419328075,30.460847516908828],[114.12973420877796,30.46084748036779],[114.12973422434438,30.460847443848913],[114.12973423997992,30.460847407352198],[114.12973425568457,30.460847370877982],[114.12973427145819,30.46084733442604],[114.12973428730056,30.460847297996434],[114.12973430321183,30.46084726158967],[114.12973431919244,30.460847225205406],[114.12973433524168,30.460847188843985],[114.12973435135945,30.46084715250558],[114.12973436754645,30.460847116189903],[114.12973438380163,30.46084707989758],[114.1297344001257,30.460847043628274],[114.12973441651842,30.46084700738232],[114.12973443297979,30.460846971159608],[114.12973444950947,30.460846934960653],[114.1297344661079,30.46084689878505],[114.12973448277431,30.4608468626332],[114.12973449950948,30.460846826504934],[114.12973451631328,30.460846790400765],[114.12973453318486,30.46084675432063],[114.12973455012506,30.460846718264136],[114.12973456713335,30.460846682232365],[114.12973458420971,30.4608466462244],[114.12973460135419,30.460846610240875],[114.12973461856694,30.460846574281785],[114.12973463584757,30.460846538347475],[114.12973465319605,30.460846502437654],[114.12973467061272,30.460846466552436],[114.12973468809724,30.46084643069223],[114.1297347056494,30.460846394856965],[114.12973472326964,30.46084635904671],[114.1297347409575,30.46084632326146],[114.12973475871311,30.46084628750184],[114.12973477653622,30.460846251766938],[114.12973479442687,30.460846216057842],[114.12973481238535,30.460846180374087],[114.12973483041137,30.46084614471608],[114.12973484850464,30.460846109083704],[114.12973486666525,30.460846073477246],[114.12973488489334,30.460846037896466],[114.12973490318895,30.460846002341892],[114.12973492155162,30.4608459668134],[114.12973493998136,30.460845931310942],[114.12973495847815,30.460845895834915],[114.12973497704223,30.460845860385252],[114.12973499567349,30.46084582496201],[114.1297350143717,30.46084578956538],[114.12973503313673,30.460845754195446],[114.12973505196894,30.460845718852177],[114.12973507086788,30.460845683535787],[114.12973508983355,30.460845648246394],[114.12973510886603,30.460845612984055],[114.12973512796502,30.460845577748707],[114.12973514713129,30.460845542540756],[114.1297351663636,30.4608455073602],[114.12973518566251,30.460845472207044],[114.12973520502815,30.460845437081385],[114.12973522447447,30.460845401957243],[114.12973524398728,30.46084536686072],[114.12973526356659,30.460845331792104],[114.1297352832125,30.46084529675162],[114.12973530292444,30.46084526173927],[114.12973532270277,30.460845226754653],[114.12973534254714,30.46084519179834],[114.1297353624581,30.460845156870626],[114.12973538243492,30.460845121971378],[114.12973540247751,30.460845087100207],[114.12973542258638,30.460845052257852],[114.12973544276153,30.46084501744426],[114.12973546300248,30.46084498265943],[114.12973548330854,30.460844947903578],[114.12973550368125,30.46084491317649],[114.12973552411918,30.460844878478678],[114.12973554462316,30.460844843809905],[114.12973556519239,30.460844809170403],[114.1297355858273,30.46084477456029],[114.1297356065278,30.460844739979848],[114.12973562729402,30.460844705428787],[114.12973564812548,30.46084467090734],[114.12973566902218,30.460844636415676],[114.12973568998424,30.460844601953795],[114.12973571101132,30.460844567521697],[114.12973573210387,30.460844533120067],[114.12973575326133,30.460844498747935],[114.12973577448392,30.460844464406215],[114.12973579577152,30.46084443009507],[114.1297358171238,30.460844395814338],[114.12973583854122,30.46084436156379],[114.1297358600239,30.460844327343764],[114.12973588157078,30.460844293154544],[114.12973590318224,30.46084425899602],[114.12973592485882,30.460844224868357],[114.12973594659975,30.46084419077167],[114.12973596840513,30.460844156706084],[114.1297359902752,30.460844122671467],[114.12973601220969,30.460844088668065],[114.12973603420832,30.460844054696032],[114.12973605627127,30.46084402075561],[114.12973607839879,30.46084398684639],[114.12973610059021,30.460843952968883],[114.12973612284573,30.460843919123104],[114.12973614516581,30.460843885309092],[114.12973616754921,30.460843851526803],[114.12973618999683,30.460843817776457],[114.12973621250835,30.46084378405834],[114.12973623508363,30.460843750372167],[114.12973625772257,30.460843716718458],[114.12973628042562,30.460843683096805],[114.12973630319165,30.46084364950778],[114.1297363260219,30.460843615951042],[114.12973634891513,30.46084358242699],[114.12973637187213,30.46084354893551],[114.12973639489235,30.460843515477],[114.1297364179762,30.460843482051338],[114.12973644112304,30.46084344865843],[114.12973646433308,30.460843415298715],[114.12973648760621,30.46084338197197],[114.12973651094244,30.46084334867893],[114.12973653434175,30.460843315418522],[114.12973655780404,30.460843282192165],[114.1297365813292,30.460843248998835],[114.12973660491721,30.460843215839155],[114.12973662856798,30.46084318271313],[114.12973665228115,30.46084314962104],[114.12973667605745,30.460843116562604],[114.12973669989564,30.460843083538276],[114.12973672379708,30.46084305054811],[114.12973674776023,30.460843017591717],[114.12973677178626,30.46084298466965],[114.12973679587434,30.460842951782038],[114.12973682002496,30.460842918928645],[114.1297368442373,30.460842886109816],[114.12973686851215,30.46084285332538],[114.1297368928491,30.460842820575735],[114.12973691724753,30.460842787860877],[114.12973694170815,30.460842755180927],[114.12973696623061,30.460842722535595],[114.12973699081468,30.46084268992551],[114.12973701546082,30.460842657350554],[114.12973704016846,30.46084262481056],[114.12973706493784,30.46084259230604],[114.1297370897685,30.460842559836937],[114.12973711466043,30.460842527402853],[114.12973713961374,30.460842495004698],[114.12973716462866,30.46084246264207],[114.12973718970476,30.46084243031509],[114.12973721484202,30.46084239802409],[114.1297372400401,30.460842365768798],[114.12973726529978,30.46084233354943],[114.12973729061983,30.460842301366167],[114.12973731600107,30.460842269219334],[114.12973734144309,30.46084223710821],[114.12973736694595,30.46084220503398],[114.12973739250975,30.460842172995626],[114.12973741813359,30.460842140994103],[114.12973744381833,30.460842109028967],[114.1297374695639,30.460842077100555],[114.12973749536906,30.460842045209155],[114.12973752123504,30.460842013354196],[114.12973754716137,30.460841981536305],[114.12973757314764,30.46084194975559],[114.12973759919439,30.460841918012],[114.12973762530102,30.460841886305307],[114.1297376514678,30.460841854635913],[114.1297376776943,30.46084182300403],[114.12973770393836,30.460841791460155],[114.12973773024235,30.46084175995368],[114.12973775660524,30.460841728484848],[114.12973778302835,30.460841697053365],[114.12973780951026,30.460841665659746],[114.1297378360514,30.460841634303875],[114.12973786265248,30.460841602985642],[114.12973788931195,30.4608415717055],[114.12973791603115,30.46084154046334],[114.12973794280876,30.46084150925915],[114.12973796964584,30.460841478093283],[114.1297379965416,30.460841446965734],[114.12973802349589,30.46084141587616],[114.1297380505093,30.46084138482542],[114.12973807758071,30.460841353812825],[114.12973810471146,30.460841322838952],[114.12973813190021,30.460841291903677],[114.1297381591477,30.460841261007296],[114.12973818645354,30.46084123014957],[114.12973821381759,30.46084119933091],[114.12973824123986,30.460841168551244],[114.12973826872056,30.460841137810586],[114.12973829625902,30.46084110710921],[114.12973832385524,30.460841076447178],[114.12973835150966,30.46084104582449],[114.12973837922208,30.46084101524092],[114.1297384069918,30.460840984696862],[114.12973843481961,30.460840954192545],[114.12973846270495,30.460840923727798],[114.12973849064808,30.460840893302965],[114.12973851864791,30.460840862917873],[114.12973854670584,30.46084083257269],[114.1297385748211,30.460840802267484],[114.12973860299297,30.460840772002296],[114.1297386312228,30.460840741777428],[114.12973865950896,30.460840711592525],[114.12973868785284,30.460840681448328],[114.12973871625313,30.460840651344103],[114.1297387447104,30.460840621280703],[114.12973877322459,30.46084059125772],[114.12973880179534,30.460840561275567],[114.12973883042291,30.460840531333726],[114.12973885910677,30.46084050143316],[114.12973888784748,30.460840471573245],[114.12973891664413,30.460840441753987],[114.1297389454974,30.460840411976232],[114.12973897440673,30.46084038223947],[114.12973900337235,30.460840352543872],[114.1297390323938,30.460840322889613],[114.12973906147154,30.460840293276515],[114.12973909060543,30.460840263705265],[114.12973911979493,30.460840234175237],[114.1297391490399,30.460840204686885],[114.12973917834107,30.46084017524021],[114.12973920769737,30.46084014583527],[114.12973923710942,30.460840116472177],[114.12973926657692,30.460840087151162],[114.12973929609981,30.460840057871987],[114.12973932567807,30.46084002863506],[114.12973935531136,30.46083999944016],[114.1297393849997,30.46083997028755],[114.12973941474364,30.460839941177138],[114.12973944454214,30.46083991210931],[114.12973947439559,30.4608398830839],[114.12973950430361,30.460839854100907],[114.12973953426678,30.46083982516067],[114.12973956428452,30.46083979626319],[114.12973959435695,30.460839767408473],[114.1297396244833,30.4608397385964],[114.12973965466446,30.460839709827706],[114.12973968490007,30.460839681101834],[114.12973971518971,30.460839652418773],[114.12973974553346,30.46083962377926],[114.12973977593147,30.460839595182573],[114.12973980638337,30.460839566629723],[114.12973983688916,30.4608395381198],[114.12973986744888,30.460839509653717],[114.12973989806237,30.460839481231133],[114.12973992872953,30.46083945285216],[114.12973995945013,30.460839424516685],[114.1297399902242,30.46083939622522],[114.12974002105238,30.46083936797777],[114.12974005193287,30.460839339773983],[114.12974008286739,30.46083931161438],[114.12974011385488,30.460839283498785],[114.12974014489504,30.460839255427377],[114.12974017598887,30.460839227400314],[114.12974020713499,30.460839199417492],[114.12974023833446,30.460839171478966],[114.12974026958672,30.460839143585194],[114.12974030089127,30.460839115735656],[114.12974033224835,30.460839087930925],[114.12974036365799,30.460839060170827],[114.12974039512015,30.460839032455368],[114.12974042663507,30.46083900478506],[114.12974045820164,30.460838977159444],[114.12974048982052,30.460838949578918],[114.12974052149148,30.460838922043603],[114.1297405532143,30.46083889455304],[114.12974058498955,30.46083886710796],[114.12974061681597,30.46083883970826],[114.12974064869458,30.460838812353654],[114.12974068062474,30.460838785044647],[114.12974071260616,30.460838757781246],[114.12974074463935,30.46083873056328],[114.1297407767237,30.460838703391087],[114.12974080885945,30.46083867626456],[114.12974084104661,30.460838649183746],[114.12974087328485,30.460838622148994],[114.12974090557401,30.460838595160133],[114.12974093791401,30.460838568217273],[114.12974097030511,30.4608385413207],[114.12974100274688,30.46083851446996],[114.12974103523953,30.460838487665733],[114.12974106778245,30.460838460907624],[114.1297411003762,30.460838434195974],[114.12974113301986,30.460838407530893],[114.12974116571438,30.460838380912445],[114.12974119845916,30.46083835434051],[114.12974123125375,30.46083832781492],[114.12974126409861,30.460838301336356],[114.12974129699319,30.46083827490448],[114.12974132993791,30.46083824851963],[114.12974136293302,30.460838222181806],[114.12974139597692,30.46083819589084],[114.12974142907109,30.460838169647076],[114.12974146221428,30.460838143450506],[114.1297414954073,30.460838117301023],[114.12974152864933,30.46083809119908],[114.12974156194095,30.460838065144387],[114.12974159528159,30.460838039137183],[114.12974162867171,30.46083801317763],[114.12974166211028,30.460837987265446],[114.12974169559821,30.460837961400973],[114.12974172913461,30.46083793558444],[114.12974176271968,30.460837909815503],[114.12974179635388,30.46083788409445],[114.12974183017747,30.460837858313894],[114.12974186405006,30.460837832581735],[114.12974189797144,30.46083780689825],[114.12974193194164,30.460837781263045],[114.12974196596016,30.46083775567652],[114.12974200002715,30.4608377301389],[114.1297420341427,30.460837704649958],[114.12974206830681,30.46083767920969],[114.12974210251882,30.46083765381833],[114.12974213677917,30.460837628476046],[114.12974217108763,30.460837603182895],[114.1297422054433,30.46083757793871],[114.12974223984742,30.460837552743712],[114.12974227429932,30.46083752759807],[114.12974230879898,30.460837502501565],[114.12974234334588,30.460837477454763],[114.12974237794029,30.460837452457156],[114.12974241258215,30.460837427509244],[114.12974244727121,30.460837402610863],[114.12974248200759,30.46083737776185],[114.12974251679118,30.46083735296271],[114.12974255162163,30.460837328213604],[114.12974258649906,30.46083730351421],[114.1297426214229,30.460837278864684],[114.12974265639365,30.460837254265208],[114.12974269141121,30.460837229715832],[114.12974272647509,30.460837205216492],[114.12974276158572,30.460837180767374],[114.12974279674265,30.46083715636853],[114.12974283194544,30.460837132019893],[114.1297428671945,30.460837107722163],[114.12974290248987,30.460837083474356],[114.12974293783107,30.460837059277164],[114.1297429732178,30.460837035130705],[114.1297430086505,30.460837011034858],[114.12974304412889,30.460836986989623],[114.12974307965314,30.460836962995113],[114.12974311522257,30.460836939051614],[114.12974315083704,30.46083691515902],[114.12974318649712,30.460836891317378],[114.12974322220249,30.46083686752686],[114.12974325795291,30.460836843787302],[114.12974329374815,30.460836820099036],[114.12974332958821,30.46083679646207],[114.12974336547343,30.460836772876398],[114.12974340140303,30.460836749342025],[114.12974343737723,30.460836725859007],[114.1297434733959,30.460836702427624],[114.12974350945919,30.460836679047713],[114.12974354556673,30.460836655719433],[114.12974358171827,30.460836632443026],[114.12974361791409,30.460836609218145],[114.12974365415394,30.46083658604524],[114.12974369043783,30.460836562924147],[114.12974372676494,30.46083653985492],[114.12974376313619,30.460836516837844],[114.12974379955125,30.46083649387269],[114.12974383600931,30.46083647095963],[114.12974387251109,30.46083644809918],[114.12974390905619,30.460836425290427],[114.1297439456444,30.46083640253416],[114.12974398227597,30.460836379830443],[114.12974401895045,30.460836357179108],[114.12974405566725,30.460836334580154],[114.12974409242761,30.46083631203392],[114.12974412923009,30.460836289540236],[114.12974416607557,30.460836267099154],[114.12974420296361,30.460836244710915],[114.12974423989408,30.46083622237539],[114.12974427686679,30.46083620009298],[114.12974431388136,30.460836177863303],[114.12974435093882,30.460836155686337],[114.12974438803758,30.460836133563006],[114.12974442517869,30.460836111492398],[114.1297444623613,30.460836089474903],[114.12974449958558,30.46083606751082],[114.1297445368516,30.460836045599965],[114.12974457415919,30.460836023742743],[114.12974461150816,30.460836001938468],[114.12974464889825,30.46083598018794],[114.12974468632983,30.46083595849071],[114.12974472380219,30.460835936847275],[114.12974476131606,30.460835915257412],[114.12974479887022,30.460835893721246],[114.12974483646546,30.46083587223899],[114.1297448741012,30.460835850810366],[114.12974491177792,30.460835829435553],[114.12974494949492,30.460835808114876],[114.12974498725211,30.460835786848175],[114.12974502504957,30.46083576563545],[114.12974506288778,30.460835744476864],[114.12974510076567,30.460835723372433],[114.1297451386832,30.460835702322193],[114.12974517664112,30.46083568132656],[114.12974521463842,30.460835660384785],[114.12974525267579,30.460835639497663],[114.12974529075241,30.46083561866475],[114.12974532886874,30.46083559788666],[114.12974536702389,30.46083557716289],[114.12974540521888,30.46083555649383],[114.12974544345278,30.46083553587977],[114.12974548172573,30.46083551531991],[114.12974552003749,30.460835494815225],[114.12974555838827,30.46083547436525],[114.12974559677788,30.460835453970162],[114.12974563520561,30.46083543363007],[114.12974567367226,30.460835413344867],[114.1297457121775,30.460835393115058],[114.12974575072053,30.460835372939968],[114.1297457893018,30.460835352820215],[114.12974582792164,30.46083533275558],[114.12974586657919,30.46083531274645],[114.12974590527472,30.460835292792723],[114.12974594400761,30.46083527289411],[114.12974598277896,30.460835253051005],[114.1297460215873,30.46083523326381],[114.12974606043319,30.46083521353156],[114.12974609931645,30.46083519385533],[114.12974613823725,30.46083517423473],[114.12974617719517,30.4608351546697],[114.1297462161895,30.460835135160465],[114.12974625522142,30.460835115706914],[114.12974629429009,30.4608350963095],[114.12974633339519,30.460835076968],[114.12974637253728,30.46083505768229],[114.12974981756975,30.460834461321785],[114.12975222151356,30.460834512012003],[114.12975434025654,30.460834914699298],[114.12976090056145,30.46083708761061],[114.12982096010788,30.46086824150853],[114.1298175011319,30.46086928551613],[114.12981050537311,30.460871066987227],[114.12979936369597,30.460874063653247],[114.12979263196452,30.460875893396935],[114.12979259448348,30.460875904666846],[114.12979255697874,30.460875915876738],[114.12979251945042,30.460875927026613],[114.12979248189846,30.46087593811624],[114.12979244432323,30.460875949145738],[114.12979240672453,30.460875960115096],[114.12979236910267,30.460875971023988],[114.12979233145778,30.460875981872633],[114.12979229378993,30.460875992661375],[114.12979225609931,30.460876003389412],[114.12979221838631,30.46087601405692],[114.12979218065027,30.46087602466435],[114.129792142892,30.460876035211367],[114.12979210511146,30.46087604569751],[114.12979206730893,30.460876056123237],[114.12979202948424,30.4608760664886],[114.12979199163789,30.460876076793205],[114.12979195376964,30.46087608703705],[114.12979191587971,30.460876097220535],[114.12979187796786,30.460876107343093],[114.1297918400349,30.460876117405228],[114.12979180208131,30.460876127406152],[114.12979176410579,30.4608761373466],[114.12979172610963,30.460876147226003],[114.1297916880928,30.460876157044474],[114.12979165005488,30.460876166802358],[114.12979161199628,30.460876176498967],[114.12979157391773,30.460876186134705],[114.12979153581874,30.460876195709453],[114.12979149769932,30.460876205223098],[114.12979145955968,30.4608762146757],[114.12979142140031,30.460876224067487],[114.12979138322119,30.460876233397716],[114.12979134502208,30.460876242666952],[114.12979130680324,30.46087625187509],[114.12979126856567,30.460876261021607],[114.12979123030844,30.46087627010726],[114.12979119203195,30.460876279131575],[114.12979115373625,30.46087628809456],[114.1297911154223,30.460876296995988],[114.12979107708894,30.46087630583626],[114.12979103873741,30.460876314614968],[114.12979100036694,30.46087632333212],[114.12979096197827,30.460876331988217],[114.12979092357159,30.46087634058237],[114.12979088514618,30.460876349115182],[114.1297908467035,30.46087635758633],[114.12979080824222,30.460876365996082],[114.12979076976369,30.460876374343997],[114.12979073126733,30.46087638263046],[114.12979069275352,30.460876390855084],[114.12979065422287,30.460876399018087],[114.12979061567431,30.46087640711925],[114.12979057710872,30.460876415158793],[114.12979053852645,30.460876423136554],[114.12979049992747,30.46087643105247],[114.12979046131183,30.460876438906364],[114.12979042267939,30.460876446698474],[114.12979038403058,30.46087645442891],[114.12979034536556,30.460876462097218],[114.12979030668455,30.460876469703678],[114.12979026798719,30.46087647724801],[114.12979022927402,30.4608764847305],[114.12979019054524,30.46087649215097],[114.12979015180063,30.460876499509308],[114.12979011304073,30.460876506805462],[114.12979007426503,30.4608765140396],[114.12979003547437,30.46087652121161],[114.1297899966687,30.46087652832154],[114.1297899578484,30.4608765353694],[114.12978991901268,30.460876542354843],[114.12978988016253,30.46087654927827],[114.12978984129761,30.460876556139226],[114.12978980241817,30.460876562937994],[114.12978976352488,30.46087656967457],[114.1297897246173,30.46087657634862],[114.12978968569543,30.46087658296054],[114.12978964675968,30.460876589510043],[114.12978960781008,30.46087659599724],[114.12978956884733,30.460876602422026],[114.1297895298704,30.460876608784165],[114.12978949088074,30.460876615083944],[114.12978945187746,30.460876621321365],[114.12978941286111,30.460876627496027],[114.12978937383161,30.460876633608557],[114.12978933478963,30.460876639658554],[114.12978929974295,30.460876645027668],[114.12978926468301,30.460876650333397],[114.12978922961057,30.46087665557597],[114.12978919452598,30.460876660755215],[114.12978915942948,30.460876665871133],[114.12978912432025,30.460876670923497],[114.12978908919935,30.460876675912814],[114.12978905406663,30.460876680838524],[114.12978901892234,30.46087668570079],[114.12978898376646,30.46087669049967],[114.12978894859923,30.460876695234937],[114.12978891342065,30.460876699906763],[114.12978887823151,30.4608767045152],[114.12978884303136,30.460876709059974],[114.12978880782032,30.460876713541246],[114.12978877259859,30.460876717958843],[114.12978873736677,30.46087672231289],[114.12978870212451,30.460876726603264],[114.12978866687213,30.460876730830133],[114.12978863160942,30.460876734993338],[114.12978859633718,30.460876739092754],[114.1297885610554,30.460876743128615],[114.1297885257642,30.46087674710069],[114.12978849046311,30.46087675100932],[114.12978845515353,30.460876754853825],[114.1297884198344,30.4608767586346],[114.12978838450641,30.460876762351702],[114.12978834916967,30.46087676600508],[114.12978831382443,30.460876769594435],[114.12978827847101,30.46087677312041],[114.12978824310873,30.460876776582257],[114.12978820773874,30.460876779980143],[114.12978817236103,30.46087678331424],[114.12978813697501,30.460876786584727],[114.12978810158127,30.460876789790916],[114.12978806618005,30.46087679293337],[114.12978803077156,30.46087679601181],[114.12978799535614,30.460876799026465],[114.129787959933,30.46087680197716],[114.12978792450339,30.4608768048639],[114.1297878890673,30.460876807686564],[114.12978785362394,30.460876810445217],[114.12978781817445,30.460876813140075],[114.12978778271884,30.46087681577081],[114.12978774725674,30.460876818337578],[114.12978771178896,30.46087682084039],[114.12978767631517,30.46087682327874],[114.1297876408357,30.460876825653457],[114.1297876053509,30.46087682796388],[114.12978756986041,30.460876830210342],[114.12978753436492,30.460876832392962],[114.12978749886432,30.460876834510994],[114.12978746335861,30.460876836565344],[114.12978742784836,30.460876838555286],[114.12978739233343,30.460876840481205],[114.12978735681386,30.46087684234317],[114.12978732129055,30.460876844140657],[114.12978728576235,30.460876845874353],[114.1297872502305,30.460876847543748],[114.12978721469503,30.460876849148953],[114.12978717915546,30.460876850690084],[114.12978714361304,30.460876852167026],[114.12978710806676,30.460876853579666],[114.12978707251695,30.46087685492817],[114.12978703696456,30.460876856212714],[114.12978700140896,30.460876857432783],[114.12978696585073,30.46087685858849],[114.1297869302897,30.460876859680464],[114.12978689472659,30.460876860707906],[114.12978685916099,30.46087686167121],[114.12978682359321,30.46087686257039],[114.12978678802351,30.460876863405257],[114.12978675245176,30.460876864175933],[114.12978671687853,30.460876864882483],[114.12978668130383,30.46087686552455],[114.12978664572775,30.460876866102655],[114.12978661015008,30.46087686661628],[114.12978657457182,30.460876867065778],[114.12978653899222,30.460876867451077],[114.12978650341203,30.46087686777208],[114.12978646783127,30.460876868028766],[114.12978643224972,30.460876868221323],[114.12978639666827,30.460876868349516],[114.12978636108646,30.460876868413628],[114.12978632550501,30.460876868413372],[114.12978628992353,30.460876868348816],[114.12978625434218,30.46087686822024],[114.1297862187615,30.46087686802724],[114.12978618318125,30.460876867770214],[114.12978614760226,30.46087686744854],[114.1297861120236,30.4608768670629],[114.12978607644618,30.460876866613123],[114.12978604086999,30.460876866098697],[114.1297860052954,30.46087686552025],[114.1297859697224,30.460876864877665],[114.12978593415062,30.460876864170597],[114.1297858985809,30.460876863399566],[114.12978586301378,30.460876862564227],[114.12978582744789,30.460876861664687],[114.12978579188496,30.460876860700843],[114.12978575632417,30.46087685967292],[114.12978572076631,30.46087685858057],[114.12978568521085,30.460876857424477],[114.12978564965854,30.460876856203736],[114.12978561410942,30.460876854918965],[114.12978557856312,30.460876853569776],[114.12978097293913,30.46087611735825],[114.1300935983725,30.46104443000447],[114.13009199872872,30.46104334989119],[114.130090791552,30.461042350214225],[114.13008921072786,30.461040981833918],[114.13008820653799,30.461039436169777],[114.13008735704764,30.461037929848207],[114.13008647082899,30.461035547741947],[114.13008592970188,30.461032379083246],[114.13008728405228,30.461027226146687],[114.13008886666402,30.46102387032586],[114.13009347073576,30.461014625663626],[114.13011271804483,30.46102349111015],[114.13012760768065,30.461030570401856],[114.13015916719633,30.461045895195372],[114.13018302670666,30.46105712354523],[114.13024107267701,30.46108509338401],[114.13024120877141,30.46108524890782],[114.1302412605094,30.461085359787887],[114.13024127308707,30.461085493710335],[114.13024125252475,30.461085677628468],[114.13022792770946,30.461107473223144],[114.13031431801001,30.46114830931335],[114.13034715161534,30.46116368102704],[114.13035959409316,30.46114331674733],[114.13036020068445,30.4611428650941],[114.13036047855408,30.461142763543243],[114.1303608793548,30.461142688448973],[114.13036167174162,30.461142769382395],[114.13036864345355,30.461146033323974],[114.13045399634161,30.4611861869654],[114.13056643045437,30.46123894413378],[114.13059972652555,30.461254650283248],[114.13061898402532,30.46122158770834]]],[[[114.13045166013256,30.461508860656757],[114.13045113236419,30.46150861167757],[114.13038902579527,30.46147940019146],[114.13027236933345,30.461424469991137],[114.1302449042618,30.461411600882368],[114.13018342073529,30.461382257284672],[114.13011697073304,30.461350836168783],[114.13004360269801,30.46131650873754],[114.13000090329518,30.46129648487721],[114.12996200134592,30.461278417298047],[114.12993616252682,30.46126615249132],[114.12990291072697,30.46125045788717],[114.12988256235427,30.46124067768216],[114.12987179255991,30.461235567370608],[114.12986722261299,30.461233910100944],[114.12986195939581,30.461232654867565],[114.12985731372267,30.461231510175065],[114.12983345211502,30.461224729028125],[114.1298274737889,30.46122221362121],[114.12972906729911,30.46118220384203],[114.12968234577546,30.461163346656495],[114.12965518684442,30.461152377241042],[114.12962904070915,30.46114174420687],[114.12959327721632,30.461127199295024],[114.12956497393517,30.461115876272295],[114.12954623041804,30.461108177464922],[114.12951793337623,30.46109663971318],[114.12950043861774,30.4610896689159],[114.12948335261126,30.46108282129353],[114.12946834944965,30.461076664409667],[114.12946210779079,30.461074079083684],[114.12945622023722,30.46107146514424],[114.12943923937732,30.461063403164466],[114.12941961991667,30.461054257135935],[114.12934096022525,30.46101713680265],[114.1292531611024,30.460975781088365],[114.12915358364316,30.460928821850516],[114.12914446705692,30.4609245336199],[114.12911195461106,30.460909240500985],[114.12906661686613,30.460887931138817],[114.12900696519473,30.460859674520563],[114.12899472493436,30.46085400383098],[114.12897711937539,30.460845605200472],[114.12895897965275,30.46083701325433],[114.1289346889824,30.460825927957725],[114.12891435148165,30.460816098825497],[114.12888630584379,30.460803373586902],[114.12887836259212,30.46079988818031],[114.12886817096455,30.4607959182663],[114.12883288424162,30.46078344055786],[114.12880683904324,30.460775348266598],[114.12878095499761,30.46076926421222],[114.12876268288106,30.460766109565174],[114.12874165770062,30.46076308767697],[114.12872509369107,30.460761687541552],[114.12870763931349,30.460760650459722],[114.12868966534714,30.460759518850644],[114.12868556332796,30.460758775820878],[114.12868139785537,30.460757852444367],[114.12867930532934,30.460757079594252],[114.1286743946421,30.460755258055634],[114.12867217333921,30.460753659737904],[114.12866915506972,30.460748762815165],[114.12866651324336,30.460740080827907],[114.1286615349295,30.460740621283133],[114.12865159543583,30.460742072415314],[114.1286445447878,30.460743259994725],[114.12863472942703,30.460745282694166],[114.12862751842948,30.460746573884617],[114.12862310380396,30.460747364359552],[114.1286129285285,30.46074959393931],[114.1286036936273,30.460751644932678],[114.12859305335921,30.460754496127752],[114.12858272295519,30.460757439015904],[114.12858684681783,30.460756974895318],[114.1285896592975,30.460757211799937],[114.12859336695772,30.46075893258156],[114.12859483318482,30.46076096842862],[114.12859511158445,30.460762728564493],[114.12859446761085,30.46076500622275],[114.12859354876066,30.4607668126579],[114.12859116968379,30.460769196850375],[114.12858763843047,30.46077126133305],[114.12857545492906,30.460774983201105],[114.12856651479777,30.46077799213247],[114.12854889649874,30.46078481146621],[114.12853447052267,30.460790639959814],[114.12852557335621,30.460794471642462],[114.12851594301046,30.46079905130281],[114.12849904731934,30.46080789334833],[114.12848921767336,30.46081381903344],[114.12847995375431,30.460820365739195],[114.12845718211763,30.46083835215128],[114.12845199062264,30.460842482142652],[114.12843863190128,30.460856657982863],[114.12842208172918,30.460877284195742],[114.12840342322,30.460900828623902],[114.12838529881427,30.46092675780746],[114.1280351192241,30.4614916579163],[114.12772665457429,30.461977474726904],[114.12773666316124,30.46198267919212],[114.12838019747124,30.46096860236104],[114.12839862491309,30.460940485061798],[114.12841620397128,30.460914721940537],[114.12843438168466,30.460891047924747],[114.12845081874062,30.46087048759082],[114.12846385693705,30.460856156383116],[114.12846784607905,30.460852302594482],[114.12849139753372,30.46083382588418],[114.12850049625027,30.460827287615476],[114.12851003248754,30.460821443548152],[114.1285190183587,30.460816503231946],[114.128527396434,30.460812748170223],[114.1285377280812,30.460809171635077],[114.12854770191542,30.460805844107],[114.12856078586411,30.46080192595596],[114.12857238348518,30.460798490726418],[114.12858337520525,30.460794904254787],[114.12859945021059,30.460790470513647],[114.12861472608745,30.46078759112877],[114.12863298746211,30.460785640678257],[114.12864145553547,30.460784861359542],[114.12867263363336,30.46078576380548],[114.12869005218914,30.4607870865643],[114.12871293776365,30.460789479520425],[114.12873005711924,30.46079163128663],[114.1287544930627,30.46079647154317],[114.12877633262853,30.46080491582557],[114.12878327578939,30.46080767851991],[114.1289198979808,30.460870976248305],[114.12906165250779,30.460938179307167],[114.12938691893761,30.46109103651788],[114.12958281068029,30.461181831471286],[114.12970186691408,30.461237068649936],[114.12977138918183,30.461269340469094],[114.13000395955547,30.461377765191333],[114.1301615044232,30.46145164530947],[114.13037598615774,30.46155321754522],[114.13041321793216,30.461570825330455],[114.13041506318048,30.461571692773276],[114.13045166013256,30.461508860656757]]]]}},{"type":"Feature","id":"12","properties":{"type":4},"geometry":{"type":"MultiPolygon","coordinates":[[[[114.12765978321049,30.45988651522178],[114.12764383468487,30.459882807400575],[114.12765100334624,30.45988657107926],[114.12764139830975,30.459889506914333],[114.12761387234207,30.459876742501372],[114.1276124028338,30.459879128935576],[114.12764749557435,30.459895403858667],[114.12766126254193,30.45989195806849],[114.12766940339483,30.459896232150424],[114.12765978321049,30.45988651522178]]],[[[114.12763427339303,30.45991154790557],[114.12763291194328,30.459913757116947],[114.12759912524707,30.45989808805539],[114.12759765571056,30.45990047538993],[114.1276314403325,30.459916144409654],[114.1276300810117,30.45991835186179],[114.12765300104127,30.45992460771415],[114.12763427339303,30.45991154790557]]],[[[114.12761755190054,30.459938684458045],[114.12761611977218,30.459940858795086],[114.12758296657559,30.459925484570334],[114.12758149703767,30.45992787190393],[114.12761457127884,30.4599432101801],[114.12761311527514,30.459945419192206],[114.12763583774534,30.459951538273963],[114.12761755190054,30.459938684458045]]],[[[114.12760400423902,30.45996289423156],[114.12756944059383,30.45994678740718],[114.12756796389054,30.459949170979908],[114.12759459934937,30.45996158282404],[114.12759725797072,30.459971110870285],[114.12758979292224,30.459967603401395],[114.12760189361006,30.459977350333364],[114.12761687330165,30.459980327738513],[114.12760864362862,30.459976460395396],[114.12760400423902,30.45996289423156]]],[[[114.12799440218569,30.460048124375334],[114.12800243788405,30.460051976656732],[114.12799057087545,30.460042416186628],[114.12797537561978,30.460039000558577],[114.12798337851463,30.460042837719275],[114.1279726697087,30.460045822498866],[114.12794553803901,30.460032980209355],[114.12794404498791,30.460035355341343],[114.12797953758076,30.46005215693364],[114.12799440218569,30.460048124375334]]],[[[114.12796557032368,30.460067280495235],[114.12796418208411,30.46006958928911],[114.1279323869172,30.460054371474623],[114.12793088153516,30.460056741834425],[114.12796273821913,30.46007198801494],[114.12796131066906,30.460074361827907],[114.12798568849392,30.460081426697982],[114.12796557032368,30.460067280495235]]],[[[114.12794871296117,30.46009438283973],[114.12794741459626,30.46009663850804],[114.12791534084874,30.460081565421028],[114.12791385391927,30.460083944289185],[114.1279460209531,30.460099060847472],[114.1279447280439,30.460101307610305],[114.12796929043392,30.460108457589786],[114.12794871296117,30.46009438283973]]],[[[114.12793709442113,30.460120036352464],[114.12790195505336,30.460103411760084],[114.12790046199727,30.46010578688927],[114.12792761745402,30.460118635147563],[114.12793127303058,30.460128785938068],[114.12792429530099,30.460125601002048],[114.12793536229401,30.460134449981247],[114.12795051437212,30.460137567038498],[114.12794231951005,30.460133826836465],[114.12793709442113,30.460120036352464]]],[[[114.12835891047108,30.460191093536803],[114.1283690188825,30.460192677511287],[114.12835412543915,30.460187121572954],[114.12834021308942,30.460188162473116],[114.12834685654148,30.460189203212558],[114.1283384784417,30.460195350486952],[114.12830723173941,30.46019030660117],[114.12830665829725,30.460192965509346],[114.12834646485058,30.460199392851074],[114.12835891047108,30.460191093536803]]],[[[114.12870975662688,30.460212982914015],[114.12870665669686,30.460211400130223],[114.1286890621461,30.46023761963853],[114.12867780266502,30.4602396197635],[114.12868292426433,30.460232229061138],[114.12867230300633,30.460241891336665],[114.12866889768308,30.460253871059727],[114.12867427962401,30.46024567940838],[114.12868956421664,30.460242122565393],[114.12870975662688,30.460212982914015]]],[[[114.12873258742432,30.460223963610332],[114.12872855443425,30.46022230969746],[114.12871141229377,30.46025121432643],[114.12870839683895,30.460249798406643],[114.12870077475303,30.460270240679073],[114.1287167478244,30.46025309602353],[114.12871452320131,30.46025224437172],[114.12873258742432,30.460223963610332]]],[[[114.12837647935412,30.46026156409562],[114.1283752151951,30.46026329099487],[114.12834192553392,30.460247423299087],[114.12834042431034,30.460249794675402],[114.12837354178777,30.46026558115513],[114.12837196579537,30.4602677380636],[114.1283946904409,30.46027411807514],[114.12837647935412,30.46026156409562]]],[[[114.12876241641918,30.460237315744084],[114.12875882836029,30.460235903068593],[114.1287414802644,30.46026382191426],[114.12873876272113,30.460262771317108],[114.12873078746144,30.460283317092614],[114.12874715699776,30.460266187107642],[114.12874496976379,30.46026515854007],[114.12876241641918,30.460237315744084]]],[[[114.12880287063375,30.460269826219278],[114.12880057854322,30.46027534189185],[114.12879499567953,30.460267155271225],[114.12880594534293,30.460241237531278],[114.12880302093224,30.46024030792805],[114.12878906971548,30.460273325397395],[114.12879641747973,30.4602853492421],[114.12879292942804,30.460293741602786],[114.1288017286223,30.46028190815778],[114.12880287063375,30.460269826219278]]],[[[114.12834895482324,30.46028357000437],[114.12834761758594,30.460285734666073],[114.12831461477353,30.46027032493471],[114.12831313610232,30.460272706709155],[114.1283461440566,30.46028811835546],[114.12834469014169,30.46029047088728],[114.12836871610331,30.460297121998284],[114.12834895482324,30.46028357000437]]],[[[114.12833628018815,30.46030807470138],[114.12830123480336,30.4602914490679],[114.12829973767604,30.46029382233258],[114.12832709672114,30.460306803443775],[114.12833006918703,30.4603164363764],[114.12832213338147,30.46031268824945],[114.12833389186757,30.46032250966981],[114.12835043401269,30.460326052779433],[114.12834147430807,30.460321822050688],[114.12833628018815,30.46030807470138]]],[[[114.12874925720942,30.46058753420879],[114.12875633276082,30.460590743526613],[114.12874529087212,30.460582287850315],[114.12873214771808,30.460579773021713],[114.12873971340865,30.460583205588684],[114.12874193185147,30.46059058991506],[114.1287647929814,30.460601281663973],[114.12876627265992,30.46059889988208],[114.12874884830025,30.460590749575427],[114.12874925720942,30.46058753420879]]],[[[114.12871821605825,30.4606105640714],[114.12871959460246,30.460608229769374],[114.12869575525808,30.460601600988607],[114.12871534205641,30.460615428660677],[114.12871679376168,30.460612973222794],[114.12874984384793,30.460628424004636],[114.12875132459295,30.46062604134405],[114.12871821605825,30.4606105640714]]],[[[114.1282713090051,30.460612952415406],[114.12826086657832,30.460601581970952],[114.12826129431166,30.46059513218137],[114.1282566641438,30.46060741971045],[114.12825991855485,30.46061977475574],[114.12826022942248,30.460613803298962],[114.12826630595534,30.46061596778145],[114.12826866071323,30.46061965292968],[114.1282682144802,30.460646263638203],[114.12827089724746,30.460646906719386],[114.1282713090051,30.460612952415406]]],[[[114.12870076159,30.460637924067537],[114.12870236859342,30.46063552332987],[114.12867844351734,30.4606289829275],[114.12869772909524,30.46064245244094],[114.1286991937367,30.460640265193003],[114.12873219886704,30.460655761957565],[114.1287336826763,30.460653381166896],[114.12870076159,30.460637924067537]]],[[[114.12831797511244,30.46063243172318],[114.12832119644226,30.460632779482374],[114.12832893290955,30.46061305238447],[114.12831239255001,30.460630040454017],[114.12831582544989,30.460630970013842],[114.1282984920438,30.460658369257313],[114.12830113451777,30.46065967901653],[114.12831797511244,30.46063243172318]]],[[[114.12834864625601,30.460645501797423],[114.12835063030111,30.460646373655234],[114.12835899930107,30.460625234647328],[114.12834321867702,30.46064257256931],[114.1283455814283,30.46064374741235],[114.12832807040849,30.460672459045476],[114.12833113221508,30.460673961690624],[114.12834864625601,30.460645501797423]]],[[[114.12837912681817,30.460659686372537],[114.12837676038178,30.460664922481147],[114.12838729349056,30.460655438886963],[114.12839074447052,30.460642612219186],[114.12838590538631,30.46064925740684],[114.12837091827653,30.460652936711554],[114.1283527536239,30.460683177988436],[114.12835493179958,30.460684339907434],[114.12837001606486,30.46066033266817],[114.12837912681817,30.460659686372537]]],[[[114.12872896548119,30.460704124997644],[114.12876084568157,30.46070725039113],[114.12876119762366,30.46070456153281],[114.12871989417552,30.460700515380775],[114.12870691650812,30.46070992221255],[114.12869839289986,30.46070925589473],[114.12871279406163,30.46071423201666],[114.12872941048305,30.460711679332316],[114.12872029803995,30.460710967204506],[114.12872896548119,30.460704124997644]]],[[[114.12913444135532,30.460768798505974],[114.12914149179586,30.460772188498577],[114.12913097470313,30.460763556443407],[114.12911396829095,30.46075895585527],[114.12912571153127,30.460764601784295],[114.12912590284351,30.460775678762786],[114.12916231806324,30.460793185492257],[114.12916383051939,30.46079081881016],[114.12913598426056,30.46077743143822],[114.12913444135532,30.460768798505974]]],[[[114.12911697977484,30.460797693086274],[114.12911843007527,30.460795462210044],[114.12909439426096,30.46078881163049],[114.12911400178062,30.460802278592816],[114.1291154488873,30.46080005035548],[114.1291485118678,30.4608152951975],[114.12914997511503,30.460812905827964],[114.12911697977484,30.460797693086274]]],[[[114.12909986376616,30.460824861167204],[114.12910122728519,30.460822684388322],[114.12907774100891,30.460816072456357],[114.12909695474436,30.460829504056413],[114.12909837487132,30.460827239167585],[114.12913135974942,30.460842633030847],[114.12913283740411,30.460840248476778],[114.12909986376616,30.460824861167204]]],[[[114.1290832891964,30.460844033872448],[114.12906817778146,30.46084821163846],[114.1290595466183,30.460844157698915],[114.12907309910834,30.460854163685678],[114.1290881839199,30.460857607867585],[114.12907976595324,30.46085365405626],[114.12909012325989,30.460850345634984],[114.12911828965193,30.460863660437624],[114.12911978267327,30.460861283425245],[114.1290832891964,30.460844033872448]]],[[[114.1295170729681,30.46094868857821],[114.12952471659668,30.460952292136778],[114.12951319270734,30.46094273963868],[114.12949712644783,30.460939288545823],[114.12950565870332,30.460943309573725],[114.12950977301703,30.460956744341534],[114.1295459846389,30.460973346840195],[114.12954744170554,30.460970954609365],[114.12951970910399,30.46095823940797],[114.1295170729681,30.46094868857821]]],[[[114.12950069463872,30.460978320737603],[114.1295022112425,30.46097574033008],[114.12947838746287,30.460969275015792],[114.12949799054365,30.46098292199377],[114.12949927753735,30.460980731850242],[114.12953219914202,30.460996356573943],[114.12953369721849,30.4609939832506],[114.12950069463872,30.460978320737603]]],[[[114.12948462553341,30.461005586056444],[114.12948598100314,30.461003470426377],[114.12946220363271,30.460996518955422],[114.12948148441804,30.46101049291415],[114.12948311211464,30.461007950934665],[114.12951612229881,30.46102356762681],[114.12951761523743,30.461021192391577],[114.12948462553341,30.461005586056444]]],[[[114.1294685281634,30.461024581467186],[114.12945465384469,30.461029561357567],[114.12944516793,30.461025143123695],[114.12945798782363,30.461035146972872],[114.12947318283328,30.461038191819814],[114.12946503873893,30.46103439817314],[114.12947539290786,30.461030872121057],[114.12950386704058,30.461044204700624],[114.12950534876923,30.461041822013147],[114.1294685281634,30.461024581467186]]]]}},{"type":"Feature","id":"13","properties":{"type":3},"geometry":{"type":"MultiPolygon","coordinates":[[[[114.1266944684298,30.45974113955593],[114.12672905702632,30.45975739984173],[114.12674527261686,30.459764919502927],[114.12676616927213,30.459774842276527],[114.12681593344244,30.459798270346727],[114.12686208851598,30.459820014857755],[114.12692777688568,30.459851058867],[114.12699795709268,30.459884486205194],[114.12701184103294,30.45989105531921],[114.12702132622897,30.459895863598756],[114.12716906978987,30.45997472622542],[114.12725782616269,30.460023057967557],[114.12731527526354,30.460054300237914],[114.12731760904978,30.460055567526968],[114.12731813428435,30.460053516543667],[114.12731900249183,30.46004906066548],[114.12731982237837,30.460044125665828],[114.12732042243266,30.460039600961448],[114.12732094425235,30.46003526403447],[114.12732096403349,30.460033523456232],[114.12732052050677,30.460030682767634],[114.12731943525426,30.46003001809118],[114.12731561342733,30.46002781724937],[114.12730266390733,30.4600207953853],[114.1272944154204,30.460016316299836],[114.12723696629992,30.459985073980643],[114.12713720190709,30.459930747807572],[114.12705918757291,30.459888076994318],[114.12704970239025,30.459883268730508],[114.12703581846966,30.45987669963919],[114.12696563836118,30.459843272416002],[114.12689995008387,30.459812228514114],[114.12685379507562,30.459790484078905],[114.12680403097558,30.45976705609049],[114.12678313434951,30.459757133351058],[114.126766918782,30.45974961371671],[114.12671858919056,30.45972689376226],[114.12670588722376,30.459720823910242],[114.1266944684298,30.45974113955593]]],[[[114.12675814499713,30.459627849957624],[114.12688251033282,30.4596865974618],[114.12707051661417,30.459775086771863],[114.12719733974058,30.45983468642013],[114.12734472023581,30.45990416351866],[114.12742926333294,30.459943885087256],[114.12750732255947,30.45998070502695],[114.1275779458936,30.460013852051457],[114.12762297926939,30.46003518344493],[114.12768145827465,30.46006255464501],[114.12769094188808,30.460066929215547],[114.12777515220014,30.460106613454087],[114.12781781683972,30.460126637479288],[114.12786681803642,30.46014968461648],[114.12793397862626,30.460181425380462],[114.12796362762155,30.460195355841083],[114.1280563612488,30.460239138512893],[114.1280974603508,30.460258332389202],[114.12812174927116,30.460269841792726],[114.1281473346031,30.460281966551854],[114.12817332494056,30.460294337560583],[114.1281918699316,30.460303041631594],[114.12821516367895,30.460314187259602],[114.12822254996298,30.460317789390476],[114.12822908233838,30.460320390201996],[114.12823183884626,30.460320998763187],[114.12823522173838,30.460321336764043],[114.12823732737714,30.460321277212827],[114.1282396830356,30.460321077878376],[114.12824192577881,30.46032034723348],[114.12824466576511,30.460319159303868],[114.12824900487033,30.460316311803368],[114.12824903044557,30.460316293513653],[114.12824905597351,30.46031627517361],[114.12824908145355,30.460316256784147],[114.12824910688614,30.46031623834515],[114.12824913227085,30.460316219856164],[114.12824915760778,30.460316201317926],[114.12824918289648,30.460316182730047],[114.12824920813742,30.46031616409314],[114.12824923332965,30.460316145406875],[114.12824925847366,30.46031612667147],[114.128249283569,30.460316107887273],[114.12824930861576,30.460316089054],[114.12824933361371,30.460316070171817],[114.12824935856253,30.460316051240955],[114.12824938346256,30.46031603226147],[114.12824940831311,30.460316013233538],[114.1282494331145,30.460315994157263],[114.12824945786632,30.460315975032252],[114.12824948256899,30.460315955859418],[114.12824950722138,30.46031593663801],[114.12824953182431,30.460315917368842],[114.12824955637696,30.460315898051558],[114.1282495808798,30.460315878686508],[114.1282496053328,30.460315859273578],[114.12824962973511,30.46031583981322],[114.12824965408676,30.460315820304974],[114.12824967838806,30.460315800749473],[114.1282497026385,30.4603157811466],[114.12824972683855,30.46031576149665],[114.12824975098745,30.46031574179904],[114.12824977508514,30.46031572205468],[114.12824979913199,30.460315702263298],[114.12824982312755,30.460315682424888],[114.12824984707149,30.460315662539955],[114.12824987096366,30.46031564260817],[114.12824989480455,30.460315622629924],[114.12824991859347,30.46031560260516],[114.1282499423303,30.460315582534054],[114.12824996601559,30.460315562416774],[114.1282499896487,30.4603155422532],[114.12825001322925,30.46031552204351],[114.12825003675762,30.460315501787985],[114.12825006023343,30.46031548148669],[114.12825008365613,30.460315461139437],[114.12825010702663,30.4603154407467],[114.12825013034448,30.46031542030824],[114.128250153609,30.460315399824633],[114.12825017682074,30.460315379295302],[114.12825019997902,30.460315358720827],[114.12825022308364,30.46031533810148],[114.12825024613504,30.46031531743693],[114.12825026913309,30.4603152967274],[114.12825029207771,30.460315275973173],[114.12825031496807,30.460315255174255],[114.12825033780463,30.460315234330583],[114.12825036058753,30.46031521344267],[114.12825038331597,30.46031519250995],[114.12825040599014,30.460315171533157],[114.12825042860962,30.460315150512187],[114.12825045117509,30.460315129447203],[114.12825047368585,30.460315108338374],[114.12825049614159,30.460315087185197],[114.12825051854318,30.460315065988745],[114.1282505408893,30.460315044748455],[114.12825056318046,30.460315023464435],[114.1282505854165,30.4603150021372],[114.12825060759738,30.460314980766636],[114.12825062972276,30.460314959352914],[114.12825065179265,30.46031493789592],[114.12825067380695,30.460314916395994],[114.12825069576563,30.46031489485308],[114.12825071766825,30.46031487326764],[114.12825073951514,30.46031485163955],[114.12825076130575,30.46031482996859],[114.12825078304067,30.46031480825549],[114.12825080471872,30.46031478649952],[114.12825082634046,30.460314764701813],[114.1282508479057,30.460314742861687],[114.12825086941466,30.460314720979657],[114.12825089086688,30.460314699055836],[114.1282509122619,30.46031467709005],[114.12825093360064,30.46031465508258],[114.12825095488195,30.46031463303361],[114.12825097610583,30.460314610943474],[114.12825099727287,30.46031458881166],[114.12825101838263,30.460314566638505],[114.12825103943457,30.46031454442464],[114.12825106042901,30.460314522169323],[114.12825108136593,30.460314499873355],[114.12825110224462,30.460314477536734],[114.12825112306598,30.460314455159],[114.12825114382905,30.460314432740955],[114.12825116453386,30.460314410282596],[114.12825118518117,30.460314387783644],[114.12825120576936,30.460314365244603],[114.12825122629965,30.460314342665477],[114.12825124658667,30.46031432025074],[114.12825126681595,30.460314297796767],[114.1282512869879,30.46031427530379],[114.12825130710188,30.460314252771923],[114.12825132715821,30.46031423020105],[114.12825134715654,30.46031420759185],[114.12825136709644,30.46031418494365],[114.12825138697845,30.46031416225707],[114.12825140680238,30.460314139532223],[114.12825142656739,30.460314116768995],[114.12825144627443,30.460314093967845],[114.12825146592279,30.460314071128373],[114.12825148551238,30.460314048251092],[114.12825150504305,30.460314025336054],[114.12825152451508,30.46031400238338],[114.12825154392831,30.460313979393067],[114.12825156328276,30.460313956365166],[114.12825158257729,30.460313933300025],[114.12825160181306,30.46031391019776],[114.12825162098945,30.46031388705814],[114.12825164010638,30.46031386388156],[114.12825165916377,30.460313840668423],[114.1282516781613,30.46031381741793],[114.12825169709974,30.460313794131334],[114.12825171597783,30.460313770807726],[114.12825173479607,30.460313747447902],[114.12825175355442,30.460313724051915],[114.12825177225272,30.46031370061954],[114.12825179089103,30.46031367715129],[114.12825180946858,30.46031365364677],[114.12825182798602,30.46031363010654],[114.12825184644298,30.460313606530608],[114.12825186483931,30.460313582919255],[114.12825188317528,30.460313559272084],[114.1282519014501,30.460313535589776],[114.12825191966442,30.46031351187199],[114.1282519378177,30.460313488119244],[114.12825195591026,30.460313464331357],[114.12825197394119,30.46031344050873],[114.12825199191133,30.4603134166512],[114.12825200982003,30.460313392758874],[114.12825202766739,30.460313368832267],[114.12825204545332,30.460313344871146],[114.12825206317798,30.460313320875574],[114.12825208084024,30.46031329684595],[114.12825209844144,30.46031327278227],[114.12825211598069,30.460313248684702],[114.12825213345819,30.46031322455297],[114.12825215087362,30.460313200387805],[114.12825216822664,30.460313176188986],[114.12825218551781,30.460313151956797],[114.12825220274713,30.46031312769129],[114.12825221991372,30.46031310339207],[114.12825223701766,30.460313079060054],[114.1282522540595,30.460313054695394],[114.12825227103863,30.460313030297424],[114.12825228795496,30.460313005866766],[114.12825230480904,30.46031298140353],[114.12825232159955,30.460312956907835],[114.12825233832774,30.46031293237979],[114.12825235499263,30.460312907819343],[114.12825237159478,30.460312883227],[114.12825238813313,30.460312858602368],[114.12825240460852,30.460312833946126],[114.12825242102103,30.46031280925782],[114.12825243736954,30.460312784538143],[114.12825245365518,30.460312759787016],[114.1282524698768,30.460312735004063],[114.1282524860348,30.460312710190003],[114.12825250212911,30.46031268534474],[114.12825251815977,30.460312660468663],[114.12825253412632,30.460312635561493],[114.12825255002898,30.460312610623507],[114.12825256586763,30.460312585655],[114.12825258164227,30.460312560656188],[114.12825259735236,30.4603125356264],[114.12825261299821,30.46031251056659],[114.12825262857994,30.460312485476763],[114.12825264409734,30.4603124603567],[114.12825265954983,30.460312435206788],[114.12825267493797,30.460312410027147],[114.12825269026165,30.460312384817666],[114.1282527055202,30.46031235957879],[114.12825272071397,30.460312334310466],[114.12825273584305,30.460312309012924],[114.128252750907,30.460312283686225],[114.12825276590588,30.46031225833036],[114.12825278083979,30.460312232945732],[114.12825279570822,30.460312207532347],[114.12825281051138,30.460312182090135],[114.12825282524965,30.460312156619327],[114.12825283992197,30.460312131120332],[114.12825285452939,30.460312105593022],[114.12825286907076,30.46031208003764],[114.12825288354688,30.46031205445394],[114.12825289795707,30.46031202884273],[114.12825291230135,30.460312003203384],[114.12825463446963,30.460307266151542],[114.1282546958477,30.460302914042497],[114.12825359360119,30.46029924379911],[114.12825230343141,30.460296734308333],[114.1282500427877,30.46029431885763],[114.12824661664233,30.4602918978516],[114.12820494374954,30.460272114380142],[114.1281922841449,30.460265869404232],[114.12808402777658,30.460214870952296],[114.12766577985062,30.460017600721127],[114.12743693679198,30.459909957721468],[114.12725923541228,30.459826070251896],[114.12719650698922,30.45979676876443],[114.12694653473818,30.459678949412346],[114.12682802881133,30.45962329819676],[114.1267747664414,30.45959827806291],[114.12675814499713,30.459627849957624]]],[[[114.12682661116624,30.459506038981907],[114.12687232754989,30.459527608277465],[114.12699863219632,30.459587234089707],[114.12704767575964,30.459609865759525],[114.127111274572,30.459640029638972],[114.12713397060182,30.459650554557154],[114.12720629683558,30.459684360024074],[114.12725407711113,30.45970325251408],[114.12729733260204,30.45972041396493],[114.1273112377685,30.459726030135034],[114.1273387493548,30.45973707466334],[114.12741960052145,30.459769520858135],[114.12742953675937,30.459773454640874],[114.12743318029702,30.45977451987903],[114.12743665719199,30.459775369308982],[114.12744153599023,30.45977603826525],[114.12744835080035,30.459775950769664],[114.12745466568676,30.45977550488621],[114.12745847719489,30.459774694886836],[114.12746506493548,30.459772979191133],[114.12746847546522,30.459771795045626],[114.1274624922081,30.459769262478748],[114.12745538343432,30.459766253507013],[114.12738740482354,30.459737479731473],[114.1272636764614,30.45968616892877],[114.12719322462243,30.45965567716349],[114.12714142965808,30.45963118944914],[114.12702821462499,30.459577594181486],[114.12694685086942,30.45953947148556],[114.12683688354451,30.459487762971463],[114.12682661116624,30.459506038981907]]],[[[114.1305997264454,30.461254650420855],[114.13056643077365,30.46123894446055],[114.13045399666704,30.46118618729741],[114.13036864378365,30.461146033660274],[114.1303616720721,30.461142769719032],[114.13036087968533,30.46114268878565],[114.13036047888463,30.46114276387994],[114.13036020101502,30.461142865430812],[114.13035959442377,30.46114331708407],[114.13034715194661,30.461163681364365],[114.13035309354153,30.461166463038413],[114.1304384464855,30.46120661673209],[114.13055088067259,30.461259373969753],[114.13058704027621,30.46127643089205],[114.1305997264454,30.461254650420855]]],[[[114.13053418430762,30.461367177585128],[114.13049318476216,30.461347923823414],[114.1304353730375,30.4613206455615],[114.1303847801091,30.46129699218056],[114.13033804043904,30.461275160858154],[114.13031869977469,30.461266093584754],[114.13025878549531,30.461237868552704],[114.13022311086705,30.461221015009325],[114.13019982360476,30.461210114477765],[114.1301856974576,30.46120357451651],[114.13014370712925,30.461183827450988],[114.13007393532432,30.46115088390394],[114.13002322306909,30.461127028815913],[114.13000194661323,30.461117000383993],[114.12998034182208,30.4611068297025],[114.12996651839514,30.461100593680996],[114.1298697425707,30.461055209083533],[114.12984185474966,30.461042091356937],[114.12982176097213,30.461032475595395],[114.1298039961753,30.46102422594877],[114.12974578440762,30.460996730168535],[114.12969922356724,30.46097479316306],[114.1296529204122,30.46095292464968],[114.12960720785264,30.460931465418557],[114.12956090431067,30.460909605744693],[114.12951580005522,30.460888303507012],[114.12947708153946,30.46086998646089],[114.12940928452369,30.460838038896387],[114.12918331261375,30.46073195799231],[114.12913177782775,30.46070775896083],[114.12907421815683,30.460680663249114],[114.12898231868293,30.46063720932726],[114.1289153688462,30.460605747365257],[114.12885654370466,30.460578209460085],[114.1288389962118,30.46056985731374],[114.12882684847357,30.460563863524182],[114.1288141725599,30.4605581652759],[114.12880782774334,30.46055555054327],[114.1288057848866,30.46055505635136],[114.12880362948478,30.460554857465603],[114.12880095013205,30.460554809883952],[114.12879839023064,30.460555075444944],[114.12879690449662,30.460555292723512],[114.1287931846466,30.460556524724897],[114.12879028514973,30.460558073317237],[114.12878727750986,30.460560462065427],[114.1287849094132,30.46056331802322],[114.12878327828815,30.46056649000981],[114.12878233237424,30.460570669087307],[114.12878249837452,30.46057437526104],[114.12878361332838,30.460578040744846],[114.12878565826341,30.460581439852827],[114.12878879981872,30.460584578808444],[114.12882004927184,30.460598789178334],[114.12882910982678,30.46060304892338],[114.12883741646571,30.460606796599585],[114.12888886204941,30.460631219887322],[114.12907550677258,30.460718986866333],[114.12919481048306,30.460775052584722],[114.1292711416073,30.46081079817736],[114.1294100633873,30.460876387833594],[114.12953601405539,30.460935704296794],[114.12962270083028,30.460976511380327],[114.1300316137447,30.46116872249151],[114.13018471241936,30.461240365703688],[114.13042502797587,30.461353907089034],[114.13051678799235,30.461397044752903],[114.13053418430762,30.461367177585128]]],[[[114.13046385794472,30.46148791862986],[114.13040616042963,30.461460780940797],[114.13028950406114,30.461405850830815],[114.13026203901191,30.46139298174333],[114.13020055553451,30.461363638193205],[114.13013410558624,30.46133221712907],[114.13006073761063,30.461297889754782],[114.1300180382422,30.461277865927773],[114.1299791363247,30.461259798379114],[114.12995329752638,30.46124753359232],[114.12992004575335,30.46123183901411],[114.12989969739692,30.461222058825008],[114.12988892761159,30.461216948521958],[114.12988435766842,30.461215291255623],[114.12987909445536,30.46121403602663],[114.1298744487866,30.461212891337485],[114.12985058719934,30.461206110209485],[114.1298446088779,30.46120359480715],[114.12974620246875,30.46116358510477],[114.12969948098392,30.461144727955627],[114.12967232207535,30.46113375856144],[114.12964617596162,30.46112312554772],[114.12961041249778,30.461108580663897],[114.12958210924047,30.461097257663308],[114.1295633657393,30.461089558870512],[114.12953506872064,30.461078021140928],[114.12951757397664,30.461071050357365],[114.12950048798439,30.46106420274828],[114.12948548483459,30.461058045876143],[114.12947924318162,30.461055460555205],[114.12947335563234,30.46105284662048],[114.1294563747867,30.461044784653954],[114.12943675534166,30.461035638640777],[114.12935809571461,30.460998518369415],[114.12927029666353,30.460957162723787],[114.12917071928544,30.460910203564357],[114.12912909028749,30.46089062224755],[114.1290837525801,30.46086931292122],[114.12902410095711,30.460841056350123],[114.12901186070705,30.4608353856701],[114.1289942551628,30.46082698705355],[114.12897611545476,30.460818395121724],[114.12895182480428,30.460807309844117],[114.12893148732057,30.460797480728047],[114.12890344170553,30.46078475551124],[114.12889549846048,30.46078127011125],[114.12884170032781,30.460760314277042],[114.1288347519017,30.4607580098877],[114.12882607768718,30.460755306255887],[114.12881584291304,30.460752569654307],[114.12880273716975,30.46074948598957],[114.12879713995717,30.460748355768576],[114.12879353452269,30.460747554032856],[114.12878923020261,30.460746584780672],[114.12878632197628,30.46074600780591],[114.12878070054767,30.460744953445282],[114.12877733130436,30.46074434763582],[114.1287733065773,30.460743594320274],[114.12876752626345,30.46074270844498],[114.1287603003112,30.460741620180574],[114.12874927521233,30.46074033708395],[114.12873845929876,30.460739421186133],[114.12872899998375,30.460738743979512],[114.12872142087552,30.46073845018549],[114.12871229983101,30.460738238373125],[114.12870264177718,30.460738339806475],[114.12869255624352,30.460738538047984],[114.12868470145209,30.46073887723521],[114.12867494039402,30.46073949201651],[114.12866963265316,30.460739742643483],[114.12866651366794,30.46074008124729],[114.12866915549414,30.460748763234417],[114.12867217376335,30.460753660157007],[114.12867439506623,30.460755258474624],[114.1286793057532,30.460757080013],[114.12868139827911,30.460757852863008],[114.12868556375125,30.460758776239317],[114.12868966577041,30.460759519268876],[114.12870763973575,30.46076065087707],[114.12872509411235,30.460761687958037],[114.12874165812096,30.46076308809264],[114.12876268329988,30.460766109979808],[114.12878095541575,30.460769264625952],[114.12880683945993,30.46077534867905],[114.12883288465684,30.460783440969028],[114.1288681713778,30.460795918675725],[114.12887836300479,30.460799888589232],[114.12888630625602,30.46080337399543],[114.1289143518923,30.46081609923264],[114.12893468939191,30.460825928363864],[114.1289589800609,30.46083701365927],[114.12897711978253,30.460845605604515],[114.12899472534052,30.460854004234157],[114.12900696560021,30.460859674923135],[114.12906661726825,30.46088793153845],[114.12911195501067,30.460909240898385],[114.1291444674547,30.460924534015696],[114.12915358404044,30.460928822245865],[114.12925316149412,30.46097578147882],[114.12934096061207,30.46101713718879],[114.12941962029913,30.46105425751788],[114.12943923975868,30.46106340354579],[114.12945622061763,30.461071465524732],[114.12946210817088,30.461074079463888],[114.1294683498294,30.461076664789566],[114.12948335299018,30.461082821672694],[114.1295004389957,30.46108966929423],[114.12951793375322,30.461096640090652],[114.12954623079347,30.461108177841012],[114.12956497430956,30.461115876647472],[114.12959327758891,30.461127199668816],[114.12962904107998,30.461141744578914],[114.1296551872138,30.46115237761181],[114.12968234614334,30.46116334702594],[114.1297290676644,30.461182204209194],[114.12982747414874,30.461222213983582],[114.12983345247454,30.461224729390207],[114.12985731408087,30.46123151053599],[114.12986195975375,30.461232655228265],[114.12986722297065,30.461233910461385],[114.12987179291733,30.461235567730828],[114.12988256271107,30.461240678041854],[114.12990291108265,30.46125045824588],[114.12993616288067,30.46126615284841],[114.12996200169835,30.461278417653883],[114.13000090364547,30.461296485230985],[114.13004360304595,30.46131650908941],[114.13011697107694,30.4613508365171],[114.13018342107554,30.461382257629765],[114.13024490459868,30.461411601224487],[114.13027236966882,30.461424470331927],[114.13038902612425,30.461479400526617],[114.13045113268977,30.46150861200973],[114.13045166005098,30.461508860796823],[114.13046385794472,30.46148791862986]]],[[[114.12838443020314,30.460133341602067],[114.12837846261124,30.460133861140605],[114.1283699891731,30.460134063864203],[114.12835427072939,30.460133826990184],[114.12833517330242,30.460132563964283],[114.12832069165516,30.46013139852543],[114.12830984786484,30.46012992812529],[114.12828752320745,30.46012621008306],[114.12825057243269,30.46011874580906],[114.12822395781758,30.460111309143702],[114.12820544788224,30.46010499855947],[114.12817294785577,30.460091995602493],[114.12812695711021,30.460071335740665],[114.12804831847087,30.460034140966847],[114.1279144381997,30.45997081742014],[114.12779109603392,30.459912477811564],[114.1277751204641,30.45990500277956],[114.1276701278791,30.45985553077131],[114.12763036619752,30.45983774081049],[114.12762840913243,30.459836865541],[114.12762836860277,30.45983684895803],[114.1276283279475,30.459836832607156],[114.12762828716846,30.45983681648975],[114.12762824626789,30.459836800605576],[114.12762821551723,30.459836788839294],[114.12762818469247,30.45983677722],[114.12762814349567,30.45983676192334],[114.12762810218283,30.459836746863317],[114.12762806075638,30.459836732040046],[114.12762802961645,30.459836721070833],[114.12762799840706,30.459836710250425],[114.1276279567045,30.459836696022094],[114.12762791489392,30.459836682032556],[114.12762787297827,30.459836668282776],[114.1276278309587,30.459836654773262],[114.12762778883703,30.459836641504527],[114.12762774661529,30.459836628477305],[114.1276277042951,30.459836615691998],[114.12762766187866,30.45983660314928],[114.12762761936739,30.45983659084943],[114.12762757676379,30.459836578793425],[114.12762754475389,30.45983656990394],[114.12762751268797,30.459836561167286],[114.12762746986063,30.45983654972238],[114.12762743768602,30.459836541292116],[114.12762740545831,30.459836533015483],[114.1276273624185,30.459836522185185],[114.12762731929688,30.45983651160144],[114.12762727609562,30.45983650126454],[114.12762724364647,30.45983649366683],[114.12762721114912,30.459836486223768],[114.12762716775659,30.45983647650663],[114.12762713516688,30.459836469374043],[114.12762710253213,30.459836462397003],[114.1276270589606,30.459836453302234],[114.12762702623918,30.459836446636917],[114.12762699347569,30.459836440127994],[114.12762694973672,30.4598364316578],[114.12762690593424,30.459836423438013],[114.12762686207003,30.459836415469027],[114.12762681814617,30.459836407751467],[114.12762677416377,30.45983640028539],[114.12762673024915,30.45983639309108],[114.12762668665239,30.459836386204987],[114.12762665392206,30.459836381194762],[114.1276266211611,30.45983637633899],[114.12762657744001,30.459836370070946],[114.12762653367199,30.45983636405028],[114.12762650081824,30.45983635968949],[114.12762646793738,30.459836355483777],[114.12762643503599,30.45983635140226],[114.12762640210897,30.45983634747593],[114.127626358174,30.45983634244777],[114.12762631420206,30.459836337668627],[114.12762627019484,30.45983633313838],[114.1276262261544,30.459836328856976],[114.12762618208221,30.45983632482504],[114.12762613798041,30.45983632104274],[114.12762609385085,30.459836317509783],[114.12762604969566,30.459836314227033],[114.1276260165642,30.45983631192067],[114.1276259834181,30.459836309770974],[114.12762593920633,30.45983630711286],[114.12762589497518,30.459836304705398],[114.12762585072659,30.459836302547842],[114.12762580646259,30.459836300640816],[114.12762577325597,30.4598362993673],[114.12762574004165,30.459836298250032],[114.12762570682145,30.459836297258203],[114.12762567359557,30.45983629642308],[114.12762562928798,30.45983629551856],[114.12762559576993,30.459836294993252],[114.12762556210721,30.45983629462586],[114.12762552844248,30.4598362943868],[114.12762549477696,30.45983629430882],[114.12762544988901,30.459836294418913],[114.12762540500245,30.459836294786577],[114.12762536011977,30.459836295411062],[114.12762531524221,30.45983629629294],[114.12762527037239,30.459836297431924],[114.12762523672546,30.459836298446906],[114.12762520308536,30.459836299622452],[114.12762515824227,30.459836301404135],[114.12762511341339,30.459836303442685],[114.12762506860075,30.45983630573816],[114.12762502380629,30.459836308290168],[114.12762497903172,30.459836311099092],[114.12762494546469,30.459836313366072],[114.12762491191184,30.45983631579315],[114.12762486719666,30.459836319242925],[114.12762483367631,30.459836321990196],[114.127624800174,30.459836324897676],[114.12762475552913,30.45983632898738],[114.12762472206498,30.45983633221465],[114.12762468862229,30.459836335601615],[114.12762465519617,30.45983633911615],[114.1276246217931,30.45983634279044],[114.12762457728701,30.459836347901863],[114.1276245328204,30.45983635326826],[114.12762448839558,30.459836358889504],[114.12762444401379,30.459836364764868],[114.12762439967753,30.459836370894458],[114.1276243553885,30.459836377278044],[114.12762431114874,30.459836383915114],[114.12762426696017,30.459836390805716],[114.12762422292073,30.459836397933223],[114.12762417922436,30.459836405263164],[114.12762414648644,30.459836410915933],[114.12762411378517,30.45983641672315],[114.12762407023173,30.45983642467282],[114.1276240267391,30.459836432869306],[114.12762398330916,30.459836441312838],[114.1276239507767,30.45983644779963],[114.12762391828501,30.459836454440303],[114.12762387501897,30.45983646349917],[114.12762383182195,30.459836472803538],[114.12762379946815,30.45983647993499],[114.12762376715968,30.459836487219633],[114.12762373488773,30.459836494626593],[114.127623702663,30.459836502186125],[114.12762365975962,30.459836512468513],[114.12762361693527,30.45983652299429],[114.12762358486626,30.459836531040718],[114.12762355284848,30.45983653923868],[114.12762351022704,30.459836550371],[114.12762347831344,30.45983655887163],[114.12762344645421,30.459836567522885],[114.1276234040472,30.459836579258948],[114.12762337229795,30.459836588211218],[114.12762334060511,30.459836597313817],[114.1276233089579,30.459836606536296],[114.12762327736935,30.459836615908305],[114.12762324582772,30.45983662539985],[114.12762321434622,30.459836635040585],[114.12762317245144,30.45983664809316],[114.12762313065642,30.459836661383363],[114.12762308896173,30.4598366749108],[114.1276230473699,30.459836688674496],[114.12762300588246,30.459836702674057],[114.12762296450092,30.459836716908963],[114.12762292322763,30.45983673137848],[114.12762288206346,30.4598367460822],[114.1276228410695,30.459836760997447],[114.12762280036458,30.459836776079236],[114.12762276990655,30.459836787534186],[114.12762273952062,30.459836799132127],[114.12762269910273,30.459836814786488],[114.12762265880288,30.45983683066868],[114.12762262865232,30.45983684272252],[114.12762259857782,30.459836854917857],[114.12762256856425,30.459836867226166],[114.12762253862776,30.459836879675816],[114.12762249881615,30.45983689646266],[114.12762245913135,30.45983691347381],[114.12762241957452,30.45983693070835],[114.12762238014746,30.45983694816616],[114.12762234085166,30.459836965846215],[114.12762231146311,30.459836979244585],[114.1276222821582,30.45983699278104],[114.12762225292117,30.45983700642762],[114.12762222376958,30.459837020211605],[114.1276221850163,30.45983703877284],[114.12762215603836,30.459837052830146],[114.12762212714861,30.459837067023827],[114.1276220983298,30.459837081325865],[114.1276220696005,30.459837095763145],[114.12762203141526,30.45983711519309],[114.12762199337662,30.459837134838043],[114.12762196493958,30.45983714970557],[114.12762193659584,30.459837164706798],[114.12762189892922,30.459837184886037],[114.12762186108847,30.45983720545634],[114.1276218232954,30.45983722630116],[114.12762178566054,30.45983724736167],[114.12762175753487,30.45983726329141],[114.12762172950961,30.45983727935421],[114.12762169227855,30.459837300949314],[114.12762165521181,30.459837322756112],[114.12762162751534,30.45983733924339],[114.12762159992351,30.45983735586208],[114.12762156327396,30.45983737819527],[114.12762153589367,30.459837395075652],[114.12762150861974,30.45983741208602],[114.12762147239825,30.459837434939054],[114.12762143635086,30.459837457998546],[114.12762140942452,30.459837475421487],[114.12762138260884,30.459837492972426],[114.12762134700236,30.459837516543274],[114.12762132040922,30.4598375343481],[114.12762129392969,30.45983755227932],[114.12762125877431,30.45983757635521],[114.12762123252163,30.459837594537248],[114.12762120638483,30.45983761284398],[114.12762118034041,30.4598376312499],[114.12762115441242,30.45983764977954],[114.1276211285784,30.459837668407918],[114.12762110286229,30.459837687158597],[114.12762107724106,30.459837706006923],[114.127621051739,30.459837724976808],[114.127621017897,30.459837750431678],[114.12762098424793,30.459837776079],[114.12762095079383,30.45983780191797],[114.1276209175363,30.45983782794712],[114.12762088447656,30.459837854165812],[114.12762085161621,30.459837880572458],[114.12762081895652,30.45983790716621],[114.12762079458794,30.459837927227774],[114.12762077034647,30.459837947404928],[114.12762073819461,30.459837974461017],[114.1276207062484,30.45983800170016],[114.12762067450895,30.459838029121173],[114.12762064297809,30.45983805672239],[114.12762061165706,30.4598380845032],[114.1276205805471,30.45983811246234],[114.1276205573472,30.459838133542032],[114.12762053428062,30.459838154732015],[114.12762050370418,30.459838183131183],[114.12762047334368,30.459838211704074],[114.12762044320004,30.45983824044978],[114.12762041327487,30.459838269366823],[114.12762039096796,30.45983829116111],[114.12762036884001,30.459838313020498],[114.12762033952039,30.45983834230591],[114.12762031042284,30.45983837175765],[114.12762028873873,30.45983839395009],[114.12762026719523,30.45983841624474],[114.12762023865783,30.459838446106975],[114.12762021034742,30.45983847613115],[114.12762018226395,30.459838506316082],[114.12762015440984,30.459838536659873],[114.12762012678577,30.459838567161917],[114.12762009939274,30.45983859782078],[114.12762007899315,30.45983862091195],[114.12762005873914,30.459838644099637],[114.12762003860283,30.459838667364142],[114.12762001861302,30.459838690723796],[114.12761999215621,30.459838721995837],[114.12761996593728,30.459838753417987],[114.12761993995598,30.459838784988815],[114.12761992061982,30.459838808759166],[114.12761990143335,30.459838832620687],[114.12761987605282,30.459838864557106],[114.12761985091379,30.45983889663744],[114.12761982601839,30.459838928860368],[114.1276198074992,30.459838953115334],[114.1276197891332,30.459838977458347],[114.1276197708901,30.45983900187066],[114.12761975280124,30.45983902636931],[114.12761972888869,30.459839059147654],[114.12761971110986,30.459839083816473],[114.1276196934865,30.459839108569174],[114.12761967019709,30.459839141683588],[114.12761964715848,30.45983917493035],[114.12761962437214,30.45983920830697],[114.12761960183853,30.459839241813157],[114.12761957955888,30.459839275446402],[114.12761955753355,30.45983930920586],[114.12761953576491,30.459839343090163],[114.12761951425206,30.459839377097545],[114.12761949299771,30.459839411226756],[114.12761947200165,30.459839445476035],[114.12761945126489,30.45983947984424],[114.12761943587527,30.459839505693772],[114.12761942064874,30.459839531615984],[114.12761940555305,30.459839557595664],[114.1276193906211,30.45983958364661],[114.12761937582103,30.459839609753768],[114.12761936118548,30.459839635930937],[114.12761934189163,30.45983967092571],[114.1276193228632,30.459839706029513],[114.12761930402522,30.45983974138382],[114.12761928543088,30.459839776892835],[114.12761926710763,30.459839812507347],[114.12761925353418,30.4598398392837],[114.12761924013094,30.45983986612391],[114.127619222487,30.459839901995572],[114.12761920511619,30.459839937967555],[114.12761918801932,30.459839974038033],[114.1276191711972,30.45984001020549],[114.12761915875262,30.459840037390883],[114.12761914648067,30.459840064635245],[114.12761913434706,30.459840091926043],[114.12761912238625,30.459840119274276],[114.12761911056447,30.459840146667975],[114.12761909891618,30.459840174117343],[114.12761908740727,30.45984020161087],[114.12761907607344,30.459840229159045],[114.12761906119373,30.459840265960946],[114.12761904659479,30.459840302847468],[114.12761903227624,30.459840339816335],[114.12761902171361,30.459840367593937],[114.12761901132711,30.459840395421235],[114.12761900108167,30.459840423288085],[114.12761899101291,30.459840451203267],[114.12761897782404,30.459840488486794],[114.1276189649191,30.45984052584471],[114.1276189522991,30.459840563275755],[114.12761894301164,30.459840591393576],[114.12761893390257,30.459840619555518],[114.12761892199579,30.459840657161408],[114.1276189132444,30.459840685408295],[114.12761890467253,30.459840713696238],[114.12761889348214,30.45984075146724],[114.12761888257951,30.459840789301538],[114.12761887196457,30.459840827196913],[114.12761886163878,30.459840865152394],[114.12761885407448,30.459840893655265],[114.12761884669118,30.45984092219384],[114.12761883945247,30.459840950760203],[114.12761883239541,30.459840979360447],[114.12761882548295,30.45984100798744],[114.12761881875272,30.459841036646793],[114.12761881002072,30.459841074901362],[114.12761880158001,30.45984111320473],[114.12761879543179,30.459841141962116],[114.12761878946586,30.459841170748103],[114.12761878175492,30.45984120916665],[114.12761877433641,30.459841247628418],[114.12761876721046,30.459841286131805],[114.12761876204941,30.459841315033813],[114.12761875707136,30.459841343960434],[114.12761875067861,30.459841382559674],[114.12761874457951,30.45984142119468],[114.12761874018916,30.45984145019221],[114.1276187359827,30.45984147920998],[114.12761873192328,30.459841508243642],[114.12761872804813,30.45984153729595],[114.12761872312669,30.45984157605629],[114.12761871962003,30.459841605143612],[114.12761871629831,30.459841634247077],[114.1276187131238,30.45984166336285],[114.12761871013377,30.459841692493065],[114.12761870729186,30.459841721634284],[114.12761870463422,30.459841750788797],[114.12761870133754,30.459841789677174],[114.12761869833716,30.45984182858323],[114.12761869563249,30.45984186750567],[114.1276186937895,30.459841896706717],[114.12761869213125,30.459841925915605],[114.12761869062098,30.45984195513055],[114.12761868929645,30.4598419843522],[114.12761868777706,30.459842023321897],[114.1276186865544,30.45984206229927],[114.12761868582245,30.459842091536046],[114.12761868527593,30.45984212077583],[114.12761868487829,30.459842150017064],[114.12761868466548,30.45984217925972],[114.12761868460136,30.4598422085028],[114.12761868472242,30.459842237745644],[114.12761868513086,30.459842276734594],[114.12761868583661,30.459842315719577],[114.12761868655103,30.45984234495549],[114.12761868745064,30.45984237418736],[114.12761868849857,30.459842403415447],[114.12761868973193,30.459842432637902],[114.12761869111358,30.459842461854933],[114.12761869268054,30.45984249106513],[114.12761869501627,30.459842530000905],[114.12761869764839,30.459842568922024],[114.12761870057666,30.459842607827213],[114.1276187038008,30.45984264671456],[114.12761870732085,30.459842685582235],[114.12761871113665,30.459842724428814],[114.1276187152479,30.45984276325186],[114.12761871616526,30.4598428018164],[114.12762504625675,30.45984893543803],[114.12762949228308,30.459852225409115],[114.1276374231897,30.459856310308833],[114.1276616430547,30.459868103554726],[114.12776663568344,30.459917575602454],[114.12778261125963,30.45992505064024],[114.12813135914148,30.4600900038603],[114.12814639053336,30.460097240706176],[114.12816552011387,30.460106150285668],[114.1281841717783,30.46011497172923],[114.12820244821832,30.460121744953625],[114.12821657240998,30.460126959969266],[114.12822533712574,30.460129617014047],[114.12823552448107,30.460132304143233],[114.12825978483049,30.460138049224465],[114.12826945708734,30.460140229306568],[114.1282785540599,30.46014182891401],[114.12829062480424,30.460144068728933],[114.12829917773735,30.460145440326883],[114.12832593940803,30.460147729175638],[114.12834108083686,30.46014871687025],[114.1283521088651,30.4601489500489],[114.12835742918749,30.46014891256776],[114.12837417703372,30.460148560094062],[114.12838159291634,30.460148039918515],[114.12838443020314,30.460133341602067]]],[[[114.12848080370732,30.46021996386791],[114.1284606817147,30.460222550643277],[114.12842428170627,30.460225607748736],[114.1284355436386,30.460270462042168],[114.12847819403144,30.460289804892188],[114.12848303613838,30.460290623268943],[114.12848869445524,30.460290794092238],[114.12849910512165,30.46028920664694],[114.12850612224027,30.46028710502764],[114.12851218636065,30.46028186305056],[114.12851944887758,30.460272354453615],[114.12853544237866,30.460248200417247],[114.12848080370732,30.46021996386791]]],[[[114.12876098693175,30.460327242497126],[114.12871829029552,30.460330156172663],[114.12869807501227,30.460363024516155],[114.12869614675651,30.460371470071735],[114.1286958516941,30.46037890808962],[114.1286974221503,30.46038724711732],[114.12869941534363,30.460391301317554],[114.12870328283655,30.460395533591264],[114.12870918351372,30.460399367437283],[114.12871169761027,30.460401000920996],[114.12875440144785,30.460421163179134],[114.12877706273908,30.460376546593384],[114.12876098693175,30.460327242497126]]],[[[114.12753905401199,30.46014761437533],[114.1275338599891,30.46014791932803],[114.12752450151488,30.460149209546806],[114.12752101223012,30.46014984828435],[114.12751615369005,30.460151116533744],[114.12750968572972,30.46015305431144],[114.12750768991903,30.4601537788084],[114.12762175207672,30.460208054824022],[114.12767435871464,30.46023264059284],[114.12791771848921,30.46034719627658],[114.12797508357396,30.460374199251135],[114.12799381256822,30.460382912647844],[114.12801514013283,30.460393016077504],[114.12802370784166,30.460397437090197],[114.1280280824312,30.460399920137075],[114.12803869484003,30.460406549377637],[114.12805096331688,30.460414819338357],[114.1280637371187,30.46042392988968],[114.12807228893135,30.460430668873688],[114.1280896451314,30.46044474754322],[114.12809933904086,30.460453695358574],[114.12811173214664,30.4604661789348],[114.12812094518,30.460476242493055],[114.12812933396587,30.46048571128608],[114.1281362289695,30.460494029909185],[114.1281388873361,30.46049745989069],[114.12815040705412,30.460510719082308],[114.12815427578668,30.46051473903124],[114.12815698491843,30.46051610904076],[114.12816000021421,30.460516411494666],[114.12817484541584,30.46051340764445],[114.12817310991078,30.460510604870887],[114.12816915556422,30.46050441420295],[114.12816408899778,30.46049610720233],[114.12815907912268,30.46048870347132],[114.12815495677987,30.460482572393023],[114.1281496400492,30.460475712433443],[114.1281427450495,30.46046739381447],[114.12813435626802,30.460457925026244],[114.1281251432397,30.460447861473753],[114.12811275014104,30.460435377904666],[114.12810305623711,30.460426430095055],[114.12808570004728,30.460412351435764],[114.12807714823967,30.46040561245698],[114.12806437444586,30.460396501913202],[114.12805210597658,30.460388231959485],[114.12804149357405,30.460381602725292],[114.12803711898684,30.460379119681253],[114.12802855128386,30.46037469867336],[114.12800722373298,30.460364595256255],[114.12798849475082,30.460355881870463],[114.12768777008509,30.460214323390005],[114.12763516348114,30.46018973765222],[114.12758087853727,30.46016426831105],[114.127549333318,30.460149654253833],[114.1275469608798,30.460148701939403],[114.12754442581192,30.460147980719363],[114.12753905401199,30.46014761437533]]],[[[114.1283105764358,30.46046782138418],[114.1282703180798,30.46049109632768],[114.12827622553172,30.460502624397915],[114.12828152820767,30.460515645183932],[114.12828729169814,30.460530317484707],[114.12829079823528,30.46053987849389],[114.12834003760709,30.460556818168108],[114.12836063324018,30.46052378574948],[114.12836376369664,30.460518006512633],[114.12836459736319,30.46051508338462],[114.12836541369076,30.460512042620753],[114.12836514161567,30.46050926750724],[114.1283647553862,30.460505777343094],[114.12836374082613,30.460502454308315],[114.12836197313399,30.460498592147694],[114.12836034108322,30.460496131066375],[114.12835756235341,30.46049308645533],[114.12835458131129,30.460490208959772],[114.12835106750647,30.46048742844768],[114.12834542677822,30.46048453981788],[114.12832747170387,30.46047566264443],[114.1283105764358,30.46046782138418]]],[[[114.1285716711271,30.460593593058658],[114.12856745920965,30.460593720536945],[114.12856122741601,30.46059469837452],[114.12855772059082,30.460595941281333],[114.1285522979939,30.460598027727023],[114.12854839016643,30.460600191291086],[114.12854500787812,30.46060214946514],[114.12854230576508,30.460604654237667],[114.12854039690191,30.460607392273527],[114.12851933861432,30.460639796030616],[114.12858022522585,30.460671950374703],[114.1285895537819,30.460669925476665],[114.12860621810192,30.460667869126652],[114.12862116348589,30.46066596587378],[114.12863837299199,30.460664351103055],[114.12862658288586,30.460617500482353],[114.12858066923467,30.460596083515824],[114.12857532038161,30.460594229469987],[114.1285716711271,30.460593593058658]]],[[[114.12897714429718,30.460453957032687],[114.1289692687468,30.460455342094463],[114.1289652256492,30.46045728543705],[114.12896128503084,30.460460424312014],[114.12895919341386,30.460464000223794],[114.12896141988213,30.46046569754427],[114.12896562637829,30.460469304460965],[114.12897456271966,30.460476495050344],[114.12897886427122,30.460479812759715],[114.12897935757692,30.460480189934927],[114.12898217988712,30.46048225042216],[114.12899244448124,30.46048937726859],[114.12900031997047,30.460494519353627],[114.12900805299152,30.460499048680862],[114.1290194750882,30.46050498195191],[114.1290352411703,30.46051301856558],[114.12905582952001,30.460523502378468],[114.12908361933961,30.460538052699043],[114.12945622823806,30.46071324291898],[114.12947048490607,30.460719948422255],[114.1294720816535,30.46072074574196],[114.12947592248388,30.460706120587197],[114.12946322937594,30.46070015049239],[114.1290906206135,30.460524960386824],[114.12906283080406,30.460510410074754],[114.12904224246184,30.460499926268238],[114.1290372063477,30.460497359157156],[114.1290197734809,30.460486242838083],[114.12900059389624,30.460471954879953],[114.12898620514909,30.46045904119022],[114.12898247039269,30.460455143540806],[114.1289800186363,30.460454375743627],[114.12897714429718,30.460453957032687]]],[[[114.12819690408288,30.460564240180304],[114.1281865261613,30.4605674144638],[114.1281822186377,30.460568994070364],[114.12818053522149,30.460570151832588],[114.12817949550193,30.460571246937015],[114.12817924512454,30.460573839898743],[114.12817989715913,30.46057786340805],[114.12818195099064,30.46058659457124],[114.1281856385046,30.460597393987406],[114.12818940537856,30.460607712761643],[114.1281927953916,30.460620418854052],[114.12819466214823,30.460635542514808],[114.12819561518553,30.460648069173835],[114.12819602530844,30.4606641167207],[114.12819591336795,30.46067465478047],[114.12819434854497,30.460695108837097],[114.12819258769844,30.460709383066078],[114.12818963393035,30.460722462959897],[114.12817555905188,30.460752954166573],[114.12816054083802,30.460779511162954],[114.1281489573597,30.460799553409373],[114.1281397254731,30.460814394572154],[114.12808936576536,30.460894645704666],[114.12797226529804,30.461083207482826],[114.12748164436566,30.461863076578037],[114.12749605673096,30.46186848121499],[114.12801970600025,30.46103656746764],[114.12815589593656,30.46081798258573],[114.12816512781515,30.460803141416246],[114.12817671128303,30.460783099161485],[114.12819172948392,30.460756542154517],[114.128205804349,30.4607260509376],[114.1282087581131,30.460712971041602],[114.12821092672456,30.4606980520665],[114.12821208377527,30.460678242753254],[114.12821257112043,30.46066722633577],[114.1282122678114,30.460651493822446],[114.12821182592609,30.460639142547798],[114.12821021761332,30.460623642476424],[114.12820830338848,30.460610140794113],[114.12820676016533,30.460600871482857],[114.1282041467479,30.460588715452456],[114.12820072582716,30.460575969525987],[114.12819690408288,30.460564240180304]]],[[[114.12974981793391,30.46083446168812],[114.12974637290162,30.46083505804879],[114.12974633375953,30.4608350773345],[114.12974629465444,30.460835096676004],[114.12974625558576,30.46083511607342],[114.12974621655387,30.460835135526747],[114.12974617755953,30.46083515503621],[114.12974613860139,30.460835174601243],[114.12974609968082,30.460835194221847],[114.12974606079756,30.460835213898076],[114.12974602195166,30.460835233629986],[114.12974598314332,30.460835253417525],[114.12974594437198,30.46083527326063],[114.1297459056391,30.460835293159246],[114.12974586694355,30.460835313112977],[114.129745828286,30.460835333122105],[114.12974578966617,30.460835353186745],[114.12974575108491,30.460835373306328],[114.12974571254188,30.46083539348142],[114.12974567403664,30.4608354137114],[114.12974563556999,30.460835433996607],[114.12974559714226,30.46083545433653],[114.12974555875266,30.46083547473179],[114.12974552040188,30.460835495181765],[114.12974548209013,30.460835515686455],[114.12974544381717,30.460835536246314],[114.12974540558328,30.460835556860378],[114.1297453673883,30.460835577529437],[114.12974532923315,30.46083559825321],[114.12974529111682,30.460835619031304],[114.1297452530402,30.460835639864218],[114.12974521500283,30.460835660751343],[114.12974517700553,30.46083568169312],[114.12974513904761,30.460835702688755],[114.1297451011301,30.460835723738995],[114.12974506325219,30.46083574484343],[114.129745025414,30.460835766002017],[114.12974498761653,30.46083578721474],[114.12974494985934,30.460835808481445],[114.12974491214234,30.460835829802125],[114.12974487446563,30.46083585117694],[114.1297448368299,30.460835872605397],[114.12974479923466,30.460835894087822],[114.1297447616805,30.460835915623992],[114.12974472416663,30.460835937213854],[114.12974468669428,30.46083595885729],[114.12974464926269,30.460835980554528],[114.12974461187261,30.460836002305054],[114.12974457452363,30.46083602410933],[114.12974453721606,30.460836045966555],[114.12974449995004,30.46083606787741],[114.12974446272574,30.460836089841496],[114.12974442554314,30.460836111858995],[114.12974438840205,30.460836133929604],[114.12974435130327,30.460836156052935],[114.12974431424581,30.460836178229904],[114.12974427723125,30.460836200459585],[114.12974424025855,30.460836222741996],[114.12974420332807,30.46083624507735],[114.12974416644003,30.460836267465762],[114.12974412959456,30.460836289906847],[114.12974409279208,30.46083631240053],[114.12974405603171,30.460836334946766],[114.12974401931491,30.460836357545723],[114.12974398264045,30.460836380197062],[114.12974394600889,30.460836402900778],[114.12974390942067,30.460836425657046],[114.12974387287557,30.460836448465802],[114.12974383637379,30.460836471326257],[114.12974379991573,30.460836494239317],[114.12974376350067,30.46083651720447],[114.12974372712944,30.46083654022155],[114.12974369080233,30.460836563290776],[114.12974365451844,30.460836586411872],[114.1297436182786,30.460836609584778],[114.12974358208278,30.460836632809663],[114.12974354593123,30.46083665608607],[114.12974350982368,30.46083667941435],[114.12974347376041,30.460836702794264],[114.12974343774174,30.460836726225647],[114.12974340176754,30.46083674970867],[114.12974336583773,30.460836773243045],[114.12974332995272,30.46083679682872],[114.12974329411266,30.460836820465687],[114.12974325831743,30.460836844153953],[114.129743222567,30.460836867893516],[114.12974318686165,30.460836891684032],[114.12974315120157,30.46083691552568],[114.12974311558709,30.46083693941827],[114.12974308001766,30.460836963361775],[114.12974304449341,30.460836987356114],[114.12974300901502,30.460837011401523],[114.12974297358232,30.460837035497374],[114.1297429381956,30.460837059643833],[114.1297429028544,30.460837083841025],[114.12974286755903,30.46083710808849],[114.12974283230997,30.460837132386736],[114.12974279710718,30.460837156735202],[114.12974276195025,30.46083718113405],[114.12974272683962,30.46083720558317],[114.12974269177575,30.46083723008234],[114.1297426567582,30.46083725463189],[114.12974262178747,30.460837279231367],[114.12974258686361,30.460837303880897],[114.12974255198618,30.46083732858029],[114.12974251715573,30.460837353329396],[114.12974248237215,30.46083737812854],[114.12974244763576,30.460837402977557],[114.12974241294671,30.460837427875934],[114.12974237830485,30.46083745282385],[114.12974234371045,30.46083747782146],[114.12974230916356,30.460837502868262],[114.12974227466388,30.46083752796477],[114.129742240212,30.460837553110412],[114.12974220580787,30.460837578305412],[114.12974217145187,30.460837603549603],[114.12974213714375,30.460837628842754],[114.1297421028834,30.46083765418504],[114.1297420686714,30.4608376795764],[114.12974203450729,30.460837705016665],[114.12974200039173,30.460837730505613],[114.12974196632476,30.460837756043233],[114.12974193230623,30.46083778162976],[114.12974189833604,30.460837807264966],[114.12974186441465,30.460837832948457],[114.12974183054206,30.460837858680613],[114.12974179671848,30.46083788446117],[114.12974176308428,30.460837910182224],[114.12974172949909,30.460837935950995],[114.12974169596282,30.4608379617677],[114.12974166247488,30.460837987632175],[114.12974162903632,30.46083801354419],[114.1297415956462,30.460838039503916],[114.12974156230555,30.460838065511123],[114.12974152901394,30.46083809156581],[114.12974149577191,30.46083811766776],[114.1297414625789,30.460838143817245],[114.12974142943571,30.460838170013815],[114.12974139634154,30.460838196257583],[114.12974136329764,30.46083822254855],[114.12974133030254,30.460838248886375],[114.12974129735781,30.460838275271225],[114.12974126446325,30.460838301703102],[114.12974123161838,30.46083832818167],[114.12974119882378,30.46083835470726],[114.129741166079,30.460838381279196],[114.1297411333845,30.460838407897647],[114.12974110074083,30.460838434562728],[114.12974106814708,30.46083846127438],[114.12974103560418,30.460838488032493],[114.12974100311153,30.46083851483672],[114.12974097066974,30.460838541687462],[114.12974093827866,30.460838568584037],[114.12974090593866,30.460838595526898],[114.12974087364948,30.46083862251576],[114.12974084141126,30.460838649550514],[114.1297408092241,30.46083867663133],[114.12974077708834,30.460838703757858],[114.129740745004,30.460838730930053],[114.12974071297083,30.460838758147847],[114.1297406809894,30.460838785411422],[114.12974064905924,30.46083881272043],[114.12974061718063,30.460838840074867],[114.12974058535421,30.46083886747474],[114.12974055357897,30.46083889491982],[114.12974052185616,30.460838922410385],[114.12974049018518,30.460838949945703],[114.1297404585663,30.46083897752623],[114.12974042699975,30.46083900515185],[114.12974039548483,30.460839032822157],[114.12974036402267,30.460839060537616],[114.12974033261303,30.460839088297718],[114.12974030125595,30.46083911610245],[114.1297402699514,30.460839143951986],[114.12974023869893,30.460839171845763],[114.12974020749968,30.460839199784292],[114.12974017635355,30.460839227767114],[114.12974014525973,30.46083925579418],[114.12974011421957,30.46083928386559],[114.12974008323208,30.460839311981182],[114.12974005229756,30.46083934014079],[114.12974002141708,30.46083936834458],[114.12973999058889,30.460839396592032],[114.12973995981484,30.460839424883495],[114.12973992909423,30.46083945321897],[114.12973989842708,30.460839481597944],[114.12973986781358,30.46083951002053],[114.12973983725387,30.460839538486617],[114.12973980674808,30.46083956699654],[114.12973977629618,30.46083959554939],[114.12973974589816,30.460839624146082],[114.12973971555442,30.460839652785594],[114.1297396852648,30.460839681468485],[114.12973965502917,30.46083971019453],[114.12973962484803,30.460839738963223],[114.12973959472167,30.4608397677753],[114.12973956464924,30.460839796630022],[114.1297395346315,30.4608398255275],[114.12973950466834,30.46083985446774],[114.12973947476031,30.460839883450735],[114.12973944490686,30.460839912476146],[114.12973941510836,30.460839941543973],[114.12973938536442,30.46083997065439],[114.1297393556761,30.460839999807],[114.12973932604281,30.460840029001904],[114.12973929646455,30.46084005823883],[114.12973926694166,30.460840087518005],[114.12973923747415,30.460840116839023],[114.12973920806212,30.460840146202116],[114.1297391787058,30.46084017560706],[114.12973914940466,30.460840205053735],[114.12973912015968,30.460840234542086],[114.12973909097016,30.460840264072118],[114.12973906183629,30.46084029364337],[114.12973903275855,30.460840323256296],[114.1297390037371,30.46084035291073],[114.12973897477148,30.460840382606158],[114.12973894586216,30.46084041234309],[114.12973891700888,30.460840442120848],[114.12973888821224,30.46084047194011],[114.12973885947152,30.46084050180002],[114.12973883078767,30.46084053170059],[114.1297388021601,30.460840561642435],[114.12973877358935,30.46084059162459],[114.12973874507516,30.46084062164757],[114.1297387166179,30.460840651710974],[114.1297386882176,30.4608406818152],[114.12973865987372,30.460840711959396],[114.12973863158757,30.4608407421443],[114.12973860335774,30.46084077236917],[114.12973857518587,30.460840802634362],[114.12973854707062,30.460840832939567],[114.12973851901269,30.46084086328475],[114.12973849101286,30.460840893669847],[114.12973846306974,30.46084092409468],[114.1297384351844,30.460840954559426],[114.1297384073566,30.460840985063747],[114.12973837958685,30.460841015607635],[114.12973835187445,30.46084104619138],[114.12973832422003,30.460841076813896],[114.12973829662381,30.4608411074761],[114.12973826908535,30.46084113817748],[114.12973824160466,30.460841168918137],[114.1297382141824,30.460841199697803],[114.12973818681834,30.460841230516465],[114.1297381595125,30.460841261374195],[114.129738132265,30.460841292270576],[114.12973810507626,30.46084132320585],[114.12973807794552,30.460841354179724],[114.12973805087411,30.46084138519232],[114.1297380238607,30.460841416243063],[114.12973799690607,30.460841447332637],[114.12973797001065,30.46084147846019],[114.12973794317357,30.460841509626057],[114.12973791639597,30.46084154083025],[114.12973788967678,30.46084157207241],[114.12973786301728,30.460841603352552],[114.12973783641623,30.46084163467062],[114.12973780987508,30.46084166602666],[114.12973778339317,30.46084169742028],[114.12973775697006,30.46084172885176],[114.12973773060718,30.460841760320598],[114.12973770430318,30.460841791827075],[114.12973767805912,30.46084182337095],[114.12973765183263,30.460841855002833],[114.12973762566584,30.460841886672227],[114.1297375995592,30.460841918378925],[114.12973757351246,30.460841950122514],[114.1297375475262,30.460841981903233],[114.12973752159988,30.460842013721123],[114.12973749573389,30.460842045576083],[114.12973746992874,30.460842077467486],[114.12973744418316,30.460842109395898],[114.12973741849842,30.460842141361034],[114.12973739287459,30.460842173362558],[114.1297373673108,30.460842205400915],[114.12973734180794,30.460842237475145],[114.1297373163659,30.460842269586273],[114.12973729098468,30.460842301733106],[114.12973726566462,30.46084233391637],[114.12973724040495,30.46084236613574],[114.12973721520687,30.460842398391033],[114.1297371900696,30.460842430682032],[114.12973716499351,30.460842463009016],[114.12973713997859,30.460842495371644],[114.12973711502528,30.460842527769803],[114.12973709013336,30.460842560203886],[114.1297370653027,30.46084259267299],[114.12973704053333,30.460842625177513],[114.12973701582568,30.460842657717507],[114.12973699117954,30.460842690292466],[114.12973696659547,30.460842722902548],[114.12973694207301,30.46084275554771],[114.12973691761239,30.460842788227833],[114.12973689321396,30.460842820942695],[114.12973686887703,30.46084285369234],[114.12973684460216,30.460842886476776],[114.12973682038982,30.460842919295605],[114.12973679623921,30.460842952149],[114.12973677215113,30.460842985036614],[114.12973674812511,30.46084301795868],[114.12973672416196,30.460843050915077],[114.12973670026052,30.460843083905242],[114.12973667642233,30.46084311692957],[114.12973665264605,30.46084314998801],[114.12973662893286,30.4608431830801],[114.1297366052821,30.460843216206126],[114.12973658169409,30.460843249365634],[114.12973655816893,30.46084328255914],[114.12973653470664,30.460843315785496],[114.12973651130744,30.460843349045906],[114.1297364879711,30.460843382338947],[114.12973646469763,30.460843415665693],[114.12973644148794,30.460843449025408],[114.1297364183411,30.46084348241832],[114.12973639525725,30.46084351584398],[114.12973637223703,30.460843549302492],[114.12973634928004,30.46084358279397],[114.1297363263868,30.460843616318027],[114.12973630355656,30.460843649874768],[114.12973628079052,30.460843683463793],[114.12973625808748,30.46084371708522],[114.12973623544853,30.460843750739155],[114.12973621287325,30.460843784425332],[114.12973619036174,30.46084381814345],[114.12973616791412,30.460843851893795],[114.12973614553071,30.460843885675917],[114.12973612321063,30.460843919490095],[114.12973610095511,30.46084395333588],[114.12973607876371,30.460843987213384],[114.12973605663619,30.460844021122607],[114.12973603457323,30.46084405506303],[114.12973601257461,30.460844089035064],[114.1297359906401,30.460844123038466],[114.12973596877005,30.460844157073083],[114.12973594696467,30.460844191138673],[114.12973592522376,30.46084422523536],[114.12973590354716,30.46084425936302],[114.1297358819357,30.46084429352155],[114.12973586038882,30.46084432771077],[114.12973583890616,30.4608443619308],[114.12973581748874,30.460844396181177],[114.12973579613644,30.460844430462082],[114.12973577484884,30.460844464773228],[114.12973575362625,30.460844499114945],[114.1297357324688,30.46084453348708],[114.12973571137626,30.46084456788871],[114.12973569034918,30.46084460232064],[114.12973566938712,30.460844636782692],[114.12973564849041,30.460844671274355],[114.12973562765896,30.460844705795804],[114.12973560689274,30.460844740346865],[114.12973558619225,30.46084477492731],[114.12973556555733,30.460844809537424],[114.12973554498811,30.460844844176926],[114.12973552448413,30.460844878845702],[114.1297355040462,30.460844913543514],[114.12973548367349,30.460844948270605],[114.12973546336686,30.46084498302645],[114.12973544312648,30.460845017811288],[114.12973542295134,30.460845052624883],[114.12973540284247,30.460845087467234],[114.12973538279986,30.460845122338068],[114.12973536282306,30.460845157237486],[114.1297353429121,30.460845192165376],[114.12973532306773,30.460845227121684],[114.1297353032894,30.460845262106304],[114.12973528357745,30.460845297118656],[114.12973526393154,30.46084533215931],[114.12973524435202,30.46084536722776],[114.12973522483944,30.46084540232411],[114.12973520539312,30.460845437448253],[114.12973518602747,30.460845472574082],[114.12973516672857,30.460845507727242],[114.12973514749625,30.460845542907798],[114.12973512832998,30.460845578115748],[114.12973510923099,30.460845613351097],[114.12973509019851,30.460845648613436],[114.12973507123286,30.460845683902832],[114.12973505233391,30.460845719219222],[114.12973503350169,30.460845754562495],[114.12973501473667,30.460845789932424],[114.12973499603847,30.46084582532906],[114.12973497740721,30.4608458607523],[114.12973495884313,30.460845896201963],[114.12973494034634,30.460845931677994],[114.12973492191625,30.460845967180454],[114.12973490355392,30.460846002708944],[114.12973488525832,30.460846038263522],[114.12973486703022,30.4608460738443],[114.12973484886962,30.46084610945076],[114.12973483077634,30.46084614508314],[114.12973481275034,30.460846180741147],[114.12973479479184,30.460846216424898],[114.12973477690122,30.460846252133997],[114.12973475907809,30.4608462878689],[114.12973474132248,30.46084632362852],[114.12973472363463,30.46084635941377],[114.12973470601439,30.460846395224028],[114.12973468846224,30.46084643105929],[114.12973467097771,30.4608464669195],[114.12973465356104,30.460846502804547],[114.12973463621256,30.460846538714367],[114.12973461893193,30.460846574648848],[114.12973460171918,30.46084661060794],[114.1297345845747,30.46084664659147],[114.12973456749836,30.46084668259943],[114.12973455049007,30.460846718631206],[114.12973453354985,30.4608467546877],[114.12973451667828,30.460846790767835],[114.12973449987447,30.460846826872007],[114.12973448313932,30.460846863000274],[114.12973446647291,30.460846899152124],[114.12973444987446,30.460846935327726],[114.1297344333448,30.460846971526685],[114.12973441688342,30.460847007749397],[114.1297344004907,30.46084704399535],[114.12973438416665,30.460847080264656],[114.12973436791145,30.46084711655698],[114.12973435172445,30.460847152872656],[114.12973433560668,30.460847189211066],[114.12973431955744,30.460847225572486],[114.12973430357685,30.46084726195675],[114.12973428766557,30.460847298363515],[114.1297342718232,30.460847334793126],[114.12973425604935,30.460847371245066],[114.12973424034494,30.46084740771928],[114.1297342247094,30.460847444215997],[114.12973420914298,30.460847480734877],[114.12973419364577,30.460847517275745],[114.12973417821766,30.460847553838715],[114.12973416285875,30.46084759042368],[114.12973414756908,30.46084762703023],[114.12973413234874,30.460847663658544],[114.12973411719773,30.460847700308626],[114.12973410211626,30.46084773698001],[114.12973408710438,30.460847773672988],[114.12973407216123,30.460847810387047],[114.1297340572882,30.460847847122526],[114.12973404248488,30.4608478838792],[114.12973402775042,30.460847920656498],[114.12973401308629,30.460847957455154],[114.12973399849137,30.46084799427439],[114.12973398396615,30.460848031114182],[114.12973396951094,30.460848067974663],[114.12973395512537,30.460848104855714],[114.12973394080963,30.46084814175727],[114.12973392656387,30.460848178679],[114.1297339123879,30.460848215620956],[114.12973389828205,30.46084825258308],[114.1297338842462,30.460848289565206],[114.12973387027971,30.460848326567326],[114.1297338563841,30.46084836358911],[114.12973384255785,30.46084840063072],[114.12973382880251,30.460848437691986],[114.12973381511672,30.460848474772686],[114.1297338015013,30.4608485118727],[114.12973378795623,30.460848548992203],[114.12973377448137,30.460848586130847],[114.12973376107654,30.460848623288587],[114.12973374774252,30.460848660465413],[114.12973373447873,30.460848697661216],[114.12973372128518,30.460848734875423],[114.12973370816232,30.460848772108548],[114.1297336951098,30.46084880936048],[114.12973368212799,30.460848846630757],[114.1297336692164,30.460848883919507],[114.12973365637585,30.4608489212266],[114.12973364360587,30.460848958551814],[114.12973363090637,30.46084899589504],[114.12973361827731,30.4608490332565],[114.12973360571931,30.460849070635625],[114.12973359323186,30.46084910803265],[114.12973358081558,30.46084914544757],[114.12973356846986,30.460849182879752],[114.12973355619496,30.46084922032972],[114.1297335439911,30.46084925779696],[114.12973353185824,30.460849295281747],[114.12973351979586,30.46084933278318],[114.12973350780496,30.460849370302277],[114.12973349588535,30.46084940783802],[114.12973348403617,30.460849445390746],[114.12973347225812,30.460849482960118],[114.1297334605517,30.460849520546475],[114.1297334489162,30.46084955814942],[114.1297334373516,30.460849595768664],[114.12973342585872,30.460849633404205],[114.12973341443677,30.460849671056167],[114.12973340308616,30.460849708724265],[114.12973339180684,30.46084974640838],[114.12973338059889,30.460849784108678],[114.12973336946253,30.460849821824652],[114.12973335839732,30.460849859556586],[114.12973334740369,30.460849897304204],[114.12973333648145,30.460849935067095],[114.12973332563045,30.460849972845722],[114.12973331485165,30.46085001063951],[114.12973330414376,30.460850048448865],[114.12973329350778,30.46085008627327],[114.12973328294329,30.460850124112376],[114.12973327245044,30.460850161966828],[114.12973326202902,30.460850199835985],[114.1297332516797,30.460850237719967],[114.12973324140182,30.46085027561877],[114.12973323119591,30.460850313531775],[114.12973322106194,30.46085035145926],[114.12973321099923,30.460850389401223],[114.12973320100902,30.460850427357677],[114.12973319109005,30.460850465327923],[114.12973318124291,30.46085050331214],[114.1297331714683,30.460850541310503],[114.12973316176529,30.460850579322493],[114.12975322379191,30.46086161535706],[114.12977616132954,30.460873949171205],[114.12978097330156,30.460876117722883],[114.1297855789253,30.460876853934355],[114.1297856144716,30.46087685528354],[114.12978565002072,30.46087685656814],[114.12978568557301,30.460876857789053],[114.12978572112847,30.46087685894514],[114.12978575668633,30.460876860037487],[114.12978579224712,30.46087686106541],[114.12978582781005,30.460876862029256],[114.12978586337594,30.460876862928792],[114.12978589894306,30.460876863764128],[114.12978593451278,30.46087686453516],[114.12978597008455,30.460876865242227],[114.12978600565755,30.46087686588481],[114.12978604123214,30.460876866463256],[114.12978607680833,30.460876866977337],[114.12978611238574,30.460876867427455],[114.1297861479644,30.46087686781309],[114.1297861835434,30.460876868134424],[114.12978621912363,30.460876868391786],[114.12978625470431,30.460876868584783],[114.12978629028534,30.46087686871336],[114.12978632586714,30.460876868777916],[114.1297863614486,30.46087686877817],[114.1297863970304,30.460876868714053],[114.12978643261184,30.46087686858586],[114.1297864681934,30.4608768683933],[114.12978650377416,30.460876868136612],[114.129786539354,30.46087686781561],[114.12978657493395,30.460876867430308],[114.1297866105122,30.46087686698081],[114.12978664608987,30.46087686646718],[114.12978668166595,30.460876865889077],[114.12978671724063,30.460876865247005],[114.12978675281389,30.460876864540456],[114.12978678838552,30.46087686376978],[114.12978682395556,30.460876862934906],[114.1297868595231,30.46087686203573],[114.12978689508869,30.46087686107242],[114.12978693065179,30.46087686004498],[114.12978696621283,30.460876858953004],[114.12978700177105,30.46087685779729],[114.12978703732665,30.460876856577222],[114.12978707287905,30.46087685529268],[114.12978710842886,30.460876853944168],[114.12978714397514,30.46087685253153],[114.12978717951754,30.460876851054586],[114.1297872150571,30.460876849513454],[114.12978725059259,30.460876847908246],[114.12978728612443,30.46087684623885],[114.12978732165263,30.46087684450515],[114.12978735717594,30.46087684270749],[114.1297873926955,30.460876840845696],[114.12978742821043,30.460876838919773],[114.12978746372067,30.460876836929835],[114.1297874992264,30.46087683487548],[114.12978753472699,30.460876832757446],[114.12978757022248,30.460876830574826],[114.12978760571296,30.46087682832836],[114.12978764119777,30.460876826017937],[114.12978767667724,30.460876823643215],[114.12978771215103,30.460876821204867],[114.12978774761879,30.46087681870205],[114.12978778308089,30.46087681613528],[114.12978781853651,30.460876813504544],[114.12978785398599,30.460876810809683],[114.12978788942935,30.46087680805103],[114.12978792486543,30.460876805228366],[114.12978796029505,30.460876802341623],[114.12978799571819,30.460876799390928],[114.1297880311336,30.460876796376272],[114.12978806654209,30.460876793297828],[114.12978810194332,30.460876790155375],[114.12978813733704,30.460876786949182],[114.12978817272283,30.460876783678696],[114.12978820810076,30.460876780344595],[114.12978824347076,30.460876776946705],[114.12978827883303,30.46087677348486],[114.12978831418646,30.46087676995888],[114.1297883495317,30.460876766369523],[114.12978838486843,30.460876762716147],[114.12978842019642,30.460876758999042],[114.12978845551554,30.460876755218266],[114.12978849082513,30.460876751373757],[114.1297885261262,30.460876747465125],[114.12978856141741,30.46087674349305],[114.12978859669919,30.460876739457188],[114.12978863197142,30.46087673535777],[114.12978866723412,30.46087673119456],[114.12978870248652,30.46087672696769],[114.12978873772877,30.460876722677316],[114.1297887729606,30.460876718323266],[114.12978880818231,30.46087671390567],[114.12978884339336,30.460876709424394],[114.1297888785935,30.46087670487962],[114.12978891378265,30.46087670027118],[114.12978894896122,30.460876695599353],[114.12978898412845,30.460876690864083],[114.12978901928432,30.460876686065202],[114.12978905442861,30.460876681202933],[114.12978908956133,30.460876676277223],[114.12978912468225,30.460876671287902],[114.12978915979146,30.46087666623554],[114.12978919488796,30.46087666111962],[114.12978922997254,30.46087665594037],[114.12978926504498,30.460876650697795],[114.12978930010493,30.460876645392066],[114.1297893351516,30.46087664002295],[114.12978937419358,30.46087663397295],[114.12978941322308,30.46087662786042],[114.12978945223941,30.460876621685756],[114.12978949124259,30.460876615448335],[114.12978953023236,30.460876609148553],[114.12978956920928,30.46087660278641],[114.12978960817203,30.460876596361455],[114.12978964712163,30.460876589874424],[114.12978968605736,30.460876583324918],[114.12978972497925,30.460876576712998],[114.12978976388683,30.46087657003895],[114.12978980278011,30.460876563302367],[114.12978984165954,30.4608765565036],[114.12978988052447,30.46087654964264],[114.12978991937462,30.460876542719213],[114.12978995821034,30.46087653573377],[114.12978999703064,30.460876528685905],[114.1297900358363,30.460876521575972],[114.12979007462695,30.46087651440396],[114.12979011340265,30.46087650716982],[114.12979015216257,30.460876499873667],[114.12979019090714,30.460876492515325],[114.12979022963594,30.46087648509485],[114.12979026834911,30.460876477612363],[114.12979030704648,30.46087647006803],[114.12979034572749,30.460876462461563],[114.1297903843925,30.460876454793254],[114.1297904230413,30.46087644706282],[114.12979046167351,30.460876439270706],[114.12979050028937,30.46087643141681],[114.12979053888834,30.460876423500892],[114.12979057747062,30.46087641552313],[114.12979061603622,30.460876407483585],[114.12979065458444,30.46087639938242],[114.12979069311541,30.460876391219415],[114.12979073162923,30.460876382994787],[114.12979077012558,30.460876374708324],[114.12979080860411,30.460876366360406],[114.12979084706505,30.460876357950653],[114.12979088550806,30.460876349479506],[114.12979092393347,30.46087634094669],[114.12979096234015,30.460876332352537],[114.12979100072882,30.460876323696432],[114.12979103909929,30.46087631497928],[114.12979107745082,30.460876306200568],[114.12979111578417,30.4608762973603],[114.12979115409813,30.46087628845887],[114.12979119239381,30.46087627949588],[114.1297912306703,30.460876270471562],[114.12979126892753,30.460876261385913],[114.1297913071651,30.46087625223939],[114.12979134538395,30.460876243031255],[114.12979138358304,30.46087623376201],[114.12979142176216,30.460876224431782],[114.12979145992153,30.460876215039992],[114.12979149806117,30.46087620558739],[114.12979153618058,30.460876196073745],[114.12979157427958,30.460876186498993],[114.12979161235812,30.460876176863255],[114.12979165041672,30.460876167166642],[114.12979168845465,30.460876157408755],[114.12979172647147,30.460876147590113],[114.12979176446763,30.460876137710876],[114.12979180244315,30.46087612777043],[114.12979184039673,30.460876117769505],[114.1297918783297,30.460876107707364],[114.12979191624153,30.46087609758481],[114.12979195413146,30.46087608740132],[114.12979199199971,30.460876077157472],[114.12979202984606,30.460876066852865],[114.12979206767073,30.4608760564875],[114.12979210547329,30.460876046061774],[114.12979214325381,30.460876035575627],[114.12979218101198,30.46087602502861],[114.12979221874812,30.460876014421178],[114.12979225646112,30.46087600375367],[114.12979229415174,30.460875993025628],[114.12979233181957,30.460875982236885],[114.12979236946448,30.46087597138824],[114.12979240708634,30.460875960479346],[114.12979244468502,30.460875949509983],[114.12979248226026,30.460875938480484],[114.12979251981221,30.460875927390685],[114.12979255734054,30.460875916240976],[114.12979259484527,30.460875905031084],[114.1297926323263,30.460875893761173],[114.12979936405739,30.46087406401716],[114.12981050573391,30.4608710673506],[114.12981750149231,30.460869285879166],[114.1298209604681,30.460868241871395],[114.12976090092499,30.460837087976408],[114.12975434062045,30.460834915065185],[114.12975222187758,30.46083451237822],[114.12974981793391,30.46083446168812]]],[[[114.12858684724688,30.46075697531846],[114.12858272338447,30.46075743943942],[114.12856332046812,30.460763483455032],[114.12855289699057,30.46076738027048],[114.12853600009338,30.460774064010007],[114.12852229595235,30.46077968152313],[114.12851351920625,30.460783672797096],[114.12850361728512,30.460788535310336],[114.12848832696787,30.460796685018366],[114.12847775624438,30.460803263768174],[114.12846849232196,30.460809810470042],[114.12844572067664,30.460827796871854],[114.1284405291799,30.460831926860674],[114.12842717045298,30.46084610269508],[114.1284102909892,30.460866749129064],[114.12839123126408,30.460890499948565],[114.12837310477931,30.460916179875024],[114.12831646060333,30.46100674187852],[114.12827962900336,30.46106619288501],[114.12783498522268,30.461781744044416],[114.12771556983981,30.46197223706339],[114.12772665457429,30.461977474726904],[114.1280351192241,30.4614916579163],[114.1281772411588,30.4612674655686],[114.12838529925469,30.460926758240657],[114.1284034236594,30.460900829056214],[114.12842208216752,30.46087728462714],[114.12843863233869,30.46085665841345],[114.12845199105928,30.46084248257259],[114.12845718255365,30.460838352580964],[114.12847995418937,30.460820366167756],[114.12848921810793,30.46081381946155],[114.12849904775334,30.460807893775957],[114.1285159434435,30.460799051729605],[114.12852557378872,30.46079447206878],[114.12853447095468,30.460790640385696],[114.1285488969296,30.46078481189138],[114.12856651522796,30.460777992556775],[114.12857545535876,30.46077498362497],[114.12858763885949,30.460771261756314],[114.1285911701126,30.460769197273464],[114.12859354918935,30.460766813080873],[114.12859446803948,30.460765006645683],[114.12859511201303,30.46076272898739],[114.12859483361342,30.46076096885153],[114.12859336738642,30.460758933004715],[114.1285896597264,30.460757212223108],[114.12858684724688,30.46075697531846]]],[[[114.13009347108097,30.461014626013245],[114.13008886700948,30.461023870675692],[114.13008728439782,30.461027226496423],[114.1300859300475,30.461032379433217],[114.13008647117458,30.46103554809189],[114.13008735739318,30.461037930198106],[114.13008820688349,30.461039436519634],[114.1300892110733,30.46104098218373],[114.13009079189736,30.46104235056373],[114.130091999074,30.461043350240864],[114.13009359871769,30.461044430354065],[114.13009820163818,30.46104655701199],[114.13010687464767,30.46105055553842],[114.13012176428953,30.461057634833466],[114.13014504672978,30.46106834589903],[114.13016890625546,30.461079574262442],[114.13022792804726,30.461107473566233],[114.13024125286185,30.461085677970928],[114.13024127342416,30.461085494052796],[114.13024126084613,30.461085360130177],[114.1302412091085,30.46108524925028],[114.1302410730141,30.461085093726478],[114.13018302704694,30.46105712389051],[114.13015916753793,30.461045895541805],[114.13012760802397,30.46103057074982],[114.13011271838874,30.461023491458835],[114.13009347108097,30.461014626013245]]]]}},{"type":"Feature","id":"7","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12897724043302,30.459732524015177],[114.12891906233625,30.459825264551682],[114.12890990575438,30.45983986082598],[114.12889960140261,30.459856286714437],[114.12889000609071,30.459871582326734],[114.12888026176522,30.45988711546247],[114.12864624354339,30.460260301463716]]]}},{"type":"Feature","id":"8","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864239023739,30.460258399832206],[114.12887584219082,30.459885109720673],[114.12888531735717,30.45986997481169],[114.1288949542723,30.459854581522617],[114.12890533748563,30.459837996136145],[114.12891431475633,30.45982365648563],[114.1289726900806,30.45973041160152]]]}},{"type":"Feature","id":"9","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12798139225067,30.461314706510002],[114.12818869480081,30.460981353806087],[114.12826611586888,30.460858573455294],[114.12828593157752,30.460826843153637],[114.12830427901403,30.4607973730555],[114.1283271156673,30.46076105246407],[114.12835723198545,30.460712713413564],[114.1283905424493,30.460659501312453],[114.128417691069,30.46061609233018]]]}},{"type":"Feature","id":"133","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12865536915062,30.460575768862906],[114.12860099077626,30.46055000215731]]]}},{"type":"Feature","id":"10","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12842232979635,30.460618247013066],[114.12839561301983,30.460660968957175],[114.12836230256465,30.460714181065516],[114.1283321862545,30.460762520122845],[114.12830934960708,30.46079884071929],[114.12829100217542,30.46082831082157],[114.12827118647216,30.460860041127475],[114.12819326150827,30.460983494816098],[114.12798675932302,30.461316769922696]]]}},{"type":"Feature","id":"11","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.1276263870675,30.459859579177976],[114.12765470102313,30.459872401753966],[114.1277611168518,30.45992200439],[114.12785763040516,30.459967328638495],[114.12795992499306,30.460015354753946],[114.12806337194627,30.460064469260022],[114.1281687176785,30.46011402039716],[114.12817873041107,30.460118769600683],[114.12819169404607,30.460124230705205],[114.12820708953562,30.460130031899652],[114.12821613630753,30.460132992597295],[114.12823052993473,30.460137166900232],[114.12824385853544,30.460140452682115],[114.1282629296385,30.46014492434246],[114.12828147269322,30.460148266252308],[114.12829512641986,30.460150737996006],[114.12830596819813,30.46015236546864],[114.12832484330475,30.460153946306104],[114.12834015915607,30.460155009855193],[114.12835000355373,30.46015485717896],[114.1283672902542,30.46015497010353],[114.12838643674475,30.460154021808915],[114.12842096859087,30.46015197565487],[114.12845680056239,30.46014888906762],[114.1284761476081,30.460144326962837],[114.12849573392417,30.460138975734004],[114.1285041216086,30.460136520265024],[114.12851533122597,30.460132817087857],[114.12857414466598,30.460105119057804],[114.12859590035085,30.46009128145205],[114.12861675115045,30.460076494569353],[114.12863473366819,30.46006165603471],[114.12865044714968,30.460046543068422],[114.12867676973335,30.46001821876738],[114.12870434742325,30.45998209755369],[114.12871540287891,30.45996827591854],[114.1287635441821,30.459891209799093],[114.1288325033461,30.45977921371121],[114.12931873755855,30.45899917261138]]]}},{"type":"Feature","id":"12","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.1281739457557,30.460499207358286],[114.12816786865055,30.460490635424566],[114.12816119187117,30.460480896152717],[114.12814380378107,30.46045903197977],[114.1281301141407,30.46044429121061],[114.12811888705954,30.460433102565368],[114.12810904456339,30.460423954825124],[114.12810046818328,30.460415925371965],[114.12808968907709,30.46040718178407],[114.12808045375098,30.460400365201558],[114.12806850516638,30.460391461552604],[114.12805994843728,30.46038524566546],[114.12804986520692,30.460378997479467],[114.12803683191065,30.460371297683672],[114.12802338123912,30.460365077468524],[114.12800764492603,30.46035778052335],[114.1279762006981,30.460342925602617],[114.12792388558171,30.460318337642654],[114.12785992698542,30.460288262105767],[114.12777807934471,30.46024987852015],[114.12763956850337,30.46018455884436],[114.12756786932079,30.4601508907607],[114.1275107233993,30.46012411581605],[114.12748347584443,30.46011106306988],[114.12746972809653,30.460103979285517],[114.1274417805846,30.460089278917057],[114.12740655657191,30.46007005834914],[114.12737168965059,30.460051061792136],[114.12732726914756,30.460026883341335],[114.12728229494306,30.46000244951703],[114.12721682417789,30.459966837594138],[114.12715307732698,30.459931965645627],[114.12707893531862,30.4598919027675],[114.12706085704859,30.459881885428633],[114.12703969926034,30.459871640420573],[114.127018713248,30.459861561408193],[114.12698511844061,30.4598455951542],[114.12695360451545,30.45983060210644],[114.12689695521986,30.45980386328977],[114.12685392296001,30.459783709573067],[114.12677732862484,30.459747526781303],[114.12673059734941,30.459725228642583],[114.12670908584316,30.459715133114983]]]}},{"type":"Feature","id":"13","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.1300794624898,30.461044218499723],[114.13009057474109,30.461049720771378],[114.13009996732531,30.46105433885233],[114.13013979351264,30.461072768465435],[114.13032645466019,30.461160692824794],[114.13049466355547,30.461239693644842],[114.13058393128345,30.461281768620577]]]}},{"type":"Feature","id":"14","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12882277886841,30.460453322501046],[114.12881787573671,30.46044796947164],[114.12881158715328,30.460441153762385],[114.1288067493305,30.460433949312808],[114.1288001539798,30.46042339124335],[114.12879200707901,30.460408797342957],[114.12879030510373,30.46040557140097],[114.12877706273885,30.460376546593384],[114.12876098693141,30.460327242497126],[114.12876040839092,30.460323767807377],[114.12875876209284,30.46031388020817],[114.12875749610893,30.460306276759738],[114.12875726121462,30.460304281487893],[114.12875671862989,30.460299672590363],[114.12875557087689,30.46027086584409]]]}},{"type":"Feature","id":"15","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12949925637302,30.45908381036229],[114.12906863093419,30.4597741843955],[114.12891042032965,30.460030184134702],[114.12889413422447,30.460063075917926],[114.12887563750385,30.460103438899957],[114.12886082796645,30.46014235388471],[114.12885542999538,30.46015957257983],[114.12884703119313,30.46020354754154],[114.12884559342868,30.46023920152125],[114.12885026874397,30.46028359561936],[114.12885975256741,30.46032134141008],[114.12886846975947,30.46034453853017],[114.12888687051543,30.460381603499457],[114.12890777512125,30.460412498531188],[114.12892180163672,30.46042897047228],[114.12893971038247,30.460446944158406],[114.12895523364386,30.46046147406139],[114.12897886427122,30.460479812759715],[114.12899464865654,30.460491584118675],[114.12902440946912,30.460509604536686],[114.12907435794124,30.460534409868426],[114.12946898303335,30.460720122328183],[114.12951775750942,30.460744631244008],[114.12957155440026,30.46077032952545]]]}},{"type":"Feature","id":"16","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12832699736397,30.46022001983161],[114.12836404472645,30.460224633610444],[114.12837302917117,30.46022523007898],[114.12840022780429,30.460226284750153],[114.12842428170627,30.460225607748736],[114.12846068171436,30.46022255064311],[114.12848080370732,30.460219963867853],[114.12851469333377,30.46021348906026],[114.1285323730125,30.46020906884395],[114.1285457397978,30.460205305973776],[114.12857029988476,30.460196571231123],[114.1285571368483,30.460216067679987],[114.12854918983022,30.460227838404215],[114.12854690634074,30.460231220592163],[114.12853544237832,30.460248200417247],[114.12851944887736,30.460272354453615],[114.12851218636042,30.460281863050504],[114.12850612224015,30.46028710502764],[114.12849910512165,30.46028920664694],[114.12848869445502,30.460290794092067],[114.12848303613814,30.460290623268772],[114.12847819403144,30.460289804892017],[114.12843554363837,30.460270462042168],[114.1284132054234,30.460260081520367]]]}},{"type":"Feature","id":"17","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12834021985782,30.460376936480415],[114.12810549252377,30.46026661249821],[114.12793108680269,30.46018447827835],[114.12779346360745,30.460119746592724],[114.12766184114018,30.460057657953552],[114.12740989311544,30.459939040044063],[114.1272249764619,30.459851996295452],[114.12711248460293,30.459799160175752],[114.1270374599439,30.45976392176662],[114.12688333686737,30.459691203549152],[114.12683064376942,30.45966633457376],[114.12675626234494,30.459631199461516]]]}},{"type":"Feature","id":"18","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12875557087689,30.46027086584409],[114.12873789098559,30.460298886010346],[114.12871981540793,30.460327731040778],[114.1287182902953,30.460330156172322],[114.12869807501215,30.4603630245161],[114.12869614675628,30.460371470071735],[114.12869585169376,30.46037890808945],[114.1286974221503,30.46038724711732],[114.1286994153434,30.460391301317557],[114.12870328283621,30.460395533591093],[114.12870918351338,30.460399367437283],[114.12871169761004,30.460401000920825],[114.1287744345245,30.460430621610467],[114.12878720135707,30.460436649346487],[114.12882277886841,30.460453322501046]]]}},{"type":"Feature","id":"19","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12882277886841,30.460453322501046],[114.12885346434857,30.460467799461636]]]}},{"type":"Feature","id":"20","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.13051485347947,30.461400366055145],[114.13047468454016,30.46138144875007],[114.13036160436964,30.46132802805536],[114.1302430504507,30.46127204691718],[114.1300838515681,30.46119719434247],[114.1299357044339,30.461127711511185],[114.12985598514405,30.461090321932144],[114.12967374660232,30.46100455302151],[114.1295007516756,30.460921568803485],[114.12932234861319,30.460836796072016],[114.12920795046435,30.460783250131836],[114.12909064982529,30.46072760238839],[114.12890469941287,30.460639511974996],[114.12871283204083,30.46054890605957]]]}},{"type":"Feature","id":"21","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.13046693610107,30.461482633843357],[114.13038606822798,30.46144436951979],[114.13027501012286,30.4613921366407],[114.13018847763566,30.46135108250479],[114.13007930919306,30.46129958315995],[114.12999872539311,30.461261874120172],[114.12994170529474,30.461235206755003],[114.12990102253175,30.46121585668478],[114.12988478386872,30.461209425370157],[114.12985747674861,30.461202030021965],[114.1297774932942,30.46116970927307],[114.12964453600448,30.46111622700083],[114.129524958337,30.46106744642272],[114.12948286936863,30.461050330200425],[114.1294301436001,30.46102565562422],[114.12934054948825,30.460983694989807],[114.12922067006116,30.460927076570584],[114.12911757403806,30.460878867417346],[114.12896104638719,30.460804857790933],[114.1289304419777,30.46079080664426],[114.12890193910415,30.4607777289812],[114.12885190818976,30.460757512192895],[114.12884261314063,30.46075451942377],[114.12883176802906,30.460750880498],[114.12881766336771,30.46074713662746],[114.1288054216991,30.460744352218846],[114.12878830599986,30.46074088750907],[114.12876715087545,30.460737337447174],[114.12874513289626,30.46073415702747],[114.12873048194281,30.46073274695193],[114.1287122944105,30.460731821427643],[114.12869397199555,30.460732309292833],[114.12867794830335,30.460733719129816]]]}},{"type":"Feature","id":"22","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12872278820035,30.460511370529066],[114.1289441332922,30.46061511953031],[114.12907372630534,30.46067607587177],[114.12915700261074,30.460715585767986],[114.12941289814538,30.46083589256067],[114.12948970387153,30.46087208950104],[114.12967491583197,30.460959248519227],[114.12984005696497,30.461036682635438],[114.13001956598467,30.461121247154537],[114.13002864825172,30.461125519620687],[114.13023762396803,30.461223825032437],[114.13049887038697,30.461346686944687],[114.13053596428729,30.461364121595935]]]}},{"type":"Feature","id":"23","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12744830877914,30.459787106092207],[114.127409509125,30.459771833036406],[114.12734184036161,30.459744943040576],[114.12727440106164,30.459718373439795],[114.12724792645402,30.459707664153637],[114.12720629223848,30.459690887389243],[114.12719205714096,30.459684830502855],[114.1271733457091,30.459676567893943],[114.12712578434419,30.45965425294694],[114.12701691017116,30.45960300772696],[114.126899589828,30.459548137001413],[114.12682318366205,30.459512136995478]]]}},{"type":"Feature","id":"24","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12677639632612,30.459595378268002],[114.1268063691351,30.45960957564021],[114.1269081789495,30.45965724416581],[114.12698359383919,30.459692526512907],[114.1270627904483,30.459729954529234],[114.12713925938512,30.459766133737045],[114.12714094893008,30.459766925349616],[114.12719342297875,30.459791511290355],[114.12725858217806,30.45982214625411],[114.12735578395005,30.45986815464524],[114.12745253202482,30.459913323126912],[114.12752961389185,30.459949829796287],[114.12760816252492,30.45998662887781],[114.12771488077304,30.460036914687965],[114.12782355844776,30.46008840517057],[114.12790583405823,30.460127068267425],[114.12796595398729,30.46015515907547],[114.12807572841606,30.460206779878195],[114.12819454217355,30.460262668735545],[114.12826353994915,30.46029521337293],[114.12833443017455,30.46032877206684],[114.12836213336487,30.46034185152361]]]}},{"type":"Feature","id":"25","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.1275157981083,30.46187798662634],[114.12797076401056,30.461152091581972],[114.12816304230289,30.460845174464335],[114.12818825060478,30.460800740137387],[114.12820316339932,30.46077096459124],[114.12821272159984,30.46074629342443],[114.12821752572157,30.460728878483152],[114.12822172932809,30.46071246440057],[114.12822433156069,30.46069344808538],[114.12822473618087,30.460681806547388],[114.12822444614423,30.460664208945],[114.12822246300462,30.460643601869652],[114.12821906341146,30.460622894878266],[114.12820991919551,30.46058905852358]]]}},{"type":"Feature","id":"26","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12859695125248,30.460749296000518],[114.1285641230873,30.460757102698334],[114.12854010247865,30.46076470922441],[114.1285110831788,30.46077431491476],[114.12848550415981,30.460785412429615],[114.1284664392788,30.460795735843927],[114.12843821506361,30.46081595318955],[114.12841409692503,30.460836788640815],[114.12839077436152,30.4608625932047],[114.12837556130937,30.46087940763076],[114.1283566115678,30.46090253222412],[114.12827917639183,30.461023774191535],[114.12769151696821,30.46196088920321]]]}},{"type":"Feature","id":"27","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864842897787,30.4606276302832],[114.12862898061579,30.460618618930347],[114.12862658288563,30.460617500482353],[114.12858066923444,30.460596083515824],[114.1285753203815,30.460594229469987],[114.12857167112699,30.460593593058658],[114.12856745920931,30.460593720536774],[114.12856122741601,30.460594698374464],[114.12855772059048,30.460595941281277],[114.1285522979939,30.460598027726796],[114.1285483901662,30.460600191291086],[114.12854500787789,30.460602149465135],[114.12854230576485,30.460604654237496],[114.12854039690191,30.460607392273527],[114.1285193386141,30.460639796030616],[114.1285079053109,30.46065799662477],[114.12850706592427,30.460659350460933],[114.12849941394543,30.460671692237295],[114.12849141514032,30.460684593394582],[114.12847872288397,30.460705064530792]]]}},{"type":"Feature","id":"28","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12847872288397,30.460705064530792],[114.1284732864388,30.460713834476344]]]}},{"type":"Feature","id":"29","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12823116917164,30.460430565779617],[114.12823513363749,30.460435047164694],[114.1282410985911,30.460442480098674],[114.12824699456962,30.460450849727245],[114.12825612343089,30.460464853507645],[114.1282621451196,30.460475318598228],[114.12827004285334,30.460490559238714],[114.12827622553161,30.460502624397915],[114.12828152820744,30.460515645183932],[114.12828729169814,30.460530317484707],[114.12829185294262,30.46054275428451],[114.12829655198448,30.46055864893324],[114.12830072615508,30.460573305664308],[114.12830350703699,30.460586218995534],[114.1283057058045,30.460595944723526],[114.12830651401676,30.460604188710853],[114.12829868477651,30.46062353496956],[114.12831145944958,30.460602786826506],[114.12832884115144,30.460574821538472],[114.1283379454297,30.460560173714523],[114.12836063323996,30.46052378574931],[114.12836376369641,30.460518006512462],[114.12836459736296,30.46051508338462],[114.12836541369053,30.460512042620753],[114.12836514161545,30.46050926750707],[114.12836475538597,30.460505777343094],[114.12836374082613,30.460502454308315],[114.12836197313365,30.460498592147694],[114.12836034108288,30.460496131066375],[114.12835756235341,30.46049308645533],[114.12835458131094,30.460490208959772],[114.12835106750626,30.46048742844768],[114.12834542677811,30.460484539817884],[114.12832747170351,30.46047566264443],[114.12828954958354,30.46045806260415],[114.12824607343725,30.46043758783169],[114.12819295080001,30.46041255940355]]]}},{"type":"Feature","id":"134","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12860622058159,30.46054207521988],[114.12866069424561,30.46056778078176]]]}},{"type":"Feature","id":"135","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12868075434154,30.460536668227384],[114.1286261815418,30.46051044643603]]]}},{"type":"Feature","id":"30","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12912278314475,30.46085068839886],[114.12904124105474,30.460812270419204],[114.12897783505122,30.460782561304338],[114.12889343028047,30.460742891641594],[114.128827187441,30.460711760087573],[114.12876201360413,30.460681020788293],[114.12871871350927,30.460660873763988],[114.12871540763784,30.46065931028169],[114.12871156753305,30.460659360647206],[114.12870155760558,30.460659491930894],[114.1286930532053,30.460660015850845],[114.12868308922111,30.460660707382488],[114.12867346315322,30.460661452813614],[114.12866670776984,30.460661975939868],[114.12866189334268,30.460662264222407],[114.12865149721733,30.46066288672742],[114.1286501161541,30.460662969423137],[114.12863837299199,30.460664351103055],[114.12862116348589,30.460665965873723],[114.12860621810181,30.460667869126482],[114.12858955378167,30.46066992547661],[114.12858022522573,30.460671950374874],[114.12857584464733,30.460672901241058],[114.1285661300287,30.46067430161067],[114.12855794056122,30.460675779314688],[114.12854994047656,30.46067753164059],[114.12853872035802,30.460680668239995],[114.1285338333218,30.460682142372615],[114.1285263397709,30.46068440273543],[114.12851723573179,30.46068725357128],[114.12851419616699,30.460688205378652],[114.12850163985091,30.460692973529728],[114.1285007257757,30.460693399910237],[114.12848441607913,30.46070100774001],[114.12847872288397,30.460705064530792]]]}},{"type":"Feature","id":"31","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12914007973365,30.460822483792516],[114.12887456443299,30.460697201305514],[114.1286858024831,30.46060822496752],[114.12867798025998,30.460604378204753]]]}},{"type":"Feature","id":"32","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12915765731448,30.460794860092545],[114.12869540616136,30.460576642138296]]]}},{"type":"Feature","id":"33","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12773503437458,30.460011515042158],[114.12781700454225,30.460050325325824],[114.12791906931785,30.460098400883528],[114.12801287260986,30.46014223287044],[114.12809008867563,30.460178722541972],[114.12818780640409,30.46022476554824],[114.12828835912829,30.460272275342994],[114.1283586035856,30.46030481900877],[114.12837928019152,30.460314398279753]]]}},{"type":"Feature","id":"34","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.1277518461424,30.459984503356356],[114.12794365282777,30.460075007194664],[114.1279982520951,30.460100716895834],[114.12806983858272,30.46013429109269],[114.1281313933395,30.460163404118905],[114.12819844034753,30.460195149932535],[114.12826302682811,30.460225400211414],[114.12832956263576,30.460257207017207],[114.1283852969419,30.460283165363396],[114.1283957314117,30.46028805870498]]]}},{"type":"Feature","id":"35","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12776888004775,30.459957134711782],[114.12776927905365,30.459957321868618],[114.12783777064836,30.459989793855886],[114.12785845211445,30.459999252293215],[114.12793323694133,30.460034680685403],[114.1280053462846,30.460068500674236],[114.12808125053584,30.460104367246164],[114.12813446219064,30.460129551143037],[114.12820666640502,30.460163309393074],[114.12828587593559,30.460200544213052],[114.12832699736397,30.46022001983161]]]}},{"type":"Feature","id":"36","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12832699736397,30.46022001983161],[114.1283270929556,30.460220065105005]]]}},{"type":"Feature","id":"37","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12884403956247,30.460007641707215],[114.12867760736032,30.46027479966971]]]}},{"type":"Feature","id":"38","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12887150044769,30.46001997208708],[114.12870731410098,30.46028872831528]]]}},{"type":"Feature","id":"39","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12818014087874,30.460874810459718],[114.12822144234136,30.460808716806646],[114.12824696465553,30.460767995861808],[114.12827292522499,30.460726389131928],[114.128304829952,30.46067542079121],[114.12833107999349,30.46063331293882],[114.12835891643661,30.460588791534825]]]}},{"type":"Feature","id":"40","properties":{"type":1},"geometry":{"type":"MultiLineString","coordinates":[[[114.12821012605546,30.46088886842086],[114.1282412682786,30.460839424206632],[114.12826592705372,30.46080002751735],[114.12829868801084,30.46074734443058],[114.1283396266124,30.460681891115996],[114.12836003370703,30.460649206631423],[114.12838906067488,30.46060279353459]]]}},{"type":"Feature","id":"41","properties":{"type":4},"geometry":{"type":"MultiLineString","coordinates":[[[114.12791250027578,30.461436033512047],[114.12798675932302,30.461316769922696]]]}},{"type":"Feature","id":"42","properties":{"type":4},"geometry":{"type":"MultiLineString","coordinates":[[[114.12798139225067,30.461314706510002],[114.12790721558684,30.461432066904347]]]}},{"type":"Feature","id":"43","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12791250027578,30.461436033512047],[114.12760695187441,30.461921826983442]]]}},{"type":"Feature","id":"44","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12790721558684,30.461432066904347],[114.12760124697984,30.46191922475083]]]}},{"type":"Feature","id":"45","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12802134345617,30.461185257079794],[114.12757284979007,30.461904903192504]]]}},{"type":"Feature","id":"46","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12799359944563,30.461170059048992],[114.12754318292802,30.461890906595524]]]}},{"type":"Feature","id":"47","properties":{"type":4},"geometry":{"type":"MultiLineString","coordinates":[[[114.1289726900806,30.45973041160152],[114.12905084067081,30.459610143655393],[114.12912860670679,30.459484907374254]]]}},{"type":"Feature","id":"48","properties":{"type":4},"geometry":{"type":"MultiLineString","coordinates":[[[114.12897724043302,30.459732524015177],[114.12908987415992,30.459554495911952],[114.12913176009636,30.45948693795003]]]}},{"type":"Feature","id":"49","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12912860670679,30.459484907374254],[114.12918860887022,30.459388703669106],[114.12940692117314,30.459040354800283]]]}},{"type":"Feature","id":"50","properties":{"type":3},"geometry":{"type":"MultiLineString","coordinates":[[[114.12913176009636,30.45948693795003],[114.1292186688281,30.459347626355402],[114.12928769388098,30.459236272889843],[114.1294088728476,30.45904135565898]]]}},{"type":"Feature","id":"51","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12900740340355,30.45974525957922],[114.12930185603157,30.459273004403947],[114.12943776550892,30.459054989774348]]]}},{"type":"Feature","id":"52","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12903468416134,30.459759191112973],[114.12910643837142,30.459647825985254],[114.12924000296428,30.459433141795156],[114.12939441460196,30.459185760032476],[114.12946726576742,30.45906881136676]]]}},{"type":"Feature","id":"53","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12679204348328,30.459567539767527],[114.1268360594006,30.459588194797206],[114.12694201630637,30.4596379537218],[114.1269954117388,30.459663315435186],[114.12705192223714,30.45968981652831],[114.12710391907243,30.459714318445624],[114.1271579633486,30.459739595227425],[114.12716006239806,30.459740576961867],[114.12720414426339,30.459761419895838],[114.1272685192033,30.45979186681696],[114.12737430459964,30.45984175550143],[114.12743273349024,30.459869432945784],[114.12748623372657,30.459894380831127],[114.12754058206845,30.459919815635516],[114.12759280774002,30.45994463706296],[114.12765154108888,30.459972194132174],[114.12773503437458,30.460011515042158]]]}},{"type":"Feature","id":"54","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12830640567694,30.46043107504182],[114.12818935621225,30.46037596750847],[114.12808795522145,30.460328313054408],[114.12798911993617,30.460281740825522],[114.1278981990045,30.46023882670201],[114.12772954924898,30.46015951002228],[114.12707926064358,30.459852396534487],[114.12672577159651,30.45968544680535]]]}},{"type":"Feature","id":"55","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12819295080001,30.46041255940355],[114.128150914052,30.46039288912343],[114.12805481170027,30.46034773709655],[114.12795560218164,30.460301165830813],[114.12782851745932,30.460241330062537],[114.12771865053372,30.46018999482562],[114.12760991752974,30.460138601911694],[114.12750038561464,30.460087155502507],[114.12739544994673,30.460037709331758],[114.12729047372582,30.459988216739237],[114.1271836077708,30.45993795295971],[114.12710503081357,30.459901016565624],[114.12706085704825,30.459881885428633]]]}},{"type":"Feature","id":"56","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12885346434857,30.460467799461636],[114.12888611478475,30.46048330114584],[114.12894212166319,30.460509498511577],[114.12899770191459,30.46053571380165],[114.12906103846264,30.46056530411545],[114.12933228326845,30.460692990021055],[114.12939922183315,30.460724450600186],[114.12945219117499,30.460749319804087],[114.1294949523801,30.460769381987685],[114.1295646720375,30.46080237116679],[114.12961044647972,30.460824201627865],[114.12966760787087,30.460851026077375],[114.12972489851822,30.460877690747047],[114.1297846468849,30.46090557979937],[114.12983406031677,30.460929092262713],[114.12989603575154,30.46095827285938],[114.12994965349259,30.46098332605729],[114.13000503482809,30.461009534879153],[114.13001051246,30.46101195061097],[114.1300794624898,30.461044218499893]]]}},{"type":"Feature","id":"57","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12988478386872,30.461209425370157],[114.12983517790201,30.461185033927062],[114.12980747191082,30.461172091385784],[114.12976235278241,30.461150951652858],[114.12965581771897,30.46110102144966],[114.12954634172344,30.461049675638765],[114.1294427140344,30.46100085224905],[114.12932650533043,30.46094627831662],[114.12921344859751,30.46089333997407],[114.12912278314475,30.46085068839886]]]}},{"type":"Feature","id":"58","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12857813510907,30.460244863218666],[114.12934615820019,30.459012029066628]]]}},{"type":"Feature","id":"59","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12861197266949,30.46025280528113],[114.12937586387014,30.45902595685755]]]}},{"type":"Feature","id":"60","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12744830877914,30.459787106092207],[114.12746862128559,30.459794978424974],[114.12748735101675,30.45980260974082],[114.12759346209813,30.45984560350452],[114.12761138449859,30.459853479228062],[114.1276263870675,30.459859579177976]]]}},{"type":"Feature","id":"61","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12776888004775,30.459957134711782],[114.12774231094184,30.459944672249055],[114.12763228066444,30.45989288214787],[114.12757785286539,30.459867328602357],[114.12745358071072,30.459808632927086],[114.12741197479716,30.45978912384476],[114.12735687524035,30.45976307747049],[114.12730078342108,30.459736847572746],[114.12725003300609,30.459713085134545],[114.12723471844387,30.459705922914285],[114.1271920571413,30.45968483050285]]]}},{"type":"Feature","id":"62","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12957155440026,30.46077032952545],[114.12962560879784,30.46079536052207],[114.12969807096734,30.460830190404653],[114.12973316176529,30.460850579322493],[114.12977092851051,30.460870448151194],[114.1298018736042,30.460887031239576],[114.12983899187161,30.46090765686766],[114.12988703308896,30.46093287850678],[114.12991570697065,30.460952002731872],[114.12994598007576,30.460972153130776],[114.12998268083776,30.46099194239249],[114.13001781280236,30.461010661152926],[114.1300438771927,30.461024660365503],[114.13006143062687,30.461034447929116],[114.1300794624898,30.461044218499723]]]}},{"type":"Feature","id":"63","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.1304828959087,30.461455232968806],[114.13044635911619,30.461438034725898],[114.13033843458149,30.461387374583097],[114.13023006248848,30.46133601896736],[114.13010079461621,30.461277151980173],[114.12914007973365,30.460822483792516]]]}},{"type":"Feature","id":"64","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.1304988990673,30.46142775766637],[114.13040188534445,30.46138206264984],[114.13029319275141,30.461331016288316],[114.13018197267012,30.461278274427475],[114.13011821926273,30.461249414854397],[114.1297996469823,30.461098948695362],[114.12958253083829,30.460996747637843],[114.12915765731448,30.460794860092545]]]}},{"type":"Feature","id":"65","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12875753260568,30.46045704777092],[114.1288271635834,30.46048990394257],[114.12893137978958,30.46053891373819],[114.12898145903952,30.460562441979395],[114.12903942169002,30.460589618580357],[114.1290929564931,30.460615068807137],[114.12915022534027,30.46064215833168],[114.12925606054661,30.460691552731266],[114.1295927629289,30.460850115037506],[114.1297118410154,30.460906011102722],[114.12976728953336,30.460932068522915],[114.12981863325906,30.460956370440798],[114.12988131598836,30.46098588171369],[114.12998954955715,30.461036676630748],[114.13004608969733,30.46106340592618],[114.13006261309012,30.46107117703442],[114.1300997639906,30.46108864942799],[114.1302601720615,30.461164068601292],[114.13031469283304,30.46118984365158],[114.13037267427723,30.46121700849508],[114.13042454324432,30.461241437490035],[114.13048268457972,30.461268812921542],[114.13053567157775,30.46129408611399],[114.13056801034821,30.4613091027564]]]}},{"type":"Feature","id":"66","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12874030932966,30.46048397632716],[114.12880312320125,30.460513589668718],[114.12881408934,30.46051877370536],[114.12886034248127,30.460540615681648],[114.12901969078055,30.460615885242152],[114.12918879345622,30.46069546410517],[114.12950619915098,30.46084488819738],[114.12958058913902,30.460879834131823],[114.1297382830412,30.46095399968528],[114.1298989137917,30.46102932667027],[114.13004557426913,30.461098438591197],[114.13006054778565,30.46110549464093],[114.13012839752761,30.461137369360586],[114.13023304710003,30.461186698506765],[114.13040098147604,30.46126551377191],[114.13050675607363,30.461315424744768],[114.13055195920352,30.461336660444562]]]}},{"type":"Feature","id":"136","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12867558659867,30.4605446055135],[114.12862146241301,30.460518321024757]]]}},{"type":"Feature","id":"137","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12867066233986,30.460552033762347],[114.12861603989573,30.460526442348822]]]}},{"type":"Feature","id":"67","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12832328945066,30.460404043115755],[114.12821293483425,30.460352462075083],[114.12810882062381,30.46030343330221],[114.12800898333393,30.46025630773833],[114.12791284517074,30.460210946918565],[114.1278139899509,30.46016432845007],[114.12769539677514,30.460108307070723],[114.12758815968235,30.460057992138477],[114.12748280123796,30.460008447196017],[114.12737433762597,30.45995741073594],[114.12726317223137,30.4599049453874],[114.12715576088877,30.459854498539748],[114.1270956712156,30.45982610110306],[114.12704304909965,30.45980123263368],[114.12693907122653,30.45975203028999],[114.12683084070854,30.459701131617745],[114.12674074829447,30.459658801146414]]]}},{"type":"Feature","id":"68","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12680725956686,30.45954046820778],[114.12717449067327,30.459713047474622],[114.12759520741717,30.459910756278994],[114.1277516632006,30.459984417034843],[114.1277518461424,30.459984503356356]]]}},{"type":"Feature","id":"69","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12844958148462,30.460630905376078],[114.12841802968548,30.460681649005544],[114.12839198638993,30.46072327204628],[114.12836266917118,30.460769877974602],[114.12833395960327,30.460816395178412],[114.1283037234543,30.46086464582302],[114.1282911770312,30.460884715512925],[114.1282211342061,30.460996562363395],[114.12801243920661,30.461332549159803],[114.1277442196444,30.461759037316693],[114.12763446549414,30.461933972927216]]]}},{"type":"Feature","id":"70","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12848254571205,30.460644386019133],[114.12845049214515,30.460695881009226],[114.12839457762426,30.46078340026094],[114.12836346977788,30.460832468671114],[114.12825786032651,30.46100118711294],[114.12766413227311,30.46194796940813]]]}},{"type":"Feature","id":"71","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12900740340355,30.45974525957922],[114.128957960984,30.4598241272444],[114.12894004561335,30.459853352318294],[114.12893003702641,30.459869065799808],[114.12891992835358,30.459885680054146],[114.12891052233593,30.459900943156093],[114.12890109275257,30.45991602938203],[114.12889116629341,30.45993199281133],[114.12884403956247,30.460007641707215]]]}},{"type":"Feature","id":"72","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12903468416134,30.459759191112973],[114.12898507654947,30.459838468600687],[114.12897867293883,30.45984870212572],[114.12896815212967,30.459865515277198],[114.12895874916521,30.459880542002033],[114.1289500604375,30.459894427304334],[114.12893775904018,30.45991408592911],[114.12892769870186,30.459930192766993],[114.12891807753455,30.459945538511995],[114.12887150044769,30.46001997208708]]]}},{"type":"Feature","id":"73","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12799359944563,30.461170059048992],[114.12813058642354,30.460954110798646],[114.12818014087874,30.460874810459718]]]}},{"type":"Feature","id":"74","properties":{"type":2},"geometry":{"type":"MultiLineString","coordinates":[[[114.12802134345617,30.461185257079794],[114.12816026839353,30.460968026637453],[114.12821012605546,30.46088886842086]]]}},{"type":"Feature","id":"75","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12871659988954,30.460480973960003],[114.12866201732668,30.460455225703175]]]}},{"type":"Feature","id":"76","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12871181937012,30.4604891720155],[114.12865696068012,30.460463293495675]]]}},{"type":"Feature","id":"77","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12870625051855,30.460496613714092],[114.12865209001964,30.46047106454629]]]}},{"type":"Feature","id":"78","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12870119737104,30.460504896214353],[114.12864693030672,30.46047929677077]]]}},{"type":"Feature","id":"79","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12869621892378,30.46051275631796],[114.12864199196972,30.46048717579036]]]}},{"type":"Feature","id":"80","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12869144604645,30.460520692930068],[114.12863706353075,30.46049503901376]]]}},{"type":"Feature","id":"81","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12875206317393,30.46042591203504],[114.12869730457149,30.46039987443165]]]}},{"type":"Feature","id":"82","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12835089014197,30.460401168581985],[114.12840556493653,30.460426761698077]]]}},{"type":"Feature","id":"83","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12841050808538,30.460419045241387],[114.12835610108812,30.460393178156856]]]}},{"type":"Feature","id":"84","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1284204973323,30.460403289755668],[114.12836583665592,30.46037757068294]]]}},{"type":"Feature","id":"85","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12843095112684,30.460387282485826],[114.1283756424364,30.460361694068006]]]}},{"type":"Feature","id":"86","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12844040891842,30.460371578901405],[114.12838552459199,30.46034569277411]]]}},{"type":"Feature","id":"87","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12845530333595,30.460347628036693],[114.12840068992743,30.460322072418204]]]}},{"type":"Feature","id":"88","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1284709841291,30.460324144822113],[114.12841544462637,30.460298280998398]]]}},{"type":"Feature","id":"89","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12846564956904,30.460331744768236],[114.12841033665775,30.46030594886737]]]}},{"type":"Feature","id":"90","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12840565911587,30.460314176099065],[114.12846039800873,30.460340059104666]]]}},{"type":"Feature","id":"91","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12845085257727,30.46035556238818],[114.12839547046075,30.460329999527442]]]}},{"type":"Feature","id":"92","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12839045330449,30.46033775951954],[114.12844535891315,30.460363628043794]]]}},{"type":"Feature","id":"93","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12838043800423,30.460353694855385],[114.12843551638234,30.46037934153785]]]}},{"type":"Feature","id":"94","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1283709198198,30.460369685801584],[114.12842598062204,30.46039522388622]]]}},{"type":"Feature","id":"95","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12836108578529,30.460385462589116],[114.12841554195626,30.460411069100882]]]}},{"type":"Feature","id":"96","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12866685244558,30.460447511350914],[114.12872170540942,30.460473387158626]]]}},{"type":"Feature","id":"97","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12872659724891,30.460465290681256],[114.12867213942197,30.460439405192986]]]}},{"type":"Feature","id":"98","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12869225340944,30.460407869796136],[114.12874702860445,30.46043391529476]]]}},{"type":"Feature","id":"99","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12874224061314,30.460442013970503],[114.12868702900892,30.460415968259927]]]}},{"type":"Feature","id":"100","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12873675247232,30.460449538567893],[114.12868213874421,30.460424010637905]]]}},{"type":"Feature","id":"101","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12867707775104,30.46043185095154],[114.12873153978501,30.460457592185595]]]}},{"type":"Feature","id":"102","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12836533305023,30.46049065278955],[114.12831126438999,30.46046493708541]]]}},{"type":"Feature","id":"103","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12837057320787,30.460482374315422],[114.12831627884167,30.460456915451616]]]}},{"type":"Feature","id":"104","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.128375869697,30.460474656315874],[114.12832115618573,30.460448963083365]]]}},{"type":"Feature","id":"105","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12840057586054,30.460434982343063],[114.12834581797811,30.4604093874583]]]}},{"type":"Feature","id":"106","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12839557850852,30.460443130646244],[114.1283409619393,30.46041732224048]]]}},{"type":"Feature","id":"107","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12839069502158,30.46045093856841],[114.12833610026247,30.460425094529544]]]}},{"type":"Feature","id":"108","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12838572529347,30.460458852915],[114.1283310701576,30.460433296250798]]]}},{"type":"Feature","id":"109","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12838079578135,30.46046681321431],[114.12832598302165,30.46044131635064]]]}},{"type":"Feature","id":"110","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12837649906545,30.4605135128412],[114.12834694946365,30.460560688846577]]]}},{"type":"Feature","id":"111","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12854178593888,30.46058975003992],[114.12851202635618,30.460637363832053]]]}},{"type":"Feature","id":"112","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12853243489933,30.460585826834613],[114.12850285552688,30.46063331813388]]]}},{"type":"Feature","id":"113","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12852298308462,30.460581441438844],[114.1284932007625,30.460628766063113]]]}},{"type":"Feature","id":"114","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12851419252213,30.46057753909397],[114.12848460959708,30.460624795756164]]]}},{"type":"Feature","id":"115","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12850517472779,30.460573235031077],[114.12847496389253,30.460620288973356]]]}},{"type":"Feature","id":"116","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12849593701645,30.460568989461134],[114.12846619724603,30.460616278867587]]]}},{"type":"Feature","id":"117","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12843089699246,30.46027479298183],[114.12848603051319,30.46029982760062]]]}},{"type":"Feature","id":"118","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12842542716572,30.460282399079777],[114.12848046957723,30.460308814914708]]]}},{"type":"Feature","id":"119","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12842025416148,30.46028980397502],[114.1284756669986,30.460316576604242]]]}},{"type":"Feature","id":"120","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12848666488827,30.460564499600935],[114.12845654247133,30.46061172678205]]]}},{"type":"Feature","id":"121","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12847753955164,30.46056031954453],[114.12844747388621,30.46060773734857]]]}},{"type":"Feature","id":"122","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1284680946764,30.460556051543414],[114.12843832116972,30.460603782216417]]]}},{"type":"Feature","id":"123","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12845940547105,30.460551880699725],[114.12842920689394,30.46059922350488]]]}},{"type":"Feature","id":"124","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12845034586127,30.46054758475326],[114.12842010151311,30.460595070910582]]]}},{"type":"Feature","id":"125","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12844042699773,30.460543180431102],[114.12841116676191,30.460590768567833]]]}},{"type":"Feature","id":"126","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.128432077035,30.460539134018713],[114.12840149499199,30.460586441619128]]]}},{"type":"Feature","id":"127","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12842263910166,30.460534983416075],[114.12839221993035,30.460582050888107]]]}},{"type":"Feature","id":"128","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.128413479861,30.460530541018002],[114.12838377174411,30.460578164755052]]]}},{"type":"Feature","id":"129","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12840446028889,30.46052665183327],[114.12837433184895,30.460573725428528]]]}},{"type":"Feature","id":"130","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1283951588295,30.4605220981727],[114.12836525681436,30.460569600517413]]]}},{"type":"Feature","id":"131","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12838603082982,30.46051800823294],[114.12835560702274,30.460564877103256]]]}},{"type":"Feature","id":"132","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12863027713958,30.460615146289182],[114.12857543625488,30.460589559185415]]]}},{"type":"Feature","id":"138","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1286655605906,30.460559846150645],[114.12861113559894,30.460533898069162]]]}},{"type":"Feature","id":"139","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1286506066429,30.460583705686243],[114.12859608673774,30.460557448859458]]]}},{"type":"Feature","id":"140","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864519873487,30.460591682994284],[114.12859087146254,30.46056594440731]]]}},{"type":"Feature","id":"141","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1286402269583,30.46059966951767],[114.12858567860708,30.460573673666502]]]}},{"type":"Feature","id":"142","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12863514112153,30.460607292800816],[114.12858079067996,30.460581634887177]]]}},{"type":"Feature","id":"143","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12868567339142,30.460528707643714],[114.12863162666974,30.460502262442503]]]}},{"type":"Feature","id":"144","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12884532738447,30.460284588142553],[114.12883379933665,30.46028716606622],[114.12875726121462,30.460304281487893]]]}},{"type":"Feature","id":"145","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12875726121462,30.460304281487893],[114.1287495824418,30.46029087357002],[114.12873789098559,30.460298886010346]]]}},{"type":"Feature","id":"146","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864239023739,30.460258399832206],[114.1286478584979,30.46026107747178],[114.12867760736032,30.46027479966971],[114.12870731410098,30.46028872831528],[114.12873586697903,30.460302115922662]]]}},{"type":"Feature","id":"147","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12873586697903,30.460302115922662],[114.12873930594586,30.460303728353384]]]}},{"type":"Feature","id":"148","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12840022780429,30.460226284750153],[114.12838643674475,30.460154021808915]]]}},{"type":"Feature","id":"149","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12836213336523,30.46034185152361],[114.12837928019152,30.460314398279753],[114.1283957314117,30.46028805870498],[114.1284132054234,30.460260081520367]]]}},{"type":"Feature","id":"150","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12870837067852,30.46032404079281],[114.1286803577796,30.4603724503829]]]}},{"type":"Feature","id":"151","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12867109294197,30.460368381122688],[114.12869912869216,30.460319932077443]]]}},{"type":"Feature","id":"152","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12866183174938,30.46036430555569],[114.12868988865519,30.460315819985393]]]}},{"type":"Feature","id":"153","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12865257055421,30.46036022998521],[114.12868064861574,30.46031170788965]]]}},{"type":"Feature","id":"154","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864330935608,30.460356154411024],[114.1286715030655,30.46030752306435]]]}},{"type":"Feature","id":"155","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12863404815567,30.460352078833306],[114.12866216852906,30.460303483687547]]]}},{"type":"Feature","id":"156","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12862478695267,30.460348003251934],[114.12865292848181,30.460299371581126]]]}},{"type":"Feature","id":"157","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12861548891867,30.46034392349733],[114.12864368843199,30.460295259471124]]]}},{"type":"Feature","id":"158","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12860626453867,30.460339852078516],[114.12863444837981,30.460291147357605]]]}},{"type":"Feature","id":"159","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12859700332784,30.460335776486477],[114.1286252083248,30.460287035240558]]]}},{"type":"Feature","id":"160","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12854143600869,30.460311322858644],[114.12856976794158,30.46026236246309]]]}},{"type":"Feature","id":"161","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12853138266078,30.46030689865123],[114.12856027238867,30.460258136623867]]]}},{"type":"Feature","id":"162","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285209155493,30.460302292354605],[114.12855120127928,30.460254099673847]]]}},{"type":"Feature","id":"163","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12851162280957,30.46029825874834],[114.1285419586371,30.46024998638347]]]}},{"type":"Feature","id":"164","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12840807113457,30.460157725686944],[114.12847011220352,30.460151560245418]]]}},{"type":"Feature","id":"165","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1284101654926,30.46016659318303],[114.12847220656202,30.460160427736348]]]}},{"type":"Feature","id":"166","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12841225985079,30.460175460679043],[114.12847430092049,30.460169295227086]]]}},{"type":"Feature","id":"167","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12841435420903,30.46018432817498],[114.12847639527922,30.46017816271769]]]}},{"type":"Feature","id":"168","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12841644856744,30.460193195670612],[114.12847848963814,30.46018703020827]]]}},{"type":"Feature","id":"169","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12841854292579,30.460202063166392],[114.1284805839972,30.4601958976989]]]}},{"type":"Feature","id":"170","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12842063728452,30.460210930662107],[114.1284826783562,30.460204765189165]]]}},{"type":"Feature","id":"171","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12842273164331,30.460219798157567],[114.12848477271548,30.46021363267947]]]}},{"type":"Feature","id":"172","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12876862589741,30.4603243180969],[114.12878429704632,30.460375964015952]]]}},{"type":"Feature","id":"173","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12877832341091,30.460321867552405],[114.1287941676972,30.460373502290036]]]}},{"type":"Feature","id":"174","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12878830215362,30.460319345936522],[114.12880415582616,30.460371011259845]]]}},{"type":"Feature","id":"175","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12879828089203,30.460316824316447],[114.1288141439507,30.46036852022551]]]}},{"type":"Feature","id":"176","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12883819580152,30.46030673779352],[114.12885409640481,30.460358556045676]]]}},{"type":"Feature","id":"177","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12886408450736,30.460356064989977],[114.12884817451787,30.460304216152167]]]}},{"type":"Feature","id":"178","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12857900801183,30.460266474601568],[114.128550697235,30.460315398472268]]]}},{"type":"Feature","id":"179","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12858824807958,30.460270586736574],[114.12855995845868,30.460319474082347]]]}},{"type":"Feature","id":"180","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12859748814465,30.460274698867888],[114.12856921967986,30.460323549688777]]]}},{"type":"Feature","id":"181","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12860672820732,30.460278810995728],[114.12857848089843,30.460327625291548]]]}},{"type":"Feature","id":"182","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1286159682674,30.46028292311988],[114.1285877421146,30.46033170089078]]]}},{"type":"Feature","id":"183","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1288083184198,30.460314494295],[114.12882418147751,30.460366190197742]]]}},{"type":"Feature","id":"184","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12881835594338,30.46031216426939],[114.128834219,30.46036386016563]]]}},{"type":"Feature","id":"185","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12882815828664,30.460309067827627],[114.12884405889112,30.460360886086296]]]}},{"type":"Feature","id":"186","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12857969415214,30.46067716294352],[114.12864065377806,30.460669210477786]]]}},{"type":"Feature","id":"187","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12821095471107,30.460587805847116],[114.12822264139515,30.460583823177657],[114.12829655198448,30.46055864893324]]]}},{"type":"Feature","id":"188","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12859393102332,30.46073889902016],[114.12865733633403,30.46073080933146]]]}},{"type":"Feature","id":"189","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12865499232646,30.460722154294714],[114.12859194575512,30.460730290201745]]]}},{"type":"Feature","id":"190","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12859000904008,30.460721891924944],[114.12865258593759,30.460713268918468]]]}},{"type":"Feature","id":"191","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12865010693449,30.460704115420466],[114.12858781952768,30.46071239743059]]]}},{"type":"Feature","id":"192","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864775433783,30.460695428666835],[114.1285859385222,30.46070424073049]]]}},{"type":"Feature","id":"193","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864547745343,30.46068702147333],[114.1285838040565,30.460694984936232]]]}},{"type":"Feature","id":"194","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12864312222841,30.460678325013305],[114.12858175677763,30.46068610721425]]]}},{"type":"Feature","id":"195","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12826540486421,30.46049167731894],[114.128285766533,30.460541251276027]]]}},{"type":"Feature","id":"196","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12827623056698,30.46054452442842],[114.12825556202769,30.46049482491383]]]}},{"type":"Feature","id":"197","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12826632154945,30.460547925622357],[114.12824607169709,30.460497859777327]]]}},{"type":"Feature","id":"198","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12820831432073,30.46056783604067],[114.12818747687564,30.460516597394804]]]}},{"type":"Feature","id":"199","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1282178745576,30.46056455459578],[114.12819694070524,30.460513571038067]]]}},{"type":"Feature","id":"200","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12822757560343,30.460561224814413],[114.12820677922646,30.46051042485647]]]}},{"type":"Feature","id":"201","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12823755308571,30.46055780014313],[114.12821652067532,30.460507234215115]]]}},{"type":"Feature","id":"202","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12824678210885,30.460554632367874],[114.12822641133666,30.46050414684501]]]}},{"type":"Feature","id":"203","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12825617266687,30.460551409142138],[114.12823638920834,30.460500956084466]]]}},{"type":"Feature","id":"204","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12842232979635,30.460618247013066],[114.128417691069,30.46061609233018],[114.12838906067488,30.46060279353459],[114.12835891643661,30.460588791534825],[114.12832884115144,30.460574821538472]]]}},{"type":"Feature","id":"205","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12866189334268,30.460662264222407],[114.12867794830335,30.460733719129816]]]}},{"type":"Feature","id":"206","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285007257757,30.460693399910237],[114.12850290961815,30.460684535480944],[114.12849141514032,30.460684593394582]]]}},{"type":"Feature","id":"207","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12849941394543,30.460671692237295],[114.12851919055488,30.460673065270363],[114.12851723573179,30.46068725357128]]]}},{"type":"Feature","id":"208","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12850706592427,30.460659350460933],[114.12853856699874,30.460659154402517],[114.1285338333218,30.460682142372615]]]}},{"type":"Feature","id":"209","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1284732864388,30.460713834476344],[114.12847872288397,30.460705064530792]]]}},{"type":"Feature","id":"210","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12847872288397,30.460705064530792],[114.12849141514032,30.460684593394582]]]}},{"type":"Feature","id":"211","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12849141514032,30.460684593394582],[114.12849941394543,30.460671692237295]]]}},{"type":"Feature","id":"212","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12849941394543,30.460671692237295],[114.12850706592427,30.460659350460933]]]}},{"type":"Feature","id":"213","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12850706592427,30.460659350460933],[114.1285079053109,30.46065799662477],[114.1285193386141,30.460639796030616],[114.12858022522573,30.460671950374874],[114.12857584464733,30.460672901241058],[114.1285661300287,30.46067430161067],[114.12855794056122,30.460675779314688],[114.12854994047656,30.46067753164059],[114.12853872035802,30.460680668239995],[114.1285338333218,30.460682142372615]]]}},{"type":"Feature","id":"214","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285338333218,30.460682142372615],[114.1285263397709,30.46068440273543],[114.12851723573179,30.46068725357128]]]}},{"type":"Feature","id":"215","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12851723573179,30.46068725357128],[114.12851419616699,30.460688205378652],[114.12850163985091,30.460692973529728],[114.1285007257757,30.460693399910237]]]}},{"type":"Feature","id":"216","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285007257757,30.460693399910237],[114.12848441607913,30.46070100774001],[114.12847872288397,30.460705064530792]]]}},{"type":"Feature","id":"217","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12862658288552,30.460617500482353],[114.12862756382431,30.460621398461406]]]}},{"type":"Feature","id":"218","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12862756382431,30.460621398461406],[114.12863622422122,30.46065581248366],[114.12863622422124,30.460655812483694]]]}},{"type":"Feature","id":"219","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12863622422124,30.460655812483694],[114.12863837299199,30.460664351103055],[114.1286501161541,30.460662969423137],[114.12865149721733,30.46066288672742]]]}},{"type":"Feature","id":"220","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12865149721733,30.46066288672742],[114.12866189334268,30.460662264222407]]]}},{"type":"Feature","id":"221","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12866189334268,30.460662264222407],[114.12866670776984,30.460661975939868],[114.12867346315322,30.460661452813614]]]}},{"type":"Feature","id":"222","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12867346315322,30.460661452813614],[114.12868308922111,30.460660707382488],[114.1286930532053,30.460660015850845],[114.12870155760558,30.460659491930894],[114.12871156753305,30.460659360647206],[114.12871540763784,30.46065931028169],[114.12870178263275,30.46065286645476],[114.12868143900795,30.46064307478373]]]}},{"type":"Feature","id":"223","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12868143900795,30.46064307478373],[114.12867312388286,30.460639072594365],[114.12865979441074,30.460632896425178]]]}},{"type":"Feature","id":"224","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12865979441074,30.460632896425178],[114.12864842897787,30.4606276302832],[114.12862898061579,30.460618618930347],[114.12862658288563,30.460617500482353]]]}},{"type":"Feature","id":"225","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12862756382431,30.460621398461406],[114.1286536981893,30.460646726631666],[114.12863622422124,30.460655812483694]]]}},{"type":"Feature","id":"226","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12863622422124,30.460655812483694],[114.12863621216883,30.46065581875052]]]}},{"type":"Feature","id":"227","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12865979441074,30.460632896425178],[114.12867149838834,30.460650389640094],[114.12865149721733,30.46066288672742]]]}},{"type":"Feature","id":"228","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12868143900795,30.46064307478373],[114.12868869484318,30.46065366482945],[114.12867346315322,30.460661452813614]]]}},{"type":"Feature","id":"229","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12854918983022,30.460227838404215],[114.12852234392066,30.460227942017273],[114.1285323730125,30.46020906884395]]]}},{"type":"Feature","id":"230","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285457397978,30.460205305973776],[114.12854351565343,30.460216013087265],[114.1285571368483,30.460216067679987]]]}},{"type":"Feature","id":"231","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12857029988476,30.460196571231123],[114.1285571368483,30.460216067679987]]]}},{"type":"Feature","id":"232","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285571368483,30.460216067679987],[114.12854918983022,30.460227838404215]]]}},{"type":"Feature","id":"233","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12854918983022,30.460227838404215],[114.12853544237832,30.460248200417247],[114.12848080370732,30.460219963867853],[114.12851469333377,30.46021348906026],[114.1285323730125,30.46020906884395]]]}},{"type":"Feature","id":"234","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285323730125,30.46020906884395],[114.1285457397978,30.460205305973776]]]}},{"type":"Feature","id":"235","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1285457397978,30.460205305973776],[114.12857029988476,30.460196571231123]]]}},{"type":"Feature","id":"236","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12876040839092,30.460323767807377],[114.12874507413414,30.4603128520213],[114.12871981540793,30.460327731040778]]]}},{"type":"Feature","id":"237","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12872991929535,30.46031166450774],[114.12874684379823,30.46030173077994],[114.12875876209284,30.46031388020817]]]}},{"type":"Feature","id":"238","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12875557087689,30.46027086584409],[114.12873789098559,30.460298886010346]]]}},{"type":"Feature","id":"239","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12873789098559,30.460298886010346],[114.12873586697903,30.460302115922662]]]}},{"type":"Feature","id":"240","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12873586697903,30.460302115922662],[114.12871981540793,30.460327731040778]]]}},{"type":"Feature","id":"241","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12871981540793,30.460327731040778],[114.1287182902953,30.460330156172322],[114.12876098693141,30.460327242497126],[114.12876040839092,30.460323767807377]]]}},{"type":"Feature","id":"242","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12876040839092,30.460323767807377],[114.12875876209284,30.46031388020817]]]}},{"type":"Feature","id":"243","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12875876209284,30.46031388020817],[114.12875749610893,30.460306276759738],[114.12875726121462,30.460304281487893]]]}},{"type":"Feature","id":"244","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12875726121462,30.460304281487893],[114.12875671862989,30.460299672590363],[114.12875557087689,30.46027086584409]]]}},{"type":"Feature","id":"245","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12879200707901,30.460408797342957],[114.12877165734105,30.460407878627482],[114.12877202801093,30.460429485397974]]]}},{"type":"Feature","id":"246","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12878636449918,30.46043625423225],[114.12878583346638,30.460423139484174],[114.1288001539798,30.46042339124335]]]}},{"type":"Feature","id":"247","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1288067493305,30.460433949312637],[114.12880674933038,30.460433949312637]]]}},{"type":"Feature","id":"248","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12880674933038,30.460433949312637],[114.12879787001776,30.46043388887097],[114.12879753025778,30.460441489701243]]]}},{"type":"Feature","id":"249","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1288535470595,30.46046745842134],[114.12882277886841,30.460453322501046]]]}},{"type":"Feature","id":"250","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12882277886841,30.460453322501046],[114.12879753025778,30.460441489701243]]]}},{"type":"Feature","id":"251","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12879753025778,30.460441489701243],[114.12878636449918,30.46043625423225]]]}},{"type":"Feature","id":"252","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12878636449918,30.46043625423225],[114.12877202801093,30.460429485397974]]]}},{"type":"Feature","id":"253","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12877202801093,30.460429485397974],[114.12875440144785,30.460421163179134],[114.12877706273885,30.460376546593384],[114.12879030510373,30.46040557140097],[114.12879200707901,30.460408797342957]]]}},{"type":"Feature","id":"254","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12879200707901,30.460408797342957],[114.1288001539798,30.46042339124335]]]}},{"type":"Feature","id":"255","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1288001539798,30.46042339124335],[114.12880674933038,30.460433949312637]]]}},{"type":"Feature","id":"256","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12880674933038,30.460433949312637],[114.1288067493305,30.460433949312808],[114.12881158715328,30.460441153762385],[114.12881787573671,30.46044796947164],[114.12882277886841,30.460453322501046]]]}},{"type":"Feature","id":"257","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12837302917117,30.46022523007898],[114.12836095507677,30.46022907665788],[114.12836617769591,30.460238227787542]]]}},{"type":"Feature","id":"258","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1283270929556,30.460220065105005],[114.12836404472645,30.460224633610444],[114.12837302917117,30.46022523007898]]]}},{"type":"Feature","id":"259","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12837302917117,30.46022523007898],[114.12840022780429,30.460226284750153]]]}},{"type":"Feature","id":"260","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12840022780429,30.460226284750153],[114.12842428170627,30.460225607748736],[114.12843554363837,30.460270462042168],[114.1284132054234,30.460260081520367]]]}},{"type":"Feature","id":"261","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1284132054234,30.460260081520367],[114.12836617769591,30.460238227787542]]]}},{"type":"Feature","id":"262","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12836617769591,30.460238227787542],[114.12834456974755,30.460228342316125],[114.1283270929556,30.460220065105005]]]}},{"type":"Feature","id":"263","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1282594361881,30.460470610747606],[114.12828158879684,30.460469862575287],[114.12827668978332,30.460452006380965]]]}},{"type":"Feature","id":"264","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12824988935719,30.460455290365648],[114.1282626235959,30.46045470735359],[114.12825957569038,30.460443946624476]]]}},{"type":"Feature","id":"265","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12831057643545,30.460467821384352],[114.12828954958354,30.46045806260415],[114.12827668978332,30.460452006380965]]]}},{"type":"Feature","id":"266","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12827668978332,30.460452006380965],[114.12825957569038,30.460443946624476]]]}},{"type":"Feature","id":"267","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12825957569038,30.460443946624476],[114.12823116917164,30.460430565779617],[114.12823513363749,30.460435047164694],[114.1282410985911,30.460442480098674],[114.12824699456962,30.460450849727245],[114.12824988935719,30.460455290365648]]]}},{"type":"Feature","id":"268","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12824988935719,30.460455290365648],[114.12825612343089,30.460464853507645],[114.1282594361881,30.460470610747606]]]}},{"type":"Feature","id":"269","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1282594361881,30.460470610747606],[114.1282621451196,30.460475318598228],[114.12827004285334,30.460490559238714],[114.12827031807959,30.460491096327623],[114.12831057643545,30.460467821384352]]]}},{"type":"Feature","id":"270","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12829079823528,30.46053987849389],[114.12834003760709,30.46055681816805]]]}},{"type":"Feature","id":"271","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12830107585357,30.460574929528217],[114.12831134133391,30.4605855186706],[114.1283308918113,30.460571522243782]]]}},{"type":"Feature","id":"272","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1283308918113,30.460571522243782],[114.12833090616948,30.460571511964588]]]}},{"type":"Feature","id":"273","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12829079823528,30.46053987849389],[114.12829185294262,30.46054275428451],[114.12829352173044,30.460548399008697]]]}},{"type":"Feature","id":"274","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12829352173044,30.460548399008697],[114.12829352173046,30.460548399008758],[114.12829655198448,30.46055864893324]]]}},{"type":"Feature","id":"275","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12829655198448,30.46055864893324],[114.12830072615508,30.460573305664308],[114.12830107585357,30.460574929528217]]]}},{"type":"Feature","id":"276","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12830107585357,30.460574929528217],[114.12830350703699,30.460586218995534],[114.1283057058045,30.460595944723526],[114.12830651401676,30.460604188710853],[114.12829868477651,30.46062353496956],[114.12831145944958,30.460602786826506],[114.12832884115144,30.460574821538472]]]}},{"type":"Feature","id":"277","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12832884115144,30.460574821538472],[114.1283308918113,30.460571522243782]]]}},{"type":"Feature","id":"278","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.1283308918113,30.460571522243782],[114.12833089181142,30.460571522243605],[114.1283379454297,30.460560173714523],[114.12834003760709,30.46055681816805]]]}},{"type":"Feature","id":"279","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12834003760709,30.46055681816805],[114.12831411299864,30.460568000808475],[114.12829352173044,30.460548399008697]]]}},{"type":"Feature","id":"280","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12829352173044,30.460548399008697],[114.12829350753343,30.460548385493894]]]}},{"type":"Feature","id":"281","properties":{"type":5},"geometry":{"type":"MultiLineString","coordinates":[[[114.12871283204083,30.46054890605957],[114.12869540616136,30.460576642138296],[114.12867798025998,30.460604378204753],[114.12866062846345,30.460631996274866]]]}}]} \ No newline at end of file +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "id": "10", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [114.12670642509887, 30.459719866954526], + [114.12672480418713, 30.459728657730796], + [114.12674493919597, 30.459737997306732], + [114.1267833123607, 30.459756158076946], + [114.12680436982367, 30.45976593212108], + [114.12685397313732, 30.459789508849415], + [114.12690012817846, 30.45981125331352], + [114.12696581650293, 30.459842297256657], + [114.12703599666129, 30.459875724524046], + [114.1270498805918, 30.45988229362388], + [114.12705936578135, 30.459887101893713], + [114.12713738017123, 30.459929772755643], + [114.1272371446353, 30.459984098991324], + [114.12732032068887, 30.460029332274345], + [114.12756802667649, 30.460156811375963], + [114.1276272681774, 30.46018476753933], + [114.12767330666927, 30.460206334960937], + [114.12774798860086, 30.460241752793856], + [114.12781876709889, 30.460274913778548], + [114.12793737942023, 30.46033070051492], + [114.12802476152558, 30.460371550079984], + [114.12803377753897, 30.460375985604387], + [114.12804110941299, 30.460380028660342], + [114.12805101374497, 30.460386354257707], + [114.12806883527543, 30.460398025244622], + [114.12808654794983, 30.46041118828605], + [114.12809933710143, 30.46042164630667], + [114.1281073950421, 30.460428573291047], + [114.12811395128384, 30.460434620557336], + [114.12811938213294, 30.460440066689017], + [114.12812926407065, 30.460450347349173], + [114.12813473454605, 30.46045657462467], + [114.12814164327082, 30.460464424462735], + [114.12814730374363, 30.460471255615666], + [114.12815294371121, 30.460478077312562], + [114.12815879253623, 30.460486283596325], + [114.1281645602278, 30.460494776825477], + [114.12816883816927, 30.460501272024807], + [114.12817349886402, 30.460509245696944], + [114.1281771352045, 30.460517071410813], + [114.12817986770692, 30.460522406328934], + [114.12818358105403, 30.460530441147434], + [114.12818686670175, 30.46053817825591], + [114.12819018776598, 30.460545819342492], + [114.12819418910176, 30.46055564186251], + [114.12820380410353, 30.460582194804786], + [114.12820842032188, 30.46060375471472], + [114.12821202983478, 30.46062954010985], + [114.1282137381741, 30.46064526447438], + [114.12821400817043, 30.460653439918755], + [114.12821393731382, 30.46067277098964], + [114.12821257637805, 30.46069672891237], + [114.12821098332131, 30.460710299619304], + [114.12820721720577, 30.460726258836115], + [114.12819314233184, 30.460756750044617], + [114.12817805241743, 30.46078387834311], + [114.1281664689422, 30.460803920591136], + [114.12815723705758, 30.46081876175518], + [114.12813556102246, 30.46085392543641], + [114.12786187058398, 30.461286782141404], + [114.12767310863411, 30.461588040608625], + [114.12749605673096, 30.46186848121499], + [114.12771556983981, 30.46197223706339], + [114.12829921553568, 30.46103318454979], + [114.12834745214138, 30.460953266211526], + [114.12837228716977, 30.460914859036752], + [114.1283903573218, 30.460889225666122], + [114.12840962380116, 30.46086537779413], + [114.12842658919257, 30.46084461385105], + [114.12844027647458, 30.460830720633155], + [114.12845700359827, 30.460817290594896], + [114.12846927807756, 30.460807952031754], + [114.1284793259837, 30.46080120044445], + [114.12848732126338, 30.46079615416839], + [114.12849824982679, 30.460790108078196], + [114.12851735673152, 30.46078057113085], + [114.12853428994015, 30.460773234382117], + [114.12854767478771, 30.460768204092016], + [114.12856867125925, 30.460760493145365], + [114.12858706934223, 30.460754937340415], + [114.12861834861083, 30.46074820545063], + [114.12862756243905, 30.46074661029612], + [114.12864869683986, 30.46074212232996], + [114.12865920934019, 30.460740441110147], + [114.12866868493343, 30.460739396487128], + [114.12868036248904, 30.460738380348605], + [114.12868990874968, 30.460737761184348], + [114.12870180719327, 30.460737363112344], + [114.12871667998598, 30.460737392256405], + [114.1287292771692, 30.46073780823301], + [114.1287499886682, 30.46073912084974], + [114.12876640653077, 30.460741578573987], + [114.12877789632182, 30.460743427010925], + [114.12878992723334, 30.460745602599992], + [114.12880068089633, 30.46074775119988], + [114.12881210760563, 30.460750689795557], + [114.12882252563773, 30.460753300372676], + [114.12882949358202, 30.460755215632997], + [114.12884206581988, 30.460759486418695], + [114.12884823128418, 30.460761483949376], + [114.12890089866964, 30.460782505183808], + [114.12891485544512, 30.460788816797255], + [114.12895540785316, 30.460808048501978], + [114.12902501127174, 30.46084044145126], + [114.12908466293443, 30.460868698056956], + [114.12910546198879, 30.46087847390208], + [114.1291300006723, 30.460890007409656], + [114.12917162969823, 30.4609095887503], + [114.12927120714293, 30.460956547967346], + [114.12935900625274, 30.460997903663344], + [114.12943766593234, 30.461035023980024], + [114.12945728539037, 30.461044170004904], + [114.12947426624748, 30.461052231980972], + [114.12948015380047, 30.46105484591913], + [114.12948639545796, 30.4610574312437], + [114.1294967439802, 30.461061699880304], + [114.12951325767341, 30.461068326698438], + [114.12953869523535, 30.4610785199137], + [114.12956431897612, 30.461088970193117], + [114.12959304786149, 30.461100566489673], + [114.1296316067205, 30.461116134315017], + [114.12965073525396, 30.461123943783882], + [114.1296767364358, 30.461134433008322], + [114.12971617913543, 30.461150365527534], + [114.1297826382227, 30.461177241498255], + [114.12984084737079, 30.46120092141013], + [114.12985385983599, 30.46120636036464], + [114.12988107460019, 30.46121346938985], + [114.12988717793624, 30.461215056315822], + [114.1299011234562, 30.461221666468255], + [114.12991177953043, 30.461226717093556], + [114.12993382466588, 30.46123713451079], + [114.12998083593384, 30.461259449163787], + [114.13001973787617, 30.46127751673334], + [114.13006243727204, 30.461297540583438], + [114.13013580529461, 30.461331867997014], + [114.13020225528513, 30.461363289096944], + [114.13026373880149, 30.461392632679807], + [114.13029120386813, 30.46140550178206], + [114.13040786031041, 30.461460431954237], + [114.13046438304347, 30.461487017104876], + [114.13058639783064, 30.46127753388595], + [114.1305697754666, 30.461269607588438], + [114.13044610731077, 30.461211548873724], + [114.13036451796869, 30.461173104230493], + [114.13028045912006, 30.46113356386891], + [114.13023878831747, 30.46111384483735], + [114.1301812215938, 30.46108665817515], + [114.1301369597027, 30.461066069392857], + [114.13012052111407, 30.461058307966308], + [114.13010307916063, 30.461050038266155], + [114.13009311705432, 30.461045354054807], + [114.12976904001516, 30.460871316765], + [114.12973565501949, 30.460853218474796], + [114.12971961636799, 30.460845148883948], + [114.1295129460683, 30.46074116541183], + [114.12945722337031, 30.46071487608635], + [114.12926460885387, 30.460624157320336], + [114.12907783250328, 30.460536187270748], + [114.12906810320003, 30.46053099337933], + [114.12905584211939, 30.460524970242773], + [114.12902430272453, 30.460508788612035], + [114.12900645526051, 30.460499046548748], + [114.12899922449976, 30.46049483639869], + [114.12899068600481, 30.460489375426576], + [114.12897983929717, 30.460481324246036], + [114.12896495996615, 30.460469828796573], + [114.1289488405392, 30.460456110632084], + [114.12893156889058, 30.460438092299785], + [114.12889747265079, 30.460394122293796], + [114.12889453203489, 30.46039033011819], + [114.12888995839599, 30.46038307458886], + [114.12888289382553, 30.46037047324551], + [114.12887002948217, 30.46033996405978], + [114.12886289038934, 30.460319750928605], + [114.12885395471388, 30.46029112420495], + [114.1288521680376, 30.46028171852131], + [114.12884964067962, 30.46026239223364], + [114.1288483923799, 30.46025102886467], + [114.12884796986727, 30.460232582397943], + [114.12884875193501, 30.460206802769104], + [114.1288503392283, 30.46019201466402], + [114.12885801135928, 30.460157905337283], + [114.12886084427603, 30.46014882845719], + [114.12886636717148, 30.46013113267193], + [114.12887584793602, 30.460110139555457], + [114.1288838291907, 30.460092466765246], + [114.12890164662322, 30.460061561716298], + [114.12891494258247, 30.460039083877408], + [114.12892546862423, 30.46002213124867], + [114.12897421219743, 30.459943575764843], + [114.1289842110455, 30.45992736538217], + [114.12899391148486, 30.459911638771093], + [114.12900365647637, 30.459895839915685], + [114.1290135248707, 30.459879840980385], + [114.12902702305738, 30.45985795729204], + [114.1290760801111, 30.459778424191825], + [114.12928780195848, 30.45945907207147], + [114.1293908264265, 30.459296790906148], + [114.12951420054556, 30.45909081702124], + [114.12929465307654, 30.45898788033217], + [114.12887225771087, 30.459683803494816], + [114.12882348281829, 30.45976346737858], + [114.12881370466052, 30.459779839929716], + [114.12881212257278, 30.45978237574835], + [114.12880649093621, 30.459791362993084], + [114.12879568258535, 30.459808611481332], + [114.1287862339858, 30.45982368999736], + [114.12877733346733, 30.459837893847396], + [114.1287684006543, 30.45985214922154], + [114.12874671778104, 30.459886751646973], + [114.12870820465953, 30.459948212292552], + [114.12869629062735, 30.459967225092058], + [114.1286909504444, 30.459975584332216], + [114.1286733227205, 30.460001654020463], + [114.12866499322047, 30.46001094922957], + [114.1286409124706, 30.460037821819622], + [114.12862056779049, 30.460055362448653], + [114.12859942445834, 30.460073591613387], + [114.12859892982115, 30.460074018074582], + [114.12858006237614, 30.46008735465943], + [114.1285703977271, 30.460094186172874], + [114.12856173865131, 30.46009965466516], + [114.12855854817336, 30.460101651529794], + [114.12854977053016, 30.460106600493948], + [114.1285407570152, 30.460111128749116], + [114.1285349971284, 30.460113638283904], + [114.12853144757541, 30.460115080735324], + [114.12851676295261, 30.460120842817474], + [114.12849833445783, 30.46012695982037], + [114.12849320364326, 30.460128659370767], + [114.12848915726917, 30.46012975301531], + [114.12847990318281, 30.46013225418443], + [114.12846243300255, 30.460136775580118], + [114.12844856827508, 30.460140200201295], + [114.12842993780764, 30.46014382686593], + [114.12841310640472, 30.46014658507335], + [114.12839158428022, 30.46014837514409], + [114.12838418126694, 30.460149173943524], + [114.12837031195335, 30.4601498098311], + [114.12834724265122, 30.460149881331255], + [114.12832457028, 30.460148815571962], + [114.12830033627363, 30.460146787633487], + [114.12828606564942, 30.460144447170077], + [114.12826758884803, 30.460140971343122], + [114.1282376466944, 30.460134095768744], + [114.12822807094831, 30.460131457652615], + [114.12821968332197, 30.46012905213806], + [114.12821317234342, 30.460126785540226], + [114.12820518502357, 30.460123838223822], + [114.12819414681434, 30.460119689764355], + [114.12818404012039, 30.460115828881552], + [114.12818024400646, 30.460114010332596], + [114.12816840687336, 30.460108401648185], + [114.12810279127679, 30.460077521889644], + [114.12801392613177, 30.460035481897506], + [114.127875067572, 30.45996989516379], + [114.1277868310944, 30.459928396957878], + [114.12776774120755, 30.459919418827678], + [114.12765252636181, 30.45986512851318], + [114.12763346721754, 30.45985561035686], + [114.1274294888824, 30.459774464597885], + [114.12729689668743, 30.459721334876495], + [114.12725367350123, 30.459704157756786], + [114.12720564883506, 30.459685197896864], + [114.12713351632975, 30.459651534720905], + [114.12711081207827, 30.459641142294075], + [114.12704721321815, 30.459610978372535], + [114.12699816961752, 30.45958834667066], + [114.12693500878125, 30.459558363528384], + [114.12687186487535, 30.459528720775257], + [114.12682601995878, 30.459507090823386], + [114.12670642509887, 30.459719866954526] + ] + ] + ] + } + }, + { + "type": "Feature", + "id": "11", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [114.12667851705592, 30.459769519300867], + [114.12678669733096, 30.459820404831465], + [114.12696098364239, 30.459902229147097], + [114.12706246005538, 30.459954887442564], + [114.12713863456968, 30.45999580532362], + [114.1272445047874, 30.460052759625377], + [114.12729073755453, 30.460077508855534], + [114.1275086759357, 30.460190139691004], + [114.12793638723153, 30.460393536847192], + [114.1280091529264, 30.460429436483583], + [114.12807692852476, 30.460488494764924], + [114.12809608381721, 30.46050926878318], + [114.12812869945513, 30.460564335635073], + [114.12814437677969, 30.460598855787218], + [114.12815471974382, 30.460634219292533], + [114.12815795586218, 30.46065232227354], + [114.12816084802566, 30.460666226523696], + [114.12816297177484, 30.460678205130506], + [114.12816330989769, 30.460697061140195], + [114.1281615490448, 30.460711335366184], + [114.12815967333917, 30.460725154080695], + [114.12815412047559, 30.460745801236786], + [114.12813685164059, 30.460778711292647], + [114.12812526814679, 30.46079875352665], + [114.12811603624742, 30.46081359467971], + [114.1279496458915, 30.46108040507848], + [114.12746442959613, 30.46185587039544], + [114.12748164436566, 30.461863076578037], + [114.12797226529804, 30.461083207482826], + [114.12808936576536, 30.460894645704666], + [114.12813972501878, 30.46081439412671], + [114.1281489569059, 30.46079955296438], + [114.12816054038488, 30.46077951071853], + [114.1281755585996, 30.46075295372288], + [114.12818963347885, 30.46072246251689], + [114.12819258724711, 30.460709382623207], + [114.12819434809374, 30.460695108394308], + [114.12819591291681, 30.46067465433775], + [114.1281960248573, 30.46066411627798], + [114.12819561473438, 30.460648068731086], + [114.12819466169702, 30.46063554207201], + [114.12819279494028, 30.460620418411153], + [114.12818940492706, 30.460607712318566], + [114.12818563805288, 30.460597393544138], + [114.1281819505387, 30.460586594127783], + [114.12817989670708, 30.460577862964488], + [114.12817924467245, 30.460573839455144], + [114.12817949504986, 30.46057124649343], + [114.12818053476947, 30.460570151389053], + [114.12818221818578, 30.46056899362691], + [114.12818652570962, 30.46056741402056], + [114.12819690363179, 30.46056423973758], + [114.12819339565145, 30.460554513371267], + [114.12818938424755, 30.460544357421085], + [114.12818682298207, 30.46053814209666], + [114.12818353733651, 30.460530404990013], + [114.12817982399187, 30.460522370173596], + [114.12817709149125, 30.460517035257006], + [114.1281748449635, 30.4605134072006], + [114.12815999976104, 30.460516411050083], + [114.1281569844651, 30.460516108596025], + [114.12815427533317, 30.46051473858637], + [114.1281504066004, 30.46051071863724], + [114.12813888688173, 30.460497459445044], + [114.12813622851498, 30.46049402946341], + [114.12812933351096, 30.460485710839954], + [114.12812094472461, 30.460476242046507], + [114.12811173169072, 30.460466178487792], + [114.12809933858425, 30.460453694910942], + [114.12808964467422, 30.460444747095103], + [114.1280722884732, 30.460430668424696], + [114.12806373666007, 30.460423929440257], + [114.12805096285751, 30.4604148188883], + [114.12803869437997, 30.460406548926965], + [114.12802808197053, 30.46039991968587], + [114.12802370738075, 30.46039743663877], + [114.12801513967143, 30.460393015625648], + [114.12799381210561, 30.46038291219492], + [114.12797508311029, 30.460374198797272], + [114.12791771802227, 30.460347195819843], + [114.12767435823383, 30.46023264012388], + [114.12762175159291, 30.460208054352417], + [114.12750768942868, 30.460153778331048], + [114.12750968523949, 30.46015305383419], + [114.12751615320019, 30.460151116056814], + [114.12752101174054, 30.460149847807664], + [114.1275245010255, 30.460149209070295], + [114.12753385950025, 30.46014791885199], + [114.12753905352344, 30.460147613899547], + [114.12754442532368, 30.460147980243853], + [114.12732052000571, 30.460030682280813], + [114.12732096353244, 30.460033522969436], + [114.1273209437513, 30.46003526354767], + [114.12732042193159, 30.460039600474627], + [114.12731982187725, 30.460044125178978], + [114.12731900199067, 30.46004906017859], + [114.12731813378313, 30.460053516056735], + [114.12731760854854, 30.460055567040012], + [114.12731527476217, 30.46005429975084], + [114.12725782565802, 30.460023057477574], + [114.1271690692801, 30.459974725730937], + [114.12702132571069, 30.459895863096776], + [114.1270118405141, 30.45989105481675], + [114.12699795657305, 30.459884485702027], + [114.126927776362, 30.45985105836026], + [114.1268620879885, 30.459820014347677], + [114.1268159329123, 30.459798269834298], + [114.12676616873911, 30.459774841761565], + [114.12674527208262, 30.4597649189869], + [114.12672905649116, 30.459757399324875], + [114.12669446854811, 30.459741139345454], + [114.12667851705592, 30.459769519300867] + ] + ], + [ + [ + [114.12683688366135, 30.459487762763597], + [114.12694685034681, 30.45953947097963], + [114.12702821410709, 30.459577593679693], + [114.1271414291467, 30.459631188953093], + [114.12719322411404, 30.459655676670067], + [114.12726367595704, 30.459686168438914], + [114.1273874043263, 30.45973747924787], + [114.12745538294098, 30.459766253026842], + [114.12746249171516, 30.459769261998932], + [114.12746847497262, 30.459771794566112], + [114.12746506444269, 30.45977297871145], + [114.12745847670172, 30.45977469440682], + [114.12745466519337, 30.459775504406007], + [114.12744835030661, 30.459775950289142], + [114.12744153549609, 30.459776037784387], + [114.12743665669757, 30.459775368827874], + [114.12743317980241, 30.459774519397744], + [114.12763742270677, 30.45985630983784], + [114.1276294917997, 30.45985222493772], + [114.12762504577312, 30.45984893496641], + [114.12761871568127, 30.45984280134446], + [114.1276187147639, 30.45984276277992], + [114.12761871065264, 30.45984272395687], + [114.12761870683686, 30.459842685110296], + [114.1276187033168, 30.45984264624262], + [114.12761870009267, 30.459842607355274], + [114.1276186971644, 30.45984256845031], + [114.12761869453227, 30.459842529528967], + [114.12761869219653, 30.45984249059319], + [114.12761869062959, 30.45984246138299], + [114.12761868924792, 30.45984243216579], + [114.12761868801458, 30.459842402943504], + [114.12761868696664, 30.459842373715418], + [114.12761868606704, 30.459842344483548], + [114.12761868535262, 30.459842315247634], + [114.1276186846472, 30.459842276262652], + [114.12761868423841, 30.4598422372737], + [114.12761868411735, 30.459842208031027], + [114.12761868418148, 30.459842178787778], + [114.12761868439428, 30.45984214954512], + [114.12761868479193, 30.459842120303886], + [114.12761868533845, 30.459842091064104], + [114.12761868607039, 30.459842061827327], + [114.12761868729305, 30.459842022849955], + [114.12761868881246, 30.459841983880256], + [114.12761869013697, 30.459841954658607], + [114.12761869164726, 30.459841925443666], + [114.12761869330551, 30.459841896234774], + [114.1276186951485, 30.459841867033727], + [114.12761869785317, 30.459841828111287], + [114.12761870085355, 30.459841789205232], + [114.12761870415021, 30.459841750316855], + [114.12761870680785, 30.45984172116234], + [114.12761870964978, 30.459841692021122], + [114.1276187126398, 30.459841662890913], + [114.12761871581431, 30.459841633775135], + [114.12761871913659, 30.45984160467167], + [114.1276187226427, 30.45984157558435], + [114.12761872756414, 30.45984153682401], + [114.12761873143927, 30.459841507771703], + [114.1276187354987, 30.459841478738042], + [114.12761873970517, 30.459841449720273], + [114.1276187440955, 30.459841420722743], + [114.1276187501946, 30.459841382087735], + [114.12761875658737, 30.459841343488495], + [114.1276187615654, 30.459841314561874], + [114.12761876672647, 30.459841285659866], + [114.12761877385242, 30.45984124715648], + [114.12761878127093, 30.459841208694883], + [114.12761878898186, 30.459841170276164], + [114.1276187949478, 30.459841141490177], + [114.12761880109602, 30.459841112732796], + [114.12761880953707, 30.459841074429427], + [114.12761881826873, 30.459841036174858], + [114.12761882499953, 30.459841007515504], + [114.12761883191142, 30.45984097888851], + [114.12761883896847, 30.459840950288267], + [114.12761884620718, 30.45984092172191], + [114.12761885359049, 30.45984089318333], + [114.12761886115479, 30.45984086468046], + [114.12761887148058, 30.45984082672498], + [114.12761888209575, 30.459840788829606], + [114.12761889299816, 30.45984075099531], + [114.12761890418855, 30.459840713224306], + [114.12761891276041, 30.459840684936363], + [114.1276189215118, 30.459840656689476], + [114.12761893341859, 30.45984061908359], + [114.12761894252766, 30.459840590921647], + [114.12761895181512, 30.459840562803826], + [114.12761896443511, 30.45984052537278], + [114.12761897734006, 30.459840488014866], + [114.12761899052893, 30.45984045073134], + [114.12761900059769, 30.45984042281616], + [114.12761901084313, 30.459840394949538], + [114.12761902122963, 30.459840367122013], + [114.12761903179226, 30.45984033934458], + [114.12761904611081, 30.459840302375543], + [114.12761906070975, 30.45984026548902], + [114.12761907558946, 30.45984022868712], + [114.12761908692329, 30.45984020113895], + [114.12761909843222, 30.45984017364542], + [114.12761911008049, 30.459840146196054], + [114.12761912190228, 30.459840118802354], + [114.12761913386308, 30.459840091454122], + [114.12761914599669, 30.459840064163327], + [114.12761915826864, 30.459840036918962], + [114.12761917071323, 30.45984000973357], + [114.12761918753536, 30.459839973566115], + [114.12761920463223, 30.459839937495637], + [114.12761922200302, 30.459839901523655], + [114.12761923964709, 30.459839865651997], + [114.12761925305021, 30.459839838811785], + [114.12761926662367, 30.459839812035433], + [114.1276192849469, 30.45983977642092], + [114.12761930354125, 30.459839740911907], + [114.12761932237923, 30.459839705557602], + [114.12761934140767, 30.4598396704538], + [114.12761936070153, 30.45983963545903], + [114.12761937533706, 30.45983960928203], + [114.12761939013714, 30.4598395831747], + [114.12761940506908, 30.459839557123757], + [114.12761942016478, 30.459839531144077], + [114.12761943539132, 30.459839505221865], + [114.12761945078094, 30.459839479372334], + [114.12761947151769, 30.459839445004132], + [114.12761949251376, 30.459839410754853], + [114.12761951376811, 30.459839376625645], + [114.12761953528096, 30.459839342618263], + [114.1276195570496, 30.459839308734132], + [114.12761957907493, 30.459839274974502], + [114.12761960135458, 30.459839241341257], + [114.1276196238882, 30.459839207835074], + [114.12761964667455, 30.45983917445846], + [114.12761966971314, 30.459839141211692], + [114.12761969300254, 30.45983910809728], + [114.12761971062592, 30.45983908334458], + [114.12761972840475, 30.459839058676106], + [114.1276197523174, 30.459839025897416], + [114.12761977040617, 30.45983900139877], + [114.12761978864927, 30.459838976986457], + [114.12761980701526, 30.459838952643675], + [114.12761982553445, 30.45983892838848], + [114.12761985042985, 30.459838896165554], + [114.1276198755689, 30.459838864085224], + [114.12761990094943, 30.4598388321488], + [114.12761992013613, 30.45983880828728], + [114.12761993947205, 30.459838784516933], + [114.12761996545335, 30.45983875294645], + [114.12761999167229, 30.45983872152413], + [114.1276200181291, 30.459838690251917], + [114.1276200381189, 30.459838666892264], + [114.12762005825522, 30.459838643627762], + [114.12762007850922, 30.459838620440074], + [114.12762009890882, 30.45983859734891], + [114.12762012630185, 30.459838566690046], + [114.12762015392593, 30.459838536188006], + [114.12762018178003, 30.45983850584421], + [114.1276202098635, 30.459838475659282], + [114.12762023817392, 30.45983844563511], + [114.12762026671132, 30.459838415772875], + [114.12762028825482, 30.459838393478226], + [114.12762030993893, 30.45983837128579], + [114.12762033903648, 30.459838341834047], + [114.12762036835612, 30.459838312548637], + [114.12762039048407, 30.45983829068925], + [114.12762041279098, 30.459838268894966], + [114.12762044271615, 30.459838239977927], + [114.12762047285979, 30.45983821123222], + [114.12762050322027, 30.45983818265933], + [114.12762053379673, 30.459838154260165], + [114.1276205568633, 30.459838133070182], + [114.12762058006322, 30.45983811199049], + [114.12762061117319, 30.45983808403135], + [114.1276206424942, 30.459838056250543], + [114.12762067402507, 30.45983802864933], + [114.12762070576474, 30.459838001228317], + [114.12762073771073, 30.459837973989174], + [114.12762076986259, 30.459837946933085], + [114.12762079410406, 30.459837926755934], + [114.12762081847264, 30.459837906694375], + [114.12762085113233, 30.459837880100626], + [114.12762088399268, 30.459837853693976], + [114.12762091705243, 30.459837827475287], + [114.12762095030996, 30.459837801446138], + [114.12762098376406, 30.45983777560717], + [114.12762101741312, 30.45983774995985], + [114.12762105125513, 30.45983772450498], + [114.1276210767572, 30.459837705535097], + [114.12762110237843, 30.45983768668677], + [114.12762112809455, 30.459837667936096], + [114.12762115392856, 30.45983764930772], + [114.12762117985655, 30.459837630778082], + [114.12762120590098, 30.459837612372162], + [114.12762123203778, 30.45983759406543], + [114.12762125829046, 30.459837575883395], + [114.12762129344584, 30.459837551807507], + [114.12762131992537, 30.459837533876286], + [114.12762134651851, 30.459837516071463], + [114.127621382125, 30.459837492500615], + [114.12762140894068, 30.45983747494968], + [114.12762143586701, 30.45983745752674], + [114.12762147191441, 30.45983743446725], + [114.1276215081359, 30.459837411614217], + [114.12762153540983, 30.45983739460385], + [114.12762156279013, 30.45983737772347], + [114.12762159943968, 30.459837355390285], + [114.1276216270315, 30.459837338771592], + [114.12762165472797, 30.459837322284315], + [114.12762169179473, 30.45983730047752], + [114.12762172902579, 30.459837278882418], + [114.12762175705105, 30.45983726281962], + [114.12762178517671, 30.45983724688988], + [114.12762182281158, 30.459837225829375], + [114.12762186060465, 30.459837204984552], + [114.12762189844541, 30.45983718441425], + [114.12762193611204, 30.45983716423502], + [114.12762196445577, 30.459837149233792], + [114.12762199289281, 30.459837134366264], + [114.12762203093145, 30.45983711472131], + [114.1276220691167, 30.45983709529137], + [114.12762209784599, 30.459837080854093], + [114.12762212666482, 30.459837066552055], + [114.12762215555456, 30.459837052358374], + [114.12762218453251, 30.45983703830107], + [114.12762222328578, 30.459837019739837], + [114.12762225243738, 30.459837005955855], + [114.1276222816744, 30.459836992309274], + [114.12762231097932, 30.45983697877282], + [114.12762234036788, 30.459836965374453], + [114.12762237966366, 30.4598369476944], + [114.12762241909073, 30.45983693023659], + [114.12762245864755, 30.459836913002057], + [114.12762249833237, 30.45983689599091], + [114.12762253814398, 30.45983687920406], + [114.12762256808047, 30.459836866754415], + [114.12762259809404, 30.459836854446106], + [114.12762262816855, 30.459836842250773], + [114.12762265831932, 30.45983683019728], + [114.12762269861896, 30.459836814314745], + [114.12762273903685, 30.459836798660557], + [114.12762276942279, 30.459836787062446], + [114.12762279988081, 30.459836775607666], + [114.12762284058573, 30.45983676052571], + [114.1276228815797, 30.459836745610463], + [114.12762292274388, 30.459836730906748], + [114.12762296401716, 30.45983671643723], + [114.1276230053987, 30.459836702202328], + [114.12762304688614, 30.45983668820277], + [114.12762308847797, 30.459836674439074], + [114.12762313017268, 30.45983666091164], + [114.1276231719677, 30.459836647621437], + [114.12762321386248, 30.459836634568866], + [114.12762324534398, 30.459836624928133], + [114.12762327688561, 30.45983661543659], + [114.12762330847417, 30.45983660606458], + [114.12762334012137, 30.459836596842106], + [114.12762337181421, 30.459836587739506], + [114.12762340356346, 30.45983657878724], + [114.12762344597049, 30.45983656705118], + [114.1276234778297, 30.459836558399925], + [114.1276235097433, 30.459836549899297], + [114.12762355236475, 30.45983653876698], + [114.12762358438253, 30.459836530569017], + [114.12762361645154, 30.459836522522764], + [114.12762365927591, 30.459836511996816], + [114.12762370217928, 30.45983650171443], + [114.12762373440403, 30.459836494154903], + [114.12762376667597, 30.459836486747943], + [114.12762379898444, 30.459836479463302], + [114.12762383133824, 30.45983647233185], + [114.12762387453527, 30.459836463027482], + [114.12762391780132, 30.45983645396862], + [114.127623950293, 30.459836447328122], + [114.12762398282547, 30.45983644084116], + [114.1276240262554, 30.459836432397626], + [114.12762406974804, 30.459836424201143], + [114.12762411330148, 30.459836416251473], + [114.12762414600274, 30.45983641044426], + [114.12762417874067, 30.459836404791663], + [114.12762422243705, 30.459836397461554], + [114.12762426647649, 30.45983639033405], + [114.12762431066506, 30.45983638344345], + [114.12762435490482, 30.459836376806383], + [114.12762439919386, 30.4598363704228], + [114.12762444353012, 30.459836364293214], + [114.12762448791192, 30.459836358417853], + [114.12762453233674, 30.459836352796604], + [114.12762457680334, 30.459836347430212], + [114.12762462130945, 30.45983634231879], + [114.1276246547125, 30.459836338644507], + [114.12762468813864, 30.45983633512997], + [114.12762472158133, 30.45983633174301], + [114.12762475504547, 30.45983632851574], + [114.12762479969035, 30.45983632442604], + [114.12762483319266, 30.45983632151856], + [114.127624866713, 30.45983631877129], + [114.1276249114282, 30.459836315321517], + [114.12762494498104, 30.459836312894442], + [114.12762497854808, 30.459836310627463], + [114.12762502332265, 30.45983630781871], + [114.12762506811713, 30.45983630526654], + [114.12762511292976, 30.459836302971063], + [114.12762515775864, 30.459836300932515], + [114.12762520260196, 30.459836299150833], + [114.12762523624184, 30.45983629797529], + [114.12762526988877, 30.459836296960308], + [114.12762531475859, 30.459836295821326], + [114.12762535963614, 30.459836294939453], + [114.12762540451882, 30.459836294314968], + [114.1276254494054, 30.45983629394731], + [114.12762549429334, 30.459836293837213], + [114.12762552795887, 30.4598362939152], + [114.12762556162362, 30.459836294154258], + [114.12762559528632, 30.459836294521654], + [114.12762562880437, 30.459836295046962], + [114.12762567311198, 30.459836295951487], + [114.12762570633784, 30.45983629678661], + [114.12762573955806, 30.45983629777844], + [114.12762577277238, 30.45983629889571], + [114.127625805979, 30.45983630016923], + [114.127625850243, 30.459836302076255], + [114.1276258944916, 30.459836304233814], + [114.12762593872274, 30.459836306641275], + [114.1276259829345, 30.459836309299394], + [114.12762601608061, 30.459836311449266], + [114.12762604921208, 30.459836313755456], + [114.12762609336727, 30.45983631703821], + [114.12762613749685, 30.459836320571167], + [114.12762618159863, 30.45983632435347], + [114.12762622567084, 30.45983632838541], + [114.12762626971127, 30.459836332666814], + [114.1276263137185, 30.459836337197064], + [114.12762635769045, 30.459836341976207], + [114.12762640162542, 30.459836347004373], + [114.12762643455244, 30.4598363509307], + [114.12762646745382, 30.45983635501222], + [114.12762650033469, 30.459836359217938], + [114.12762653318843, 30.459836363578727], + [114.12762657695646, 30.459836369599568], + [114.12762662067755, 30.459836375867443], + [114.12762665343853, 30.459836380723218], + [114.12762668616885, 30.459836385733446], + [114.12762672976561, 30.45983639261954], + [114.12762677368023, 30.45983639981385], + [114.12762681766263, 30.45983640727993], + [114.1276268615865, 30.45983641499749], + [114.1276269054507, 30.45983642296648], + [114.1276269492532, 30.459836431186268], + [114.12762699299216, 30.459836439656467], + [114.12762702575566, 30.459836446165387], + [114.12762705847707, 30.459836452830707], + [114.1276271020486, 30.45983646192548], + [114.12762713468335, 30.45983646890252], + [114.12762716727306, 30.45983647603511], + [114.12762721066561, 30.45983648575225], + [114.12762724316296, 30.459836493195315], + [114.12762727561211, 30.459836500793024], + [114.12762731881337, 30.45983651112993], + [114.12762736193498, 30.459836521713676], + [114.12762740497458, 30.45983653254432], + [114.12762743720252, 30.45983654082061], + [114.12762746937713, 30.459836549250873], + [114.12762751220447, 30.45983656069578], + [114.1276275442704, 30.459836569432436], + [114.12762757628029, 30.459836578321926], + [114.1276276188839, 30.459836590377932], + [114.12762766139517, 30.459836602677782], + [114.12762770381161, 30.459836615220507], + [114.12762774613181, 30.459836628005814], + [114.12762778835355, 30.459836641033036], + [114.12762783047522, 30.459836654301775], + [114.12762787249478, 30.45983666781129], + [114.12762791441045, 30.45983668156107], + [114.12762795622102, 30.459836695550614], + [114.12762799792417, 30.459836709778944], + [114.12762802913298, 30.459836720599355], + [114.12762806027291, 30.459836731568572], + [114.12762810169936, 30.459836746391844], + [114.1276281430122, 30.459836761451868], + [114.12762818420902, 30.459836776748535], + [114.12762821503378, 30.459836788367827], + [114.12762824578444, 30.45983680013411], + [114.12762828668501, 30.459836816018285], + [114.12762832746405, 30.459836832135693], + [114.12762836811932, 30.459836848486344], + [114.12762840864899, 30.45983686506971], + [114.12763036571418, 30.45983774033913], + [114.12767012739805, 30.45985553030195], + [114.12777511998902, 30.45990500231548], + [114.12779109555976, 30.459912477348286], + [114.12791443773256, 30.45997081696305], + [114.12804831801135, 30.460034140516466], + [114.12812695665549, 30.46007133529421], + [114.12817294740331, 30.46009199515834], + [114.12820544743163, 30.460104998116936], + [114.12822395736802, 30.460111308702093], + [114.12825057198464, 30.460118745368774], + [114.12828752276148, 30.460126209644613], + [114.12830984742014, 30.46012992768818], + [114.12832069121107, 30.46013139808863], + [114.12833517285914, 30.460132563528205], + [114.1283542702872, 30.46013382655505], + [114.12836998873179, 30.46013406342985], + [114.12837846217042, 30.460133860706673], + [114.12838442976265, 30.46013334116843], + [114.12839168437102, 30.46009575927462], + [114.12834998644269, 30.460093641808722], + [114.12828941210206, 30.460081463918776], + [114.12824893713191, 30.460074122081945], + [114.128216315597, 30.46006719885561], + [114.12780441489443, 30.459881181622045], + [114.12769332402713, 30.45982907167044], + [114.12765769404636, 30.45981272951338], + [114.12742849592884, 30.459716222464763], + [114.1272364659745, 30.459626098477848], + [114.12718111350233, 30.45960016361023], + [114.12709825116885, 30.459561251063718], + [114.12699977087117, 30.45951488076466], + [114.12691108838536, 30.45947279517776], + [114.12685896736635, 30.45944847273632], + [114.12683688366135, 30.459487762763597] + ] + ], + [ + [ + [114.13061898402532, 30.46122158770834], + [114.13050093394426, 30.4611659455433], + [114.13011032314509, 30.460982830355253], + [114.12948509785771, 30.460676729562238], + [114.12915080419201, 30.46052159776799], + [114.12910905022575, 30.460499808444705], + [114.12908108043383, 30.460480458154805], + [114.12906609730352, 30.46046825584629], + [114.12904116432249, 30.460440950193757], + [114.1290302547096, 30.46042482415912], + [114.12902839379315, 30.46042077514532], + [114.12885361666702, 30.460284356806863], + [114.12885456445991, 30.460289597977344], + [114.12885699706965, 30.460299440816254], + [114.12886055715941, 30.460311722664137], + [114.1288644893895, 30.460324983381874], + [114.12886998531407, 30.460339927505565], + [114.12887792622648, 30.46035876025066], + [114.12888132456803, 30.460365733766483], + [114.12888470366988, 30.46037169567034], + [114.12888717307217, 30.460376077774853], + [114.12889053410137, 30.460381787712556], + [114.12889339870573, 30.460386617190853], + [114.12889489628813, 30.460389215954844], + [114.12889742846474, 30.460394085724353], + [114.12893152468206, 30.460438055711222], + [114.1289487963196, 30.4604560740338], + [114.12895919300571, 30.46046399981868], + [114.12896128462303, 30.460460423906998], + [114.1289652252416, 30.46045728503223], + [114.1289692683392, 30.460455341689837], + [114.12897714389003, 30.460453956628445], + [114.12898001822931, 30.460454375339527], + [114.12898246998583, 30.460455143136826], + [114.12898620474245, 30.46045904078643], + [114.12900059349039, 30.460471954477043], + [114.12901977307614, 30.46048624243595], + [114.12903720594392, 30.460497358755884], + [114.12904224205833, 30.460499925867214], + [114.12906283040171, 30.460510409674747], + [114.1290906202127, 30.460524959988184], + [114.1294632289959, 30.460700150112054], + [114.12947592210453, 30.460706120207483], + [114.12947208127396, 30.46072074536206], + [114.12951290148226, 30.460741128494877], + [114.12971957164879, 30.460845111851004], + [114.12973316140021, 30.460850578955355], + [114.12973317110324, 30.46085054094337], + [114.12973318087785, 30.460850502945007], + [114.12973319072499, 30.460850464960792], + [114.12973320064394, 30.460850426990543], + [114.12973321063416, 30.460850389034093], + [114.12973322069686, 30.460850351092297], + [114.12973323083084, 30.460850313164645], + [114.12973324103676, 30.460850275251637], + [114.12973325131463, 30.460850237352837], + [114.12973326166396, 30.460850199468855], + [114.12973327208536, 30.4608501615997], + [114.12973328257823, 30.460850123745423], + [114.12973329314272, 30.460850085906138], + [114.12973330377868, 30.460850048081735], + [114.12973331448659, 30.460850010272384], + [114.12973332526539, 30.460849972478595], + [114.12973333611637, 30.46084993469997], + [114.12973334703862, 30.460849896937077], + [114.12973335803225, 30.460849859189462], + [114.12973336909747, 30.46084982145753], + [114.12973338023383, 30.46084978374155], + [114.12973339144177, 30.460849746041255], + [114.12973340272111, 30.46084970835714], + [114.12973341407171, 30.460849670689047], + [114.12973342549365, 30.460849633037082], + [114.12973343698654, 30.46084959540154], + [114.12973344855115, 30.460849557782296], + [114.12973346018664, 30.460849520179355], + [114.12973347189309, 30.46084948259334], + [114.12973348367112, 30.460849445023626], + [114.12973349552028, 30.4608494074709], + [114.12973350743991, 30.46084936993516], + [114.12973351943081, 30.4608493324164], + [114.12973353149319, 30.460849294914627], + [114.12973354362605, 30.46084925742984], + [114.12973355582992, 30.460849219962604], + [114.12973356810481, 30.460849182512636], + [114.12973358045053, 30.460849145080452], + [114.12973359286681, 30.46084910766571], + [114.12973360535426, 30.460849070268512], + [114.12973361791227, 30.46084903288939], + [114.12973363054132, 30.4608489955281], + [114.12973364324083, 30.4608489581847], + [114.1297336560108, 30.460848920859487], + [114.12973366885136, 30.460848883552394], + [114.12973368176294, 30.460848846263648], + [114.12973369474476, 30.460848808993372], + [114.12973370779727, 30.46084877174144], + [114.12973372092013, 30.460848734508314], + [114.12973373411369, 30.460848697294107], + [114.12973374737749, 30.460848660098307], + [114.12973376071149, 30.460848622921482], + [114.12973377411633, 30.460848585763742], + [114.12973378759119, 30.460848548625098], + [114.12973380113627, 30.460848511505596], + [114.12973381475169, 30.46084847440558], + [114.12973382843747, 30.460848437324884], + [114.1297338421928, 30.460848400263618], + [114.12973385601907, 30.460848363222006], + [114.12973386991467, 30.460848326200225], + [114.12973388388117, 30.460848289198104], + [114.12973389791702, 30.460848252215982], + [114.12973391202287, 30.460848215253858], + [114.12973392619884, 30.460848178311903], + [114.1297339404446, 30.460848141390173], + [114.12973395476035, 30.460848104488615], + [114.12973396914592, 30.460848067607564], + [114.12973398360111, 30.460848030747087], + [114.12973399812635, 30.460847993907294], + [114.12973401272126, 30.46084795708806], + [114.12973402738538, 30.460847920289403], + [114.12973404211985, 30.460847883512105], + [114.12973405692318, 30.460847846755435], + [114.1297340717962, 30.460847810019956], + [114.12973408673935, 30.460847773305897], + [114.12973410175135, 30.460847736613093], + [114.1297341168327, 30.460847699941535], + [114.12973413198372, 30.460847663291453], + [114.12973414720406, 30.460847626663142], + [114.12973416249373, 30.46084759005659], + [114.12973417785264, 30.46084755347163], + [114.12973419328075, 30.460847516908828], + [114.12973420877796, 30.46084748036779], + [114.12973422434438, 30.460847443848913], + [114.12973423997992, 30.460847407352198], + [114.12973425568457, 30.460847370877982], + [114.12973427145819, 30.46084733442604], + [114.12973428730056, 30.460847297996434], + [114.12973430321183, 30.46084726158967], + [114.12973431919244, 30.460847225205406], + [114.12973433524168, 30.460847188843985], + [114.12973435135945, 30.46084715250558], + [114.12973436754645, 30.460847116189903], + [114.12973438380163, 30.46084707989758], + [114.1297344001257, 30.460847043628274], + [114.12973441651842, 30.46084700738232], + [114.12973443297979, 30.460846971159608], + [114.12973444950947, 30.460846934960653], + [114.1297344661079, 30.46084689878505], + [114.12973448277431, 30.4608468626332], + [114.12973449950948, 30.460846826504934], + [114.12973451631328, 30.460846790400765], + [114.12973453318486, 30.46084675432063], + [114.12973455012506, 30.460846718264136], + [114.12973456713335, 30.460846682232365], + [114.12973458420971, 30.4608466462244], + [114.12973460135419, 30.460846610240875], + [114.12973461856694, 30.460846574281785], + [114.12973463584757, 30.460846538347475], + [114.12973465319605, 30.460846502437654], + [114.12973467061272, 30.460846466552436], + [114.12973468809724, 30.46084643069223], + [114.1297347056494, 30.460846394856965], + [114.12973472326964, 30.46084635904671], + [114.1297347409575, 30.46084632326146], + [114.12973475871311, 30.46084628750184], + [114.12973477653622, 30.460846251766938], + [114.12973479442687, 30.460846216057842], + [114.12973481238535, 30.460846180374087], + [114.12973483041137, 30.46084614471608], + [114.12973484850464, 30.460846109083704], + [114.12973486666525, 30.460846073477246], + [114.12973488489334, 30.460846037896466], + [114.12973490318895, 30.460846002341892], + [114.12973492155162, 30.4608459668134], + [114.12973493998136, 30.460845931310942], + [114.12973495847815, 30.460845895834915], + [114.12973497704223, 30.460845860385252], + [114.12973499567349, 30.46084582496201], + [114.1297350143717, 30.46084578956538], + [114.12973503313673, 30.460845754195446], + [114.12973505196894, 30.460845718852177], + [114.12973507086788, 30.460845683535787], + [114.12973508983355, 30.460845648246394], + [114.12973510886603, 30.460845612984055], + [114.12973512796502, 30.460845577748707], + [114.12973514713129, 30.460845542540756], + [114.1297351663636, 30.4608455073602], + [114.12973518566251, 30.460845472207044], + [114.12973520502815, 30.460845437081385], + [114.12973522447447, 30.460845401957243], + [114.12973524398728, 30.46084536686072], + [114.12973526356659, 30.460845331792104], + [114.1297352832125, 30.46084529675162], + [114.12973530292444, 30.46084526173927], + [114.12973532270277, 30.460845226754653], + [114.12973534254714, 30.46084519179834], + [114.1297353624581, 30.460845156870626], + [114.12973538243492, 30.460845121971378], + [114.12973540247751, 30.460845087100207], + [114.12973542258638, 30.460845052257852], + [114.12973544276153, 30.46084501744426], + [114.12973546300248, 30.46084498265943], + [114.12973548330854, 30.460844947903578], + [114.12973550368125, 30.46084491317649], + [114.12973552411918, 30.460844878478678], + [114.12973554462316, 30.460844843809905], + [114.12973556519239, 30.460844809170403], + [114.1297355858273, 30.46084477456029], + [114.1297356065278, 30.460844739979848], + [114.12973562729402, 30.460844705428787], + [114.12973564812548, 30.46084467090734], + [114.12973566902218, 30.460844636415676], + [114.12973568998424, 30.460844601953795], + [114.12973571101132, 30.460844567521697], + [114.12973573210387, 30.460844533120067], + [114.12973575326133, 30.460844498747935], + [114.12973577448392, 30.460844464406215], + [114.12973579577152, 30.46084443009507], + [114.1297358171238, 30.460844395814338], + [114.12973583854122, 30.46084436156379], + [114.1297358600239, 30.460844327343764], + [114.12973588157078, 30.460844293154544], + [114.12973590318224, 30.46084425899602], + [114.12973592485882, 30.460844224868357], + [114.12973594659975, 30.46084419077167], + [114.12973596840513, 30.460844156706084], + [114.1297359902752, 30.460844122671467], + [114.12973601220969, 30.460844088668065], + [114.12973603420832, 30.460844054696032], + [114.12973605627127, 30.46084402075561], + [114.12973607839879, 30.46084398684639], + [114.12973610059021, 30.460843952968883], + [114.12973612284573, 30.460843919123104], + [114.12973614516581, 30.460843885309092], + [114.12973616754921, 30.460843851526803], + [114.12973618999683, 30.460843817776457], + [114.12973621250835, 30.46084378405834], + [114.12973623508363, 30.460843750372167], + [114.12973625772257, 30.460843716718458], + [114.12973628042562, 30.460843683096805], + [114.12973630319165, 30.46084364950778], + [114.1297363260219, 30.460843615951042], + [114.12973634891513, 30.46084358242699], + [114.12973637187213, 30.46084354893551], + [114.12973639489235, 30.460843515477], + [114.1297364179762, 30.460843482051338], + [114.12973644112304, 30.46084344865843], + [114.12973646433308, 30.460843415298715], + [114.12973648760621, 30.46084338197197], + [114.12973651094244, 30.46084334867893], + [114.12973653434175, 30.460843315418522], + [114.12973655780404, 30.460843282192165], + [114.1297365813292, 30.460843248998835], + [114.12973660491721, 30.460843215839155], + [114.12973662856798, 30.46084318271313], + [114.12973665228115, 30.46084314962104], + [114.12973667605745, 30.460843116562604], + [114.12973669989564, 30.460843083538276], + [114.12973672379708, 30.46084305054811], + [114.12973674776023, 30.460843017591717], + [114.12973677178626, 30.46084298466965], + [114.12973679587434, 30.460842951782038], + [114.12973682002496, 30.460842918928645], + [114.1297368442373, 30.460842886109816], + [114.12973686851215, 30.46084285332538], + [114.1297368928491, 30.460842820575735], + [114.12973691724753, 30.460842787860877], + [114.12973694170815, 30.460842755180927], + [114.12973696623061, 30.460842722535595], + [114.12973699081468, 30.46084268992551], + [114.12973701546082, 30.460842657350554], + [114.12973704016846, 30.46084262481056], + [114.12973706493784, 30.46084259230604], + [114.1297370897685, 30.460842559836937], + [114.12973711466043, 30.460842527402853], + [114.12973713961374, 30.460842495004698], + [114.12973716462866, 30.46084246264207], + [114.12973718970476, 30.46084243031509], + [114.12973721484202, 30.46084239802409], + [114.1297372400401, 30.460842365768798], + [114.12973726529978, 30.46084233354943], + [114.12973729061983, 30.460842301366167], + [114.12973731600107, 30.460842269219334], + [114.12973734144309, 30.46084223710821], + [114.12973736694595, 30.46084220503398], + [114.12973739250975, 30.460842172995626], + [114.12973741813359, 30.460842140994103], + [114.12973744381833, 30.460842109028967], + [114.1297374695639, 30.460842077100555], + [114.12973749536906, 30.460842045209155], + [114.12973752123504, 30.460842013354196], + [114.12973754716137, 30.460841981536305], + [114.12973757314764, 30.46084194975559], + [114.12973759919439, 30.460841918012], + [114.12973762530102, 30.460841886305307], + [114.1297376514678, 30.460841854635913], + [114.1297376776943, 30.46084182300403], + [114.12973770393836, 30.460841791460155], + [114.12973773024235, 30.46084175995368], + [114.12973775660524, 30.460841728484848], + [114.12973778302835, 30.460841697053365], + [114.12973780951026, 30.460841665659746], + [114.1297378360514, 30.460841634303875], + [114.12973786265248, 30.460841602985642], + [114.12973788931195, 30.4608415717055], + [114.12973791603115, 30.46084154046334], + [114.12973794280876, 30.46084150925915], + [114.12973796964584, 30.460841478093283], + [114.1297379965416, 30.460841446965734], + [114.12973802349589, 30.46084141587616], + [114.1297380505093, 30.46084138482542], + [114.12973807758071, 30.460841353812825], + [114.12973810471146, 30.460841322838952], + [114.12973813190021, 30.460841291903677], + [114.1297381591477, 30.460841261007296], + [114.12973818645354, 30.46084123014957], + [114.12973821381759, 30.46084119933091], + [114.12973824123986, 30.460841168551244], + [114.12973826872056, 30.460841137810586], + [114.12973829625902, 30.46084110710921], + [114.12973832385524, 30.460841076447178], + [114.12973835150966, 30.46084104582449], + [114.12973837922208, 30.46084101524092], + [114.1297384069918, 30.460840984696862], + [114.12973843481961, 30.460840954192545], + [114.12973846270495, 30.460840923727798], + [114.12973849064808, 30.460840893302965], + [114.12973851864791, 30.460840862917873], + [114.12973854670584, 30.46084083257269], + [114.1297385748211, 30.460840802267484], + [114.12973860299297, 30.460840772002296], + [114.1297386312228, 30.460840741777428], + [114.12973865950896, 30.460840711592525], + [114.12973868785284, 30.460840681448328], + [114.12973871625313, 30.460840651344103], + [114.1297387447104, 30.460840621280703], + [114.12973877322459, 30.46084059125772], + [114.12973880179534, 30.460840561275567], + [114.12973883042291, 30.460840531333726], + [114.12973885910677, 30.46084050143316], + [114.12973888784748, 30.460840471573245], + [114.12973891664413, 30.460840441753987], + [114.1297389454974, 30.460840411976232], + [114.12973897440673, 30.46084038223947], + [114.12973900337235, 30.460840352543872], + [114.1297390323938, 30.460840322889613], + [114.12973906147154, 30.460840293276515], + [114.12973909060543, 30.460840263705265], + [114.12973911979493, 30.460840234175237], + [114.1297391490399, 30.460840204686885], + [114.12973917834107, 30.46084017524021], + [114.12973920769737, 30.46084014583527], + [114.12973923710942, 30.460840116472177], + [114.12973926657692, 30.460840087151162], + [114.12973929609981, 30.460840057871987], + [114.12973932567807, 30.46084002863506], + [114.12973935531136, 30.46083999944016], + [114.1297393849997, 30.46083997028755], + [114.12973941474364, 30.460839941177138], + [114.12973944454214, 30.46083991210931], + [114.12973947439559, 30.4608398830839], + [114.12973950430361, 30.460839854100907], + [114.12973953426678, 30.46083982516067], + [114.12973956428452, 30.46083979626319], + [114.12973959435695, 30.460839767408473], + [114.1297396244833, 30.4608397385964], + [114.12973965466446, 30.460839709827706], + [114.12973968490007, 30.460839681101834], + [114.12973971518971, 30.460839652418773], + [114.12973974553346, 30.46083962377926], + [114.12973977593147, 30.460839595182573], + [114.12973980638337, 30.460839566629723], + [114.12973983688916, 30.4608395381198], + [114.12973986744888, 30.460839509653717], + [114.12973989806237, 30.460839481231133], + [114.12973992872953, 30.46083945285216], + [114.12973995945013, 30.460839424516685], + [114.1297399902242, 30.46083939622522], + [114.12974002105238, 30.46083936797777], + [114.12974005193287, 30.460839339773983], + [114.12974008286739, 30.46083931161438], + [114.12974011385488, 30.460839283498785], + [114.12974014489504, 30.460839255427377], + [114.12974017598887, 30.460839227400314], + [114.12974020713499, 30.460839199417492], + [114.12974023833446, 30.460839171478966], + [114.12974026958672, 30.460839143585194], + [114.12974030089127, 30.460839115735656], + [114.12974033224835, 30.460839087930925], + [114.12974036365799, 30.460839060170827], + [114.12974039512015, 30.460839032455368], + [114.12974042663507, 30.46083900478506], + [114.12974045820164, 30.460838977159444], + [114.12974048982052, 30.460838949578918], + [114.12974052149148, 30.460838922043603], + [114.1297405532143, 30.46083889455304], + [114.12974058498955, 30.46083886710796], + [114.12974061681597, 30.46083883970826], + [114.12974064869458, 30.460838812353654], + [114.12974068062474, 30.460838785044647], + [114.12974071260616, 30.460838757781246], + [114.12974074463935, 30.46083873056328], + [114.1297407767237, 30.460838703391087], + [114.12974080885945, 30.46083867626456], + [114.12974084104661, 30.460838649183746], + [114.12974087328485, 30.460838622148994], + [114.12974090557401, 30.460838595160133], + [114.12974093791401, 30.460838568217273], + [114.12974097030511, 30.4608385413207], + [114.12974100274688, 30.46083851446996], + [114.12974103523953, 30.460838487665733], + [114.12974106778245, 30.460838460907624], + [114.1297411003762, 30.460838434195974], + [114.12974113301986, 30.460838407530893], + [114.12974116571438, 30.460838380912445], + [114.12974119845916, 30.46083835434051], + [114.12974123125375, 30.46083832781492], + [114.12974126409861, 30.460838301336356], + [114.12974129699319, 30.46083827490448], + [114.12974132993791, 30.46083824851963], + [114.12974136293302, 30.460838222181806], + [114.12974139597692, 30.46083819589084], + [114.12974142907109, 30.460838169647076], + [114.12974146221428, 30.460838143450506], + [114.1297414954073, 30.460838117301023], + [114.12974152864933, 30.46083809119908], + [114.12974156194095, 30.460838065144387], + [114.12974159528159, 30.460838039137183], + [114.12974162867171, 30.46083801317763], + [114.12974166211028, 30.460837987265446], + [114.12974169559821, 30.460837961400973], + [114.12974172913461, 30.46083793558444], + [114.12974176271968, 30.460837909815503], + [114.12974179635388, 30.46083788409445], + [114.12974183017747, 30.460837858313894], + [114.12974186405006, 30.460837832581735], + [114.12974189797144, 30.46083780689825], + [114.12974193194164, 30.460837781263045], + [114.12974196596016, 30.46083775567652], + [114.12974200002715, 30.4608377301389], + [114.1297420341427, 30.460837704649958], + [114.12974206830681, 30.46083767920969], + [114.12974210251882, 30.46083765381833], + [114.12974213677917, 30.460837628476046], + [114.12974217108763, 30.460837603182895], + [114.1297422054433, 30.46083757793871], + [114.12974223984742, 30.460837552743712], + [114.12974227429932, 30.46083752759807], + [114.12974230879898, 30.460837502501565], + [114.12974234334588, 30.460837477454763], + [114.12974237794029, 30.460837452457156], + [114.12974241258215, 30.460837427509244], + [114.12974244727121, 30.460837402610863], + [114.12974248200759, 30.46083737776185], + [114.12974251679118, 30.46083735296271], + [114.12974255162163, 30.460837328213604], + [114.12974258649906, 30.46083730351421], + [114.1297426214229, 30.460837278864684], + [114.12974265639365, 30.460837254265208], + [114.12974269141121, 30.460837229715832], + [114.12974272647509, 30.460837205216492], + [114.12974276158572, 30.460837180767374], + [114.12974279674265, 30.46083715636853], + [114.12974283194544, 30.460837132019893], + [114.1297428671945, 30.460837107722163], + [114.12974290248987, 30.460837083474356], + [114.12974293783107, 30.460837059277164], + [114.1297429732178, 30.460837035130705], + [114.1297430086505, 30.460837011034858], + [114.12974304412889, 30.460836986989623], + [114.12974307965314, 30.460836962995113], + [114.12974311522257, 30.460836939051614], + [114.12974315083704, 30.46083691515902], + [114.12974318649712, 30.460836891317378], + [114.12974322220249, 30.46083686752686], + [114.12974325795291, 30.460836843787302], + [114.12974329374815, 30.460836820099036], + [114.12974332958821, 30.46083679646207], + [114.12974336547343, 30.460836772876398], + [114.12974340140303, 30.460836749342025], + [114.12974343737723, 30.460836725859007], + [114.1297434733959, 30.460836702427624], + [114.12974350945919, 30.460836679047713], + [114.12974354556673, 30.460836655719433], + [114.12974358171827, 30.460836632443026], + [114.12974361791409, 30.460836609218145], + [114.12974365415394, 30.46083658604524], + [114.12974369043783, 30.460836562924147], + [114.12974372676494, 30.46083653985492], + [114.12974376313619, 30.460836516837844], + [114.12974379955125, 30.46083649387269], + [114.12974383600931, 30.46083647095963], + [114.12974387251109, 30.46083644809918], + [114.12974390905619, 30.460836425290427], + [114.1297439456444, 30.46083640253416], + [114.12974398227597, 30.460836379830443], + [114.12974401895045, 30.460836357179108], + [114.12974405566725, 30.460836334580154], + [114.12974409242761, 30.46083631203392], + [114.12974412923009, 30.460836289540236], + [114.12974416607557, 30.460836267099154], + [114.12974420296361, 30.460836244710915], + [114.12974423989408, 30.46083622237539], + [114.12974427686679, 30.46083620009298], + [114.12974431388136, 30.460836177863303], + [114.12974435093882, 30.460836155686337], + [114.12974438803758, 30.460836133563006], + [114.12974442517869, 30.460836111492398], + [114.1297444623613, 30.460836089474903], + [114.12974449958558, 30.46083606751082], + [114.1297445368516, 30.460836045599965], + [114.12974457415919, 30.460836023742743], + [114.12974461150816, 30.460836001938468], + [114.12974464889825, 30.46083598018794], + [114.12974468632983, 30.46083595849071], + [114.12974472380219, 30.460835936847275], + [114.12974476131606, 30.460835915257412], + [114.12974479887022, 30.460835893721246], + [114.12974483646546, 30.46083587223899], + [114.1297448741012, 30.460835850810366], + [114.12974491177792, 30.460835829435553], + [114.12974494949492, 30.460835808114876], + [114.12974498725211, 30.460835786848175], + [114.12974502504957, 30.46083576563545], + [114.12974506288778, 30.460835744476864], + [114.12974510076567, 30.460835723372433], + [114.1297451386832, 30.460835702322193], + [114.12974517664112, 30.46083568132656], + [114.12974521463842, 30.460835660384785], + [114.12974525267579, 30.460835639497663], + [114.12974529075241, 30.46083561866475], + [114.12974532886874, 30.46083559788666], + [114.12974536702389, 30.46083557716289], + [114.12974540521888, 30.46083555649383], + [114.12974544345278, 30.46083553587977], + [114.12974548172573, 30.46083551531991], + [114.12974552003749, 30.460835494815225], + [114.12974555838827, 30.46083547436525], + [114.12974559677788, 30.460835453970162], + [114.12974563520561, 30.46083543363007], + [114.12974567367226, 30.460835413344867], + [114.1297457121775, 30.460835393115058], + [114.12974575072053, 30.460835372939968], + [114.1297457893018, 30.460835352820215], + [114.12974582792164, 30.46083533275558], + [114.12974586657919, 30.46083531274645], + [114.12974590527472, 30.460835292792723], + [114.12974594400761, 30.46083527289411], + [114.12974598277896, 30.460835253051005], + [114.1297460215873, 30.46083523326381], + [114.12974606043319, 30.46083521353156], + [114.12974609931645, 30.46083519385533], + [114.12974613823725, 30.46083517423473], + [114.12974617719517, 30.4608351546697], + [114.1297462161895, 30.460835135160465], + [114.12974625522142, 30.460835115706914], + [114.12974629429009, 30.4608350963095], + [114.12974633339519, 30.460835076968], + [114.12974637253728, 30.46083505768229], + [114.12974981756975, 30.460834461321785], + [114.12975222151356, 30.460834512012003], + [114.12975434025654, 30.460834914699298], + [114.12976090056145, 30.46083708761061], + [114.12982096010788, 30.46086824150853], + [114.1298175011319, 30.46086928551613], + [114.12981050537311, 30.460871066987227], + [114.12979936369597, 30.460874063653247], + [114.12979263196452, 30.460875893396935], + [114.12979259448348, 30.460875904666846], + [114.12979255697874, 30.460875915876738], + [114.12979251945042, 30.460875927026613], + [114.12979248189846, 30.46087593811624], + [114.12979244432323, 30.460875949145738], + [114.12979240672453, 30.460875960115096], + [114.12979236910267, 30.460875971023988], + [114.12979233145778, 30.460875981872633], + [114.12979229378993, 30.460875992661375], + [114.12979225609931, 30.460876003389412], + [114.12979221838631, 30.46087601405692], + [114.12979218065027, 30.46087602466435], + [114.129792142892, 30.460876035211367], + [114.12979210511146, 30.46087604569751], + [114.12979206730893, 30.460876056123237], + [114.12979202948424, 30.4608760664886], + [114.12979199163789, 30.460876076793205], + [114.12979195376964, 30.46087608703705], + [114.12979191587971, 30.460876097220535], + [114.12979187796786, 30.460876107343093], + [114.1297918400349, 30.460876117405228], + [114.12979180208131, 30.460876127406152], + [114.12979176410579, 30.4608761373466], + [114.12979172610963, 30.460876147226003], + [114.1297916880928, 30.460876157044474], + [114.12979165005488, 30.460876166802358], + [114.12979161199628, 30.460876176498967], + [114.12979157391773, 30.460876186134705], + [114.12979153581874, 30.460876195709453], + [114.12979149769932, 30.460876205223098], + [114.12979145955968, 30.4608762146757], + [114.12979142140031, 30.460876224067487], + [114.12979138322119, 30.460876233397716], + [114.12979134502208, 30.460876242666952], + [114.12979130680324, 30.46087625187509], + [114.12979126856567, 30.460876261021607], + [114.12979123030844, 30.46087627010726], + [114.12979119203195, 30.460876279131575], + [114.12979115373625, 30.46087628809456], + [114.1297911154223, 30.460876296995988], + [114.12979107708894, 30.46087630583626], + [114.12979103873741, 30.460876314614968], + [114.12979100036694, 30.46087632333212], + [114.12979096197827, 30.460876331988217], + [114.12979092357159, 30.46087634058237], + [114.12979088514618, 30.460876349115182], + [114.1297908467035, 30.46087635758633], + [114.12979080824222, 30.460876365996082], + [114.12979076976369, 30.460876374343997], + [114.12979073126733, 30.46087638263046], + [114.12979069275352, 30.460876390855084], + [114.12979065422287, 30.460876399018087], + [114.12979061567431, 30.46087640711925], + [114.12979057710872, 30.460876415158793], + [114.12979053852645, 30.460876423136554], + [114.12979049992747, 30.46087643105247], + [114.12979046131183, 30.460876438906364], + [114.12979042267939, 30.460876446698474], + [114.12979038403058, 30.46087645442891], + [114.12979034536556, 30.460876462097218], + [114.12979030668455, 30.460876469703678], + [114.12979026798719, 30.46087647724801], + [114.12979022927402, 30.4608764847305], + [114.12979019054524, 30.46087649215097], + [114.12979015180063, 30.460876499509308], + [114.12979011304073, 30.460876506805462], + [114.12979007426503, 30.4608765140396], + [114.12979003547437, 30.46087652121161], + [114.1297899966687, 30.46087652832154], + [114.1297899578484, 30.4608765353694], + [114.12978991901268, 30.460876542354843], + [114.12978988016253, 30.46087654927827], + [114.12978984129761, 30.460876556139226], + [114.12978980241817, 30.460876562937994], + [114.12978976352488, 30.46087656967457], + [114.1297897246173, 30.46087657634862], + [114.12978968569543, 30.46087658296054], + [114.12978964675968, 30.460876589510043], + [114.12978960781008, 30.46087659599724], + [114.12978956884733, 30.460876602422026], + [114.1297895298704, 30.460876608784165], + [114.12978949088074, 30.460876615083944], + [114.12978945187746, 30.460876621321365], + [114.12978941286111, 30.460876627496027], + [114.12978937383161, 30.460876633608557], + [114.12978933478963, 30.460876639658554], + [114.12978929974295, 30.460876645027668], + [114.12978926468301, 30.460876650333397], + [114.12978922961057, 30.46087665557597], + [114.12978919452598, 30.460876660755215], + [114.12978915942948, 30.460876665871133], + [114.12978912432025, 30.460876670923497], + [114.12978908919935, 30.460876675912814], + [114.12978905406663, 30.460876680838524], + [114.12978901892234, 30.46087668570079], + [114.12978898376646, 30.46087669049967], + [114.12978894859923, 30.460876695234937], + [114.12978891342065, 30.460876699906763], + [114.12978887823151, 30.4608767045152], + [114.12978884303136, 30.460876709059974], + [114.12978880782032, 30.460876713541246], + [114.12978877259859, 30.460876717958843], + [114.12978873736677, 30.46087672231289], + [114.12978870212451, 30.460876726603264], + [114.12978866687213, 30.460876730830133], + [114.12978863160942, 30.460876734993338], + [114.12978859633718, 30.460876739092754], + [114.1297885610554, 30.460876743128615], + [114.1297885257642, 30.46087674710069], + [114.12978849046311, 30.46087675100932], + [114.12978845515353, 30.460876754853825], + [114.1297884198344, 30.4608767586346], + [114.12978838450641, 30.460876762351702], + [114.12978834916967, 30.46087676600508], + [114.12978831382443, 30.460876769594435], + [114.12978827847101, 30.46087677312041], + [114.12978824310873, 30.460876776582257], + [114.12978820773874, 30.460876779980143], + [114.12978817236103, 30.46087678331424], + [114.12978813697501, 30.460876786584727], + [114.12978810158127, 30.460876789790916], + [114.12978806618005, 30.46087679293337], + [114.12978803077156, 30.46087679601181], + [114.12978799535614, 30.460876799026465], + [114.129787959933, 30.46087680197716], + [114.12978792450339, 30.4608768048639], + [114.1297878890673, 30.460876807686564], + [114.12978785362394, 30.460876810445217], + [114.12978781817445, 30.460876813140075], + [114.12978778271884, 30.46087681577081], + [114.12978774725674, 30.460876818337578], + [114.12978771178896, 30.46087682084039], + [114.12978767631517, 30.46087682327874], + [114.1297876408357, 30.460876825653457], + [114.1297876053509, 30.46087682796388], + [114.12978756986041, 30.460876830210342], + [114.12978753436492, 30.460876832392962], + [114.12978749886432, 30.460876834510994], + [114.12978746335861, 30.460876836565344], + [114.12978742784836, 30.460876838555286], + [114.12978739233343, 30.460876840481205], + [114.12978735681386, 30.46087684234317], + [114.12978732129055, 30.460876844140657], + [114.12978728576235, 30.460876845874353], + [114.1297872502305, 30.460876847543748], + [114.12978721469503, 30.460876849148953], + [114.12978717915546, 30.460876850690084], + [114.12978714361304, 30.460876852167026], + [114.12978710806676, 30.460876853579666], + [114.12978707251695, 30.46087685492817], + [114.12978703696456, 30.460876856212714], + [114.12978700140896, 30.460876857432783], + [114.12978696585073, 30.46087685858849], + [114.1297869302897, 30.460876859680464], + [114.12978689472659, 30.460876860707906], + [114.12978685916099, 30.46087686167121], + [114.12978682359321, 30.46087686257039], + [114.12978678802351, 30.460876863405257], + [114.12978675245176, 30.460876864175933], + [114.12978671687853, 30.460876864882483], + [114.12978668130383, 30.46087686552455], + [114.12978664572775, 30.460876866102655], + [114.12978661015008, 30.46087686661628], + [114.12978657457182, 30.460876867065778], + [114.12978653899222, 30.460876867451077], + [114.12978650341203, 30.46087686777208], + [114.12978646783127, 30.460876868028766], + [114.12978643224972, 30.460876868221323], + [114.12978639666827, 30.460876868349516], + [114.12978636108646, 30.460876868413628], + [114.12978632550501, 30.460876868413372], + [114.12978628992353, 30.460876868348816], + [114.12978625434218, 30.46087686822024], + [114.1297862187615, 30.46087686802724], + [114.12978618318125, 30.460876867770214], + [114.12978614760226, 30.46087686744854], + [114.1297861120236, 30.4608768670629], + [114.12978607644618, 30.460876866613123], + [114.12978604086999, 30.460876866098697], + [114.1297860052954, 30.46087686552025], + [114.1297859697224, 30.460876864877665], + [114.12978593415062, 30.460876864170597], + [114.1297858985809, 30.460876863399566], + [114.12978586301378, 30.460876862564227], + [114.12978582744789, 30.460876861664687], + [114.12978579188496, 30.460876860700843], + [114.12978575632417, 30.46087685967292], + [114.12978572076631, 30.46087685858057], + [114.12978568521085, 30.460876857424477], + [114.12978564965854, 30.460876856203736], + [114.12978561410942, 30.460876854918965], + [114.12978557856312, 30.460876853569776], + [114.12978097293913, 30.46087611735825], + [114.1300935983725, 30.46104443000447], + [114.13009199872872, 30.46104334989119], + [114.130090791552, 30.461042350214225], + [114.13008921072786, 30.461040981833918], + [114.13008820653799, 30.461039436169777], + [114.13008735704764, 30.461037929848207], + [114.13008647082899, 30.461035547741947], + [114.13008592970188, 30.461032379083246], + [114.13008728405228, 30.461027226146687], + [114.13008886666402, 30.46102387032586], + [114.13009347073576, 30.461014625663626], + [114.13011271804483, 30.46102349111015], + [114.13012760768065, 30.461030570401856], + [114.13015916719633, 30.461045895195372], + [114.13018302670666, 30.46105712354523], + [114.13024107267701, 30.46108509338401], + [114.13024120877141, 30.46108524890782], + [114.1302412605094, 30.461085359787887], + [114.13024127308707, 30.461085493710335], + [114.13024125252475, 30.461085677628468], + [114.13022792770946, 30.461107473223144], + [114.13031431801001, 30.46114830931335], + [114.13034715161534, 30.46116368102704], + [114.13035959409316, 30.46114331674733], + [114.13036020068445, 30.4611428650941], + [114.13036047855408, 30.461142763543243], + [114.1303608793548, 30.461142688448973], + [114.13036167174162, 30.461142769382395], + [114.13036864345355, 30.461146033323974], + [114.13045399634161, 30.4611861869654], + [114.13056643045437, 30.46123894413378], + [114.13059972652555, 30.461254650283248], + [114.13061898402532, 30.46122158770834] + ] + ], + [ + [ + [114.13045166013256, 30.461508860656757], + [114.13045113236419, 30.46150861167757], + [114.13038902579527, 30.46147940019146], + [114.13027236933345, 30.461424469991137], + [114.1302449042618, 30.461411600882368], + [114.13018342073529, 30.461382257284672], + [114.13011697073304, 30.461350836168783], + [114.13004360269801, 30.46131650873754], + [114.13000090329518, 30.46129648487721], + [114.12996200134592, 30.461278417298047], + [114.12993616252682, 30.46126615249132], + [114.12990291072697, 30.46125045788717], + [114.12988256235427, 30.46124067768216], + [114.12987179255991, 30.461235567370608], + [114.12986722261299, 30.461233910100944], + [114.12986195939581, 30.461232654867565], + [114.12985731372267, 30.461231510175065], + [114.12983345211502, 30.461224729028125], + [114.1298274737889, 30.46122221362121], + [114.12972906729911, 30.46118220384203], + [114.12968234577546, 30.461163346656495], + [114.12965518684442, 30.461152377241042], + [114.12962904070915, 30.46114174420687], + [114.12959327721632, 30.461127199295024], + [114.12956497393517, 30.461115876272295], + [114.12954623041804, 30.461108177464922], + [114.12951793337623, 30.46109663971318], + [114.12950043861774, 30.4610896689159], + [114.12948335261126, 30.46108282129353], + [114.12946834944965, 30.461076664409667], + [114.12946210779079, 30.461074079083684], + [114.12945622023722, 30.46107146514424], + [114.12943923937732, 30.461063403164466], + [114.12941961991667, 30.461054257135935], + [114.12934096022525, 30.46101713680265], + [114.1292531611024, 30.460975781088365], + [114.12915358364316, 30.460928821850516], + [114.12914446705692, 30.4609245336199], + [114.12911195461106, 30.460909240500985], + [114.12906661686613, 30.460887931138817], + [114.12900696519473, 30.460859674520563], + [114.12899472493436, 30.46085400383098], + [114.12897711937539, 30.460845605200472], + [114.12895897965275, 30.46083701325433], + [114.1289346889824, 30.460825927957725], + [114.12891435148165, 30.460816098825497], + [114.12888630584379, 30.460803373586902], + [114.12887836259212, 30.46079988818031], + [114.12886817096455, 30.4607959182663], + [114.12883288424162, 30.46078344055786], + [114.12880683904324, 30.460775348266598], + [114.12878095499761, 30.46076926421222], + [114.12876268288106, 30.460766109565174], + [114.12874165770062, 30.46076308767697], + [114.12872509369107, 30.460761687541552], + [114.12870763931349, 30.460760650459722], + [114.12868966534714, 30.460759518850644], + [114.12868556332796, 30.460758775820878], + [114.12868139785537, 30.460757852444367], + [114.12867930532934, 30.460757079594252], + [114.1286743946421, 30.460755258055634], + [114.12867217333921, 30.460753659737904], + [114.12866915506972, 30.460748762815165], + [114.12866651324336, 30.460740080827907], + [114.1286615349295, 30.460740621283133], + [114.12865159543583, 30.460742072415314], + [114.1286445447878, 30.460743259994725], + [114.12863472942703, 30.460745282694166], + [114.12862751842948, 30.460746573884617], + [114.12862310380396, 30.460747364359552], + [114.1286129285285, 30.46074959393931], + [114.1286036936273, 30.460751644932678], + [114.12859305335921, 30.460754496127752], + [114.12858272295519, 30.460757439015904], + [114.12858684681783, 30.460756974895318], + [114.1285896592975, 30.460757211799937], + [114.12859336695772, 30.46075893258156], + [114.12859483318482, 30.46076096842862], + [114.12859511158445, 30.460762728564493], + [114.12859446761085, 30.46076500622275], + [114.12859354876066, 30.4607668126579], + [114.12859116968379, 30.460769196850375], + [114.12858763843047, 30.46077126133305], + [114.12857545492906, 30.460774983201105], + [114.12856651479777, 30.46077799213247], + [114.12854889649874, 30.46078481146621], + [114.12853447052267, 30.460790639959814], + [114.12852557335621, 30.460794471642462], + [114.12851594301046, 30.46079905130281], + [114.12849904731934, 30.46080789334833], + [114.12848921767336, 30.46081381903344], + [114.12847995375431, 30.460820365739195], + [114.12845718211763, 30.46083835215128], + [114.12845199062264, 30.460842482142652], + [114.12843863190128, 30.460856657982863], + [114.12842208172918, 30.460877284195742], + [114.12840342322, 30.460900828623902], + [114.12838529881427, 30.46092675780746], + [114.1280351192241, 30.4614916579163], + [114.12772665457429, 30.461977474726904], + [114.12773666316124, 30.46198267919212], + [114.12838019747124, 30.46096860236104], + [114.12839862491309, 30.460940485061798], + [114.12841620397128, 30.460914721940537], + [114.12843438168466, 30.460891047924747], + [114.12845081874062, 30.46087048759082], + [114.12846385693705, 30.460856156383116], + [114.12846784607905, 30.460852302594482], + [114.12849139753372, 30.46083382588418], + [114.12850049625027, 30.460827287615476], + [114.12851003248754, 30.460821443548152], + [114.1285190183587, 30.460816503231946], + [114.128527396434, 30.460812748170223], + [114.1285377280812, 30.460809171635077], + [114.12854770191542, 30.460805844107], + [114.12856078586411, 30.46080192595596], + [114.12857238348518, 30.460798490726418], + [114.12858337520525, 30.460794904254787], + [114.12859945021059, 30.460790470513647], + [114.12861472608745, 30.46078759112877], + [114.12863298746211, 30.460785640678257], + [114.12864145553547, 30.460784861359542], + [114.12867263363336, 30.46078576380548], + [114.12869005218914, 30.4607870865643], + [114.12871293776365, 30.460789479520425], + [114.12873005711924, 30.46079163128663], + [114.1287544930627, 30.46079647154317], + [114.12877633262853, 30.46080491582557], + [114.12878327578939, 30.46080767851991], + [114.1289198979808, 30.460870976248305], + [114.12906165250779, 30.460938179307167], + [114.12938691893761, 30.46109103651788], + [114.12958281068029, 30.461181831471286], + [114.12970186691408, 30.461237068649936], + [114.12977138918183, 30.461269340469094], + [114.13000395955547, 30.461377765191333], + [114.1301615044232, 30.46145164530947], + [114.13037598615774, 30.46155321754522], + [114.13041321793216, 30.461570825330455], + [114.13041506318048, 30.461571692773276], + [114.13045166013256, 30.461508860656757] + ] + ] + ] + } + }, + { + "type": "Feature", + "id": "12", + "properties": { "type": 4 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [114.12765978321049, 30.45988651522178], + [114.12764383468487, 30.459882807400575], + [114.12765100334624, 30.45988657107926], + [114.12764139830975, 30.459889506914333], + [114.12761387234207, 30.459876742501372], + [114.1276124028338, 30.459879128935576], + [114.12764749557435, 30.459895403858667], + [114.12766126254193, 30.45989195806849], + [114.12766940339483, 30.459896232150424], + [114.12765978321049, 30.45988651522178] + ] + ], + [ + [ + [114.12763427339303, 30.45991154790557], + [114.12763291194328, 30.459913757116947], + [114.12759912524707, 30.45989808805539], + [114.12759765571056, 30.45990047538993], + [114.1276314403325, 30.459916144409654], + [114.1276300810117, 30.45991835186179], + [114.12765300104127, 30.45992460771415], + [114.12763427339303, 30.45991154790557] + ] + ], + [ + [ + [114.12761755190054, 30.459938684458045], + [114.12761611977218, 30.459940858795086], + [114.12758296657559, 30.459925484570334], + [114.12758149703767, 30.45992787190393], + [114.12761457127884, 30.4599432101801], + [114.12761311527514, 30.459945419192206], + [114.12763583774534, 30.459951538273963], + [114.12761755190054, 30.459938684458045] + ] + ], + [ + [ + [114.12760400423902, 30.45996289423156], + [114.12756944059383, 30.45994678740718], + [114.12756796389054, 30.459949170979908], + [114.12759459934937, 30.45996158282404], + [114.12759725797072, 30.459971110870285], + [114.12758979292224, 30.459967603401395], + [114.12760189361006, 30.459977350333364], + [114.12761687330165, 30.459980327738513], + [114.12760864362862, 30.459976460395396], + [114.12760400423902, 30.45996289423156] + ] + ], + [ + [ + [114.12799440218569, 30.460048124375334], + [114.12800243788405, 30.460051976656732], + [114.12799057087545, 30.460042416186628], + [114.12797537561978, 30.460039000558577], + [114.12798337851463, 30.460042837719275], + [114.1279726697087, 30.460045822498866], + [114.12794553803901, 30.460032980209355], + [114.12794404498791, 30.460035355341343], + [114.12797953758076, 30.46005215693364], + [114.12799440218569, 30.460048124375334] + ] + ], + [ + [ + [114.12796557032368, 30.460067280495235], + [114.12796418208411, 30.46006958928911], + [114.1279323869172, 30.460054371474623], + [114.12793088153516, 30.460056741834425], + [114.12796273821913, 30.46007198801494], + [114.12796131066906, 30.460074361827907], + [114.12798568849392, 30.460081426697982], + [114.12796557032368, 30.460067280495235] + ] + ], + [ + [ + [114.12794871296117, 30.46009438283973], + [114.12794741459626, 30.46009663850804], + [114.12791534084874, 30.460081565421028], + [114.12791385391927, 30.460083944289185], + [114.1279460209531, 30.460099060847472], + [114.1279447280439, 30.460101307610305], + [114.12796929043392, 30.460108457589786], + [114.12794871296117, 30.46009438283973] + ] + ], + [ + [ + [114.12793709442113, 30.460120036352464], + [114.12790195505336, 30.460103411760084], + [114.12790046199727, 30.46010578688927], + [114.12792761745402, 30.460118635147563], + [114.12793127303058, 30.460128785938068], + [114.12792429530099, 30.460125601002048], + [114.12793536229401, 30.460134449981247], + [114.12795051437212, 30.460137567038498], + [114.12794231951005, 30.460133826836465], + [114.12793709442113, 30.460120036352464] + ] + ], + [ + [ + [114.12835891047108, 30.460191093536803], + [114.1283690188825, 30.460192677511287], + [114.12835412543915, 30.460187121572954], + [114.12834021308942, 30.460188162473116], + [114.12834685654148, 30.460189203212558], + [114.1283384784417, 30.460195350486952], + [114.12830723173941, 30.46019030660117], + [114.12830665829725, 30.460192965509346], + [114.12834646485058, 30.460199392851074], + [114.12835891047108, 30.460191093536803] + ] + ], + [ + [ + [114.12870975662688, 30.460212982914015], + [114.12870665669686, 30.460211400130223], + [114.1286890621461, 30.46023761963853], + [114.12867780266502, 30.4602396197635], + [114.12868292426433, 30.460232229061138], + [114.12867230300633, 30.460241891336665], + [114.12866889768308, 30.460253871059727], + [114.12867427962401, 30.46024567940838], + [114.12868956421664, 30.460242122565393], + [114.12870975662688, 30.460212982914015] + ] + ], + [ + [ + [114.12873258742432, 30.460223963610332], + [114.12872855443425, 30.46022230969746], + [114.12871141229377, 30.46025121432643], + [114.12870839683895, 30.460249798406643], + [114.12870077475303, 30.460270240679073], + [114.1287167478244, 30.46025309602353], + [114.12871452320131, 30.46025224437172], + [114.12873258742432, 30.460223963610332] + ] + ], + [ + [ + [114.12837647935412, 30.46026156409562], + [114.1283752151951, 30.46026329099487], + [114.12834192553392, 30.460247423299087], + [114.12834042431034, 30.460249794675402], + [114.12837354178777, 30.46026558115513], + [114.12837196579537, 30.4602677380636], + [114.1283946904409, 30.46027411807514], + [114.12837647935412, 30.46026156409562] + ] + ], + [ + [ + [114.12876241641918, 30.460237315744084], + [114.12875882836029, 30.460235903068593], + [114.1287414802644, 30.46026382191426], + [114.12873876272113, 30.460262771317108], + [114.12873078746144, 30.460283317092614], + [114.12874715699776, 30.460266187107642], + [114.12874496976379, 30.46026515854007], + [114.12876241641918, 30.460237315744084] + ] + ], + [ + [ + [114.12880287063375, 30.460269826219278], + [114.12880057854322, 30.46027534189185], + [114.12879499567953, 30.460267155271225], + [114.12880594534293, 30.460241237531278], + [114.12880302093224, 30.46024030792805], + [114.12878906971548, 30.460273325397395], + [114.12879641747973, 30.4602853492421], + [114.12879292942804, 30.460293741602786], + [114.1288017286223, 30.46028190815778], + [114.12880287063375, 30.460269826219278] + ] + ], + [ + [ + [114.12834895482324, 30.46028357000437], + [114.12834761758594, 30.460285734666073], + [114.12831461477353, 30.46027032493471], + [114.12831313610232, 30.460272706709155], + [114.1283461440566, 30.46028811835546], + [114.12834469014169, 30.46029047088728], + [114.12836871610331, 30.460297121998284], + [114.12834895482324, 30.46028357000437] + ] + ], + [ + [ + [114.12833628018815, 30.46030807470138], + [114.12830123480336, 30.4602914490679], + [114.12829973767604, 30.46029382233258], + [114.12832709672114, 30.460306803443775], + [114.12833006918703, 30.4603164363764], + [114.12832213338147, 30.46031268824945], + [114.12833389186757, 30.46032250966981], + [114.12835043401269, 30.460326052779433], + [114.12834147430807, 30.460321822050688], + [114.12833628018815, 30.46030807470138] + ] + ], + [ + [ + [114.12874925720942, 30.46058753420879], + [114.12875633276082, 30.460590743526613], + [114.12874529087212, 30.460582287850315], + [114.12873214771808, 30.460579773021713], + [114.12873971340865, 30.460583205588684], + [114.12874193185147, 30.46059058991506], + [114.1287647929814, 30.460601281663973], + [114.12876627265992, 30.46059889988208], + [114.12874884830025, 30.460590749575427], + [114.12874925720942, 30.46058753420879] + ] + ], + [ + [ + [114.12871821605825, 30.4606105640714], + [114.12871959460246, 30.460608229769374], + [114.12869575525808, 30.460601600988607], + [114.12871534205641, 30.460615428660677], + [114.12871679376168, 30.460612973222794], + [114.12874984384793, 30.460628424004636], + [114.12875132459295, 30.46062604134405], + [114.12871821605825, 30.4606105640714] + ] + ], + [ + [ + [114.1282713090051, 30.460612952415406], + [114.12826086657832, 30.460601581970952], + [114.12826129431166, 30.46059513218137], + [114.1282566641438, 30.46060741971045], + [114.12825991855485, 30.46061977475574], + [114.12826022942248, 30.460613803298962], + [114.12826630595534, 30.46061596778145], + [114.12826866071323, 30.46061965292968], + [114.1282682144802, 30.460646263638203], + [114.12827089724746, 30.460646906719386], + [114.1282713090051, 30.460612952415406] + ] + ], + [ + [ + [114.12870076159, 30.460637924067537], + [114.12870236859342, 30.46063552332987], + [114.12867844351734, 30.4606289829275], + [114.12869772909524, 30.46064245244094], + [114.1286991937367, 30.460640265193003], + [114.12873219886704, 30.460655761957565], + [114.1287336826763, 30.460653381166896], + [114.12870076159, 30.460637924067537] + ] + ], + [ + [ + [114.12831797511244, 30.46063243172318], + [114.12832119644226, 30.460632779482374], + [114.12832893290955, 30.46061305238447], + [114.12831239255001, 30.460630040454017], + [114.12831582544989, 30.460630970013842], + [114.1282984920438, 30.460658369257313], + [114.12830113451777, 30.46065967901653], + [114.12831797511244, 30.46063243172318] + ] + ], + [ + [ + [114.12834864625601, 30.460645501797423], + [114.12835063030111, 30.460646373655234], + [114.12835899930107, 30.460625234647328], + [114.12834321867702, 30.46064257256931], + [114.1283455814283, 30.46064374741235], + [114.12832807040849, 30.460672459045476], + [114.12833113221508, 30.460673961690624], + [114.12834864625601, 30.460645501797423] + ] + ], + [ + [ + [114.12837912681817, 30.460659686372537], + [114.12837676038178, 30.460664922481147], + [114.12838729349056, 30.460655438886963], + [114.12839074447052, 30.460642612219186], + [114.12838590538631, 30.46064925740684], + [114.12837091827653, 30.460652936711554], + [114.1283527536239, 30.460683177988436], + [114.12835493179958, 30.460684339907434], + [114.12837001606486, 30.46066033266817], + [114.12837912681817, 30.460659686372537] + ] + ], + [ + [ + [114.12872896548119, 30.460704124997644], + [114.12876084568157, 30.46070725039113], + [114.12876119762366, 30.46070456153281], + [114.12871989417552, 30.460700515380775], + [114.12870691650812, 30.46070992221255], + [114.12869839289986, 30.46070925589473], + [114.12871279406163, 30.46071423201666], + [114.12872941048305, 30.460711679332316], + [114.12872029803995, 30.460710967204506], + [114.12872896548119, 30.460704124997644] + ] + ], + [ + [ + [114.12913444135532, 30.460768798505974], + [114.12914149179586, 30.460772188498577], + [114.12913097470313, 30.460763556443407], + [114.12911396829095, 30.46075895585527], + [114.12912571153127, 30.460764601784295], + [114.12912590284351, 30.460775678762786], + [114.12916231806324, 30.460793185492257], + [114.12916383051939, 30.46079081881016], + [114.12913598426056, 30.46077743143822], + [114.12913444135532, 30.460768798505974] + ] + ], + [ + [ + [114.12911697977484, 30.460797693086274], + [114.12911843007527, 30.460795462210044], + [114.12909439426096, 30.46078881163049], + [114.12911400178062, 30.460802278592816], + [114.1291154488873, 30.46080005035548], + [114.1291485118678, 30.4608152951975], + [114.12914997511503, 30.460812905827964], + [114.12911697977484, 30.460797693086274] + ] + ], + [ + [ + [114.12909986376616, 30.460824861167204], + [114.12910122728519, 30.460822684388322], + [114.12907774100891, 30.460816072456357], + [114.12909695474436, 30.460829504056413], + [114.12909837487132, 30.460827239167585], + [114.12913135974942, 30.460842633030847], + [114.12913283740411, 30.460840248476778], + [114.12909986376616, 30.460824861167204] + ] + ], + [ + [ + [114.1290832891964, 30.460844033872448], + [114.12906817778146, 30.46084821163846], + [114.1290595466183, 30.460844157698915], + [114.12907309910834, 30.460854163685678], + [114.1290881839199, 30.460857607867585], + [114.12907976595324, 30.46085365405626], + [114.12909012325989, 30.460850345634984], + [114.12911828965193, 30.460863660437624], + [114.12911978267327, 30.460861283425245], + [114.1290832891964, 30.460844033872448] + ] + ], + [ + [ + [114.1295170729681, 30.46094868857821], + [114.12952471659668, 30.460952292136778], + [114.12951319270734, 30.46094273963868], + [114.12949712644783, 30.460939288545823], + [114.12950565870332, 30.460943309573725], + [114.12950977301703, 30.460956744341534], + [114.1295459846389, 30.460973346840195], + [114.12954744170554, 30.460970954609365], + [114.12951970910399, 30.46095823940797], + [114.1295170729681, 30.46094868857821] + ] + ], + [ + [ + [114.12950069463872, 30.460978320737603], + [114.1295022112425, 30.46097574033008], + [114.12947838746287, 30.460969275015792], + [114.12949799054365, 30.46098292199377], + [114.12949927753735, 30.460980731850242], + [114.12953219914202, 30.460996356573943], + [114.12953369721849, 30.4609939832506], + [114.12950069463872, 30.460978320737603] + ] + ], + [ + [ + [114.12948462553341, 30.461005586056444], + [114.12948598100314, 30.461003470426377], + [114.12946220363271, 30.460996518955422], + [114.12948148441804, 30.46101049291415], + [114.12948311211464, 30.461007950934665], + [114.12951612229881, 30.46102356762681], + [114.12951761523743, 30.461021192391577], + [114.12948462553341, 30.461005586056444] + ] + ], + [ + [ + [114.1294685281634, 30.461024581467186], + [114.12945465384469, 30.461029561357567], + [114.12944516793, 30.461025143123695], + [114.12945798782363, 30.461035146972872], + [114.12947318283328, 30.461038191819814], + [114.12946503873893, 30.46103439817314], + [114.12947539290786, 30.461030872121057], + [114.12950386704058, 30.461044204700624], + [114.12950534876923, 30.461041822013147], + [114.1294685281634, 30.461024581467186] + ] + ] + ] + } + }, + { + "type": "Feature", + "id": "13", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [114.1266944684298, 30.45974113955593], + [114.12672905702632, 30.45975739984173], + [114.12674527261686, 30.459764919502927], + [114.12676616927213, 30.459774842276527], + [114.12681593344244, 30.459798270346727], + [114.12686208851598, 30.459820014857755], + [114.12692777688568, 30.459851058867], + [114.12699795709268, 30.459884486205194], + [114.12701184103294, 30.45989105531921], + [114.12702132622897, 30.459895863598756], + [114.12716906978987, 30.45997472622542], + [114.12725782616269, 30.460023057967557], + [114.12731527526354, 30.460054300237914], + [114.12731760904978, 30.460055567526968], + [114.12731813428435, 30.460053516543667], + [114.12731900249183, 30.46004906066548], + [114.12731982237837, 30.460044125665828], + [114.12732042243266, 30.460039600961448], + [114.12732094425235, 30.46003526403447], + [114.12732096403349, 30.460033523456232], + [114.12732052050677, 30.460030682767634], + [114.12731943525426, 30.46003001809118], + [114.12731561342733, 30.46002781724937], + [114.12730266390733, 30.4600207953853], + [114.1272944154204, 30.460016316299836], + [114.12723696629992, 30.459985073980643], + [114.12713720190709, 30.459930747807572], + [114.12705918757291, 30.459888076994318], + [114.12704970239025, 30.459883268730508], + [114.12703581846966, 30.45987669963919], + [114.12696563836118, 30.459843272416002], + [114.12689995008387, 30.459812228514114], + [114.12685379507562, 30.459790484078905], + [114.12680403097558, 30.45976705609049], + [114.12678313434951, 30.459757133351058], + [114.126766918782, 30.45974961371671], + [114.12671858919056, 30.45972689376226], + [114.12670588722376, 30.459720823910242], + [114.1266944684298, 30.45974113955593] + ] + ], + [ + [ + [114.12675814499713, 30.459627849957624], + [114.12688251033282, 30.4596865974618], + [114.12707051661417, 30.459775086771863], + [114.12719733974058, 30.45983468642013], + [114.12734472023581, 30.45990416351866], + [114.12742926333294, 30.459943885087256], + [114.12750732255947, 30.45998070502695], + [114.1275779458936, 30.460013852051457], + [114.12762297926939, 30.46003518344493], + [114.12768145827465, 30.46006255464501], + [114.12769094188808, 30.460066929215547], + [114.12777515220014, 30.460106613454087], + [114.12781781683972, 30.460126637479288], + [114.12786681803642, 30.46014968461648], + [114.12793397862626, 30.460181425380462], + [114.12796362762155, 30.460195355841083], + [114.1280563612488, 30.460239138512893], + [114.1280974603508, 30.460258332389202], + [114.12812174927116, 30.460269841792726], + [114.1281473346031, 30.460281966551854], + [114.12817332494056, 30.460294337560583], + [114.1281918699316, 30.460303041631594], + [114.12821516367895, 30.460314187259602], + [114.12822254996298, 30.460317789390476], + [114.12822908233838, 30.460320390201996], + [114.12823183884626, 30.460320998763187], + [114.12823522173838, 30.460321336764043], + [114.12823732737714, 30.460321277212827], + [114.1282396830356, 30.460321077878376], + [114.12824192577881, 30.46032034723348], + [114.12824466576511, 30.460319159303868], + [114.12824900487033, 30.460316311803368], + [114.12824903044557, 30.460316293513653], + [114.12824905597351, 30.46031627517361], + [114.12824908145355, 30.460316256784147], + [114.12824910688614, 30.46031623834515], + [114.12824913227085, 30.460316219856164], + [114.12824915760778, 30.460316201317926], + [114.12824918289648, 30.460316182730047], + [114.12824920813742, 30.46031616409314], + [114.12824923332965, 30.460316145406875], + [114.12824925847366, 30.46031612667147], + [114.128249283569, 30.460316107887273], + [114.12824930861576, 30.460316089054], + [114.12824933361371, 30.460316070171817], + [114.12824935856253, 30.460316051240955], + [114.12824938346256, 30.46031603226147], + [114.12824940831311, 30.460316013233538], + [114.1282494331145, 30.460315994157263], + [114.12824945786632, 30.460315975032252], + [114.12824948256899, 30.460315955859418], + [114.12824950722138, 30.46031593663801], + [114.12824953182431, 30.460315917368842], + [114.12824955637696, 30.460315898051558], + [114.1282495808798, 30.460315878686508], + [114.1282496053328, 30.460315859273578], + [114.12824962973511, 30.46031583981322], + [114.12824965408676, 30.460315820304974], + [114.12824967838806, 30.460315800749473], + [114.1282497026385, 30.4603157811466], + [114.12824972683855, 30.46031576149665], + [114.12824975098745, 30.46031574179904], + [114.12824977508514, 30.46031572205468], + [114.12824979913199, 30.460315702263298], + [114.12824982312755, 30.460315682424888], + [114.12824984707149, 30.460315662539955], + [114.12824987096366, 30.46031564260817], + [114.12824989480455, 30.460315622629924], + [114.12824991859347, 30.46031560260516], + [114.1282499423303, 30.460315582534054], + [114.12824996601559, 30.460315562416774], + [114.1282499896487, 30.4603155422532], + [114.12825001322925, 30.46031552204351], + [114.12825003675762, 30.460315501787985], + [114.12825006023343, 30.46031548148669], + [114.12825008365613, 30.460315461139437], + [114.12825010702663, 30.4603154407467], + [114.12825013034448, 30.46031542030824], + [114.128250153609, 30.460315399824633], + [114.12825017682074, 30.460315379295302], + [114.12825019997902, 30.460315358720827], + [114.12825022308364, 30.46031533810148], + [114.12825024613504, 30.46031531743693], + [114.12825026913309, 30.4603152967274], + [114.12825029207771, 30.460315275973173], + [114.12825031496807, 30.460315255174255], + [114.12825033780463, 30.460315234330583], + [114.12825036058753, 30.46031521344267], + [114.12825038331597, 30.46031519250995], + [114.12825040599014, 30.460315171533157], + [114.12825042860962, 30.460315150512187], + [114.12825045117509, 30.460315129447203], + [114.12825047368585, 30.460315108338374], + [114.12825049614159, 30.460315087185197], + [114.12825051854318, 30.460315065988745], + [114.1282505408893, 30.460315044748455], + [114.12825056318046, 30.460315023464435], + [114.1282505854165, 30.4603150021372], + [114.12825060759738, 30.460314980766636], + [114.12825062972276, 30.460314959352914], + [114.12825065179265, 30.46031493789592], + [114.12825067380695, 30.460314916395994], + [114.12825069576563, 30.46031489485308], + [114.12825071766825, 30.46031487326764], + [114.12825073951514, 30.46031485163955], + [114.12825076130575, 30.46031482996859], + [114.12825078304067, 30.46031480825549], + [114.12825080471872, 30.46031478649952], + [114.12825082634046, 30.460314764701813], + [114.1282508479057, 30.460314742861687], + [114.12825086941466, 30.460314720979657], + [114.12825089086688, 30.460314699055836], + [114.1282509122619, 30.46031467709005], + [114.12825093360064, 30.46031465508258], + [114.12825095488195, 30.46031463303361], + [114.12825097610583, 30.460314610943474], + [114.12825099727287, 30.46031458881166], + [114.12825101838263, 30.460314566638505], + [114.12825103943457, 30.46031454442464], + [114.12825106042901, 30.460314522169323], + [114.12825108136593, 30.460314499873355], + [114.12825110224462, 30.460314477536734], + [114.12825112306598, 30.460314455159], + [114.12825114382905, 30.460314432740955], + [114.12825116453386, 30.460314410282596], + [114.12825118518117, 30.460314387783644], + [114.12825120576936, 30.460314365244603], + [114.12825122629965, 30.460314342665477], + [114.12825124658667, 30.46031432025074], + [114.12825126681595, 30.460314297796767], + [114.1282512869879, 30.46031427530379], + [114.12825130710188, 30.460314252771923], + [114.12825132715821, 30.46031423020105], + [114.12825134715654, 30.46031420759185], + [114.12825136709644, 30.46031418494365], + [114.12825138697845, 30.46031416225707], + [114.12825140680238, 30.460314139532223], + [114.12825142656739, 30.460314116768995], + [114.12825144627443, 30.460314093967845], + [114.12825146592279, 30.460314071128373], + [114.12825148551238, 30.460314048251092], + [114.12825150504305, 30.460314025336054], + [114.12825152451508, 30.46031400238338], + [114.12825154392831, 30.460313979393067], + [114.12825156328276, 30.460313956365166], + [114.12825158257729, 30.460313933300025], + [114.12825160181306, 30.46031391019776], + [114.12825162098945, 30.46031388705814], + [114.12825164010638, 30.46031386388156], + [114.12825165916377, 30.460313840668423], + [114.1282516781613, 30.46031381741793], + [114.12825169709974, 30.460313794131334], + [114.12825171597783, 30.460313770807726], + [114.12825173479607, 30.460313747447902], + [114.12825175355442, 30.460313724051915], + [114.12825177225272, 30.46031370061954], + [114.12825179089103, 30.46031367715129], + [114.12825180946858, 30.46031365364677], + [114.12825182798602, 30.46031363010654], + [114.12825184644298, 30.460313606530608], + [114.12825186483931, 30.460313582919255], + [114.12825188317528, 30.460313559272084], + [114.1282519014501, 30.460313535589776], + [114.12825191966442, 30.46031351187199], + [114.1282519378177, 30.460313488119244], + [114.12825195591026, 30.460313464331357], + [114.12825197394119, 30.46031344050873], + [114.12825199191133, 30.4603134166512], + [114.12825200982003, 30.460313392758874], + [114.12825202766739, 30.460313368832267], + [114.12825204545332, 30.460313344871146], + [114.12825206317798, 30.460313320875574], + [114.12825208084024, 30.46031329684595], + [114.12825209844144, 30.46031327278227], + [114.12825211598069, 30.460313248684702], + [114.12825213345819, 30.46031322455297], + [114.12825215087362, 30.460313200387805], + [114.12825216822664, 30.460313176188986], + [114.12825218551781, 30.460313151956797], + [114.12825220274713, 30.46031312769129], + [114.12825221991372, 30.46031310339207], + [114.12825223701766, 30.460313079060054], + [114.1282522540595, 30.460313054695394], + [114.12825227103863, 30.460313030297424], + [114.12825228795496, 30.460313005866766], + [114.12825230480904, 30.46031298140353], + [114.12825232159955, 30.460312956907835], + [114.12825233832774, 30.46031293237979], + [114.12825235499263, 30.460312907819343], + [114.12825237159478, 30.460312883227], + [114.12825238813313, 30.460312858602368], + [114.12825240460852, 30.460312833946126], + [114.12825242102103, 30.46031280925782], + [114.12825243736954, 30.460312784538143], + [114.12825245365518, 30.460312759787016], + [114.1282524698768, 30.460312735004063], + [114.1282524860348, 30.460312710190003], + [114.12825250212911, 30.46031268534474], + [114.12825251815977, 30.460312660468663], + [114.12825253412632, 30.460312635561493], + [114.12825255002898, 30.460312610623507], + [114.12825256586763, 30.460312585655], + [114.12825258164227, 30.460312560656188], + [114.12825259735236, 30.4603125356264], + [114.12825261299821, 30.46031251056659], + [114.12825262857994, 30.460312485476763], + [114.12825264409734, 30.4603124603567], + [114.12825265954983, 30.460312435206788], + [114.12825267493797, 30.460312410027147], + [114.12825269026165, 30.460312384817666], + [114.1282527055202, 30.46031235957879], + [114.12825272071397, 30.460312334310466], + [114.12825273584305, 30.460312309012924], + [114.128252750907, 30.460312283686225], + [114.12825276590588, 30.46031225833036], + [114.12825278083979, 30.460312232945732], + [114.12825279570822, 30.460312207532347], + [114.12825281051138, 30.460312182090135], + [114.12825282524965, 30.460312156619327], + [114.12825283992197, 30.460312131120332], + [114.12825285452939, 30.460312105593022], + [114.12825286907076, 30.46031208003764], + [114.12825288354688, 30.46031205445394], + [114.12825289795707, 30.46031202884273], + [114.12825291230135, 30.460312003203384], + [114.12825463446963, 30.460307266151542], + [114.1282546958477, 30.460302914042497], + [114.12825359360119, 30.46029924379911], + [114.12825230343141, 30.460296734308333], + [114.1282500427877, 30.46029431885763], + [114.12824661664233, 30.4602918978516], + [114.12820494374954, 30.460272114380142], + [114.1281922841449, 30.460265869404232], + [114.12808402777658, 30.460214870952296], + [114.12766577985062, 30.460017600721127], + [114.12743693679198, 30.459909957721468], + [114.12725923541228, 30.459826070251896], + [114.12719650698922, 30.45979676876443], + [114.12694653473818, 30.459678949412346], + [114.12682802881133, 30.45962329819676], + [114.1267747664414, 30.45959827806291], + [114.12675814499713, 30.459627849957624] + ] + ], + [ + [ + [114.12682661116624, 30.459506038981907], + [114.12687232754989, 30.459527608277465], + [114.12699863219632, 30.459587234089707], + [114.12704767575964, 30.459609865759525], + [114.127111274572, 30.459640029638972], + [114.12713397060182, 30.459650554557154], + [114.12720629683558, 30.459684360024074], + [114.12725407711113, 30.45970325251408], + [114.12729733260204, 30.45972041396493], + [114.1273112377685, 30.459726030135034], + [114.1273387493548, 30.45973707466334], + [114.12741960052145, 30.459769520858135], + [114.12742953675937, 30.459773454640874], + [114.12743318029702, 30.45977451987903], + [114.12743665719199, 30.459775369308982], + [114.12744153599023, 30.45977603826525], + [114.12744835080035, 30.459775950769664], + [114.12745466568676, 30.45977550488621], + [114.12745847719489, 30.459774694886836], + [114.12746506493548, 30.459772979191133], + [114.12746847546522, 30.459771795045626], + [114.1274624922081, 30.459769262478748], + [114.12745538343432, 30.459766253507013], + [114.12738740482354, 30.459737479731473], + [114.1272636764614, 30.45968616892877], + [114.12719322462243, 30.45965567716349], + [114.12714142965808, 30.45963118944914], + [114.12702821462499, 30.459577594181486], + [114.12694685086942, 30.45953947148556], + [114.12683688354451, 30.459487762971463], + [114.12682661116624, 30.459506038981907] + ] + ], + [ + [ + [114.1305997264454, 30.461254650420855], + [114.13056643077365, 30.46123894446055], + [114.13045399666704, 30.46118618729741], + [114.13036864378365, 30.461146033660274], + [114.1303616720721, 30.461142769719032], + [114.13036087968533, 30.46114268878565], + [114.13036047888463, 30.46114276387994], + [114.13036020101502, 30.461142865430812], + [114.13035959442377, 30.46114331708407], + [114.13034715194661, 30.461163681364365], + [114.13035309354153, 30.461166463038413], + [114.1304384464855, 30.46120661673209], + [114.13055088067259, 30.461259373969753], + [114.13058704027621, 30.46127643089205], + [114.1305997264454, 30.461254650420855] + ] + ], + [ + [ + [114.13053418430762, 30.461367177585128], + [114.13049318476216, 30.461347923823414], + [114.1304353730375, 30.4613206455615], + [114.1303847801091, 30.46129699218056], + [114.13033804043904, 30.461275160858154], + [114.13031869977469, 30.461266093584754], + [114.13025878549531, 30.461237868552704], + [114.13022311086705, 30.461221015009325], + [114.13019982360476, 30.461210114477765], + [114.1301856974576, 30.46120357451651], + [114.13014370712925, 30.461183827450988], + [114.13007393532432, 30.46115088390394], + [114.13002322306909, 30.461127028815913], + [114.13000194661323, 30.461117000383993], + [114.12998034182208, 30.4611068297025], + [114.12996651839514, 30.461100593680996], + [114.1298697425707, 30.461055209083533], + [114.12984185474966, 30.461042091356937], + [114.12982176097213, 30.461032475595395], + [114.1298039961753, 30.46102422594877], + [114.12974578440762, 30.460996730168535], + [114.12969922356724, 30.46097479316306], + [114.1296529204122, 30.46095292464968], + [114.12960720785264, 30.460931465418557], + [114.12956090431067, 30.460909605744693], + [114.12951580005522, 30.460888303507012], + [114.12947708153946, 30.46086998646089], + [114.12940928452369, 30.460838038896387], + [114.12918331261375, 30.46073195799231], + [114.12913177782775, 30.46070775896083], + [114.12907421815683, 30.460680663249114], + [114.12898231868293, 30.46063720932726], + [114.1289153688462, 30.460605747365257], + [114.12885654370466, 30.460578209460085], + [114.1288389962118, 30.46056985731374], + [114.12882684847357, 30.460563863524182], + [114.1288141725599, 30.4605581652759], + [114.12880782774334, 30.46055555054327], + [114.1288057848866, 30.46055505635136], + [114.12880362948478, 30.460554857465603], + [114.12880095013205, 30.460554809883952], + [114.12879839023064, 30.460555075444944], + [114.12879690449662, 30.460555292723512], + [114.1287931846466, 30.460556524724897], + [114.12879028514973, 30.460558073317237], + [114.12878727750986, 30.460560462065427], + [114.1287849094132, 30.46056331802322], + [114.12878327828815, 30.46056649000981], + [114.12878233237424, 30.460570669087307], + [114.12878249837452, 30.46057437526104], + [114.12878361332838, 30.460578040744846], + [114.12878565826341, 30.460581439852827], + [114.12878879981872, 30.460584578808444], + [114.12882004927184, 30.460598789178334], + [114.12882910982678, 30.46060304892338], + [114.12883741646571, 30.460606796599585], + [114.12888886204941, 30.460631219887322], + [114.12907550677258, 30.460718986866333], + [114.12919481048306, 30.460775052584722], + [114.1292711416073, 30.46081079817736], + [114.1294100633873, 30.460876387833594], + [114.12953601405539, 30.460935704296794], + [114.12962270083028, 30.460976511380327], + [114.1300316137447, 30.46116872249151], + [114.13018471241936, 30.461240365703688], + [114.13042502797587, 30.461353907089034], + [114.13051678799235, 30.461397044752903], + [114.13053418430762, 30.461367177585128] + ] + ], + [ + [ + [114.13046385794472, 30.46148791862986], + [114.13040616042963, 30.461460780940797], + [114.13028950406114, 30.461405850830815], + [114.13026203901191, 30.46139298174333], + [114.13020055553451, 30.461363638193205], + [114.13013410558624, 30.46133221712907], + [114.13006073761063, 30.461297889754782], + [114.1300180382422, 30.461277865927773], + [114.1299791363247, 30.461259798379114], + [114.12995329752638, 30.46124753359232], + [114.12992004575335, 30.46123183901411], + [114.12989969739692, 30.461222058825008], + [114.12988892761159, 30.461216948521958], + [114.12988435766842, 30.461215291255623], + [114.12987909445536, 30.46121403602663], + [114.1298744487866, 30.461212891337485], + [114.12985058719934, 30.461206110209485], + [114.1298446088779, 30.46120359480715], + [114.12974620246875, 30.46116358510477], + [114.12969948098392, 30.461144727955627], + [114.12967232207535, 30.46113375856144], + [114.12964617596162, 30.46112312554772], + [114.12961041249778, 30.461108580663897], + [114.12958210924047, 30.461097257663308], + [114.1295633657393, 30.461089558870512], + [114.12953506872064, 30.461078021140928], + [114.12951757397664, 30.461071050357365], + [114.12950048798439, 30.46106420274828], + [114.12948548483459, 30.461058045876143], + [114.12947924318162, 30.461055460555205], + [114.12947335563234, 30.46105284662048], + [114.1294563747867, 30.461044784653954], + [114.12943675534166, 30.461035638640777], + [114.12935809571461, 30.460998518369415], + [114.12927029666353, 30.460957162723787], + [114.12917071928544, 30.460910203564357], + [114.12912909028749, 30.46089062224755], + [114.1290837525801, 30.46086931292122], + [114.12902410095711, 30.460841056350123], + [114.12901186070705, 30.4608353856701], + [114.1289942551628, 30.46082698705355], + [114.12897611545476, 30.460818395121724], + [114.12895182480428, 30.460807309844117], + [114.12893148732057, 30.460797480728047], + [114.12890344170553, 30.46078475551124], + [114.12889549846048, 30.46078127011125], + [114.12884170032781, 30.460760314277042], + [114.1288347519017, 30.4607580098877], + [114.12882607768718, 30.460755306255887], + [114.12881584291304, 30.460752569654307], + [114.12880273716975, 30.46074948598957], + [114.12879713995717, 30.460748355768576], + [114.12879353452269, 30.460747554032856], + [114.12878923020261, 30.460746584780672], + [114.12878632197628, 30.46074600780591], + [114.12878070054767, 30.460744953445282], + [114.12877733130436, 30.46074434763582], + [114.1287733065773, 30.460743594320274], + [114.12876752626345, 30.46074270844498], + [114.1287603003112, 30.460741620180574], + [114.12874927521233, 30.46074033708395], + [114.12873845929876, 30.460739421186133], + [114.12872899998375, 30.460738743979512], + [114.12872142087552, 30.46073845018549], + [114.12871229983101, 30.460738238373125], + [114.12870264177718, 30.460738339806475], + [114.12869255624352, 30.460738538047984], + [114.12868470145209, 30.46073887723521], + [114.12867494039402, 30.46073949201651], + [114.12866963265316, 30.460739742643483], + [114.12866651366794, 30.46074008124729], + [114.12866915549414, 30.460748763234417], + [114.12867217376335, 30.460753660157007], + [114.12867439506623, 30.460755258474624], + [114.1286793057532, 30.460757080013], + [114.12868139827911, 30.460757852863008], + [114.12868556375125, 30.460758776239317], + [114.12868966577041, 30.460759519268876], + [114.12870763973575, 30.46076065087707], + [114.12872509411235, 30.460761687958037], + [114.12874165812096, 30.46076308809264], + [114.12876268329988, 30.460766109979808], + [114.12878095541575, 30.460769264625952], + [114.12880683945993, 30.46077534867905], + [114.12883288465684, 30.460783440969028], + [114.1288681713778, 30.460795918675725], + [114.12887836300479, 30.460799888589232], + [114.12888630625602, 30.46080337399543], + [114.1289143518923, 30.46081609923264], + [114.12893468939191, 30.460825928363864], + [114.1289589800609, 30.46083701365927], + [114.12897711978253, 30.460845605604515], + [114.12899472534052, 30.460854004234157], + [114.12900696560021, 30.460859674923135], + [114.12906661726825, 30.46088793153845], + [114.12911195501067, 30.460909240898385], + [114.1291444674547, 30.460924534015696], + [114.12915358404044, 30.460928822245865], + [114.12925316149412, 30.46097578147882], + [114.12934096061207, 30.46101713718879], + [114.12941962029913, 30.46105425751788], + [114.12943923975868, 30.46106340354579], + [114.12945622061763, 30.461071465524732], + [114.12946210817088, 30.461074079463888], + [114.1294683498294, 30.461076664789566], + [114.12948335299018, 30.461082821672694], + [114.1295004389957, 30.46108966929423], + [114.12951793375322, 30.461096640090652], + [114.12954623079347, 30.461108177841012], + [114.12956497430956, 30.461115876647472], + [114.12959327758891, 30.461127199668816], + [114.12962904107998, 30.461141744578914], + [114.1296551872138, 30.46115237761181], + [114.12968234614334, 30.46116334702594], + [114.1297290676644, 30.461182204209194], + [114.12982747414874, 30.461222213983582], + [114.12983345247454, 30.461224729390207], + [114.12985731408087, 30.46123151053599], + [114.12986195975375, 30.461232655228265], + [114.12986722297065, 30.461233910461385], + [114.12987179291733, 30.461235567730828], + [114.12988256271107, 30.461240678041854], + [114.12990291108265, 30.46125045824588], + [114.12993616288067, 30.46126615284841], + [114.12996200169835, 30.461278417653883], + [114.13000090364547, 30.461296485230985], + [114.13004360304595, 30.46131650908941], + [114.13011697107694, 30.4613508365171], + [114.13018342107554, 30.461382257629765], + [114.13024490459868, 30.461411601224487], + [114.13027236966882, 30.461424470331927], + [114.13038902612425, 30.461479400526617], + [114.13045113268977, 30.46150861200973], + [114.13045166005098, 30.461508860796823], + [114.13046385794472, 30.46148791862986] + ] + ], + [ + [ + [114.12838443020314, 30.460133341602067], + [114.12837846261124, 30.460133861140605], + [114.1283699891731, 30.460134063864203], + [114.12835427072939, 30.460133826990184], + [114.12833517330242, 30.460132563964283], + [114.12832069165516, 30.46013139852543], + [114.12830984786484, 30.46012992812529], + [114.12828752320745, 30.46012621008306], + [114.12825057243269, 30.46011874580906], + [114.12822395781758, 30.460111309143702], + [114.12820544788224, 30.46010499855947], + [114.12817294785577, 30.460091995602493], + [114.12812695711021, 30.460071335740665], + [114.12804831847087, 30.460034140966847], + [114.1279144381997, 30.45997081742014], + [114.12779109603392, 30.459912477811564], + [114.1277751204641, 30.45990500277956], + [114.1276701278791, 30.45985553077131], + [114.12763036619752, 30.45983774081049], + [114.12762840913243, 30.459836865541], + [114.12762836860277, 30.45983684895803], + [114.1276283279475, 30.459836832607156], + [114.12762828716846, 30.45983681648975], + [114.12762824626789, 30.459836800605576], + [114.12762821551723, 30.459836788839294], + [114.12762818469247, 30.45983677722], + [114.12762814349567, 30.45983676192334], + [114.12762810218283, 30.459836746863317], + [114.12762806075638, 30.459836732040046], + [114.12762802961645, 30.459836721070833], + [114.12762799840706, 30.459836710250425], + [114.1276279567045, 30.459836696022094], + [114.12762791489392, 30.459836682032556], + [114.12762787297827, 30.459836668282776], + [114.1276278309587, 30.459836654773262], + [114.12762778883703, 30.459836641504527], + [114.12762774661529, 30.459836628477305], + [114.1276277042951, 30.459836615691998], + [114.12762766187866, 30.45983660314928], + [114.12762761936739, 30.45983659084943], + [114.12762757676379, 30.459836578793425], + [114.12762754475389, 30.45983656990394], + [114.12762751268797, 30.459836561167286], + [114.12762746986063, 30.45983654972238], + [114.12762743768602, 30.459836541292116], + [114.12762740545831, 30.459836533015483], + [114.1276273624185, 30.459836522185185], + [114.12762731929688, 30.45983651160144], + [114.12762727609562, 30.45983650126454], + [114.12762724364647, 30.45983649366683], + [114.12762721114912, 30.459836486223768], + [114.12762716775659, 30.45983647650663], + [114.12762713516688, 30.459836469374043], + [114.12762710253213, 30.459836462397003], + [114.1276270589606, 30.459836453302234], + [114.12762702623918, 30.459836446636917], + [114.12762699347569, 30.459836440127994], + [114.12762694973672, 30.4598364316578], + [114.12762690593424, 30.459836423438013], + [114.12762686207003, 30.459836415469027], + [114.12762681814617, 30.459836407751467], + [114.12762677416377, 30.45983640028539], + [114.12762673024915, 30.45983639309108], + [114.12762668665239, 30.459836386204987], + [114.12762665392206, 30.459836381194762], + [114.1276266211611, 30.45983637633899], + [114.12762657744001, 30.459836370070946], + [114.12762653367199, 30.45983636405028], + [114.12762650081824, 30.45983635968949], + [114.12762646793738, 30.459836355483777], + [114.12762643503599, 30.45983635140226], + [114.12762640210897, 30.45983634747593], + [114.127626358174, 30.45983634244777], + [114.12762631420206, 30.459836337668627], + [114.12762627019484, 30.45983633313838], + [114.1276262261544, 30.459836328856976], + [114.12762618208221, 30.45983632482504], + [114.12762613798041, 30.45983632104274], + [114.12762609385085, 30.459836317509783], + [114.12762604969566, 30.459836314227033], + [114.1276260165642, 30.45983631192067], + [114.1276259834181, 30.459836309770974], + [114.12762593920633, 30.45983630711286], + [114.12762589497518, 30.459836304705398], + [114.12762585072659, 30.459836302547842], + [114.12762580646259, 30.459836300640816], + [114.12762577325597, 30.4598362993673], + [114.12762574004165, 30.459836298250032], + [114.12762570682145, 30.459836297258203], + [114.12762567359557, 30.45983629642308], + [114.12762562928798, 30.45983629551856], + [114.12762559576993, 30.459836294993252], + [114.12762556210721, 30.45983629462586], + [114.12762552844248, 30.4598362943868], + [114.12762549477696, 30.45983629430882], + [114.12762544988901, 30.459836294418913], + [114.12762540500245, 30.459836294786577], + [114.12762536011977, 30.459836295411062], + [114.12762531524221, 30.45983629629294], + [114.12762527037239, 30.459836297431924], + [114.12762523672546, 30.459836298446906], + [114.12762520308536, 30.459836299622452], + [114.12762515824227, 30.459836301404135], + [114.12762511341339, 30.459836303442685], + [114.12762506860075, 30.45983630573816], + [114.12762502380629, 30.459836308290168], + [114.12762497903172, 30.459836311099092], + [114.12762494546469, 30.459836313366072], + [114.12762491191184, 30.45983631579315], + [114.12762486719666, 30.459836319242925], + [114.12762483367631, 30.459836321990196], + [114.127624800174, 30.459836324897676], + [114.12762475552913, 30.45983632898738], + [114.12762472206498, 30.45983633221465], + [114.12762468862229, 30.459836335601615], + [114.12762465519617, 30.45983633911615], + [114.1276246217931, 30.45983634279044], + [114.12762457728701, 30.459836347901863], + [114.1276245328204, 30.45983635326826], + [114.12762448839558, 30.459836358889504], + [114.12762444401379, 30.459836364764868], + [114.12762439967753, 30.459836370894458], + [114.1276243553885, 30.459836377278044], + [114.12762431114874, 30.459836383915114], + [114.12762426696017, 30.459836390805716], + [114.12762422292073, 30.459836397933223], + [114.12762417922436, 30.459836405263164], + [114.12762414648644, 30.459836410915933], + [114.12762411378517, 30.45983641672315], + [114.12762407023173, 30.45983642467282], + [114.1276240267391, 30.459836432869306], + [114.12762398330916, 30.459836441312838], + [114.1276239507767, 30.45983644779963], + [114.12762391828501, 30.459836454440303], + [114.12762387501897, 30.45983646349917], + [114.12762383182195, 30.459836472803538], + [114.12762379946815, 30.45983647993499], + [114.12762376715968, 30.459836487219633], + [114.12762373488773, 30.459836494626593], + [114.127623702663, 30.459836502186125], + [114.12762365975962, 30.459836512468513], + [114.12762361693527, 30.45983652299429], + [114.12762358486626, 30.459836531040718], + [114.12762355284848, 30.45983653923868], + [114.12762351022704, 30.459836550371], + [114.12762347831344, 30.45983655887163], + [114.12762344645421, 30.459836567522885], + [114.1276234040472, 30.459836579258948], + [114.12762337229795, 30.459836588211218], + [114.12762334060511, 30.459836597313817], + [114.1276233089579, 30.459836606536296], + [114.12762327736935, 30.459836615908305], + [114.12762324582772, 30.45983662539985], + [114.12762321434622, 30.459836635040585], + [114.12762317245144, 30.45983664809316], + [114.12762313065642, 30.459836661383363], + [114.12762308896173, 30.4598366749108], + [114.1276230473699, 30.459836688674496], + [114.12762300588246, 30.459836702674057], + [114.12762296450092, 30.459836716908963], + [114.12762292322763, 30.45983673137848], + [114.12762288206346, 30.4598367460822], + [114.1276228410695, 30.459836760997447], + [114.12762280036458, 30.459836776079236], + [114.12762276990655, 30.459836787534186], + [114.12762273952062, 30.459836799132127], + [114.12762269910273, 30.459836814786488], + [114.12762265880288, 30.45983683066868], + [114.12762262865232, 30.45983684272252], + [114.12762259857782, 30.459836854917857], + [114.12762256856425, 30.459836867226166], + [114.12762253862776, 30.459836879675816], + [114.12762249881615, 30.45983689646266], + [114.12762245913135, 30.45983691347381], + [114.12762241957452, 30.45983693070835], + [114.12762238014746, 30.45983694816616], + [114.12762234085166, 30.459836965846215], + [114.12762231146311, 30.459836979244585], + [114.1276222821582, 30.45983699278104], + [114.12762225292117, 30.45983700642762], + [114.12762222376958, 30.459837020211605], + [114.1276221850163, 30.45983703877284], + [114.12762215603836, 30.459837052830146], + [114.12762212714861, 30.459837067023827], + [114.1276220983298, 30.459837081325865], + [114.1276220696005, 30.459837095763145], + [114.12762203141526, 30.45983711519309], + [114.12762199337662, 30.459837134838043], + [114.12762196493958, 30.45983714970557], + [114.12762193659584, 30.459837164706798], + [114.12762189892922, 30.459837184886037], + [114.12762186108847, 30.45983720545634], + [114.1276218232954, 30.45983722630116], + [114.12762178566054, 30.45983724736167], + [114.12762175753487, 30.45983726329141], + [114.12762172950961, 30.45983727935421], + [114.12762169227855, 30.459837300949314], + [114.12762165521181, 30.459837322756112], + [114.12762162751534, 30.45983733924339], + [114.12762159992351, 30.45983735586208], + [114.12762156327396, 30.45983737819527], + [114.12762153589367, 30.459837395075652], + [114.12762150861974, 30.45983741208602], + [114.12762147239825, 30.459837434939054], + [114.12762143635086, 30.459837457998546], + [114.12762140942452, 30.459837475421487], + [114.12762138260884, 30.459837492972426], + [114.12762134700236, 30.459837516543274], + [114.12762132040922, 30.4598375343481], + [114.12762129392969, 30.45983755227932], + [114.12762125877431, 30.45983757635521], + [114.12762123252163, 30.459837594537248], + [114.12762120638483, 30.45983761284398], + [114.12762118034041, 30.4598376312499], + [114.12762115441242, 30.45983764977954], + [114.1276211285784, 30.459837668407918], + [114.12762110286229, 30.459837687158597], + [114.12762107724106, 30.459837706006923], + [114.127621051739, 30.459837724976808], + [114.127621017897, 30.459837750431678], + [114.12762098424793, 30.459837776079], + [114.12762095079383, 30.45983780191797], + [114.1276209175363, 30.45983782794712], + [114.12762088447656, 30.459837854165812], + [114.12762085161621, 30.459837880572458], + [114.12762081895652, 30.45983790716621], + [114.12762079458794, 30.459837927227774], + [114.12762077034647, 30.459837947404928], + [114.12762073819461, 30.459837974461017], + [114.1276207062484, 30.45983800170016], + [114.12762067450895, 30.459838029121173], + [114.12762064297809, 30.45983805672239], + [114.12762061165706, 30.4598380845032], + [114.1276205805471, 30.45983811246234], + [114.1276205573472, 30.459838133542032], + [114.12762053428062, 30.459838154732015], + [114.12762050370418, 30.459838183131183], + [114.12762047334368, 30.459838211704074], + [114.12762044320004, 30.45983824044978], + [114.12762041327487, 30.459838269366823], + [114.12762039096796, 30.45983829116111], + [114.12762036884001, 30.459838313020498], + [114.12762033952039, 30.45983834230591], + [114.12762031042284, 30.45983837175765], + [114.12762028873873, 30.45983839395009], + [114.12762026719523, 30.45983841624474], + [114.12762023865783, 30.459838446106975], + [114.12762021034742, 30.45983847613115], + [114.12762018226395, 30.459838506316082], + [114.12762015440984, 30.459838536659873], + [114.12762012678577, 30.459838567161917], + [114.12762009939274, 30.45983859782078], + [114.12762007899315, 30.45983862091195], + [114.12762005873914, 30.459838644099637], + [114.12762003860283, 30.459838667364142], + [114.12762001861302, 30.459838690723796], + [114.12761999215621, 30.459838721995837], + [114.12761996593728, 30.459838753417987], + [114.12761993995598, 30.459838784988815], + [114.12761992061982, 30.459838808759166], + [114.12761990143335, 30.459838832620687], + [114.12761987605282, 30.459838864557106], + [114.12761985091379, 30.45983889663744], + [114.12761982601839, 30.459838928860368], + [114.1276198074992, 30.459838953115334], + [114.1276197891332, 30.459838977458347], + [114.1276197708901, 30.45983900187066], + [114.12761975280124, 30.45983902636931], + [114.12761972888869, 30.459839059147654], + [114.12761971110986, 30.459839083816473], + [114.1276196934865, 30.459839108569174], + [114.12761967019709, 30.459839141683588], + [114.12761964715848, 30.45983917493035], + [114.12761962437214, 30.45983920830697], + [114.12761960183853, 30.459839241813157], + [114.12761957955888, 30.459839275446402], + [114.12761955753355, 30.45983930920586], + [114.12761953576491, 30.459839343090163], + [114.12761951425206, 30.459839377097545], + [114.12761949299771, 30.459839411226756], + [114.12761947200165, 30.459839445476035], + [114.12761945126489, 30.45983947984424], + [114.12761943587527, 30.459839505693772], + [114.12761942064874, 30.459839531615984], + [114.12761940555305, 30.459839557595664], + [114.1276193906211, 30.45983958364661], + [114.12761937582103, 30.459839609753768], + [114.12761936118548, 30.459839635930937], + [114.12761934189163, 30.45983967092571], + [114.1276193228632, 30.459839706029513], + [114.12761930402522, 30.45983974138382], + [114.12761928543088, 30.459839776892835], + [114.12761926710763, 30.459839812507347], + [114.12761925353418, 30.4598398392837], + [114.12761924013094, 30.45983986612391], + [114.127619222487, 30.459839901995572], + [114.12761920511619, 30.459839937967555], + [114.12761918801932, 30.459839974038033], + [114.1276191711972, 30.45984001020549], + [114.12761915875262, 30.459840037390883], + [114.12761914648067, 30.459840064635245], + [114.12761913434706, 30.459840091926043], + [114.12761912238625, 30.459840119274276], + [114.12761911056447, 30.459840146667975], + [114.12761909891618, 30.459840174117343], + [114.12761908740727, 30.45984020161087], + [114.12761907607344, 30.459840229159045], + [114.12761906119373, 30.459840265960946], + [114.12761904659479, 30.459840302847468], + [114.12761903227624, 30.459840339816335], + [114.12761902171361, 30.459840367593937], + [114.12761901132711, 30.459840395421235], + [114.12761900108167, 30.459840423288085], + [114.12761899101291, 30.459840451203267], + [114.12761897782404, 30.459840488486794], + [114.1276189649191, 30.45984052584471], + [114.1276189522991, 30.459840563275755], + [114.12761894301164, 30.459840591393576], + [114.12761893390257, 30.459840619555518], + [114.12761892199579, 30.459840657161408], + [114.1276189132444, 30.459840685408295], + [114.12761890467253, 30.459840713696238], + [114.12761889348214, 30.45984075146724], + [114.12761888257951, 30.459840789301538], + [114.12761887196457, 30.459840827196913], + [114.12761886163878, 30.459840865152394], + [114.12761885407448, 30.459840893655265], + [114.12761884669118, 30.45984092219384], + [114.12761883945247, 30.459840950760203], + [114.12761883239541, 30.459840979360447], + [114.12761882548295, 30.45984100798744], + [114.12761881875272, 30.459841036646793], + [114.12761881002072, 30.459841074901362], + [114.12761880158001, 30.45984111320473], + [114.12761879543179, 30.459841141962116], + [114.12761878946586, 30.459841170748103], + [114.12761878175492, 30.45984120916665], + [114.12761877433641, 30.459841247628418], + [114.12761876721046, 30.459841286131805], + [114.12761876204941, 30.459841315033813], + [114.12761875707136, 30.459841343960434], + [114.12761875067861, 30.459841382559674], + [114.12761874457951, 30.45984142119468], + [114.12761874018916, 30.45984145019221], + [114.1276187359827, 30.45984147920998], + [114.12761873192328, 30.459841508243642], + [114.12761872804813, 30.45984153729595], + [114.12761872312669, 30.45984157605629], + [114.12761871962003, 30.459841605143612], + [114.12761871629831, 30.459841634247077], + [114.1276187131238, 30.45984166336285], + [114.12761871013377, 30.459841692493065], + [114.12761870729186, 30.459841721634284], + [114.12761870463422, 30.459841750788797], + [114.12761870133754, 30.459841789677174], + [114.12761869833716, 30.45984182858323], + [114.12761869563249, 30.45984186750567], + [114.1276186937895, 30.459841896706717], + [114.12761869213125, 30.459841925915605], + [114.12761869062098, 30.45984195513055], + [114.12761868929645, 30.4598419843522], + [114.12761868777706, 30.459842023321897], + [114.1276186865544, 30.45984206229927], + [114.12761868582245, 30.459842091536046], + [114.12761868527593, 30.45984212077583], + [114.12761868487829, 30.459842150017064], + [114.12761868466548, 30.45984217925972], + [114.12761868460136, 30.4598422085028], + [114.12761868472242, 30.459842237745644], + [114.12761868513086, 30.459842276734594], + [114.12761868583661, 30.459842315719577], + [114.12761868655103, 30.45984234495549], + [114.12761868745064, 30.45984237418736], + [114.12761868849857, 30.459842403415447], + [114.12761868973193, 30.459842432637902], + [114.12761869111358, 30.459842461854933], + [114.12761869268054, 30.45984249106513], + [114.12761869501627, 30.459842530000905], + [114.12761869764839, 30.459842568922024], + [114.12761870057666, 30.459842607827213], + [114.1276187038008, 30.45984264671456], + [114.12761870732085, 30.459842685582235], + [114.12761871113665, 30.459842724428814], + [114.1276187152479, 30.45984276325186], + [114.12761871616526, 30.4598428018164], + [114.12762504625675, 30.45984893543803], + [114.12762949228308, 30.459852225409115], + [114.1276374231897, 30.459856310308833], + [114.1276616430547, 30.459868103554726], + [114.12776663568344, 30.459917575602454], + [114.12778261125963, 30.45992505064024], + [114.12813135914148, 30.4600900038603], + [114.12814639053336, 30.460097240706176], + [114.12816552011387, 30.460106150285668], + [114.1281841717783, 30.46011497172923], + [114.12820244821832, 30.460121744953625], + [114.12821657240998, 30.460126959969266], + [114.12822533712574, 30.460129617014047], + [114.12823552448107, 30.460132304143233], + [114.12825978483049, 30.460138049224465], + [114.12826945708734, 30.460140229306568], + [114.1282785540599, 30.46014182891401], + [114.12829062480424, 30.460144068728933], + [114.12829917773735, 30.460145440326883], + [114.12832593940803, 30.460147729175638], + [114.12834108083686, 30.46014871687025], + [114.1283521088651, 30.4601489500489], + [114.12835742918749, 30.46014891256776], + [114.12837417703372, 30.460148560094062], + [114.12838159291634, 30.460148039918515], + [114.12838443020314, 30.460133341602067] + ] + ], + [ + [ + [114.12848080370732, 30.46021996386791], + [114.1284606817147, 30.460222550643277], + [114.12842428170627, 30.460225607748736], + [114.1284355436386, 30.460270462042168], + [114.12847819403144, 30.460289804892188], + [114.12848303613838, 30.460290623268943], + [114.12848869445524, 30.460290794092238], + [114.12849910512165, 30.46028920664694], + [114.12850612224027, 30.46028710502764], + [114.12851218636065, 30.46028186305056], + [114.12851944887758, 30.460272354453615], + [114.12853544237866, 30.460248200417247], + [114.12848080370732, 30.46021996386791] + ] + ], + [ + [ + [114.12876098693175, 30.460327242497126], + [114.12871829029552, 30.460330156172663], + [114.12869807501227, 30.460363024516155], + [114.12869614675651, 30.460371470071735], + [114.1286958516941, 30.46037890808962], + [114.1286974221503, 30.46038724711732], + [114.12869941534363, 30.460391301317554], + [114.12870328283655, 30.460395533591264], + [114.12870918351372, 30.460399367437283], + [114.12871169761027, 30.460401000920996], + [114.12875440144785, 30.460421163179134], + [114.12877706273908, 30.460376546593384], + [114.12876098693175, 30.460327242497126] + ] + ], + [ + [ + [114.12753905401199, 30.46014761437533], + [114.1275338599891, 30.46014791932803], + [114.12752450151488, 30.460149209546806], + [114.12752101223012, 30.46014984828435], + [114.12751615369005, 30.460151116533744], + [114.12750968572972, 30.46015305431144], + [114.12750768991903, 30.4601537788084], + [114.12762175207672, 30.460208054824022], + [114.12767435871464, 30.46023264059284], + [114.12791771848921, 30.46034719627658], + [114.12797508357396, 30.460374199251135], + [114.12799381256822, 30.460382912647844], + [114.12801514013283, 30.460393016077504], + [114.12802370784166, 30.460397437090197], + [114.1280280824312, 30.460399920137075], + [114.12803869484003, 30.460406549377637], + [114.12805096331688, 30.460414819338357], + [114.1280637371187, 30.46042392988968], + [114.12807228893135, 30.460430668873688], + [114.1280896451314, 30.46044474754322], + [114.12809933904086, 30.460453695358574], + [114.12811173214664, 30.4604661789348], + [114.12812094518, 30.460476242493055], + [114.12812933396587, 30.46048571128608], + [114.1281362289695, 30.460494029909185], + [114.1281388873361, 30.46049745989069], + [114.12815040705412, 30.460510719082308], + [114.12815427578668, 30.46051473903124], + [114.12815698491843, 30.46051610904076], + [114.12816000021421, 30.460516411494666], + [114.12817484541584, 30.46051340764445], + [114.12817310991078, 30.460510604870887], + [114.12816915556422, 30.46050441420295], + [114.12816408899778, 30.46049610720233], + [114.12815907912268, 30.46048870347132], + [114.12815495677987, 30.460482572393023], + [114.1281496400492, 30.460475712433443], + [114.1281427450495, 30.46046739381447], + [114.12813435626802, 30.460457925026244], + [114.1281251432397, 30.460447861473753], + [114.12811275014104, 30.460435377904666], + [114.12810305623711, 30.460426430095055], + [114.12808570004728, 30.460412351435764], + [114.12807714823967, 30.46040561245698], + [114.12806437444586, 30.460396501913202], + [114.12805210597658, 30.460388231959485], + [114.12804149357405, 30.460381602725292], + [114.12803711898684, 30.460379119681253], + [114.12802855128386, 30.46037469867336], + [114.12800722373298, 30.460364595256255], + [114.12798849475082, 30.460355881870463], + [114.12768777008509, 30.460214323390005], + [114.12763516348114, 30.46018973765222], + [114.12758087853727, 30.46016426831105], + [114.127549333318, 30.460149654253833], + [114.1275469608798, 30.460148701939403], + [114.12754442581192, 30.460147980719363], + [114.12753905401199, 30.46014761437533] + ] + ], + [ + [ + [114.1283105764358, 30.46046782138418], + [114.1282703180798, 30.46049109632768], + [114.12827622553172, 30.460502624397915], + [114.12828152820767, 30.460515645183932], + [114.12828729169814, 30.460530317484707], + [114.12829079823528, 30.46053987849389], + [114.12834003760709, 30.460556818168108], + [114.12836063324018, 30.46052378574948], + [114.12836376369664, 30.460518006512633], + [114.12836459736319, 30.46051508338462], + [114.12836541369076, 30.460512042620753], + [114.12836514161567, 30.46050926750724], + [114.1283647553862, 30.460505777343094], + [114.12836374082613, 30.460502454308315], + [114.12836197313399, 30.460498592147694], + [114.12836034108322, 30.460496131066375], + [114.12835756235341, 30.46049308645533], + [114.12835458131129, 30.460490208959772], + [114.12835106750647, 30.46048742844768], + [114.12834542677822, 30.46048453981788], + [114.12832747170387, 30.46047566264443], + [114.1283105764358, 30.46046782138418] + ] + ], + [ + [ + [114.1285716711271, 30.460593593058658], + [114.12856745920965, 30.460593720536945], + [114.12856122741601, 30.46059469837452], + [114.12855772059082, 30.460595941281333], + [114.1285522979939, 30.460598027727023], + [114.12854839016643, 30.460600191291086], + [114.12854500787812, 30.46060214946514], + [114.12854230576508, 30.460604654237667], + [114.12854039690191, 30.460607392273527], + [114.12851933861432, 30.460639796030616], + [114.12858022522585, 30.460671950374703], + [114.1285895537819, 30.460669925476665], + [114.12860621810192, 30.460667869126652], + [114.12862116348589, 30.46066596587378], + [114.12863837299199, 30.460664351103055], + [114.12862658288586, 30.460617500482353], + [114.12858066923467, 30.460596083515824], + [114.12857532038161, 30.460594229469987], + [114.1285716711271, 30.460593593058658] + ] + ], + [ + [ + [114.12897714429718, 30.460453957032687], + [114.1289692687468, 30.460455342094463], + [114.1289652256492, 30.46045728543705], + [114.12896128503084, 30.460460424312014], + [114.12895919341386, 30.460464000223794], + [114.12896141988213, 30.46046569754427], + [114.12896562637829, 30.460469304460965], + [114.12897456271966, 30.460476495050344], + [114.12897886427122, 30.460479812759715], + [114.12897935757692, 30.460480189934927], + [114.12898217988712, 30.46048225042216], + [114.12899244448124, 30.46048937726859], + [114.12900031997047, 30.460494519353627], + [114.12900805299152, 30.460499048680862], + [114.1290194750882, 30.46050498195191], + [114.1290352411703, 30.46051301856558], + [114.12905582952001, 30.460523502378468], + [114.12908361933961, 30.460538052699043], + [114.12945622823806, 30.46071324291898], + [114.12947048490607, 30.460719948422255], + [114.1294720816535, 30.46072074574196], + [114.12947592248388, 30.460706120587197], + [114.12946322937594, 30.46070015049239], + [114.1290906206135, 30.460524960386824], + [114.12906283080406, 30.460510410074754], + [114.12904224246184, 30.460499926268238], + [114.1290372063477, 30.460497359157156], + [114.1290197734809, 30.460486242838083], + [114.12900059389624, 30.460471954879953], + [114.12898620514909, 30.46045904119022], + [114.12898247039269, 30.460455143540806], + [114.1289800186363, 30.460454375743627], + [114.12897714429718, 30.460453957032687] + ] + ], + [ + [ + [114.12819690408288, 30.460564240180304], + [114.1281865261613, 30.4605674144638], + [114.1281822186377, 30.460568994070364], + [114.12818053522149, 30.460570151832588], + [114.12817949550193, 30.460571246937015], + [114.12817924512454, 30.460573839898743], + [114.12817989715913, 30.46057786340805], + [114.12818195099064, 30.46058659457124], + [114.1281856385046, 30.460597393987406], + [114.12818940537856, 30.460607712761643], + [114.1281927953916, 30.460620418854052], + [114.12819466214823, 30.460635542514808], + [114.12819561518553, 30.460648069173835], + [114.12819602530844, 30.4606641167207], + [114.12819591336795, 30.46067465478047], + [114.12819434854497, 30.460695108837097], + [114.12819258769844, 30.460709383066078], + [114.12818963393035, 30.460722462959897], + [114.12817555905188, 30.460752954166573], + [114.12816054083802, 30.460779511162954], + [114.1281489573597, 30.460799553409373], + [114.1281397254731, 30.460814394572154], + [114.12808936576536, 30.460894645704666], + [114.12797226529804, 30.461083207482826], + [114.12748164436566, 30.461863076578037], + [114.12749605673096, 30.46186848121499], + [114.12801970600025, 30.46103656746764], + [114.12815589593656, 30.46081798258573], + [114.12816512781515, 30.460803141416246], + [114.12817671128303, 30.460783099161485], + [114.12819172948392, 30.460756542154517], + [114.128205804349, 30.4607260509376], + [114.1282087581131, 30.460712971041602], + [114.12821092672456, 30.4606980520665], + [114.12821208377527, 30.460678242753254], + [114.12821257112043, 30.46066722633577], + [114.1282122678114, 30.460651493822446], + [114.12821182592609, 30.460639142547798], + [114.12821021761332, 30.460623642476424], + [114.12820830338848, 30.460610140794113], + [114.12820676016533, 30.460600871482857], + [114.1282041467479, 30.460588715452456], + [114.12820072582716, 30.460575969525987], + [114.12819690408288, 30.460564240180304] + ] + ], + [ + [ + [114.12974981793391, 30.46083446168812], + [114.12974637290162, 30.46083505804879], + [114.12974633375953, 30.4608350773345], + [114.12974629465444, 30.460835096676004], + [114.12974625558576, 30.46083511607342], + [114.12974621655387, 30.460835135526747], + [114.12974617755953, 30.46083515503621], + [114.12974613860139, 30.460835174601243], + [114.12974609968082, 30.460835194221847], + [114.12974606079756, 30.460835213898076], + [114.12974602195166, 30.460835233629986], + [114.12974598314332, 30.460835253417525], + [114.12974594437198, 30.46083527326063], + [114.1297459056391, 30.460835293159246], + [114.12974586694355, 30.460835313112977], + [114.129745828286, 30.460835333122105], + [114.12974578966617, 30.460835353186745], + [114.12974575108491, 30.460835373306328], + [114.12974571254188, 30.46083539348142], + [114.12974567403664, 30.4608354137114], + [114.12974563556999, 30.460835433996607], + [114.12974559714226, 30.46083545433653], + [114.12974555875266, 30.46083547473179], + [114.12974552040188, 30.460835495181765], + [114.12974548209013, 30.460835515686455], + [114.12974544381717, 30.460835536246314], + [114.12974540558328, 30.460835556860378], + [114.1297453673883, 30.460835577529437], + [114.12974532923315, 30.46083559825321], + [114.12974529111682, 30.460835619031304], + [114.1297452530402, 30.460835639864218], + [114.12974521500283, 30.460835660751343], + [114.12974517700553, 30.46083568169312], + [114.12974513904761, 30.460835702688755], + [114.1297451011301, 30.460835723738995], + [114.12974506325219, 30.46083574484343], + [114.129745025414, 30.460835766002017], + [114.12974498761653, 30.46083578721474], + [114.12974494985934, 30.460835808481445], + [114.12974491214234, 30.460835829802125], + [114.12974487446563, 30.46083585117694], + [114.1297448368299, 30.460835872605397], + [114.12974479923466, 30.460835894087822], + [114.1297447616805, 30.460835915623992], + [114.12974472416663, 30.460835937213854], + [114.12974468669428, 30.46083595885729], + [114.12974464926269, 30.460835980554528], + [114.12974461187261, 30.460836002305054], + [114.12974457452363, 30.46083602410933], + [114.12974453721606, 30.460836045966555], + [114.12974449995004, 30.46083606787741], + [114.12974446272574, 30.460836089841496], + [114.12974442554314, 30.460836111858995], + [114.12974438840205, 30.460836133929604], + [114.12974435130327, 30.460836156052935], + [114.12974431424581, 30.460836178229904], + [114.12974427723125, 30.460836200459585], + [114.12974424025855, 30.460836222741996], + [114.12974420332807, 30.46083624507735], + [114.12974416644003, 30.460836267465762], + [114.12974412959456, 30.460836289906847], + [114.12974409279208, 30.46083631240053], + [114.12974405603171, 30.460836334946766], + [114.12974401931491, 30.460836357545723], + [114.12974398264045, 30.460836380197062], + [114.12974394600889, 30.460836402900778], + [114.12974390942067, 30.460836425657046], + [114.12974387287557, 30.460836448465802], + [114.12974383637379, 30.460836471326257], + [114.12974379991573, 30.460836494239317], + [114.12974376350067, 30.46083651720447], + [114.12974372712944, 30.46083654022155], + [114.12974369080233, 30.460836563290776], + [114.12974365451844, 30.460836586411872], + [114.1297436182786, 30.460836609584778], + [114.12974358208278, 30.460836632809663], + [114.12974354593123, 30.46083665608607], + [114.12974350982368, 30.46083667941435], + [114.12974347376041, 30.460836702794264], + [114.12974343774174, 30.460836726225647], + [114.12974340176754, 30.46083674970867], + [114.12974336583773, 30.460836773243045], + [114.12974332995272, 30.46083679682872], + [114.12974329411266, 30.460836820465687], + [114.12974325831743, 30.460836844153953], + [114.129743222567, 30.460836867893516], + [114.12974318686165, 30.460836891684032], + [114.12974315120157, 30.46083691552568], + [114.12974311558709, 30.46083693941827], + [114.12974308001766, 30.460836963361775], + [114.12974304449341, 30.460836987356114], + [114.12974300901502, 30.460837011401523], + [114.12974297358232, 30.460837035497374], + [114.1297429381956, 30.460837059643833], + [114.1297429028544, 30.460837083841025], + [114.12974286755903, 30.46083710808849], + [114.12974283230997, 30.460837132386736], + [114.12974279710718, 30.460837156735202], + [114.12974276195025, 30.46083718113405], + [114.12974272683962, 30.46083720558317], + [114.12974269177575, 30.46083723008234], + [114.1297426567582, 30.46083725463189], + [114.12974262178747, 30.460837279231367], + [114.12974258686361, 30.460837303880897], + [114.12974255198618, 30.46083732858029], + [114.12974251715573, 30.460837353329396], + [114.12974248237215, 30.46083737812854], + [114.12974244763576, 30.460837402977557], + [114.12974241294671, 30.460837427875934], + [114.12974237830485, 30.46083745282385], + [114.12974234371045, 30.46083747782146], + [114.12974230916356, 30.460837502868262], + [114.12974227466388, 30.46083752796477], + [114.129742240212, 30.460837553110412], + [114.12974220580787, 30.460837578305412], + [114.12974217145187, 30.460837603549603], + [114.12974213714375, 30.460837628842754], + [114.1297421028834, 30.46083765418504], + [114.1297420686714, 30.4608376795764], + [114.12974203450729, 30.460837705016665], + [114.12974200039173, 30.460837730505613], + [114.12974196632476, 30.460837756043233], + [114.12974193230623, 30.46083778162976], + [114.12974189833604, 30.460837807264966], + [114.12974186441465, 30.460837832948457], + [114.12974183054206, 30.460837858680613], + [114.12974179671848, 30.46083788446117], + [114.12974176308428, 30.460837910182224], + [114.12974172949909, 30.460837935950995], + [114.12974169596282, 30.4608379617677], + [114.12974166247488, 30.460837987632175], + [114.12974162903632, 30.46083801354419], + [114.1297415956462, 30.460838039503916], + [114.12974156230555, 30.460838065511123], + [114.12974152901394, 30.46083809156581], + [114.12974149577191, 30.46083811766776], + [114.1297414625789, 30.460838143817245], + [114.12974142943571, 30.460838170013815], + [114.12974139634154, 30.460838196257583], + [114.12974136329764, 30.46083822254855], + [114.12974133030254, 30.460838248886375], + [114.12974129735781, 30.460838275271225], + [114.12974126446325, 30.460838301703102], + [114.12974123161838, 30.46083832818167], + [114.12974119882378, 30.46083835470726], + [114.129741166079, 30.460838381279196], + [114.1297411333845, 30.460838407897647], + [114.12974110074083, 30.460838434562728], + [114.12974106814708, 30.46083846127438], + [114.12974103560418, 30.460838488032493], + [114.12974100311153, 30.46083851483672], + [114.12974097066974, 30.460838541687462], + [114.12974093827866, 30.460838568584037], + [114.12974090593866, 30.460838595526898], + [114.12974087364948, 30.46083862251576], + [114.12974084141126, 30.460838649550514], + [114.1297408092241, 30.46083867663133], + [114.12974077708834, 30.460838703757858], + [114.129740745004, 30.460838730930053], + [114.12974071297083, 30.460838758147847], + [114.1297406809894, 30.460838785411422], + [114.12974064905924, 30.46083881272043], + [114.12974061718063, 30.460838840074867], + [114.12974058535421, 30.46083886747474], + [114.12974055357897, 30.46083889491982], + [114.12974052185616, 30.460838922410385], + [114.12974049018518, 30.460838949945703], + [114.1297404585663, 30.46083897752623], + [114.12974042699975, 30.46083900515185], + [114.12974039548483, 30.460839032822157], + [114.12974036402267, 30.460839060537616], + [114.12974033261303, 30.460839088297718], + [114.12974030125595, 30.46083911610245], + [114.1297402699514, 30.460839143951986], + [114.12974023869893, 30.460839171845763], + [114.12974020749968, 30.460839199784292], + [114.12974017635355, 30.460839227767114], + [114.12974014525973, 30.46083925579418], + [114.12974011421957, 30.46083928386559], + [114.12974008323208, 30.460839311981182], + [114.12974005229756, 30.46083934014079], + [114.12974002141708, 30.46083936834458], + [114.12973999058889, 30.460839396592032], + [114.12973995981484, 30.460839424883495], + [114.12973992909423, 30.46083945321897], + [114.12973989842708, 30.460839481597944], + [114.12973986781358, 30.46083951002053], + [114.12973983725387, 30.460839538486617], + [114.12973980674808, 30.46083956699654], + [114.12973977629618, 30.46083959554939], + [114.12973974589816, 30.460839624146082], + [114.12973971555442, 30.460839652785594], + [114.1297396852648, 30.460839681468485], + [114.12973965502917, 30.46083971019453], + [114.12973962484803, 30.460839738963223], + [114.12973959472167, 30.4608397677753], + [114.12973956464924, 30.460839796630022], + [114.1297395346315, 30.4608398255275], + [114.12973950466834, 30.46083985446774], + [114.12973947476031, 30.460839883450735], + [114.12973944490686, 30.460839912476146], + [114.12973941510836, 30.460839941543973], + [114.12973938536442, 30.46083997065439], + [114.1297393556761, 30.460839999807], + [114.12973932604281, 30.460840029001904], + [114.12973929646455, 30.46084005823883], + [114.12973926694166, 30.460840087518005], + [114.12973923747415, 30.460840116839023], + [114.12973920806212, 30.460840146202116], + [114.1297391787058, 30.46084017560706], + [114.12973914940466, 30.460840205053735], + [114.12973912015968, 30.460840234542086], + [114.12973909097016, 30.460840264072118], + [114.12973906183629, 30.46084029364337], + [114.12973903275855, 30.460840323256296], + [114.1297390037371, 30.46084035291073], + [114.12973897477148, 30.460840382606158], + [114.12973894586216, 30.46084041234309], + [114.12973891700888, 30.460840442120848], + [114.12973888821224, 30.46084047194011], + [114.12973885947152, 30.46084050180002], + [114.12973883078767, 30.46084053170059], + [114.1297388021601, 30.460840561642435], + [114.12973877358935, 30.46084059162459], + [114.12973874507516, 30.46084062164757], + [114.1297387166179, 30.460840651710974], + [114.1297386882176, 30.4608406818152], + [114.12973865987372, 30.460840711959396], + [114.12973863158757, 30.4608407421443], + [114.12973860335774, 30.46084077236917], + [114.12973857518587, 30.460840802634362], + [114.12973854707062, 30.460840832939567], + [114.12973851901269, 30.46084086328475], + [114.12973849101286, 30.460840893669847], + [114.12973846306974, 30.46084092409468], + [114.1297384351844, 30.460840954559426], + [114.1297384073566, 30.460840985063747], + [114.12973837958685, 30.460841015607635], + [114.12973835187445, 30.46084104619138], + [114.12973832422003, 30.460841076813896], + [114.12973829662381, 30.4608411074761], + [114.12973826908535, 30.46084113817748], + [114.12973824160466, 30.460841168918137], + [114.1297382141824, 30.460841199697803], + [114.12973818681834, 30.460841230516465], + [114.1297381595125, 30.460841261374195], + [114.129738132265, 30.460841292270576], + [114.12973810507626, 30.46084132320585], + [114.12973807794552, 30.460841354179724], + [114.12973805087411, 30.46084138519232], + [114.1297380238607, 30.460841416243063], + [114.12973799690607, 30.460841447332637], + [114.12973797001065, 30.46084147846019], + [114.12973794317357, 30.460841509626057], + [114.12973791639597, 30.46084154083025], + [114.12973788967678, 30.46084157207241], + [114.12973786301728, 30.460841603352552], + [114.12973783641623, 30.46084163467062], + [114.12973780987508, 30.46084166602666], + [114.12973778339317, 30.46084169742028], + [114.12973775697006, 30.46084172885176], + [114.12973773060718, 30.460841760320598], + [114.12973770430318, 30.460841791827075], + [114.12973767805912, 30.46084182337095], + [114.12973765183263, 30.460841855002833], + [114.12973762566584, 30.460841886672227], + [114.1297375995592, 30.460841918378925], + [114.12973757351246, 30.460841950122514], + [114.1297375475262, 30.460841981903233], + [114.12973752159988, 30.460842013721123], + [114.12973749573389, 30.460842045576083], + [114.12973746992874, 30.460842077467486], + [114.12973744418316, 30.460842109395898], + [114.12973741849842, 30.460842141361034], + [114.12973739287459, 30.460842173362558], + [114.1297373673108, 30.460842205400915], + [114.12973734180794, 30.460842237475145], + [114.1297373163659, 30.460842269586273], + [114.12973729098468, 30.460842301733106], + [114.12973726566462, 30.46084233391637], + [114.12973724040495, 30.46084236613574], + [114.12973721520687, 30.460842398391033], + [114.1297371900696, 30.460842430682032], + [114.12973716499351, 30.460842463009016], + [114.12973713997859, 30.460842495371644], + [114.12973711502528, 30.460842527769803], + [114.12973709013336, 30.460842560203886], + [114.1297370653027, 30.46084259267299], + [114.12973704053333, 30.460842625177513], + [114.12973701582568, 30.460842657717507], + [114.12973699117954, 30.460842690292466], + [114.12973696659547, 30.460842722902548], + [114.12973694207301, 30.46084275554771], + [114.12973691761239, 30.460842788227833], + [114.12973689321396, 30.460842820942695], + [114.12973686887703, 30.46084285369234], + [114.12973684460216, 30.460842886476776], + [114.12973682038982, 30.460842919295605], + [114.12973679623921, 30.460842952149], + [114.12973677215113, 30.460842985036614], + [114.12973674812511, 30.46084301795868], + [114.12973672416196, 30.460843050915077], + [114.12973670026052, 30.460843083905242], + [114.12973667642233, 30.46084311692957], + [114.12973665264605, 30.46084314998801], + [114.12973662893286, 30.4608431830801], + [114.1297366052821, 30.460843216206126], + [114.12973658169409, 30.460843249365634], + [114.12973655816893, 30.46084328255914], + [114.12973653470664, 30.460843315785496], + [114.12973651130744, 30.460843349045906], + [114.1297364879711, 30.460843382338947], + [114.12973646469763, 30.460843415665693], + [114.12973644148794, 30.460843449025408], + [114.1297364183411, 30.46084348241832], + [114.12973639525725, 30.46084351584398], + [114.12973637223703, 30.460843549302492], + [114.12973634928004, 30.46084358279397], + [114.1297363263868, 30.460843616318027], + [114.12973630355656, 30.460843649874768], + [114.12973628079052, 30.460843683463793], + [114.12973625808748, 30.46084371708522], + [114.12973623544853, 30.460843750739155], + [114.12973621287325, 30.460843784425332], + [114.12973619036174, 30.46084381814345], + [114.12973616791412, 30.460843851893795], + [114.12973614553071, 30.460843885675917], + [114.12973612321063, 30.460843919490095], + [114.12973610095511, 30.46084395333588], + [114.12973607876371, 30.460843987213384], + [114.12973605663619, 30.460844021122607], + [114.12973603457323, 30.46084405506303], + [114.12973601257461, 30.460844089035064], + [114.1297359906401, 30.460844123038466], + [114.12973596877005, 30.460844157073083], + [114.12973594696467, 30.460844191138673], + [114.12973592522376, 30.46084422523536], + [114.12973590354716, 30.46084425936302], + [114.1297358819357, 30.46084429352155], + [114.12973586038882, 30.46084432771077], + [114.12973583890616, 30.4608443619308], + [114.12973581748874, 30.460844396181177], + [114.12973579613644, 30.460844430462082], + [114.12973577484884, 30.460844464773228], + [114.12973575362625, 30.460844499114945], + [114.1297357324688, 30.46084453348708], + [114.12973571137626, 30.46084456788871], + [114.12973569034918, 30.46084460232064], + [114.12973566938712, 30.460844636782692], + [114.12973564849041, 30.460844671274355], + [114.12973562765896, 30.460844705795804], + [114.12973560689274, 30.460844740346865], + [114.12973558619225, 30.46084477492731], + [114.12973556555733, 30.460844809537424], + [114.12973554498811, 30.460844844176926], + [114.12973552448413, 30.460844878845702], + [114.1297355040462, 30.460844913543514], + [114.12973548367349, 30.460844948270605], + [114.12973546336686, 30.46084498302645], + [114.12973544312648, 30.460845017811288], + [114.12973542295134, 30.460845052624883], + [114.12973540284247, 30.460845087467234], + [114.12973538279986, 30.460845122338068], + [114.12973536282306, 30.460845157237486], + [114.1297353429121, 30.460845192165376], + [114.12973532306773, 30.460845227121684], + [114.1297353032894, 30.460845262106304], + [114.12973528357745, 30.460845297118656], + [114.12973526393154, 30.46084533215931], + [114.12973524435202, 30.46084536722776], + [114.12973522483944, 30.46084540232411], + [114.12973520539312, 30.460845437448253], + [114.12973518602747, 30.460845472574082], + [114.12973516672857, 30.460845507727242], + [114.12973514749625, 30.460845542907798], + [114.12973512832998, 30.460845578115748], + [114.12973510923099, 30.460845613351097], + [114.12973509019851, 30.460845648613436], + [114.12973507123286, 30.460845683902832], + [114.12973505233391, 30.460845719219222], + [114.12973503350169, 30.460845754562495], + [114.12973501473667, 30.460845789932424], + [114.12973499603847, 30.46084582532906], + [114.12973497740721, 30.4608458607523], + [114.12973495884313, 30.460845896201963], + [114.12973494034634, 30.460845931677994], + [114.12973492191625, 30.460845967180454], + [114.12973490355392, 30.460846002708944], + [114.12973488525832, 30.460846038263522], + [114.12973486703022, 30.4608460738443], + [114.12973484886962, 30.46084610945076], + [114.12973483077634, 30.46084614508314], + [114.12973481275034, 30.460846180741147], + [114.12973479479184, 30.460846216424898], + [114.12973477690122, 30.460846252133997], + [114.12973475907809, 30.4608462878689], + [114.12973474132248, 30.46084632362852], + [114.12973472363463, 30.46084635941377], + [114.12973470601439, 30.460846395224028], + [114.12973468846224, 30.46084643105929], + [114.12973467097771, 30.4608464669195], + [114.12973465356104, 30.460846502804547], + [114.12973463621256, 30.460846538714367], + [114.12973461893193, 30.460846574648848], + [114.12973460171918, 30.46084661060794], + [114.1297345845747, 30.46084664659147], + [114.12973456749836, 30.46084668259943], + [114.12973455049007, 30.460846718631206], + [114.12973453354985, 30.4608467546877], + [114.12973451667828, 30.460846790767835], + [114.12973449987447, 30.460846826872007], + [114.12973448313932, 30.460846863000274], + [114.12973446647291, 30.460846899152124], + [114.12973444987446, 30.460846935327726], + [114.1297344333448, 30.460846971526685], + [114.12973441688342, 30.460847007749397], + [114.1297344004907, 30.46084704399535], + [114.12973438416665, 30.460847080264656], + [114.12973436791145, 30.46084711655698], + [114.12973435172445, 30.460847152872656], + [114.12973433560668, 30.460847189211066], + [114.12973431955744, 30.460847225572486], + [114.12973430357685, 30.46084726195675], + [114.12973428766557, 30.460847298363515], + [114.1297342718232, 30.460847334793126], + [114.12973425604935, 30.460847371245066], + [114.12973424034494, 30.46084740771928], + [114.1297342247094, 30.460847444215997], + [114.12973420914298, 30.460847480734877], + [114.12973419364577, 30.460847517275745], + [114.12973417821766, 30.460847553838715], + [114.12973416285875, 30.46084759042368], + [114.12973414756908, 30.46084762703023], + [114.12973413234874, 30.460847663658544], + [114.12973411719773, 30.460847700308626], + [114.12973410211626, 30.46084773698001], + [114.12973408710438, 30.460847773672988], + [114.12973407216123, 30.460847810387047], + [114.1297340572882, 30.460847847122526], + [114.12973404248488, 30.4608478838792], + [114.12973402775042, 30.460847920656498], + [114.12973401308629, 30.460847957455154], + [114.12973399849137, 30.46084799427439], + [114.12973398396615, 30.460848031114182], + [114.12973396951094, 30.460848067974663], + [114.12973395512537, 30.460848104855714], + [114.12973394080963, 30.46084814175727], + [114.12973392656387, 30.460848178679], + [114.1297339123879, 30.460848215620956], + [114.12973389828205, 30.46084825258308], + [114.1297338842462, 30.460848289565206], + [114.12973387027971, 30.460848326567326], + [114.1297338563841, 30.46084836358911], + [114.12973384255785, 30.46084840063072], + [114.12973382880251, 30.460848437691986], + [114.12973381511672, 30.460848474772686], + [114.1297338015013, 30.4608485118727], + [114.12973378795623, 30.460848548992203], + [114.12973377448137, 30.460848586130847], + [114.12973376107654, 30.460848623288587], + [114.12973374774252, 30.460848660465413], + [114.12973373447873, 30.460848697661216], + [114.12973372128518, 30.460848734875423], + [114.12973370816232, 30.460848772108548], + [114.1297336951098, 30.46084880936048], + [114.12973368212799, 30.460848846630757], + [114.1297336692164, 30.460848883919507], + [114.12973365637585, 30.4608489212266], + [114.12973364360587, 30.460848958551814], + [114.12973363090637, 30.46084899589504], + [114.12973361827731, 30.4608490332565], + [114.12973360571931, 30.460849070635625], + [114.12973359323186, 30.46084910803265], + [114.12973358081558, 30.46084914544757], + [114.12973356846986, 30.460849182879752], + [114.12973355619496, 30.46084922032972], + [114.1297335439911, 30.46084925779696], + [114.12973353185824, 30.460849295281747], + [114.12973351979586, 30.46084933278318], + [114.12973350780496, 30.460849370302277], + [114.12973349588535, 30.46084940783802], + [114.12973348403617, 30.460849445390746], + [114.12973347225812, 30.460849482960118], + [114.1297334605517, 30.460849520546475], + [114.1297334489162, 30.46084955814942], + [114.1297334373516, 30.460849595768664], + [114.12973342585872, 30.460849633404205], + [114.12973341443677, 30.460849671056167], + [114.12973340308616, 30.460849708724265], + [114.12973339180684, 30.46084974640838], + [114.12973338059889, 30.460849784108678], + [114.12973336946253, 30.460849821824652], + [114.12973335839732, 30.460849859556586], + [114.12973334740369, 30.460849897304204], + [114.12973333648145, 30.460849935067095], + [114.12973332563045, 30.460849972845722], + [114.12973331485165, 30.46085001063951], + [114.12973330414376, 30.460850048448865], + [114.12973329350778, 30.46085008627327], + [114.12973328294329, 30.460850124112376], + [114.12973327245044, 30.460850161966828], + [114.12973326202902, 30.460850199835985], + [114.1297332516797, 30.460850237719967], + [114.12973324140182, 30.46085027561877], + [114.12973323119591, 30.460850313531775], + [114.12973322106194, 30.46085035145926], + [114.12973321099923, 30.460850389401223], + [114.12973320100902, 30.460850427357677], + [114.12973319109005, 30.460850465327923], + [114.12973318124291, 30.46085050331214], + [114.1297331714683, 30.460850541310503], + [114.12973316176529, 30.460850579322493], + [114.12975322379191, 30.46086161535706], + [114.12977616132954, 30.460873949171205], + [114.12978097330156, 30.460876117722883], + [114.1297855789253, 30.460876853934355], + [114.1297856144716, 30.46087685528354], + [114.12978565002072, 30.46087685656814], + [114.12978568557301, 30.460876857789053], + [114.12978572112847, 30.46087685894514], + [114.12978575668633, 30.460876860037487], + [114.12978579224712, 30.46087686106541], + [114.12978582781005, 30.460876862029256], + [114.12978586337594, 30.460876862928792], + [114.12978589894306, 30.460876863764128], + [114.12978593451278, 30.46087686453516], + [114.12978597008455, 30.460876865242227], + [114.12978600565755, 30.46087686588481], + [114.12978604123214, 30.460876866463256], + [114.12978607680833, 30.460876866977337], + [114.12978611238574, 30.460876867427455], + [114.1297861479644, 30.46087686781309], + [114.1297861835434, 30.460876868134424], + [114.12978621912363, 30.460876868391786], + [114.12978625470431, 30.460876868584783], + [114.12978629028534, 30.46087686871336], + [114.12978632586714, 30.460876868777916], + [114.1297863614486, 30.46087686877817], + [114.1297863970304, 30.460876868714053], + [114.12978643261184, 30.46087686858586], + [114.1297864681934, 30.4608768683933], + [114.12978650377416, 30.460876868136612], + [114.129786539354, 30.46087686781561], + [114.12978657493395, 30.460876867430308], + [114.1297866105122, 30.46087686698081], + [114.12978664608987, 30.46087686646718], + [114.12978668166595, 30.460876865889077], + [114.12978671724063, 30.460876865247005], + [114.12978675281389, 30.460876864540456], + [114.12978678838552, 30.46087686376978], + [114.12978682395556, 30.460876862934906], + [114.1297868595231, 30.46087686203573], + [114.12978689508869, 30.46087686107242], + [114.12978693065179, 30.46087686004498], + [114.12978696621283, 30.460876858953004], + [114.12978700177105, 30.46087685779729], + [114.12978703732665, 30.460876856577222], + [114.12978707287905, 30.46087685529268], + [114.12978710842886, 30.460876853944168], + [114.12978714397514, 30.46087685253153], + [114.12978717951754, 30.460876851054586], + [114.1297872150571, 30.460876849513454], + [114.12978725059259, 30.460876847908246], + [114.12978728612443, 30.46087684623885], + [114.12978732165263, 30.46087684450515], + [114.12978735717594, 30.46087684270749], + [114.1297873926955, 30.460876840845696], + [114.12978742821043, 30.460876838919773], + [114.12978746372067, 30.460876836929835], + [114.1297874992264, 30.46087683487548], + [114.12978753472699, 30.460876832757446], + [114.12978757022248, 30.460876830574826], + [114.12978760571296, 30.46087682832836], + [114.12978764119777, 30.460876826017937], + [114.12978767667724, 30.460876823643215], + [114.12978771215103, 30.460876821204867], + [114.12978774761879, 30.46087681870205], + [114.12978778308089, 30.46087681613528], + [114.12978781853651, 30.460876813504544], + [114.12978785398599, 30.460876810809683], + [114.12978788942935, 30.46087680805103], + [114.12978792486543, 30.460876805228366], + [114.12978796029505, 30.460876802341623], + [114.12978799571819, 30.460876799390928], + [114.1297880311336, 30.460876796376272], + [114.12978806654209, 30.460876793297828], + [114.12978810194332, 30.460876790155375], + [114.12978813733704, 30.460876786949182], + [114.12978817272283, 30.460876783678696], + [114.12978820810076, 30.460876780344595], + [114.12978824347076, 30.460876776946705], + [114.12978827883303, 30.46087677348486], + [114.12978831418646, 30.46087676995888], + [114.1297883495317, 30.460876766369523], + [114.12978838486843, 30.460876762716147], + [114.12978842019642, 30.460876758999042], + [114.12978845551554, 30.460876755218266], + [114.12978849082513, 30.460876751373757], + [114.1297885261262, 30.460876747465125], + [114.12978856141741, 30.46087674349305], + [114.12978859669919, 30.460876739457188], + [114.12978863197142, 30.46087673535777], + [114.12978866723412, 30.46087673119456], + [114.12978870248652, 30.46087672696769], + [114.12978873772877, 30.460876722677316], + [114.1297887729606, 30.460876718323266], + [114.12978880818231, 30.46087671390567], + [114.12978884339336, 30.460876709424394], + [114.1297888785935, 30.46087670487962], + [114.12978891378265, 30.46087670027118], + [114.12978894896122, 30.460876695599353], + [114.12978898412845, 30.460876690864083], + [114.12978901928432, 30.460876686065202], + [114.12978905442861, 30.460876681202933], + [114.12978908956133, 30.460876676277223], + [114.12978912468225, 30.460876671287902], + [114.12978915979146, 30.46087666623554], + [114.12978919488796, 30.46087666111962], + [114.12978922997254, 30.46087665594037], + [114.12978926504498, 30.460876650697795], + [114.12978930010493, 30.460876645392066], + [114.1297893351516, 30.46087664002295], + [114.12978937419358, 30.46087663397295], + [114.12978941322308, 30.46087662786042], + [114.12978945223941, 30.460876621685756], + [114.12978949124259, 30.460876615448335], + [114.12978953023236, 30.460876609148553], + [114.12978956920928, 30.46087660278641], + [114.12978960817203, 30.460876596361455], + [114.12978964712163, 30.460876589874424], + [114.12978968605736, 30.460876583324918], + [114.12978972497925, 30.460876576712998], + [114.12978976388683, 30.46087657003895], + [114.12978980278011, 30.460876563302367], + [114.12978984165954, 30.4608765565036], + [114.12978988052447, 30.46087654964264], + [114.12978991937462, 30.460876542719213], + [114.12978995821034, 30.46087653573377], + [114.12978999703064, 30.460876528685905], + [114.1297900358363, 30.460876521575972], + [114.12979007462695, 30.46087651440396], + [114.12979011340265, 30.46087650716982], + [114.12979015216257, 30.460876499873667], + [114.12979019090714, 30.460876492515325], + [114.12979022963594, 30.46087648509485], + [114.12979026834911, 30.460876477612363], + [114.12979030704648, 30.46087647006803], + [114.12979034572749, 30.460876462461563], + [114.1297903843925, 30.460876454793254], + [114.1297904230413, 30.46087644706282], + [114.12979046167351, 30.460876439270706], + [114.12979050028937, 30.46087643141681], + [114.12979053888834, 30.460876423500892], + [114.12979057747062, 30.46087641552313], + [114.12979061603622, 30.460876407483585], + [114.12979065458444, 30.46087639938242], + [114.12979069311541, 30.460876391219415], + [114.12979073162923, 30.460876382994787], + [114.12979077012558, 30.460876374708324], + [114.12979080860411, 30.460876366360406], + [114.12979084706505, 30.460876357950653], + [114.12979088550806, 30.460876349479506], + [114.12979092393347, 30.46087634094669], + [114.12979096234015, 30.460876332352537], + [114.12979100072882, 30.460876323696432], + [114.12979103909929, 30.46087631497928], + [114.12979107745082, 30.460876306200568], + [114.12979111578417, 30.4608762973603], + [114.12979115409813, 30.46087628845887], + [114.12979119239381, 30.46087627949588], + [114.1297912306703, 30.460876270471562], + [114.12979126892753, 30.460876261385913], + [114.1297913071651, 30.46087625223939], + [114.12979134538395, 30.460876243031255], + [114.12979138358304, 30.46087623376201], + [114.12979142176216, 30.460876224431782], + [114.12979145992153, 30.460876215039992], + [114.12979149806117, 30.46087620558739], + [114.12979153618058, 30.460876196073745], + [114.12979157427958, 30.460876186498993], + [114.12979161235812, 30.460876176863255], + [114.12979165041672, 30.460876167166642], + [114.12979168845465, 30.460876157408755], + [114.12979172647147, 30.460876147590113], + [114.12979176446763, 30.460876137710876], + [114.12979180244315, 30.46087612777043], + [114.12979184039673, 30.460876117769505], + [114.1297918783297, 30.460876107707364], + [114.12979191624153, 30.46087609758481], + [114.12979195413146, 30.46087608740132], + [114.12979199199971, 30.460876077157472], + [114.12979202984606, 30.460876066852865], + [114.12979206767073, 30.4608760564875], + [114.12979210547329, 30.460876046061774], + [114.12979214325381, 30.460876035575627], + [114.12979218101198, 30.46087602502861], + [114.12979221874812, 30.460876014421178], + [114.12979225646112, 30.46087600375367], + [114.12979229415174, 30.460875993025628], + [114.12979233181957, 30.460875982236885], + [114.12979236946448, 30.46087597138824], + [114.12979240708634, 30.460875960479346], + [114.12979244468502, 30.460875949509983], + [114.12979248226026, 30.460875938480484], + [114.12979251981221, 30.460875927390685], + [114.12979255734054, 30.460875916240976], + [114.12979259484527, 30.460875905031084], + [114.1297926323263, 30.460875893761173], + [114.12979936405739, 30.46087406401716], + [114.12981050573391, 30.4608710673506], + [114.12981750149231, 30.460869285879166], + [114.1298209604681, 30.460868241871395], + [114.12976090092499, 30.460837087976408], + [114.12975434062045, 30.460834915065185], + [114.12975222187758, 30.46083451237822], + [114.12974981793391, 30.46083446168812] + ] + ], + [ + [ + [114.12858684724688, 30.46075697531846], + [114.12858272338447, 30.46075743943942], + [114.12856332046812, 30.460763483455032], + [114.12855289699057, 30.46076738027048], + [114.12853600009338, 30.460774064010007], + [114.12852229595235, 30.46077968152313], + [114.12851351920625, 30.460783672797096], + [114.12850361728512, 30.460788535310336], + [114.12848832696787, 30.460796685018366], + [114.12847775624438, 30.460803263768174], + [114.12846849232196, 30.460809810470042], + [114.12844572067664, 30.460827796871854], + [114.1284405291799, 30.460831926860674], + [114.12842717045298, 30.46084610269508], + [114.1284102909892, 30.460866749129064], + [114.12839123126408, 30.460890499948565], + [114.12837310477931, 30.460916179875024], + [114.12831646060333, 30.46100674187852], + [114.12827962900336, 30.46106619288501], + [114.12783498522268, 30.461781744044416], + [114.12771556983981, 30.46197223706339], + [114.12772665457429, 30.461977474726904], + [114.1280351192241, 30.4614916579163], + [114.1281772411588, 30.4612674655686], + [114.12838529925469, 30.460926758240657], + [114.1284034236594, 30.460900829056214], + [114.12842208216752, 30.46087728462714], + [114.12843863233869, 30.46085665841345], + [114.12845199105928, 30.46084248257259], + [114.12845718255365, 30.460838352580964], + [114.12847995418937, 30.460820366167756], + [114.12848921810793, 30.46081381946155], + [114.12849904775334, 30.460807893775957], + [114.1285159434435, 30.460799051729605], + [114.12852557378872, 30.46079447206878], + [114.12853447095468, 30.460790640385696], + [114.1285488969296, 30.46078481189138], + [114.12856651522796, 30.460777992556775], + [114.12857545535876, 30.46077498362497], + [114.12858763885949, 30.460771261756314], + [114.1285911701126, 30.460769197273464], + [114.12859354918935, 30.460766813080873], + [114.12859446803948, 30.460765006645683], + [114.12859511201303, 30.46076272898739], + [114.12859483361342, 30.46076096885153], + [114.12859336738642, 30.460758933004715], + [114.1285896597264, 30.460757212223108], + [114.12858684724688, 30.46075697531846] + ] + ], + [ + [ + [114.13009347108097, 30.461014626013245], + [114.13008886700948, 30.461023870675692], + [114.13008728439782, 30.461027226496423], + [114.1300859300475, 30.461032379433217], + [114.13008647117458, 30.46103554809189], + [114.13008735739318, 30.461037930198106], + [114.13008820688349, 30.461039436519634], + [114.1300892110733, 30.46104098218373], + [114.13009079189736, 30.46104235056373], + [114.130091999074, 30.461043350240864], + [114.13009359871769, 30.461044430354065], + [114.13009820163818, 30.46104655701199], + [114.13010687464767, 30.46105055553842], + [114.13012176428953, 30.461057634833466], + [114.13014504672978, 30.46106834589903], + [114.13016890625546, 30.461079574262442], + [114.13022792804726, 30.461107473566233], + [114.13024125286185, 30.461085677970928], + [114.13024127342416, 30.461085494052796], + [114.13024126084613, 30.461085360130177], + [114.1302412091085, 30.46108524925028], + [114.1302410730141, 30.461085093726478], + [114.13018302704694, 30.46105712389051], + [114.13015916753793, 30.461045895541805], + [114.13012760802397, 30.46103057074982], + [114.13011271838874, 30.461023491458835], + [114.13009347108097, 30.461014626013245] + ] + ] + ] + } + }, + { + "type": "Feature", + "id": "7", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12897724043302, 30.459732524015177], + [114.12891906233625, 30.459825264551682], + [114.12890990575438, 30.45983986082598], + [114.12889960140261, 30.459856286714437], + [114.12889000609071, 30.459871582326734], + [114.12888026176522, 30.45988711546247], + [114.12864624354339, 30.460260301463716] + ] + ] + } + }, + { + "type": "Feature", + "id": "8", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864239023739, 30.460258399832206], + [114.12887584219082, 30.459885109720673], + [114.12888531735717, 30.45986997481169], + [114.1288949542723, 30.459854581522617], + [114.12890533748563, 30.459837996136145], + [114.12891431475633, 30.45982365648563], + [114.1289726900806, 30.45973041160152] + ] + ] + } + }, + { + "type": "Feature", + "id": "9", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12798139225067, 30.461314706510002], + [114.12818869480081, 30.460981353806087], + [114.12826611586888, 30.460858573455294], + [114.12828593157752, 30.460826843153637], + [114.12830427901403, 30.4607973730555], + [114.1283271156673, 30.46076105246407], + [114.12835723198545, 30.460712713413564], + [114.1283905424493, 30.460659501312453], + [114.128417691069, 30.46061609233018] + ] + ] + } + }, + { + "type": "Feature", + "id": "133", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12865536915062, 30.460575768862906], + [114.12860099077626, 30.46055000215731] + ] + ] + } + }, + { + "type": "Feature", + "id": "10", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12842232979635, 30.460618247013066], + [114.12839561301983, 30.460660968957175], + [114.12836230256465, 30.460714181065516], + [114.1283321862545, 30.460762520122845], + [114.12830934960708, 30.46079884071929], + [114.12829100217542, 30.46082831082157], + [114.12827118647216, 30.460860041127475], + [114.12819326150827, 30.460983494816098], + [114.12798675932302, 30.461316769922696] + ] + ] + } + }, + { + "type": "Feature", + "id": "11", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1276263870675, 30.459859579177976], + [114.12765470102313, 30.459872401753966], + [114.1277611168518, 30.45992200439], + [114.12785763040516, 30.459967328638495], + [114.12795992499306, 30.460015354753946], + [114.12806337194627, 30.460064469260022], + [114.1281687176785, 30.46011402039716], + [114.12817873041107, 30.460118769600683], + [114.12819169404607, 30.460124230705205], + [114.12820708953562, 30.460130031899652], + [114.12821613630753, 30.460132992597295], + [114.12823052993473, 30.460137166900232], + [114.12824385853544, 30.460140452682115], + [114.1282629296385, 30.46014492434246], + [114.12828147269322, 30.460148266252308], + [114.12829512641986, 30.460150737996006], + [114.12830596819813, 30.46015236546864], + [114.12832484330475, 30.460153946306104], + [114.12834015915607, 30.460155009855193], + [114.12835000355373, 30.46015485717896], + [114.1283672902542, 30.46015497010353], + [114.12838643674475, 30.460154021808915], + [114.12842096859087, 30.46015197565487], + [114.12845680056239, 30.46014888906762], + [114.1284761476081, 30.460144326962837], + [114.12849573392417, 30.460138975734004], + [114.1285041216086, 30.460136520265024], + [114.12851533122597, 30.460132817087857], + [114.12857414466598, 30.460105119057804], + [114.12859590035085, 30.46009128145205], + [114.12861675115045, 30.460076494569353], + [114.12863473366819, 30.46006165603471], + [114.12865044714968, 30.460046543068422], + [114.12867676973335, 30.46001821876738], + [114.12870434742325, 30.45998209755369], + [114.12871540287891, 30.45996827591854], + [114.1287635441821, 30.459891209799093], + [114.1288325033461, 30.45977921371121], + [114.12931873755855, 30.45899917261138] + ] + ] + } + }, + { + "type": "Feature", + "id": "12", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1281739457557, 30.460499207358286], + [114.12816786865055, 30.460490635424566], + [114.12816119187117, 30.460480896152717], + [114.12814380378107, 30.46045903197977], + [114.1281301141407, 30.46044429121061], + [114.12811888705954, 30.460433102565368], + [114.12810904456339, 30.460423954825124], + [114.12810046818328, 30.460415925371965], + [114.12808968907709, 30.46040718178407], + [114.12808045375098, 30.460400365201558], + [114.12806850516638, 30.460391461552604], + [114.12805994843728, 30.46038524566546], + [114.12804986520692, 30.460378997479467], + [114.12803683191065, 30.460371297683672], + [114.12802338123912, 30.460365077468524], + [114.12800764492603, 30.46035778052335], + [114.1279762006981, 30.460342925602617], + [114.12792388558171, 30.460318337642654], + [114.12785992698542, 30.460288262105767], + [114.12777807934471, 30.46024987852015], + [114.12763956850337, 30.46018455884436], + [114.12756786932079, 30.4601508907607], + [114.1275107233993, 30.46012411581605], + [114.12748347584443, 30.46011106306988], + [114.12746972809653, 30.460103979285517], + [114.1274417805846, 30.460089278917057], + [114.12740655657191, 30.46007005834914], + [114.12737168965059, 30.460051061792136], + [114.12732726914756, 30.460026883341335], + [114.12728229494306, 30.46000244951703], + [114.12721682417789, 30.459966837594138], + [114.12715307732698, 30.459931965645627], + [114.12707893531862, 30.4598919027675], + [114.12706085704859, 30.459881885428633], + [114.12703969926034, 30.459871640420573], + [114.127018713248, 30.459861561408193], + [114.12698511844061, 30.4598455951542], + [114.12695360451545, 30.45983060210644], + [114.12689695521986, 30.45980386328977], + [114.12685392296001, 30.459783709573067], + [114.12677732862484, 30.459747526781303], + [114.12673059734941, 30.459725228642583], + [114.12670908584316, 30.459715133114983] + ] + ] + } + }, + { + "type": "Feature", + "id": "13", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1300794624898, 30.461044218499723], + [114.13009057474109, 30.461049720771378], + [114.13009996732531, 30.46105433885233], + [114.13013979351264, 30.461072768465435], + [114.13032645466019, 30.461160692824794], + [114.13049466355547, 30.461239693644842], + [114.13058393128345, 30.461281768620577] + ] + ] + } + }, + { + "type": "Feature", + "id": "14", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12882277886841, 30.460453322501046], + [114.12881787573671, 30.46044796947164], + [114.12881158715328, 30.460441153762385], + [114.1288067493305, 30.460433949312808], + [114.1288001539798, 30.46042339124335], + [114.12879200707901, 30.460408797342957], + [114.12879030510373, 30.46040557140097], + [114.12877706273885, 30.460376546593384], + [114.12876098693141, 30.460327242497126], + [114.12876040839092, 30.460323767807377], + [114.12875876209284, 30.46031388020817], + [114.12875749610893, 30.460306276759738], + [114.12875726121462, 30.460304281487893], + [114.12875671862989, 30.460299672590363], + [114.12875557087689, 30.46027086584409] + ] + ] + } + }, + { + "type": "Feature", + "id": "15", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12949925637302, 30.45908381036229], + [114.12906863093419, 30.4597741843955], + [114.12891042032965, 30.460030184134702], + [114.12889413422447, 30.460063075917926], + [114.12887563750385, 30.460103438899957], + [114.12886082796645, 30.46014235388471], + [114.12885542999538, 30.46015957257983], + [114.12884703119313, 30.46020354754154], + [114.12884559342868, 30.46023920152125], + [114.12885026874397, 30.46028359561936], + [114.12885975256741, 30.46032134141008], + [114.12886846975947, 30.46034453853017], + [114.12888687051543, 30.460381603499457], + [114.12890777512125, 30.460412498531188], + [114.12892180163672, 30.46042897047228], + [114.12893971038247, 30.460446944158406], + [114.12895523364386, 30.46046147406139], + [114.12897886427122, 30.460479812759715], + [114.12899464865654, 30.460491584118675], + [114.12902440946912, 30.460509604536686], + [114.12907435794124, 30.460534409868426], + [114.12946898303335, 30.460720122328183], + [114.12951775750942, 30.460744631244008], + [114.12957155440026, 30.46077032952545] + ] + ] + } + }, + { + "type": "Feature", + "id": "16", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12832699736397, 30.46022001983161], + [114.12836404472645, 30.460224633610444], + [114.12837302917117, 30.46022523007898], + [114.12840022780429, 30.460226284750153], + [114.12842428170627, 30.460225607748736], + [114.12846068171436, 30.46022255064311], + [114.12848080370732, 30.460219963867853], + [114.12851469333377, 30.46021348906026], + [114.1285323730125, 30.46020906884395], + [114.1285457397978, 30.460205305973776], + [114.12857029988476, 30.460196571231123], + [114.1285571368483, 30.460216067679987], + [114.12854918983022, 30.460227838404215], + [114.12854690634074, 30.460231220592163], + [114.12853544237832, 30.460248200417247], + [114.12851944887736, 30.460272354453615], + [114.12851218636042, 30.460281863050504], + [114.12850612224015, 30.46028710502764], + [114.12849910512165, 30.46028920664694], + [114.12848869445502, 30.460290794092067], + [114.12848303613814, 30.460290623268772], + [114.12847819403144, 30.460289804892017], + [114.12843554363837, 30.460270462042168], + [114.1284132054234, 30.460260081520367] + ] + ] + } + }, + { + "type": "Feature", + "id": "17", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12834021985782, 30.460376936480415], + [114.12810549252377, 30.46026661249821], + [114.12793108680269, 30.46018447827835], + [114.12779346360745, 30.460119746592724], + [114.12766184114018, 30.460057657953552], + [114.12740989311544, 30.459939040044063], + [114.1272249764619, 30.459851996295452], + [114.12711248460293, 30.459799160175752], + [114.1270374599439, 30.45976392176662], + [114.12688333686737, 30.459691203549152], + [114.12683064376942, 30.45966633457376], + [114.12675626234494, 30.459631199461516] + ] + ] + } + }, + { + "type": "Feature", + "id": "18", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12875557087689, 30.46027086584409], + [114.12873789098559, 30.460298886010346], + [114.12871981540793, 30.460327731040778], + [114.1287182902953, 30.460330156172322], + [114.12869807501215, 30.4603630245161], + [114.12869614675628, 30.460371470071735], + [114.12869585169376, 30.46037890808945], + [114.1286974221503, 30.46038724711732], + [114.1286994153434, 30.460391301317557], + [114.12870328283621, 30.460395533591093], + [114.12870918351338, 30.460399367437283], + [114.12871169761004, 30.460401000920825], + [114.1287744345245, 30.460430621610467], + [114.12878720135707, 30.460436649346487], + [114.12882277886841, 30.460453322501046] + ] + ] + } + }, + { + "type": "Feature", + "id": "19", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12882277886841, 30.460453322501046], + [114.12885346434857, 30.460467799461636] + ] + ] + } + }, + { + "type": "Feature", + "id": "20", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.13051485347947, 30.461400366055145], + [114.13047468454016, 30.46138144875007], + [114.13036160436964, 30.46132802805536], + [114.1302430504507, 30.46127204691718], + [114.1300838515681, 30.46119719434247], + [114.1299357044339, 30.461127711511185], + [114.12985598514405, 30.461090321932144], + [114.12967374660232, 30.46100455302151], + [114.1295007516756, 30.460921568803485], + [114.12932234861319, 30.460836796072016], + [114.12920795046435, 30.460783250131836], + [114.12909064982529, 30.46072760238839], + [114.12890469941287, 30.460639511974996], + [114.12871283204083, 30.46054890605957] + ] + ] + } + }, + { + "type": "Feature", + "id": "21", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.13046693610107, 30.461482633843357], + [114.13038606822798, 30.46144436951979], + [114.13027501012286, 30.4613921366407], + [114.13018847763566, 30.46135108250479], + [114.13007930919306, 30.46129958315995], + [114.12999872539311, 30.461261874120172], + [114.12994170529474, 30.461235206755003], + [114.12990102253175, 30.46121585668478], + [114.12988478386872, 30.461209425370157], + [114.12985747674861, 30.461202030021965], + [114.1297774932942, 30.46116970927307], + [114.12964453600448, 30.46111622700083], + [114.129524958337, 30.46106744642272], + [114.12948286936863, 30.461050330200425], + [114.1294301436001, 30.46102565562422], + [114.12934054948825, 30.460983694989807], + [114.12922067006116, 30.460927076570584], + [114.12911757403806, 30.460878867417346], + [114.12896104638719, 30.460804857790933], + [114.1289304419777, 30.46079080664426], + [114.12890193910415, 30.4607777289812], + [114.12885190818976, 30.460757512192895], + [114.12884261314063, 30.46075451942377], + [114.12883176802906, 30.460750880498], + [114.12881766336771, 30.46074713662746], + [114.1288054216991, 30.460744352218846], + [114.12878830599986, 30.46074088750907], + [114.12876715087545, 30.460737337447174], + [114.12874513289626, 30.46073415702747], + [114.12873048194281, 30.46073274695193], + [114.1287122944105, 30.460731821427643], + [114.12869397199555, 30.460732309292833], + [114.12867794830335, 30.460733719129816] + ] + ] + } + }, + { + "type": "Feature", + "id": "22", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12872278820035, 30.460511370529066], + [114.1289441332922, 30.46061511953031], + [114.12907372630534, 30.46067607587177], + [114.12915700261074, 30.460715585767986], + [114.12941289814538, 30.46083589256067], + [114.12948970387153, 30.46087208950104], + [114.12967491583197, 30.460959248519227], + [114.12984005696497, 30.461036682635438], + [114.13001956598467, 30.461121247154537], + [114.13002864825172, 30.461125519620687], + [114.13023762396803, 30.461223825032437], + [114.13049887038697, 30.461346686944687], + [114.13053596428729, 30.461364121595935] + ] + ] + } + }, + { + "type": "Feature", + "id": "23", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12744830877914, 30.459787106092207], + [114.127409509125, 30.459771833036406], + [114.12734184036161, 30.459744943040576], + [114.12727440106164, 30.459718373439795], + [114.12724792645402, 30.459707664153637], + [114.12720629223848, 30.459690887389243], + [114.12719205714096, 30.459684830502855], + [114.1271733457091, 30.459676567893943], + [114.12712578434419, 30.45965425294694], + [114.12701691017116, 30.45960300772696], + [114.126899589828, 30.459548137001413], + [114.12682318366205, 30.459512136995478] + ] + ] + } + }, + { + "type": "Feature", + "id": "24", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12677639632612, 30.459595378268002], + [114.1268063691351, 30.45960957564021], + [114.1269081789495, 30.45965724416581], + [114.12698359383919, 30.459692526512907], + [114.1270627904483, 30.459729954529234], + [114.12713925938512, 30.459766133737045], + [114.12714094893008, 30.459766925349616], + [114.12719342297875, 30.459791511290355], + [114.12725858217806, 30.45982214625411], + [114.12735578395005, 30.45986815464524], + [114.12745253202482, 30.459913323126912], + [114.12752961389185, 30.459949829796287], + [114.12760816252492, 30.45998662887781], + [114.12771488077304, 30.460036914687965], + [114.12782355844776, 30.46008840517057], + [114.12790583405823, 30.460127068267425], + [114.12796595398729, 30.46015515907547], + [114.12807572841606, 30.460206779878195], + [114.12819454217355, 30.460262668735545], + [114.12826353994915, 30.46029521337293], + [114.12833443017455, 30.46032877206684], + [114.12836213336487, 30.46034185152361] + ] + ] + } + }, + { + "type": "Feature", + "id": "25", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1275157981083, 30.46187798662634], + [114.12797076401056, 30.461152091581972], + [114.12816304230289, 30.460845174464335], + [114.12818825060478, 30.460800740137387], + [114.12820316339932, 30.46077096459124], + [114.12821272159984, 30.46074629342443], + [114.12821752572157, 30.460728878483152], + [114.12822172932809, 30.46071246440057], + [114.12822433156069, 30.46069344808538], + [114.12822473618087, 30.460681806547388], + [114.12822444614423, 30.460664208945], + [114.12822246300462, 30.460643601869652], + [114.12821906341146, 30.460622894878266], + [114.12820991919551, 30.46058905852358] + ] + ] + } + }, + { + "type": "Feature", + "id": "26", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12859695125248, 30.460749296000518], + [114.1285641230873, 30.460757102698334], + [114.12854010247865, 30.46076470922441], + [114.1285110831788, 30.46077431491476], + [114.12848550415981, 30.460785412429615], + [114.1284664392788, 30.460795735843927], + [114.12843821506361, 30.46081595318955], + [114.12841409692503, 30.460836788640815], + [114.12839077436152, 30.4608625932047], + [114.12837556130937, 30.46087940763076], + [114.1283566115678, 30.46090253222412], + [114.12827917639183, 30.461023774191535], + [114.12769151696821, 30.46196088920321] + ] + ] + } + }, + { + "type": "Feature", + "id": "27", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864842897787, 30.4606276302832], + [114.12862898061579, 30.460618618930347], + [114.12862658288563, 30.460617500482353], + [114.12858066923444, 30.460596083515824], + [114.1285753203815, 30.460594229469987], + [114.12857167112699, 30.460593593058658], + [114.12856745920931, 30.460593720536774], + [114.12856122741601, 30.460594698374464], + [114.12855772059048, 30.460595941281277], + [114.1285522979939, 30.460598027726796], + [114.1285483901662, 30.460600191291086], + [114.12854500787789, 30.460602149465135], + [114.12854230576485, 30.460604654237496], + [114.12854039690191, 30.460607392273527], + [114.1285193386141, 30.460639796030616], + [114.1285079053109, 30.46065799662477], + [114.12850706592427, 30.460659350460933], + [114.12849941394543, 30.460671692237295], + [114.12849141514032, 30.460684593394582], + [114.12847872288397, 30.460705064530792] + ] + ] + } + }, + { + "type": "Feature", + "id": "28", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12847872288397, 30.460705064530792], + [114.1284732864388, 30.460713834476344] + ] + ] + } + }, + { + "type": "Feature", + "id": "29", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12823116917164, 30.460430565779617], + [114.12823513363749, 30.460435047164694], + [114.1282410985911, 30.460442480098674], + [114.12824699456962, 30.460450849727245], + [114.12825612343089, 30.460464853507645], + [114.1282621451196, 30.460475318598228], + [114.12827004285334, 30.460490559238714], + [114.12827622553161, 30.460502624397915], + [114.12828152820744, 30.460515645183932], + [114.12828729169814, 30.460530317484707], + [114.12829185294262, 30.46054275428451], + [114.12829655198448, 30.46055864893324], + [114.12830072615508, 30.460573305664308], + [114.12830350703699, 30.460586218995534], + [114.1283057058045, 30.460595944723526], + [114.12830651401676, 30.460604188710853], + [114.12829868477651, 30.46062353496956], + [114.12831145944958, 30.460602786826506], + [114.12832884115144, 30.460574821538472], + [114.1283379454297, 30.460560173714523], + [114.12836063323996, 30.46052378574931], + [114.12836376369641, 30.460518006512462], + [114.12836459736296, 30.46051508338462], + [114.12836541369053, 30.460512042620753], + [114.12836514161545, 30.46050926750707], + [114.12836475538597, 30.460505777343094], + [114.12836374082613, 30.460502454308315], + [114.12836197313365, 30.460498592147694], + [114.12836034108288, 30.460496131066375], + [114.12835756235341, 30.46049308645533], + [114.12835458131094, 30.460490208959772], + [114.12835106750626, 30.46048742844768], + [114.12834542677811, 30.460484539817884], + [114.12832747170351, 30.46047566264443], + [114.12828954958354, 30.46045806260415], + [114.12824607343725, 30.46043758783169], + [114.12819295080001, 30.46041255940355] + ] + ] + } + }, + { + "type": "Feature", + "id": "134", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12860622058159, 30.46054207521988], + [114.12866069424561, 30.46056778078176] + ] + ] + } + }, + { + "type": "Feature", + "id": "135", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12868075434154, 30.460536668227384], + [114.1286261815418, 30.46051044643603] + ] + ] + } + }, + { + "type": "Feature", + "id": "30", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12912278314475, 30.46085068839886], + [114.12904124105474, 30.460812270419204], + [114.12897783505122, 30.460782561304338], + [114.12889343028047, 30.460742891641594], + [114.128827187441, 30.460711760087573], + [114.12876201360413, 30.460681020788293], + [114.12871871350927, 30.460660873763988], + [114.12871540763784, 30.46065931028169], + [114.12871156753305, 30.460659360647206], + [114.12870155760558, 30.460659491930894], + [114.1286930532053, 30.460660015850845], + [114.12868308922111, 30.460660707382488], + [114.12867346315322, 30.460661452813614], + [114.12866670776984, 30.460661975939868], + [114.12866189334268, 30.460662264222407], + [114.12865149721733, 30.46066288672742], + [114.1286501161541, 30.460662969423137], + [114.12863837299199, 30.460664351103055], + [114.12862116348589, 30.460665965873723], + [114.12860621810181, 30.460667869126482], + [114.12858955378167, 30.46066992547661], + [114.12858022522573, 30.460671950374874], + [114.12857584464733, 30.460672901241058], + [114.1285661300287, 30.46067430161067], + [114.12855794056122, 30.460675779314688], + [114.12854994047656, 30.46067753164059], + [114.12853872035802, 30.460680668239995], + [114.1285338333218, 30.460682142372615], + [114.1285263397709, 30.46068440273543], + [114.12851723573179, 30.46068725357128], + [114.12851419616699, 30.460688205378652], + [114.12850163985091, 30.460692973529728], + [114.1285007257757, 30.460693399910237], + [114.12848441607913, 30.46070100774001], + [114.12847872288397, 30.460705064530792] + ] + ] + } + }, + { + "type": "Feature", + "id": "31", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12914007973365, 30.460822483792516], + [114.12887456443299, 30.460697201305514], + [114.1286858024831, 30.46060822496752], + [114.12867798025998, 30.460604378204753] + ] + ] + } + }, + { + "type": "Feature", + "id": "32", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12915765731448, 30.460794860092545], + [114.12869540616136, 30.460576642138296] + ] + ] + } + }, + { + "type": "Feature", + "id": "33", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12773503437458, 30.460011515042158], + [114.12781700454225, 30.460050325325824], + [114.12791906931785, 30.460098400883528], + [114.12801287260986, 30.46014223287044], + [114.12809008867563, 30.460178722541972], + [114.12818780640409, 30.46022476554824], + [114.12828835912829, 30.460272275342994], + [114.1283586035856, 30.46030481900877], + [114.12837928019152, 30.460314398279753] + ] + ] + } + }, + { + "type": "Feature", + "id": "34", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1277518461424, 30.459984503356356], + [114.12794365282777, 30.460075007194664], + [114.1279982520951, 30.460100716895834], + [114.12806983858272, 30.46013429109269], + [114.1281313933395, 30.460163404118905], + [114.12819844034753, 30.460195149932535], + [114.12826302682811, 30.460225400211414], + [114.12832956263576, 30.460257207017207], + [114.1283852969419, 30.460283165363396], + [114.1283957314117, 30.46028805870498] + ] + ] + } + }, + { + "type": "Feature", + "id": "35", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12776888004775, 30.459957134711782], + [114.12776927905365, 30.459957321868618], + [114.12783777064836, 30.459989793855886], + [114.12785845211445, 30.459999252293215], + [114.12793323694133, 30.460034680685403], + [114.1280053462846, 30.460068500674236], + [114.12808125053584, 30.460104367246164], + [114.12813446219064, 30.460129551143037], + [114.12820666640502, 30.460163309393074], + [114.12828587593559, 30.460200544213052], + [114.12832699736397, 30.46022001983161] + ] + ] + } + }, + { + "type": "Feature", + "id": "36", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12832699736397, 30.46022001983161], + [114.1283270929556, 30.460220065105005] + ] + ] + } + }, + { + "type": "Feature", + "id": "37", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12884403956247, 30.460007641707215], + [114.12867760736032, 30.46027479966971] + ] + ] + } + }, + { + "type": "Feature", + "id": "38", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12887150044769, 30.46001997208708], + [114.12870731410098, 30.46028872831528] + ] + ] + } + }, + { + "type": "Feature", + "id": "39", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12818014087874, 30.460874810459718], + [114.12822144234136, 30.460808716806646], + [114.12824696465553, 30.460767995861808], + [114.12827292522499, 30.460726389131928], + [114.128304829952, 30.46067542079121], + [114.12833107999349, 30.46063331293882], + [114.12835891643661, 30.460588791534825] + ] + ] + } + }, + { + "type": "Feature", + "id": "40", + "properties": { "type": 1 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12821012605546, 30.46088886842086], + [114.1282412682786, 30.460839424206632], + [114.12826592705372, 30.46080002751735], + [114.12829868801084, 30.46074734443058], + [114.1283396266124, 30.460681891115996], + [114.12836003370703, 30.460649206631423], + [114.12838906067488, 30.46060279353459] + ] + ] + } + }, + { + "type": "Feature", + "id": "41", + "properties": { "type": 4 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12791250027578, 30.461436033512047], + [114.12798675932302, 30.461316769922696] + ] + ] + } + }, + { + "type": "Feature", + "id": "42", + "properties": { "type": 4 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12798139225067, 30.461314706510002], + [114.12790721558684, 30.461432066904347] + ] + ] + } + }, + { + "type": "Feature", + "id": "43", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12791250027578, 30.461436033512047], + [114.12760695187441, 30.461921826983442] + ] + ] + } + }, + { + "type": "Feature", + "id": "44", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12790721558684, 30.461432066904347], + [114.12760124697984, 30.46191922475083] + ] + ] + } + }, + { + "type": "Feature", + "id": "45", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12802134345617, 30.461185257079794], + [114.12757284979007, 30.461904903192504] + ] + ] + } + }, + { + "type": "Feature", + "id": "46", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12799359944563, 30.461170059048992], + [114.12754318292802, 30.461890906595524] + ] + ] + } + }, + { + "type": "Feature", + "id": "47", + "properties": { "type": 4 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1289726900806, 30.45973041160152], + [114.12905084067081, 30.459610143655393], + [114.12912860670679, 30.459484907374254] + ] + ] + } + }, + { + "type": "Feature", + "id": "48", + "properties": { "type": 4 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12897724043302, 30.459732524015177], + [114.12908987415992, 30.459554495911952], + [114.12913176009636, 30.45948693795003] + ] + ] + } + }, + { + "type": "Feature", + "id": "49", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12912860670679, 30.459484907374254], + [114.12918860887022, 30.459388703669106], + [114.12940692117314, 30.459040354800283] + ] + ] + } + }, + { + "type": "Feature", + "id": "50", + "properties": { "type": 3 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12913176009636, 30.45948693795003], + [114.1292186688281, 30.459347626355402], + [114.12928769388098, 30.459236272889843], + [114.1294088728476, 30.45904135565898] + ] + ] + } + }, + { + "type": "Feature", + "id": "51", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12900740340355, 30.45974525957922], + [114.12930185603157, 30.459273004403947], + [114.12943776550892, 30.459054989774348] + ] + ] + } + }, + { + "type": "Feature", + "id": "52", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12903468416134, 30.459759191112973], + [114.12910643837142, 30.459647825985254], + [114.12924000296428, 30.459433141795156], + [114.12939441460196, 30.459185760032476], + [114.12946726576742, 30.45906881136676] + ] + ] + } + }, + { + "type": "Feature", + "id": "53", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12679204348328, 30.459567539767527], + [114.1268360594006, 30.459588194797206], + [114.12694201630637, 30.4596379537218], + [114.1269954117388, 30.459663315435186], + [114.12705192223714, 30.45968981652831], + [114.12710391907243, 30.459714318445624], + [114.1271579633486, 30.459739595227425], + [114.12716006239806, 30.459740576961867], + [114.12720414426339, 30.459761419895838], + [114.1272685192033, 30.45979186681696], + [114.12737430459964, 30.45984175550143], + [114.12743273349024, 30.459869432945784], + [114.12748623372657, 30.459894380831127], + [114.12754058206845, 30.459919815635516], + [114.12759280774002, 30.45994463706296], + [114.12765154108888, 30.459972194132174], + [114.12773503437458, 30.460011515042158] + ] + ] + } + }, + { + "type": "Feature", + "id": "54", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12830640567694, 30.46043107504182], + [114.12818935621225, 30.46037596750847], + [114.12808795522145, 30.460328313054408], + [114.12798911993617, 30.460281740825522], + [114.1278981990045, 30.46023882670201], + [114.12772954924898, 30.46015951002228], + [114.12707926064358, 30.459852396534487], + [114.12672577159651, 30.45968544680535] + ] + ] + } + }, + { + "type": "Feature", + "id": "55", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12819295080001, 30.46041255940355], + [114.128150914052, 30.46039288912343], + [114.12805481170027, 30.46034773709655], + [114.12795560218164, 30.460301165830813], + [114.12782851745932, 30.460241330062537], + [114.12771865053372, 30.46018999482562], + [114.12760991752974, 30.460138601911694], + [114.12750038561464, 30.460087155502507], + [114.12739544994673, 30.460037709331758], + [114.12729047372582, 30.459988216739237], + [114.1271836077708, 30.45993795295971], + [114.12710503081357, 30.459901016565624], + [114.12706085704825, 30.459881885428633] + ] + ] + } + }, + { + "type": "Feature", + "id": "56", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12885346434857, 30.460467799461636], + [114.12888611478475, 30.46048330114584], + [114.12894212166319, 30.460509498511577], + [114.12899770191459, 30.46053571380165], + [114.12906103846264, 30.46056530411545], + [114.12933228326845, 30.460692990021055], + [114.12939922183315, 30.460724450600186], + [114.12945219117499, 30.460749319804087], + [114.1294949523801, 30.460769381987685], + [114.1295646720375, 30.46080237116679], + [114.12961044647972, 30.460824201627865], + [114.12966760787087, 30.460851026077375], + [114.12972489851822, 30.460877690747047], + [114.1297846468849, 30.46090557979937], + [114.12983406031677, 30.460929092262713], + [114.12989603575154, 30.46095827285938], + [114.12994965349259, 30.46098332605729], + [114.13000503482809, 30.461009534879153], + [114.13001051246, 30.46101195061097], + [114.1300794624898, 30.461044218499893] + ] + ] + } + }, + { + "type": "Feature", + "id": "57", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12988478386872, 30.461209425370157], + [114.12983517790201, 30.461185033927062], + [114.12980747191082, 30.461172091385784], + [114.12976235278241, 30.461150951652858], + [114.12965581771897, 30.46110102144966], + [114.12954634172344, 30.461049675638765], + [114.1294427140344, 30.46100085224905], + [114.12932650533043, 30.46094627831662], + [114.12921344859751, 30.46089333997407], + [114.12912278314475, 30.46085068839886] + ] + ] + } + }, + { + "type": "Feature", + "id": "58", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12857813510907, 30.460244863218666], + [114.12934615820019, 30.459012029066628] + ] + ] + } + }, + { + "type": "Feature", + "id": "59", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12861197266949, 30.46025280528113], + [114.12937586387014, 30.45902595685755] + ] + ] + } + }, + { + "type": "Feature", + "id": "60", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12744830877914, 30.459787106092207], + [114.12746862128559, 30.459794978424974], + [114.12748735101675, 30.45980260974082], + [114.12759346209813, 30.45984560350452], + [114.12761138449859, 30.459853479228062], + [114.1276263870675, 30.459859579177976] + ] + ] + } + }, + { + "type": "Feature", + "id": "61", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12776888004775, 30.459957134711782], + [114.12774231094184, 30.459944672249055], + [114.12763228066444, 30.45989288214787], + [114.12757785286539, 30.459867328602357], + [114.12745358071072, 30.459808632927086], + [114.12741197479716, 30.45978912384476], + [114.12735687524035, 30.45976307747049], + [114.12730078342108, 30.459736847572746], + [114.12725003300609, 30.459713085134545], + [114.12723471844387, 30.459705922914285], + [114.1271920571413, 30.45968483050285] + ] + ] + } + }, + { + "type": "Feature", + "id": "62", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12957155440026, 30.46077032952545], + [114.12962560879784, 30.46079536052207], + [114.12969807096734, 30.460830190404653], + [114.12973316176529, 30.460850579322493], + [114.12977092851051, 30.460870448151194], + [114.1298018736042, 30.460887031239576], + [114.12983899187161, 30.46090765686766], + [114.12988703308896, 30.46093287850678], + [114.12991570697065, 30.460952002731872], + [114.12994598007576, 30.460972153130776], + [114.12998268083776, 30.46099194239249], + [114.13001781280236, 30.461010661152926], + [114.1300438771927, 30.461024660365503], + [114.13006143062687, 30.461034447929116], + [114.1300794624898, 30.461044218499723] + ] + ] + } + }, + { + "type": "Feature", + "id": "63", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1304828959087, 30.461455232968806], + [114.13044635911619, 30.461438034725898], + [114.13033843458149, 30.461387374583097], + [114.13023006248848, 30.46133601896736], + [114.13010079461621, 30.461277151980173], + [114.12914007973365, 30.460822483792516] + ] + ] + } + }, + { + "type": "Feature", + "id": "64", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1304988990673, 30.46142775766637], + [114.13040188534445, 30.46138206264984], + [114.13029319275141, 30.461331016288316], + [114.13018197267012, 30.461278274427475], + [114.13011821926273, 30.461249414854397], + [114.1297996469823, 30.461098948695362], + [114.12958253083829, 30.460996747637843], + [114.12915765731448, 30.460794860092545] + ] + ] + } + }, + { + "type": "Feature", + "id": "65", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12875753260568, 30.46045704777092], + [114.1288271635834, 30.46048990394257], + [114.12893137978958, 30.46053891373819], + [114.12898145903952, 30.460562441979395], + [114.12903942169002, 30.460589618580357], + [114.1290929564931, 30.460615068807137], + [114.12915022534027, 30.46064215833168], + [114.12925606054661, 30.460691552731266], + [114.1295927629289, 30.460850115037506], + [114.1297118410154, 30.460906011102722], + [114.12976728953336, 30.460932068522915], + [114.12981863325906, 30.460956370440798], + [114.12988131598836, 30.46098588171369], + [114.12998954955715, 30.461036676630748], + [114.13004608969733, 30.46106340592618], + [114.13006261309012, 30.46107117703442], + [114.1300997639906, 30.46108864942799], + [114.1302601720615, 30.461164068601292], + [114.13031469283304, 30.46118984365158], + [114.13037267427723, 30.46121700849508], + [114.13042454324432, 30.461241437490035], + [114.13048268457972, 30.461268812921542], + [114.13053567157775, 30.46129408611399], + [114.13056801034821, 30.4613091027564] + ] + ] + } + }, + { + "type": "Feature", + "id": "66", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12874030932966, 30.46048397632716], + [114.12880312320125, 30.460513589668718], + [114.12881408934, 30.46051877370536], + [114.12886034248127, 30.460540615681648], + [114.12901969078055, 30.460615885242152], + [114.12918879345622, 30.46069546410517], + [114.12950619915098, 30.46084488819738], + [114.12958058913902, 30.460879834131823], + [114.1297382830412, 30.46095399968528], + [114.1298989137917, 30.46102932667027], + [114.13004557426913, 30.461098438591197], + [114.13006054778565, 30.46110549464093], + [114.13012839752761, 30.461137369360586], + [114.13023304710003, 30.461186698506765], + [114.13040098147604, 30.46126551377191], + [114.13050675607363, 30.461315424744768], + [114.13055195920352, 30.461336660444562] + ] + ] + } + }, + { + "type": "Feature", + "id": "136", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12867558659867, 30.4605446055135], + [114.12862146241301, 30.460518321024757] + ] + ] + } + }, + { + "type": "Feature", + "id": "137", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12867066233986, 30.460552033762347], + [114.12861603989573, 30.460526442348822] + ] + ] + } + }, + { + "type": "Feature", + "id": "67", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12832328945066, 30.460404043115755], + [114.12821293483425, 30.460352462075083], + [114.12810882062381, 30.46030343330221], + [114.12800898333393, 30.46025630773833], + [114.12791284517074, 30.460210946918565], + [114.1278139899509, 30.46016432845007], + [114.12769539677514, 30.460108307070723], + [114.12758815968235, 30.460057992138477], + [114.12748280123796, 30.460008447196017], + [114.12737433762597, 30.45995741073594], + [114.12726317223137, 30.4599049453874], + [114.12715576088877, 30.459854498539748], + [114.1270956712156, 30.45982610110306], + [114.12704304909965, 30.45980123263368], + [114.12693907122653, 30.45975203028999], + [114.12683084070854, 30.459701131617745], + [114.12674074829447, 30.459658801146414] + ] + ] + } + }, + { + "type": "Feature", + "id": "68", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12680725956686, 30.45954046820778], + [114.12717449067327, 30.459713047474622], + [114.12759520741717, 30.459910756278994], + [114.1277516632006, 30.459984417034843], + [114.1277518461424, 30.459984503356356] + ] + ] + } + }, + { + "type": "Feature", + "id": "69", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12844958148462, 30.460630905376078], + [114.12841802968548, 30.460681649005544], + [114.12839198638993, 30.46072327204628], + [114.12836266917118, 30.460769877974602], + [114.12833395960327, 30.460816395178412], + [114.1283037234543, 30.46086464582302], + [114.1282911770312, 30.460884715512925], + [114.1282211342061, 30.460996562363395], + [114.12801243920661, 30.461332549159803], + [114.1277442196444, 30.461759037316693], + [114.12763446549414, 30.461933972927216] + ] + ] + } + }, + { + "type": "Feature", + "id": "70", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12848254571205, 30.460644386019133], + [114.12845049214515, 30.460695881009226], + [114.12839457762426, 30.46078340026094], + [114.12836346977788, 30.460832468671114], + [114.12825786032651, 30.46100118711294], + [114.12766413227311, 30.46194796940813] + ] + ] + } + }, + { + "type": "Feature", + "id": "71", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12900740340355, 30.45974525957922], + [114.128957960984, 30.4598241272444], + [114.12894004561335, 30.459853352318294], + [114.12893003702641, 30.459869065799808], + [114.12891992835358, 30.459885680054146], + [114.12891052233593, 30.459900943156093], + [114.12890109275257, 30.45991602938203], + [114.12889116629341, 30.45993199281133], + [114.12884403956247, 30.460007641707215] + ] + ] + } + }, + { + "type": "Feature", + "id": "72", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12903468416134, 30.459759191112973], + [114.12898507654947, 30.459838468600687], + [114.12897867293883, 30.45984870212572], + [114.12896815212967, 30.459865515277198], + [114.12895874916521, 30.459880542002033], + [114.1289500604375, 30.459894427304334], + [114.12893775904018, 30.45991408592911], + [114.12892769870186, 30.459930192766993], + [114.12891807753455, 30.459945538511995], + [114.12887150044769, 30.46001997208708] + ] + ] + } + }, + { + "type": "Feature", + "id": "73", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12799359944563, 30.461170059048992], + [114.12813058642354, 30.460954110798646], + [114.12818014087874, 30.460874810459718] + ] + ] + } + }, + { + "type": "Feature", + "id": "74", + "properties": { "type": 2 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12802134345617, 30.461185257079794], + [114.12816026839353, 30.460968026637453], + [114.12821012605546, 30.46088886842086] + ] + ] + } + }, + { + "type": "Feature", + "id": "75", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12871659988954, 30.460480973960003], + [114.12866201732668, 30.460455225703175] + ] + ] + } + }, + { + "type": "Feature", + "id": "76", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12871181937012, 30.4604891720155], + [114.12865696068012, 30.460463293495675] + ] + ] + } + }, + { + "type": "Feature", + "id": "77", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12870625051855, 30.460496613714092], + [114.12865209001964, 30.46047106454629] + ] + ] + } + }, + { + "type": "Feature", + "id": "78", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12870119737104, 30.460504896214353], + [114.12864693030672, 30.46047929677077] + ] + ] + } + }, + { + "type": "Feature", + "id": "79", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12869621892378, 30.46051275631796], + [114.12864199196972, 30.46048717579036] + ] + ] + } + }, + { + "type": "Feature", + "id": "80", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12869144604645, 30.460520692930068], + [114.12863706353075, 30.46049503901376] + ] + ] + } + }, + { + "type": "Feature", + "id": "81", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12875206317393, 30.46042591203504], + [114.12869730457149, 30.46039987443165] + ] + ] + } + }, + { + "type": "Feature", + "id": "82", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12835089014197, 30.460401168581985], + [114.12840556493653, 30.460426761698077] + ] + ] + } + }, + { + "type": "Feature", + "id": "83", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12841050808538, 30.460419045241387], + [114.12835610108812, 30.460393178156856] + ] + ] + } + }, + { + "type": "Feature", + "id": "84", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1284204973323, 30.460403289755668], + [114.12836583665592, 30.46037757068294] + ] + ] + } + }, + { + "type": "Feature", + "id": "85", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12843095112684, 30.460387282485826], + [114.1283756424364, 30.460361694068006] + ] + ] + } + }, + { + "type": "Feature", + "id": "86", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12844040891842, 30.460371578901405], + [114.12838552459199, 30.46034569277411] + ] + ] + } + }, + { + "type": "Feature", + "id": "87", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12845530333595, 30.460347628036693], + [114.12840068992743, 30.460322072418204] + ] + ] + } + }, + { + "type": "Feature", + "id": "88", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1284709841291, 30.460324144822113], + [114.12841544462637, 30.460298280998398] + ] + ] + } + }, + { + "type": "Feature", + "id": "89", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12846564956904, 30.460331744768236], + [114.12841033665775, 30.46030594886737] + ] + ] + } + }, + { + "type": "Feature", + "id": "90", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12840565911587, 30.460314176099065], + [114.12846039800873, 30.460340059104666] + ] + ] + } + }, + { + "type": "Feature", + "id": "91", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12845085257727, 30.46035556238818], + [114.12839547046075, 30.460329999527442] + ] + ] + } + }, + { + "type": "Feature", + "id": "92", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12839045330449, 30.46033775951954], + [114.12844535891315, 30.460363628043794] + ] + ] + } + }, + { + "type": "Feature", + "id": "93", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12838043800423, 30.460353694855385], + [114.12843551638234, 30.46037934153785] + ] + ] + } + }, + { + "type": "Feature", + "id": "94", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1283709198198, 30.460369685801584], + [114.12842598062204, 30.46039522388622] + ] + ] + } + }, + { + "type": "Feature", + "id": "95", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12836108578529, 30.460385462589116], + [114.12841554195626, 30.460411069100882] + ] + ] + } + }, + { + "type": "Feature", + "id": "96", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12866685244558, 30.460447511350914], + [114.12872170540942, 30.460473387158626] + ] + ] + } + }, + { + "type": "Feature", + "id": "97", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12872659724891, 30.460465290681256], + [114.12867213942197, 30.460439405192986] + ] + ] + } + }, + { + "type": "Feature", + "id": "98", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12869225340944, 30.460407869796136], + [114.12874702860445, 30.46043391529476] + ] + ] + } + }, + { + "type": "Feature", + "id": "99", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12874224061314, 30.460442013970503], + [114.12868702900892, 30.460415968259927] + ] + ] + } + }, + { + "type": "Feature", + "id": "100", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12873675247232, 30.460449538567893], + [114.12868213874421, 30.460424010637905] + ] + ] + } + }, + { + "type": "Feature", + "id": "101", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12867707775104, 30.46043185095154], + [114.12873153978501, 30.460457592185595] + ] + ] + } + }, + { + "type": "Feature", + "id": "102", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12836533305023, 30.46049065278955], + [114.12831126438999, 30.46046493708541] + ] + ] + } + }, + { + "type": "Feature", + "id": "103", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12837057320787, 30.460482374315422], + [114.12831627884167, 30.460456915451616] + ] + ] + } + }, + { + "type": "Feature", + "id": "104", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.128375869697, 30.460474656315874], + [114.12832115618573, 30.460448963083365] + ] + ] + } + }, + { + "type": "Feature", + "id": "105", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12840057586054, 30.460434982343063], + [114.12834581797811, 30.4604093874583] + ] + ] + } + }, + { + "type": "Feature", + "id": "106", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12839557850852, 30.460443130646244], + [114.1283409619393, 30.46041732224048] + ] + ] + } + }, + { + "type": "Feature", + "id": "107", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12839069502158, 30.46045093856841], + [114.12833610026247, 30.460425094529544] + ] + ] + } + }, + { + "type": "Feature", + "id": "108", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12838572529347, 30.460458852915], + [114.1283310701576, 30.460433296250798] + ] + ] + } + }, + { + "type": "Feature", + "id": "109", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12838079578135, 30.46046681321431], + [114.12832598302165, 30.46044131635064] + ] + ] + } + }, + { + "type": "Feature", + "id": "110", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12837649906545, 30.4605135128412], + [114.12834694946365, 30.460560688846577] + ] + ] + } + }, + { + "type": "Feature", + "id": "111", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12854178593888, 30.46058975003992], + [114.12851202635618, 30.460637363832053] + ] + ] + } + }, + { + "type": "Feature", + "id": "112", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12853243489933, 30.460585826834613], + [114.12850285552688, 30.46063331813388] + ] + ] + } + }, + { + "type": "Feature", + "id": "113", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12852298308462, 30.460581441438844], + [114.1284932007625, 30.460628766063113] + ] + ] + } + }, + { + "type": "Feature", + "id": "114", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12851419252213, 30.46057753909397], + [114.12848460959708, 30.460624795756164] + ] + ] + } + }, + { + "type": "Feature", + "id": "115", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12850517472779, 30.460573235031077], + [114.12847496389253, 30.460620288973356] + ] + ] + } + }, + { + "type": "Feature", + "id": "116", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12849593701645, 30.460568989461134], + [114.12846619724603, 30.460616278867587] + ] + ] + } + }, + { + "type": "Feature", + "id": "117", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12843089699246, 30.46027479298183], + [114.12848603051319, 30.46029982760062] + ] + ] + } + }, + { + "type": "Feature", + "id": "118", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12842542716572, 30.460282399079777], + [114.12848046957723, 30.460308814914708] + ] + ] + } + }, + { + "type": "Feature", + "id": "119", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12842025416148, 30.46028980397502], + [114.1284756669986, 30.460316576604242] + ] + ] + } + }, + { + "type": "Feature", + "id": "120", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12848666488827, 30.460564499600935], + [114.12845654247133, 30.46061172678205] + ] + ] + } + }, + { + "type": "Feature", + "id": "121", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12847753955164, 30.46056031954453], + [114.12844747388621, 30.46060773734857] + ] + ] + } + }, + { + "type": "Feature", + "id": "122", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1284680946764, 30.460556051543414], + [114.12843832116972, 30.460603782216417] + ] + ] + } + }, + { + "type": "Feature", + "id": "123", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12845940547105, 30.460551880699725], + [114.12842920689394, 30.46059922350488] + ] + ] + } + }, + { + "type": "Feature", + "id": "124", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12845034586127, 30.46054758475326], + [114.12842010151311, 30.460595070910582] + ] + ] + } + }, + { + "type": "Feature", + "id": "125", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12844042699773, 30.460543180431102], + [114.12841116676191, 30.460590768567833] + ] + ] + } + }, + { + "type": "Feature", + "id": "126", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.128432077035, 30.460539134018713], + [114.12840149499199, 30.460586441619128] + ] + ] + } + }, + { + "type": "Feature", + "id": "127", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12842263910166, 30.460534983416075], + [114.12839221993035, 30.460582050888107] + ] + ] + } + }, + { + "type": "Feature", + "id": "128", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.128413479861, 30.460530541018002], + [114.12838377174411, 30.460578164755052] + ] + ] + } + }, + { + "type": "Feature", + "id": "129", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12840446028889, 30.46052665183327], + [114.12837433184895, 30.460573725428528] + ] + ] + } + }, + { + "type": "Feature", + "id": "130", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1283951588295, 30.4605220981727], + [114.12836525681436, 30.460569600517413] + ] + ] + } + }, + { + "type": "Feature", + "id": "131", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12838603082982, 30.46051800823294], + [114.12835560702274, 30.460564877103256] + ] + ] + } + }, + { + "type": "Feature", + "id": "132", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12863027713958, 30.460615146289182], + [114.12857543625488, 30.460589559185415] + ] + ] + } + }, + { + "type": "Feature", + "id": "138", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1286655605906, 30.460559846150645], + [114.12861113559894, 30.460533898069162] + ] + ] + } + }, + { + "type": "Feature", + "id": "139", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1286506066429, 30.460583705686243], + [114.12859608673774, 30.460557448859458] + ] + ] + } + }, + { + "type": "Feature", + "id": "140", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864519873487, 30.460591682994284], + [114.12859087146254, 30.46056594440731] + ] + ] + } + }, + { + "type": "Feature", + "id": "141", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1286402269583, 30.46059966951767], + [114.12858567860708, 30.460573673666502] + ] + ] + } + }, + { + "type": "Feature", + "id": "142", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12863514112153, 30.460607292800816], + [114.12858079067996, 30.460581634887177] + ] + ] + } + }, + { + "type": "Feature", + "id": "143", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12868567339142, 30.460528707643714], + [114.12863162666974, 30.460502262442503] + ] + ] + } + }, + { + "type": "Feature", + "id": "144", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12884532738447, 30.460284588142553], + [114.12883379933665, 30.46028716606622], + [114.12875726121462, 30.460304281487893] + ] + ] + } + }, + { + "type": "Feature", + "id": "145", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12875726121462, 30.460304281487893], + [114.1287495824418, 30.46029087357002], + [114.12873789098559, 30.460298886010346] + ] + ] + } + }, + { + "type": "Feature", + "id": "146", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864239023739, 30.460258399832206], + [114.1286478584979, 30.46026107747178], + [114.12867760736032, 30.46027479966971], + [114.12870731410098, 30.46028872831528], + [114.12873586697903, 30.460302115922662] + ] + ] + } + }, + { + "type": "Feature", + "id": "147", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12873586697903, 30.460302115922662], + [114.12873930594586, 30.460303728353384] + ] + ] + } + }, + { + "type": "Feature", + "id": "148", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12840022780429, 30.460226284750153], + [114.12838643674475, 30.460154021808915] + ] + ] + } + }, + { + "type": "Feature", + "id": "149", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12836213336523, 30.46034185152361], + [114.12837928019152, 30.460314398279753], + [114.1283957314117, 30.46028805870498], + [114.1284132054234, 30.460260081520367] + ] + ] + } + }, + { + "type": "Feature", + "id": "150", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12870837067852, 30.46032404079281], + [114.1286803577796, 30.4603724503829] + ] + ] + } + }, + { + "type": "Feature", + "id": "151", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12867109294197, 30.460368381122688], + [114.12869912869216, 30.460319932077443] + ] + ] + } + }, + { + "type": "Feature", + "id": "152", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12866183174938, 30.46036430555569], + [114.12868988865519, 30.460315819985393] + ] + ] + } + }, + { + "type": "Feature", + "id": "153", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12865257055421, 30.46036022998521], + [114.12868064861574, 30.46031170788965] + ] + ] + } + }, + { + "type": "Feature", + "id": "154", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864330935608, 30.460356154411024], + [114.1286715030655, 30.46030752306435] + ] + ] + } + }, + { + "type": "Feature", + "id": "155", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12863404815567, 30.460352078833306], + [114.12866216852906, 30.460303483687547] + ] + ] + } + }, + { + "type": "Feature", + "id": "156", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12862478695267, 30.460348003251934], + [114.12865292848181, 30.460299371581126] + ] + ] + } + }, + { + "type": "Feature", + "id": "157", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12861548891867, 30.46034392349733], + [114.12864368843199, 30.460295259471124] + ] + ] + } + }, + { + "type": "Feature", + "id": "158", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12860626453867, 30.460339852078516], + [114.12863444837981, 30.460291147357605] + ] + ] + } + }, + { + "type": "Feature", + "id": "159", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12859700332784, 30.460335776486477], + [114.1286252083248, 30.460287035240558] + ] + ] + } + }, + { + "type": "Feature", + "id": "160", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12854143600869, 30.460311322858644], + [114.12856976794158, 30.46026236246309] + ] + ] + } + }, + { + "type": "Feature", + "id": "161", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12853138266078, 30.46030689865123], + [114.12856027238867, 30.460258136623867] + ] + ] + } + }, + { + "type": "Feature", + "id": "162", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285209155493, 30.460302292354605], + [114.12855120127928, 30.460254099673847] + ] + ] + } + }, + { + "type": "Feature", + "id": "163", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12851162280957, 30.46029825874834], + [114.1285419586371, 30.46024998638347] + ] + ] + } + }, + { + "type": "Feature", + "id": "164", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12840807113457, 30.460157725686944], + [114.12847011220352, 30.460151560245418] + ] + ] + } + }, + { + "type": "Feature", + "id": "165", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1284101654926, 30.46016659318303], + [114.12847220656202, 30.460160427736348] + ] + ] + } + }, + { + "type": "Feature", + "id": "166", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12841225985079, 30.460175460679043], + [114.12847430092049, 30.460169295227086] + ] + ] + } + }, + { + "type": "Feature", + "id": "167", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12841435420903, 30.46018432817498], + [114.12847639527922, 30.46017816271769] + ] + ] + } + }, + { + "type": "Feature", + "id": "168", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12841644856744, 30.460193195670612], + [114.12847848963814, 30.46018703020827] + ] + ] + } + }, + { + "type": "Feature", + "id": "169", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12841854292579, 30.460202063166392], + [114.1284805839972, 30.4601958976989] + ] + ] + } + }, + { + "type": "Feature", + "id": "170", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12842063728452, 30.460210930662107], + [114.1284826783562, 30.460204765189165] + ] + ] + } + }, + { + "type": "Feature", + "id": "171", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12842273164331, 30.460219798157567], + [114.12848477271548, 30.46021363267947] + ] + ] + } + }, + { + "type": "Feature", + "id": "172", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12876862589741, 30.4603243180969], + [114.12878429704632, 30.460375964015952] + ] + ] + } + }, + { + "type": "Feature", + "id": "173", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12877832341091, 30.460321867552405], + [114.1287941676972, 30.460373502290036] + ] + ] + } + }, + { + "type": "Feature", + "id": "174", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12878830215362, 30.460319345936522], + [114.12880415582616, 30.460371011259845] + ] + ] + } + }, + { + "type": "Feature", + "id": "175", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12879828089203, 30.460316824316447], + [114.1288141439507, 30.46036852022551] + ] + ] + } + }, + { + "type": "Feature", + "id": "176", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12883819580152, 30.46030673779352], + [114.12885409640481, 30.460358556045676] + ] + ] + } + }, + { + "type": "Feature", + "id": "177", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12886408450736, 30.460356064989977], + [114.12884817451787, 30.460304216152167] + ] + ] + } + }, + { + "type": "Feature", + "id": "178", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12857900801183, 30.460266474601568], + [114.128550697235, 30.460315398472268] + ] + ] + } + }, + { + "type": "Feature", + "id": "179", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12858824807958, 30.460270586736574], + [114.12855995845868, 30.460319474082347] + ] + ] + } + }, + { + "type": "Feature", + "id": "180", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12859748814465, 30.460274698867888], + [114.12856921967986, 30.460323549688777] + ] + ] + } + }, + { + "type": "Feature", + "id": "181", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12860672820732, 30.460278810995728], + [114.12857848089843, 30.460327625291548] + ] + ] + } + }, + { + "type": "Feature", + "id": "182", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1286159682674, 30.46028292311988], + [114.1285877421146, 30.46033170089078] + ] + ] + } + }, + { + "type": "Feature", + "id": "183", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1288083184198, 30.460314494295], + [114.12882418147751, 30.460366190197742] + ] + ] + } + }, + { + "type": "Feature", + "id": "184", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12881835594338, 30.46031216426939], + [114.128834219, 30.46036386016563] + ] + ] + } + }, + { + "type": "Feature", + "id": "185", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12882815828664, 30.460309067827627], + [114.12884405889112, 30.460360886086296] + ] + ] + } + }, + { + "type": "Feature", + "id": "186", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12857969415214, 30.46067716294352], + [114.12864065377806, 30.460669210477786] + ] + ] + } + }, + { + "type": "Feature", + "id": "187", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12821095471107, 30.460587805847116], + [114.12822264139515, 30.460583823177657], + [114.12829655198448, 30.46055864893324] + ] + ] + } + }, + { + "type": "Feature", + "id": "188", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12859393102332, 30.46073889902016], + [114.12865733633403, 30.46073080933146] + ] + ] + } + }, + { + "type": "Feature", + "id": "189", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12865499232646, 30.460722154294714], + [114.12859194575512, 30.460730290201745] + ] + ] + } + }, + { + "type": "Feature", + "id": "190", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12859000904008, 30.460721891924944], + [114.12865258593759, 30.460713268918468] + ] + ] + } + }, + { + "type": "Feature", + "id": "191", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12865010693449, 30.460704115420466], + [114.12858781952768, 30.46071239743059] + ] + ] + } + }, + { + "type": "Feature", + "id": "192", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864775433783, 30.460695428666835], + [114.1285859385222, 30.46070424073049] + ] + ] + } + }, + { + "type": "Feature", + "id": "193", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864547745343, 30.46068702147333], + [114.1285838040565, 30.460694984936232] + ] + ] + } + }, + { + "type": "Feature", + "id": "194", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12864312222841, 30.460678325013305], + [114.12858175677763, 30.46068610721425] + ] + ] + } + }, + { + "type": "Feature", + "id": "195", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12826540486421, 30.46049167731894], + [114.128285766533, 30.460541251276027] + ] + ] + } + }, + { + "type": "Feature", + "id": "196", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12827623056698, 30.46054452442842], + [114.12825556202769, 30.46049482491383] + ] + ] + } + }, + { + "type": "Feature", + "id": "197", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12826632154945, 30.460547925622357], + [114.12824607169709, 30.460497859777327] + ] + ] + } + }, + { + "type": "Feature", + "id": "198", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12820831432073, 30.46056783604067], + [114.12818747687564, 30.460516597394804] + ] + ] + } + }, + { + "type": "Feature", + "id": "199", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1282178745576, 30.46056455459578], + [114.12819694070524, 30.460513571038067] + ] + ] + } + }, + { + "type": "Feature", + "id": "200", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12822757560343, 30.460561224814413], + [114.12820677922646, 30.46051042485647] + ] + ] + } + }, + { + "type": "Feature", + "id": "201", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12823755308571, 30.46055780014313], + [114.12821652067532, 30.460507234215115] + ] + ] + } + }, + { + "type": "Feature", + "id": "202", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12824678210885, 30.460554632367874], + [114.12822641133666, 30.46050414684501] + ] + ] + } + }, + { + "type": "Feature", + "id": "203", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12825617266687, 30.460551409142138], + [114.12823638920834, 30.460500956084466] + ] + ] + } + }, + { + "type": "Feature", + "id": "204", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12842232979635, 30.460618247013066], + [114.128417691069, 30.46061609233018], + [114.12838906067488, 30.46060279353459], + [114.12835891643661, 30.460588791534825], + [114.12832884115144, 30.460574821538472] + ] + ] + } + }, + { + "type": "Feature", + "id": "205", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12866189334268, 30.460662264222407], + [114.12867794830335, 30.460733719129816] + ] + ] + } + }, + { + "type": "Feature", + "id": "206", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285007257757, 30.460693399910237], + [114.12850290961815, 30.460684535480944], + [114.12849141514032, 30.460684593394582] + ] + ] + } + }, + { + "type": "Feature", + "id": "207", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12849941394543, 30.460671692237295], + [114.12851919055488, 30.460673065270363], + [114.12851723573179, 30.46068725357128] + ] + ] + } + }, + { + "type": "Feature", + "id": "208", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12850706592427, 30.460659350460933], + [114.12853856699874, 30.460659154402517], + [114.1285338333218, 30.460682142372615] + ] + ] + } + }, + { + "type": "Feature", + "id": "209", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1284732864388, 30.460713834476344], + [114.12847872288397, 30.460705064530792] + ] + ] + } + }, + { + "type": "Feature", + "id": "210", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12847872288397, 30.460705064530792], + [114.12849141514032, 30.460684593394582] + ] + ] + } + }, + { + "type": "Feature", + "id": "211", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12849141514032, 30.460684593394582], + [114.12849941394543, 30.460671692237295] + ] + ] + } + }, + { + "type": "Feature", + "id": "212", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12849941394543, 30.460671692237295], + [114.12850706592427, 30.460659350460933] + ] + ] + } + }, + { + "type": "Feature", + "id": "213", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12850706592427, 30.460659350460933], + [114.1285079053109, 30.46065799662477], + [114.1285193386141, 30.460639796030616], + [114.12858022522573, 30.460671950374874], + [114.12857584464733, 30.460672901241058], + [114.1285661300287, 30.46067430161067], + [114.12855794056122, 30.460675779314688], + [114.12854994047656, 30.46067753164059], + [114.12853872035802, 30.460680668239995], + [114.1285338333218, 30.460682142372615] + ] + ] + } + }, + { + "type": "Feature", + "id": "214", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285338333218, 30.460682142372615], + [114.1285263397709, 30.46068440273543], + [114.12851723573179, 30.46068725357128] + ] + ] + } + }, + { + "type": "Feature", + "id": "215", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12851723573179, 30.46068725357128], + [114.12851419616699, 30.460688205378652], + [114.12850163985091, 30.460692973529728], + [114.1285007257757, 30.460693399910237] + ] + ] + } + }, + { + "type": "Feature", + "id": "216", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285007257757, 30.460693399910237], + [114.12848441607913, 30.46070100774001], + [114.12847872288397, 30.460705064530792] + ] + ] + } + }, + { + "type": "Feature", + "id": "217", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12862658288552, 30.460617500482353], + [114.12862756382431, 30.460621398461406] + ] + ] + } + }, + { + "type": "Feature", + "id": "218", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12862756382431, 30.460621398461406], + [114.12863622422122, 30.46065581248366], + [114.12863622422124, 30.460655812483694] + ] + ] + } + }, + { + "type": "Feature", + "id": "219", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12863622422124, 30.460655812483694], + [114.12863837299199, 30.460664351103055], + [114.1286501161541, 30.460662969423137], + [114.12865149721733, 30.46066288672742] + ] + ] + } + }, + { + "type": "Feature", + "id": "220", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12865149721733, 30.46066288672742], + [114.12866189334268, 30.460662264222407] + ] + ] + } + }, + { + "type": "Feature", + "id": "221", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12866189334268, 30.460662264222407], + [114.12866670776984, 30.460661975939868], + [114.12867346315322, 30.460661452813614] + ] + ] + } + }, + { + "type": "Feature", + "id": "222", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12867346315322, 30.460661452813614], + [114.12868308922111, 30.460660707382488], + [114.1286930532053, 30.460660015850845], + [114.12870155760558, 30.460659491930894], + [114.12871156753305, 30.460659360647206], + [114.12871540763784, 30.46065931028169], + [114.12870178263275, 30.46065286645476], + [114.12868143900795, 30.46064307478373] + ] + ] + } + }, + { + "type": "Feature", + "id": "223", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12868143900795, 30.46064307478373], + [114.12867312388286, 30.460639072594365], + [114.12865979441074, 30.460632896425178] + ] + ] + } + }, + { + "type": "Feature", + "id": "224", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12865979441074, 30.460632896425178], + [114.12864842897787, 30.4606276302832], + [114.12862898061579, 30.460618618930347], + [114.12862658288563, 30.460617500482353] + ] + ] + } + }, + { + "type": "Feature", + "id": "225", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12862756382431, 30.460621398461406], + [114.1286536981893, 30.460646726631666], + [114.12863622422124, 30.460655812483694] + ] + ] + } + }, + { + "type": "Feature", + "id": "226", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12863622422124, 30.460655812483694], + [114.12863621216883, 30.46065581875052] + ] + ] + } + }, + { + "type": "Feature", + "id": "227", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12865979441074, 30.460632896425178], + [114.12867149838834, 30.460650389640094], + [114.12865149721733, 30.46066288672742] + ] + ] + } + }, + { + "type": "Feature", + "id": "228", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12868143900795, 30.46064307478373], + [114.12868869484318, 30.46065366482945], + [114.12867346315322, 30.460661452813614] + ] + ] + } + }, + { + "type": "Feature", + "id": "229", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12854918983022, 30.460227838404215], + [114.12852234392066, 30.460227942017273], + [114.1285323730125, 30.46020906884395] + ] + ] + } + }, + { + "type": "Feature", + "id": "230", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285457397978, 30.460205305973776], + [114.12854351565343, 30.460216013087265], + [114.1285571368483, 30.460216067679987] + ] + ] + } + }, + { + "type": "Feature", + "id": "231", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12857029988476, 30.460196571231123], + [114.1285571368483, 30.460216067679987] + ] + ] + } + }, + { + "type": "Feature", + "id": "232", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285571368483, 30.460216067679987], + [114.12854918983022, 30.460227838404215] + ] + ] + } + }, + { + "type": "Feature", + "id": "233", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12854918983022, 30.460227838404215], + [114.12853544237832, 30.460248200417247], + [114.12848080370732, 30.460219963867853], + [114.12851469333377, 30.46021348906026], + [114.1285323730125, 30.46020906884395] + ] + ] + } + }, + { + "type": "Feature", + "id": "234", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285323730125, 30.46020906884395], + [114.1285457397978, 30.460205305973776] + ] + ] + } + }, + { + "type": "Feature", + "id": "235", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1285457397978, 30.460205305973776], + [114.12857029988476, 30.460196571231123] + ] + ] + } + }, + { + "type": "Feature", + "id": "236", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12876040839092, 30.460323767807377], + [114.12874507413414, 30.4603128520213], + [114.12871981540793, 30.460327731040778] + ] + ] + } + }, + { + "type": "Feature", + "id": "237", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12872991929535, 30.46031166450774], + [114.12874684379823, 30.46030173077994], + [114.12875876209284, 30.46031388020817] + ] + ] + } + }, + { + "type": "Feature", + "id": "238", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12875557087689, 30.46027086584409], + [114.12873789098559, 30.460298886010346] + ] + ] + } + }, + { + "type": "Feature", + "id": "239", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12873789098559, 30.460298886010346], + [114.12873586697903, 30.460302115922662] + ] + ] + } + }, + { + "type": "Feature", + "id": "240", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12873586697903, 30.460302115922662], + [114.12871981540793, 30.460327731040778] + ] + ] + } + }, + { + "type": "Feature", + "id": "241", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12871981540793, 30.460327731040778], + [114.1287182902953, 30.460330156172322], + [114.12876098693141, 30.460327242497126], + [114.12876040839092, 30.460323767807377] + ] + ] + } + }, + { + "type": "Feature", + "id": "242", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12876040839092, 30.460323767807377], + [114.12875876209284, 30.46031388020817] + ] + ] + } + }, + { + "type": "Feature", + "id": "243", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12875876209284, 30.46031388020817], + [114.12875749610893, 30.460306276759738], + [114.12875726121462, 30.460304281487893] + ] + ] + } + }, + { + "type": "Feature", + "id": "244", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12875726121462, 30.460304281487893], + [114.12875671862989, 30.460299672590363], + [114.12875557087689, 30.46027086584409] + ] + ] + } + }, + { + "type": "Feature", + "id": "245", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12879200707901, 30.460408797342957], + [114.12877165734105, 30.460407878627482], + [114.12877202801093, 30.460429485397974] + ] + ] + } + }, + { + "type": "Feature", + "id": "246", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12878636449918, 30.46043625423225], + [114.12878583346638, 30.460423139484174], + [114.1288001539798, 30.46042339124335] + ] + ] + } + }, + { + "type": "Feature", + "id": "247", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1288067493305, 30.460433949312637], + [114.12880674933038, 30.460433949312637] + ] + ] + } + }, + { + "type": "Feature", + "id": "248", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12880674933038, 30.460433949312637], + [114.12879787001776, 30.46043388887097], + [114.12879753025778, 30.460441489701243] + ] + ] + } + }, + { + "type": "Feature", + "id": "249", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1288535470595, 30.46046745842134], + [114.12882277886841, 30.460453322501046] + ] + ] + } + }, + { + "type": "Feature", + "id": "250", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12882277886841, 30.460453322501046], + [114.12879753025778, 30.460441489701243] + ] + ] + } + }, + { + "type": "Feature", + "id": "251", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12879753025778, 30.460441489701243], + [114.12878636449918, 30.46043625423225] + ] + ] + } + }, + { + "type": "Feature", + "id": "252", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12878636449918, 30.46043625423225], + [114.12877202801093, 30.460429485397974] + ] + ] + } + }, + { + "type": "Feature", + "id": "253", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12877202801093, 30.460429485397974], + [114.12875440144785, 30.460421163179134], + [114.12877706273885, 30.460376546593384], + [114.12879030510373, 30.46040557140097], + [114.12879200707901, 30.460408797342957] + ] + ] + } + }, + { + "type": "Feature", + "id": "254", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12879200707901, 30.460408797342957], + [114.1288001539798, 30.46042339124335] + ] + ] + } + }, + { + "type": "Feature", + "id": "255", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1288001539798, 30.46042339124335], + [114.12880674933038, 30.460433949312637] + ] + ] + } + }, + { + "type": "Feature", + "id": "256", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12880674933038, 30.460433949312637], + [114.1288067493305, 30.460433949312808], + [114.12881158715328, 30.460441153762385], + [114.12881787573671, 30.46044796947164], + [114.12882277886841, 30.460453322501046] + ] + ] + } + }, + { + "type": "Feature", + "id": "257", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12837302917117, 30.46022523007898], + [114.12836095507677, 30.46022907665788], + [114.12836617769591, 30.460238227787542] + ] + ] + } + }, + { + "type": "Feature", + "id": "258", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1283270929556, 30.460220065105005], + [114.12836404472645, 30.460224633610444], + [114.12837302917117, 30.46022523007898] + ] + ] + } + }, + { + "type": "Feature", + "id": "259", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12837302917117, 30.46022523007898], + [114.12840022780429, 30.460226284750153] + ] + ] + } + }, + { + "type": "Feature", + "id": "260", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12840022780429, 30.460226284750153], + [114.12842428170627, 30.460225607748736], + [114.12843554363837, 30.460270462042168], + [114.1284132054234, 30.460260081520367] + ] + ] + } + }, + { + "type": "Feature", + "id": "261", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1284132054234, 30.460260081520367], + [114.12836617769591, 30.460238227787542] + ] + ] + } + }, + { + "type": "Feature", + "id": "262", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12836617769591, 30.460238227787542], + [114.12834456974755, 30.460228342316125], + [114.1283270929556, 30.460220065105005] + ] + ] + } + }, + { + "type": "Feature", + "id": "263", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1282594361881, 30.460470610747606], + [114.12828158879684, 30.460469862575287], + [114.12827668978332, 30.460452006380965] + ] + ] + } + }, + { + "type": "Feature", + "id": "264", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12824988935719, 30.460455290365648], + [114.1282626235959, 30.46045470735359], + [114.12825957569038, 30.460443946624476] + ] + ] + } + }, + { + "type": "Feature", + "id": "265", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12831057643545, 30.460467821384352], + [114.12828954958354, 30.46045806260415], + [114.12827668978332, 30.460452006380965] + ] + ] + } + }, + { + "type": "Feature", + "id": "266", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12827668978332, 30.460452006380965], + [114.12825957569038, 30.460443946624476] + ] + ] + } + }, + { + "type": "Feature", + "id": "267", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12825957569038, 30.460443946624476], + [114.12823116917164, 30.460430565779617], + [114.12823513363749, 30.460435047164694], + [114.1282410985911, 30.460442480098674], + [114.12824699456962, 30.460450849727245], + [114.12824988935719, 30.460455290365648] + ] + ] + } + }, + { + "type": "Feature", + "id": "268", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12824988935719, 30.460455290365648], + [114.12825612343089, 30.460464853507645], + [114.1282594361881, 30.460470610747606] + ] + ] + } + }, + { + "type": "Feature", + "id": "269", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1282594361881, 30.460470610747606], + [114.1282621451196, 30.460475318598228], + [114.12827004285334, 30.460490559238714], + [114.12827031807959, 30.460491096327623], + [114.12831057643545, 30.460467821384352] + ] + ] + } + }, + { + "type": "Feature", + "id": "270", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12829079823528, 30.46053987849389], + [114.12834003760709, 30.46055681816805] + ] + ] + } + }, + { + "type": "Feature", + "id": "271", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12830107585357, 30.460574929528217], + [114.12831134133391, 30.4605855186706], + [114.1283308918113, 30.460571522243782] + ] + ] + } + }, + { + "type": "Feature", + "id": "272", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1283308918113, 30.460571522243782], + [114.12833090616948, 30.460571511964588] + ] + ] + } + }, + { + "type": "Feature", + "id": "273", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12829079823528, 30.46053987849389], + [114.12829185294262, 30.46054275428451], + [114.12829352173044, 30.460548399008697] + ] + ] + } + }, + { + "type": "Feature", + "id": "274", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12829352173044, 30.460548399008697], + [114.12829352173046, 30.460548399008758], + [114.12829655198448, 30.46055864893324] + ] + ] + } + }, + { + "type": "Feature", + "id": "275", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12829655198448, 30.46055864893324], + [114.12830072615508, 30.460573305664308], + [114.12830107585357, 30.460574929528217] + ] + ] + } + }, + { + "type": "Feature", + "id": "276", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12830107585357, 30.460574929528217], + [114.12830350703699, 30.460586218995534], + [114.1283057058045, 30.460595944723526], + [114.12830651401676, 30.460604188710853], + [114.12829868477651, 30.46062353496956], + [114.12831145944958, 30.460602786826506], + [114.12832884115144, 30.460574821538472] + ] + ] + } + }, + { + "type": "Feature", + "id": "277", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12832884115144, 30.460574821538472], + [114.1283308918113, 30.460571522243782] + ] + ] + } + }, + { + "type": "Feature", + "id": "278", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.1283308918113, 30.460571522243782], + [114.12833089181142, 30.460571522243605], + [114.1283379454297, 30.460560173714523], + [114.12834003760709, 30.46055681816805] + ] + ] + } + }, + { + "type": "Feature", + "id": "279", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12834003760709, 30.46055681816805], + [114.12831411299864, 30.460568000808475], + [114.12829352173044, 30.460548399008697] + ] + ] + } + }, + { + "type": "Feature", + "id": "280", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12829352173044, 30.460548399008697], + [114.12829350753343, 30.460548385493894] + ] + ] + } + }, + { + "type": "Feature", + "id": "281", + "properties": { "type": 5 }, + "geometry": { + "type": "MultiLineString", + "coordinates": [ + [ + [114.12871283204083, 30.46054890605957], + [114.12869540616136, 30.460576642138296], + [114.12867798025998, 30.460604378204753], + [114.12866062846345, 30.460631996274866] + ] + ] + } + } + ] +} diff --git a/test/index.js b/test/index.js index 76c9b14..d71e986 100644 --- a/test/index.js +++ b/test/index.js @@ -1,25 +1,31 @@ -"use strict"; +'use strict'; -const assert = require("assert/strict"); -const fs = require("fs"); -const path = require("path"); -const compiler = require("../src"); +const assert = require('assert/strict'); +const fs = require('fs'); +const path = require('path'); +const compiler = require('../src'); -assert.equal(typeof compiler.laneGeometry.laneCenterline, "function"); -assert.equal(typeof compiler.gaodeReference.convertGeoJson, "function"); -assert.equal(typeof compiler.turnLaneArrows.buildCustomTurnLaneArrows, "function"); -assert.equal(typeof compiler.complexJunction.buildComplexJunctionGeometry, "function"); -assert.equal(typeof compiler.nativeRoad.compileRoadModel, "function"); -assert.equal(typeof compiler.check.checkOutput, "function"); -assert.equal(typeof compiler.layerManifest.manifestForArea, "function"); -const manifest = compiler.layerManifest.manifestForArea("fixture"); -assert.equal(manifest.contract, "native-road-package/v1.1"); +assert.equal(typeof compiler.laneGeometry.laneCenterline, 'function'); +assert.equal(typeof compiler.gaodeReference.convertGeoJson, 'function'); +assert.equal(typeof compiler.turnLaneArrows.buildCustomTurnLaneArrows, 'function'); +assert.equal(typeof compiler.complexJunction.buildComplexJunctionGeometry, 'function'); +assert.equal(typeof compiler.nativeRoad.compileRoadModel, 'function'); +assert.equal(typeof compiler.check.checkOutput, 'function'); +assert.equal(typeof compiler.layerManifest.manifestForArea, 'function'); +const manifest = compiler.layerManifest.manifestForArea('fixture'); +assert.equal(manifest.contract, 'native-road-package/v1.1'); assert.equal(manifest.layers.length, 12); -assert.deepEqual(manifest.layers.filter((layer) => layer.role === "semantic").map((layer) => layer.source), ["lane_centerlines", "connectors"]); -assert.equal(manifest.layers.find((layer) => layer.source === "center_lines").splitBy.cases[0].match, "white"); -const fixture = path.join(__dirname, "fixtures", "fengshu-er-road.osm"); +assert.deepEqual( + manifest.layers.filter((layer) => layer.role === 'semantic').map((layer) => layer.source), + ['lane_centerlines', 'connectors'], +); +assert.equal(manifest.layers.find((layer) => layer.source === 'center_lines').splitBy.cases[0].match, 'white'); +const fixture = path.join(__dirname, 'fixtures', 'fengshu-er-road.osm'); assert.ok(fs.existsSync(fixture)); -assert.throws(() => compiler.compiler.validateInput({ id: "area" }), /RoadCompilerInput/); -const model = compiler.nativeRoad.compileRoadModel(fs.readFileSync(fixture, "utf8"), { schema: "native-road-overrides/v1", overrides: [] }); +assert.throws(() => compiler.compiler.validateInput({ id: 'area' }), /RoadCompilerInput/); +const model = compiler.nativeRoad.compileRoadModel(fs.readFileSync(fixture, 'utf8'), { + schema: 'native-road-overrides/v1', + overrides: [], +}); assert.equal(model.roads.length, 24); -console.log("road compiler package tests passed"); +console.log('road compiler package tests passed'); diff --git a/workbench/client/app.css b/workbench/client/app.css index 4c58311..fd289d8 100644 --- a/workbench/client/app.css +++ b/workbench/client/app.css @@ -1 +1,223 @@ -*{box-sizing:border-box}body{margin:0;background:#eef1ef;color:#202523;font:14px system-ui,sans-serif}header{height:50px;display:flex;gap:12px;align-items:center;padding:0 16px;background:#183a32;color:#fff}header span{color:#c9d8d2}#dirty-state.dirty{color:#ffe08a;font-weight:700}button{border:1px solid #82988f;background:#fff;color:#1d392f;padding:7px 10px;border-radius:3px;cursor:pointer}button:disabled{cursor:default;opacity:.55}header button:first-of-type{margin-left:auto}main{display:grid;grid-template-columns:260px minmax(0,1fr) 320px;height:calc(100vh - 50px)}aside{overflow:auto;background:#fff;padding:16px}.issues{border-right:1px solid #d5dfda}.inspector{border-left:1px solid #d5dfda}.map{position:relative;background:#d7e2de;min-height:400px}canvas{width:100%;height:100%;display:block}.legend{position:absolute;bottom:12px;left:12px;background:#fff;padding:8px;box-shadow:0 1px 4px #0003}.legend i{display:inline-block;width:18px;height:7px;margin:0 4px -1px 10px}.reference{background:#a5b0b5}.native{background:#296654}.line{height:3px!important;background:#263630}.junction{width:10px!important;height:10px!important;background:#0e7860;border-radius:50%}.warning{width:10px!important;height:10px!important;background:#d49318;border-radius:50%}h1{font-size:16px;margin:0 0 8px}h2{font-size:14px;margin:12px 0 8px}.muted,output,pre{color:#52615b}.issues ul{list-style:none;padding:0;margin:0}.issues button{width:100%;text-align:left;margin:4px 0;background:#fff7e5;border-color:#e7c67b;color:#693c00}.issues button.error{background:#fff0ee;border-color:#e3a49b;color:#8d261a}.segmented{display:flex;margin:0 0 8px}.segmented button{flex:1;border-radius:0;padding:6px 4px;font-size:12px}.segmented button+button{border-left:0}.segmented button:first-child{border-radius:3px 0 0 3px}.segmented button:last-child{border-radius:0 3px 3px 0}.segmented button.active{background:#286956;border-color:#286956;color:#fff}label{display:block;margin:10px 0}input[type=number]{display:block;width:100%;padding:7px;border:1px solid #aab8b2;border-radius:2px}output,pre{display:block;white-space:pre-wrap;overflow-wrap:anywhere}form button{margin-top:8px;background:#286956;color:white;border:0}dl{display:grid;grid-template-columns:1fr auto;gap:5px 10px;margin:0}dt{color:#52615b}dd{margin:0;font-variant-numeric:tabular-nums}hr{border:0;border-top:1px solid #dde4e1;margin:16px 0}details{margin-top:16px}summary{cursor:pointer;font-weight:600}@media(max-width:900px){main{grid-template-columns:minmax(0,1fr)}.issues{display:none}.inspector{position:absolute;right:0;bottom:0;width:min(360px,100%);max-height:55vh;border-top:1px solid #d5dfda}} +* { + box-sizing: border-box; +} +body { + margin: 0; + background: #eef1ef; + color: #202523; + font: + 14px system-ui, + sans-serif; +} +header { + height: 50px; + display: flex; + gap: 12px; + align-items: center; + padding: 0 16px; + background: #183a32; + color: #fff; +} +header span { + color: #c9d8d2; +} +#dirty-state.dirty { + color: #ffe08a; + font-weight: 700; +} +button { + border: 1px solid #82988f; + background: #fff; + color: #1d392f; + padding: 7px 10px; + border-radius: 3px; + cursor: pointer; +} +button:disabled { + cursor: default; + opacity: 0.55; +} +header button:first-of-type { + margin-left: auto; +} +main { + display: grid; + grid-template-columns: 260px minmax(0, 1fr) 320px; + height: calc(100vh - 50px); +} +aside { + overflow: auto; + background: #fff; + padding: 16px; +} +.issues { + border-right: 1px solid #d5dfda; +} +.inspector { + border-left: 1px solid #d5dfda; +} +.map { + position: relative; + background: #d7e2de; + min-height: 400px; +} +canvas { + width: 100%; + height: 100%; + display: block; +} +.legend { + position: absolute; + bottom: 12px; + left: 12px; + background: #fff; + padding: 8px; + box-shadow: 0 1px 4px #0003; +} +.legend i { + display: inline-block; + width: 18px; + height: 7px; + margin: 0 4px -1px 10px; +} +.reference { + background: #a5b0b5; +} +.native { + background: #296654; +} +.line { + height: 3px !important; + background: #263630; +} +.junction { + width: 10px !important; + height: 10px !important; + background: #0e7860; + border-radius: 50%; +} +.warning { + width: 10px !important; + height: 10px !important; + background: #d49318; + border-radius: 50%; +} +h1 { + font-size: 16px; + margin: 0 0 8px; +} +h2 { + font-size: 14px; + margin: 12px 0 8px; +} +.muted, +output, +pre { + color: #52615b; +} +.issues ul { + list-style: none; + padding: 0; + margin: 0; +} +.issues button { + width: 100%; + text-align: left; + margin: 4px 0; + background: #fff7e5; + border-color: #e7c67b; + color: #693c00; +} +.issues button.error { + background: #fff0ee; + border-color: #e3a49b; + color: #8d261a; +} +.segmented { + display: flex; + margin: 0 0 8px; +} +.segmented button { + flex: 1; + border-radius: 0; + padding: 6px 4px; + font-size: 12px; +} +.segmented button + button { + border-left: 0; +} +.segmented button:first-child { + border-radius: 3px 0 0 3px; +} +.segmented button:last-child { + border-radius: 0 3px 3px 0; +} +.segmented button.active { + background: #286956; + border-color: #286956; + color: #fff; +} +label { + display: block; + margin: 10px 0; +} +input[type='number'] { + display: block; + width: 100%; + padding: 7px; + border: 1px solid #aab8b2; + border-radius: 2px; +} +output, +pre { + display: block; + white-space: pre-wrap; + overflow-wrap: anywhere; +} +form button { + margin-top: 8px; + background: #286956; + color: white; + border: 0; +} +dl { + display: grid; + grid-template-columns: 1fr auto; + gap: 5px 10px; + margin: 0; +} +dt { + color: #52615b; +} +dd { + margin: 0; + font-variant-numeric: tabular-nums; +} +hr { + border: 0; + border-top: 1px solid #dde4e1; + margin: 16px 0; +} +details { + margin-top: 16px; +} +summary { + cursor: pointer; + font-weight: 600; +} +@media (max-width: 900px) { + main { + grid-template-columns: minmax(0, 1fr); + } + .issues { + display: none; + } + .inspector { + position: absolute; + right: 0; + bottom: 0; + width: min(360px, 100%); + max-height: 55vh; + border-top: 1px solid #d5dfda; + } +} diff --git a/workbench/client/app.js b/workbench/client/app.js index 3a3a633..c928191 100644 --- a/workbench/client/app.js +++ b/workbench/client/app.js @@ -1,77 +1,82 @@ -import Map from "/vendor/ol/Map.js"; -import View from "/vendor/ol/View.js"; -import VectorLayer from "/vendor/ol/layer/Vector.js"; -import VectorSource from "/vendor/ol/source/Vector.js"; -import GeoJSON from "/vendor/ol/format/GeoJSON.js"; -import Feature from "/vendor/ol/Feature.js"; -import LineString from "/vendor/ol/geom/LineString.js"; -import Point from "/vendor/ol/geom/Point.js"; -import Style from "/vendor/ol/style/Style.js"; -import Fill from "/vendor/ol/style/Fill.js"; -import Stroke from "/vendor/ol/style/Stroke.js"; -import CircleStyle from "/vendor/ol/style/Circle.js"; -import Text from "/vendor/ol/style/Text.js"; -import Polygon from "/vendor/ol/geom/Polygon.js"; -import RegularShape from "/vendor/ol/style/RegularShape.js"; -import Select from "/vendor/ol/interaction/Select.js"; -import { click } from "/vendor/ol/events/condition.js"; -import { fromLonLat } from "/vendor/ol/proj.js"; +import Map from '/vendor/ol/Map.js'; +import View from '/vendor/ol/View.js'; +import VectorLayer from '/vendor/ol/layer/Vector.js'; +import VectorSource from '/vendor/ol/source/Vector.js'; +import GeoJSON from '/vendor/ol/format/GeoJSON.js'; +import Feature from '/vendor/ol/Feature.js'; +import LineString from '/vendor/ol/geom/LineString.js'; +import Point from '/vendor/ol/geom/Point.js'; +import Style from '/vendor/ol/style/Style.js'; +import Fill from '/vendor/ol/style/Fill.js'; +import Stroke from '/vendor/ol/style/Stroke.js'; +import CircleStyle from '/vendor/ol/style/Circle.js'; +import Text from '/vendor/ol/style/Text.js'; +import Polygon from '/vendor/ol/geom/Polygon.js'; +import RegularShape from '/vendor/ol/style/RegularShape.js'; +import Select from '/vendor/ol/interaction/Select.js'; +import { click } from '/vendor/ol/events/condition.js'; +import { fromLonLat } from '/vendor/ol/proj.js'; const geojson = new GeoJSON(); -const areaLabel = document.querySelector("#area"); -const status = document.querySelector("#status"); -const form = document.querySelector("#road-form"); -const hint = document.querySelector("#hint"); -const roadName = document.querySelector("#road-name"); -const movementSummary = document.querySelector("#movement-summary"); -const laneConvention = document.querySelector("#lane-convention"); -const selectedMovementPanel = document.querySelector("#selected-movement"); -const movementDetail = document.querySelector("#movement-detail"); -const directionSwitch = document.querySelector("#direction-switch"); -const widthInput = document.querySelector("#width"); -const lanesInput = document.querySelector("#lanes"); -const leftInput = document.querySelector("#left"); -const rightInput = document.querySelector("#right"); -const centerLineForm = document.querySelector("#center-line-form"); -const centerLineSegment = document.querySelector("#center-line-segment"); -const markingStyleHeading = document.querySelector("#marking-style-heading"); -const centerLineStyleInput = document.querySelector("#center-line-style"); -const doubleYellowOption = document.createElement("option"); -doubleYellowOption.value = "double-yellow-solid"; doubleYellowOption.textContent = "双黄实线"; centerLineStyleInput.append(doubleYellowOption); -const evidence = document.querySelector("#evidence"); -const diagnostics = document.querySelector("#diagnostics"); -const diagnosticFilters = document.querySelector("#diagnostic-filters"); -const summary = document.querySelector("#summary"); -const connectionsBox = document.querySelector("#connections"); -const addConnectionButton = document.querySelector("#add-connection"); -const saveButton = document.querySelector("#save"); -const compileButton = document.querySelector("#compile"); -const dirtyState = document.querySelector("#dirty-state"); -const scenePreviewToggle = document.querySelector("#scene-preview"); -const selectedJunctionPanel = document.querySelector("#selected-junction"); -const junctionDetail = document.querySelector("#junction-detail"); -const directionArrowsToggle = document.createElement("label"); +const areaLabel = document.querySelector('#area'); +const status = document.querySelector('#status'); +const form = document.querySelector('#road-form'); +const hint = document.querySelector('#hint'); +const roadName = document.querySelector('#road-name'); +const movementSummary = document.querySelector('#movement-summary'); +const laneConvention = document.querySelector('#lane-convention'); +const selectedMovementPanel = document.querySelector('#selected-movement'); +const movementDetail = document.querySelector('#movement-detail'); +const directionSwitch = document.querySelector('#direction-switch'); +const widthInput = document.querySelector('#width'); +const lanesInput = document.querySelector('#lanes'); +const leftInput = document.querySelector('#left'); +const rightInput = document.querySelector('#right'); +const centerLineForm = document.querySelector('#center-line-form'); +const centerLineSegment = document.querySelector('#center-line-segment'); +const markingStyleHeading = document.querySelector('#marking-style-heading'); +const centerLineStyleInput = document.querySelector('#center-line-style'); +const doubleYellowOption = document.createElement('option'); +doubleYellowOption.value = 'double-yellow-solid'; +doubleYellowOption.textContent = '双黄实线'; +centerLineStyleInput.append(doubleYellowOption); +const evidence = document.querySelector('#evidence'); +const diagnostics = document.querySelector('#diagnostics'); +const diagnosticFilters = document.querySelector('#diagnostic-filters'); +const summary = document.querySelector('#summary'); +const connectionsBox = document.querySelector('#connections'); +const addConnectionButton = document.querySelector('#add-connection'); +const saveButton = document.querySelector('#save'); +const compileButton = document.querySelector('#compile'); +const dirtyState = document.querySelector('#dirty-state'); +const scenePreviewToggle = document.querySelector('#scene-preview'); +const selectedJunctionPanel = document.querySelector('#selected-junction'); +const junctionDetail = document.querySelector('#junction-detail'); +const directionArrowsToggle = document.createElement('label'); directionArrowsToggle.innerHTML = ' 道路方向箭头'; -const markingsToggle = document.createElement("label"); +const markingsToggle = document.createElement('label'); markingsToggle.innerHTML = ' 车道分隔线与路口转向箭头'; -const centerLinesToggle = document.createElement("label"); +const centerLinesToggle = document.createElement('label'); centerLinesToggle.innerHTML = ' 道路中心线'; -const edgeLinesToggle = document.createElement("label"); +const edgeLinesToggle = document.createElement('label'); edgeLinesToggle.innerHTML = ' 道路外缘线'; -const controlsToggle = document.createElement("label"); +const controlsToggle = document.createElement('label'); controlsToggle.innerHTML = ' 斑马线与停止线'; -const signalsToggle = document.createElement("label"); +const signalsToggle = document.createElement('label'); signalsToggle.innerHTML = ' 红绿灯设施'; -document.querySelector('[data-layer="lanes"]').closest("label").after(directionArrowsToggle, markingsToggle, centerLinesToggle, edgeLinesToggle, controlsToggle, signalsToggle); +document + .querySelector('[data-layer="lanes"]') + .closest('label') + .after(directionArrowsToggle, markingsToggle, centerLinesToggle, edgeLinesToggle, controlsToggle, signalsToggle); // Only offered when the server was started with --debug; without it the state // carries no candidates and an empty toggle would just be confusing. -const candidateAction = document.createElement("section"); -document.querySelector(".inspector").insertBefore(candidateAction, document.querySelector(".inspector details")); -const candidatesToggle = document.createElement("label"); +const candidateAction = document.createElement('section'); +document.querySelector('.inspector').insertBefore(candidateAction, document.querySelector('.inspector details')); +const candidatesToggle = document.createElement('label'); candidatesToggle.hidden = true; candidatesToggle.innerHTML = ' 复杂路口候选(debug)'; signalsToggle.after(candidatesToggle); -const gaodeReferenceColors = { 1: "#2563eb", 2: "#0f766e", 3: "#7c3aed", 4: "#ea580c", 5: "#64748b" }; +const gaodeReferenceColors = { 1: '#2563eb', 2: '#0f766e', 3: '#7c3aed', 4: '#ea580c', 5: '#64748b' }; let state; let selectedRoad = null; @@ -79,150 +84,544 @@ let selectedMovement = null; let selectedJunction = null; let staged = []; let manualFromEndpoint = null; -let diagnosticFilter = "all"; +let diagnosticFilter = 'all'; let scenePreview = false; let selectedCenterLineSegment = null; let selectedLaneSeparator = null; let selectedEdgeLine = null; let selectedSignal = null; -const signalPanel = document.createElement("section"); -signalPanel.innerHTML = '

原生红绿灯

'; -document.querySelector(".inspector").insertBefore(signalPanel, document.querySelector(".inspector details")); -const signalForm = signalPanel.querySelector("form"); -const signalOutput = signalForm.querySelector("output"); +const signalPanel = document.createElement('section'); +signalPanel.innerHTML = + '

原生红绿灯

'; +document.querySelector('.inspector').insertBefore(signalPanel, document.querySelector('.inspector details')); +const signalForm = signalPanel.querySelector('form'); +const signalOutput = signalForm.querySelector('output'); const signalPicker = signalPanel.querySelector('[name="signal-picker"]'); const source = () => new VectorSource(); const layers = { - reference: new VectorLayer({ source: source(), visible: false, style: new Style({ fill: new Fill({ color: "rgba(123, 140, 148, .28)" }), stroke: new Stroke({ color: "#8999a0", width: 1 }) }) }), - gaodeReference: new VectorLayer({ source: source(), visible: true, zIndex: 1, style: (feature) => { const color = gaodeReferenceColors[feature.get("type")] || "#475569"; return new Style({ fill: new Fill({ color: `${color}26` }), stroke: new Stroke({ color, width: 1.5 }) }); } }), + reference: new VectorLayer({ + source: source(), + visible: false, + style: new Style({ + fill: new Fill({ color: 'rgba(123, 140, 148, .28)' }), + stroke: new Stroke({ color: '#8999a0', width: 1 }), + }), + }), + gaodeReference: new VectorLayer({ + source: source(), + visible: true, + zIndex: 1, + style: (feature) => { + const color = gaodeReferenceColors[feature.get('type')] || '#475569'; + return new Style({ fill: new Fill({ color: `${color}26` }), stroke: new Stroke({ color, width: 1.5 }) }); + }, + }), native: new VectorLayer({ source: source(), style: nativeSurfaceStyle }), edgeLines: new VectorLayer({ source: source(), visible: false, style: markingStyle }), sidewalks: new VectorLayer({ source: source(), style: sidewalkSurfaceStyle }), - osm: new VectorLayer({ source: source(), style: (feature) => new Style({ stroke: new Stroke({ color: feature.get("road_id") === selectedRoad?.id ? "#006e91" : "#263630", width: feature.get("road_id") === selectedRoad?.id ? 5 : 2 }) }) }), + osm: new VectorLayer({ + source: source(), + style: (feature) => + new Style({ + stroke: new Stroke({ + color: feature.get('road_id') === selectedRoad?.id ? '#006e91' : '#263630', + width: feature.get('road_id') === selectedRoad?.id ? 5 : 2, + }), + }), + }), lanes: new VectorLayer({ source: source(), style: laneStyle }), directionArrows: new VectorLayer({ source: source(), style: markingStyle }), markings: new VectorLayer({ source: source(), style: markingStyle }), centerLines: new VectorLayer({ source: source(), style: centerLineStyle }), controls: new VectorLayer({ source: source(), style: markingStyle }), signals: new VectorLayer({ source: source(), style: signalAssemblyStyle, zIndex: 30 }), - osmDirection: new VectorLayer({ source: source(), style: (feature) => new Style({ image: new RegularShape({ points: 3, radius: 9, rotation: feature.get("rotation"), fill: new Fill({ color: "#006e91" }), stroke: new Stroke({ color: "#fff", width: 1.5 }) }) }), zIndex: 11 }), - connectors: new VectorLayer({ source: source(), style: (feature) => effectiveConnectorEnabled(feature.getProperties()) ? new Style({ stroke: new Stroke({ color: roadIdFromLane(feature.get("from_lane_id")) === selectedRoad?.id ? "#d1226f" : "#ad3a76", width: roadIdFromLane(feature.get("from_lane_id")) === selectedRoad?.id ? 4 : 2, lineDash: [7, 5] }) }) : null }), - junctionCandidates: new VectorLayer({ source: source(), visible: true, zIndex: 25, style: (feature) => [ - new Style({ fill: new Fill({ color: "rgba(219, 39, 119, .12)" }), stroke: new Stroke({ color: "#db2777", width: 2, lineDash: [8, 5] }) }), - new Style({ text: new Text({ text: `#${feature.get("index")} ${feature.get("nodeCount")}节点`, font: "bold 13px system-ui, sans-serif", fill: new Fill({ color: "#831843" }), stroke: new Stroke({ color: "#fff", width: 3 }), offsetY: -14 }) }), - ] }), - diagnostics: new VectorLayer({ source: source(), style: (feature) => new Style({ image: new CircleStyle({ radius: 6, fill: new Fill({ color: feature.get("severity") === "error" ? "#bf3b2e" : "#d49318" }), stroke: new Stroke({ color: "#fff", width: 1 }) }) }) }), - selectedRoad: new VectorLayer({ source: source(), style: new Style({ stroke: new Stroke({ color: "#00a5cf", width: 8 }) }), zIndex: 10 }), - selectedMovement: new VectorLayer({ source: source(), style: new Style({ stroke: new Stroke({ color: "#f0b323", width: 6 }) }), zIndex: 12 }), + osmDirection: new VectorLayer({ + source: source(), + style: (feature) => + new Style({ + image: new RegularShape({ + points: 3, + radius: 9, + rotation: feature.get('rotation'), + fill: new Fill({ color: '#006e91' }), + stroke: new Stroke({ color: '#fff', width: 1.5 }), + }), + }), + zIndex: 11, + }), + connectors: new VectorLayer({ + source: source(), + style: (feature) => + effectiveConnectorEnabled(feature.getProperties()) + ? new Style({ + stroke: new Stroke({ + color: roadIdFromLane(feature.get('from_lane_id')) === selectedRoad?.id ? '#d1226f' : '#ad3a76', + width: roadIdFromLane(feature.get('from_lane_id')) === selectedRoad?.id ? 4 : 2, + lineDash: [7, 5], + }), + }) + : null, + }), + junctionCandidates: new VectorLayer({ + source: source(), + visible: true, + zIndex: 25, + style: (feature) => [ + new Style({ + fill: new Fill({ color: 'rgba(219, 39, 119, .12)' }), + stroke: new Stroke({ color: '#db2777', width: 2, lineDash: [8, 5] }), + }), + new Style({ + text: new Text({ + text: `#${feature.get('index')} ${feature.get('nodeCount')}节点`, + font: 'bold 13px system-ui, sans-serif', + fill: new Fill({ color: '#831843' }), + stroke: new Stroke({ color: '#fff', width: 3 }), + offsetY: -14, + }), + }), + ], + }), + diagnostics: new VectorLayer({ + source: source(), + style: (feature) => + new Style({ + image: new CircleStyle({ + radius: 6, + fill: new Fill({ color: feature.get('severity') === 'error' ? '#bf3b2e' : '#d49318' }), + stroke: new Stroke({ color: '#fff', width: 1 }), + }), + }), + }), + selectedRoad: new VectorLayer({ + source: source(), + style: new Style({ stroke: new Stroke({ color: '#00a5cf', width: 8 }) }), + zIndex: 10, + }), + selectedMovement: new VectorLayer({ + source: source(), + style: new Style({ stroke: new Stroke({ color: '#f0b323', width: 6 }) }), + zIndex: 12, + }), }; -const map = new Map({ target: "map", layers: [layers.gaodeReference, layers.reference, layers.native, layers.edgeLines, layers.sidewalks, layers.osm, layers.lanes, layers.directionArrows, layers.markings, layers.centerLines, layers.controls, layers.signals, layers.connectors, layers.junctionCandidates, layers.diagnostics, layers.selectedRoad, layers.osmDirection, layers.selectedMovement], view: new View({ center: [0, 0], zoom: 2 }) }); -const select = new Select({ condition: click, layers: (layer) => manualFromEndpoint ? layer === layers.osm : [layers.osm, layers.lanes, layers.directionArrows, layers.markings, layers.centerLines, layers.edgeLines, layers.controls, layers.signals, layers.connectors, layers.native, layers.junctionCandidates, layers.diagnostics].includes(layer), hitTolerance: 12, style: null }); +const map = new Map({ + target: 'map', + layers: [ + layers.gaodeReference, + layers.reference, + layers.native, + layers.edgeLines, + layers.sidewalks, + layers.osm, + layers.lanes, + layers.directionArrows, + layers.markings, + layers.centerLines, + layers.controls, + layers.signals, + layers.connectors, + layers.junctionCandidates, + layers.diagnostics, + layers.selectedRoad, + layers.osmDirection, + layers.selectedMovement, + ], + view: new View({ center: [0, 0], zoom: 2 }), +}); +const select = new Select({ + condition: click, + layers: (layer) => + manualFromEndpoint + ? layer === layers.osm + : [ + layers.osm, + layers.lanes, + layers.directionArrows, + layers.markings, + layers.centerLines, + layers.edgeLines, + layers.controls, + layers.signals, + layers.connectors, + layers.native, + layers.junctionCandidates, + layers.diagnostics, + ].includes(layer), + hitTolerance: 12, + style: null, +}); map.addInteraction(select); -select.on("select", ({ selected }) => { +select.on('select', ({ selected }) => { const feature = selected[0]; if (!feature) return; - if (feature.get("candidate_index")) return selectJunctionCandidate(feature); - if (feature.get("signal_uid")) return selectSignal(poleFeatureForSignal(feature.get("signal_uid")) || feature); + if (feature.get('candidate_index')) return selectJunctionCandidate(feature); + if (feature.get('signal_uid')) return selectSignal(poleFeatureForSignal(feature.get('signal_uid')) || feature); if (manualFromEndpoint) return chooseManualTarget(roadForFeature(feature)); const junction = junctionForFeature(feature); if (junction) return selectJunction(junction); - const provenance = feature.get("provenance"); - if (provenance?.startsWith("native-road-")) { + const provenance = feature.get('provenance'); + if (provenance?.startsWith('native-road-')) { const road = roadForFeature(feature); - const directionArrow = provenance === "native-road-direction-arrow/v1"; - const turnArrow = provenance === "native-road-turn-arrow/v1"; - const centerLine = provenance === "native-road-center-line/v1"; - const edgeLine = provenance === "native-road-edge-line/v1"; - const crosswalk = provenance === "native-road-crosswalk/v1"; - const stopLine = provenance === "native-road-stop-line/v1"; - const markingType = centerLine ? "道路中心线" : edgeLine ? "道路外缘线" : crosswalk ? "斑马线" : stopLine ? "停止线" : directionArrow ? "道路方向箭头" : turnArrow ? "路口转向箭头" : "车道分隔线"; + const directionArrow = provenance === 'native-road-direction-arrow/v1'; + const turnArrow = provenance === 'native-road-turn-arrow/v1'; + const centerLine = provenance === 'native-road-center-line/v1'; + const edgeLine = provenance === 'native-road-edge-line/v1'; + const crosswalk = provenance === 'native-road-crosswalk/v1'; + const stopLine = provenance === 'native-road-stop-line/v1'; + const markingType = centerLine + ? '道路中心线' + : edgeLine + ? '道路外缘线' + : crosswalk + ? '斑马线' + : stopLine + ? '停止线' + : directionArrow + ? '道路方向箭头' + : turnArrow + ? '路口转向箭头' + : '车道分隔线'; selectRoad(road); - if (centerLine) selectCenterLine(feature); else if (edgeLine) selectEdgeLine(feature); else if (provenance === "native-road-lane-separator/v1") selectLaneSeparator(feature); else clearCenterLineSelection(); - evidence.textContent = JSON.stringify({ - 标线类型: markingType, - 人行横道节点: crosswalk || stopLine ? feature.get("crossing_node_id") : null, - OSM道路: feature.get("osm_way_ids"), - 原生道路: feature.get("road_id"), - 道路段: centerLine ? feature.get("segment_id") : null, - 方向: feature.get("direction"), - 车道: feature.get("lane_id") || feature.get("lane_index") || `${feature.get("left_lane_index")} 与 ${feature.get("right_lane_index")} 之间`, - 转向: turnArrow ? feature.get("maneuver") : null, - 样式: centerLine || edgeLine || provenance === "native-road-lane-separator/v1" ? feature.get("effective_style") : null, - 放置方法: feature.get("placement_method") || null, - 道路内距离米: feature.get("distance_along_lane_meters") || null, - 路口前距离米: feature.get("placement_distance_meters") || null, - 来源: provenance, - }, null, 2); + if (centerLine) selectCenterLine(feature); + else if (edgeLine) selectEdgeLine(feature); + else if (provenance === 'native-road-lane-separator/v1') selectLaneSeparator(feature); + else clearCenterLineSelection(); + evidence.textContent = JSON.stringify( + { + 标线类型: markingType, + 人行横道节点: crosswalk || stopLine ? feature.get('crossing_node_id') : null, + OSM道路: feature.get('osm_way_ids'), + 原生道路: feature.get('road_id'), + 道路段: centerLine ? feature.get('segment_id') : null, + 方向: feature.get('direction'), + 车道: + feature.get('lane_id') || + feature.get('lane_index') || + `${feature.get('left_lane_index')} 与 ${feature.get('right_lane_index')} 之间`, + 转向: turnArrow ? feature.get('maneuver') : null, + 样式: + centerLine || edgeLine || provenance === 'native-road-lane-separator/v1' + ? feature.get('effective_style') + : null, + 放置方法: feature.get('placement_method') || null, + 道路内距离米: feature.get('distance_along_lane_meters') || null, + 路口前距离米: feature.get('placement_distance_meters') || null, + 来源: provenance, + }, + null, + 2, + ); return message(`已选中${markingType}`); } - const movement = state.compiled.movements?.find((item) => item.id === feature.get("movement_id")) || null; + const movement = state.compiled.movements?.find((item) => item.id === feature.get('movement_id')) || null; selectRoad(roadForFeature(feature), undefined, movement); }); -map.on("pointermove", (event) => { map.getTargetElement().style.cursor = map.hasFeatureAtPixel(event.pixel, { hitTolerance: 8 }) ? "pointer" : ""; }); +map.on('pointermove', (event) => { + map.getTargetElement().style.cursor = map.hasFeatureAtPixel(event.pixel, { hitTolerance: 8 }) ? 'pointer' : ''; +}); -function message(text) { status.textContent = text; } +function message(text) { + status.textContent = text; +} function updateDirtyState() { const count = staged.length; - dirtyState.textContent = count ? `未保存修改 ${count} 项` : "所有修改已保存"; - dirtyState.classList.toggle("dirty", count > 0); + dirtyState.textContent = count ? `未保存修改 ${count} 项` : '所有修改已保存'; + dirtyState.classList.toggle('dirty', count > 0); saveButton.disabled = count === 0; } -function roadLabel(road) { return road.tags.name || `${road.highway}(OSM ${road.osmWayIds.join(", ")})`; } -function osmDirectionLabel(road) { return road?.direction === "forward" ? "沿 OSM 方向" : "逆 OSM 方向"; } -function roadIdFromLane(laneId) { return typeof laneId === "string" ? laneId.slice(5, laneId.lastIndexOf(":")) : ""; } -function laneIndex(laneId) { return Number(String(laneId).split(":").at(-1)); } -function lanePositionLabel(road, index) { return road?.laneCount === 1 ? "唯一车道" : `左起第 ${index} 车道`; } -function laneStyle(feature) { const roadId = feature.get("road_id"); const selected = Boolean(roadId && selectedRoad && roadId === selectedRoad.id); const composite = feature.get("cluster_preview"); return new Style({ stroke: new Stroke({ color: selected ? "#006e91" : "#f5f6ee", width: selected ? 3 : composite ? 1.6 : 1.3, lineDash: composite ? [7, 5] : [5, 4] }) }); } -function markingStyle(feature) { const yellow = feature?.get("color") === "yellow"; return new Style({ fill: new Fill({ color: yellow ? "#f5be2a" : "#f5f6ee" }), stroke: new Stroke({ color: yellow ? "#d29d16" : "#d9dacf", width: 1 }) }); } -function signalAssemblyStyle(feature) { const component = feature.get("signal_component"); if (component === "mast") return [new Style({ stroke: new Stroke({ color: "#fff", width: 9 }) }), new Style({ stroke: new Stroke({ color: "#007f99", width: 5 }) })]; if (component === "face") return [new Style({ stroke: new Stroke({ color: "#fff", width: 7 }) }), new Style({ stroke: new Stroke({ color: "#df2435", width: 3 }) })]; if (component === "head") { const heading = Number(feature.get("face_heading_deg")) || 0; return new Style({ image: new RegularShape({ points: 3, radius: 8, rotation: heading * Math.PI / 180, fill: new Fill({ color: "#df2435" }), stroke: new Stroke({ color: "#fff", width: 2 }) }) }); } return new Style({ image: new RegularShape({ points: 4, radius: 6, angle: Math.PI / 4, fill: new Fill({ color: "#263630" }), stroke: new Stroke({ color: "#fff", width: 2 }) }) }); } -function centerLineStyle(feature) { const white = feature.get("color") === "white"; const color = white ? "#faf9ee" : "#f5be2a"; return new Style({ fill: new Fill({ color }), stroke: new Stroke({ color: feature.get("pattern") === "solid" ? color : white ? "#aeb0aa" : "#d29d16", width: feature.get("pattern") === "solid" ? .25 : .8 }) }); } +function roadLabel(road) { + return road.tags.name || `${road.highway}(OSM ${road.osmWayIds.join(', ')})`; +} +function osmDirectionLabel(road) { + return road?.direction === 'forward' ? '沿 OSM 方向' : '逆 OSM 方向'; +} +function roadIdFromLane(laneId) { + return typeof laneId === 'string' ? laneId.slice(5, laneId.lastIndexOf(':')) : ''; +} +function laneIndex(laneId) { + return Number(String(laneId).split(':').at(-1)); +} +function lanePositionLabel(road, index) { + return road?.laneCount === 1 ? '唯一车道' : `左起第 ${index} 车道`; +} +function laneStyle(feature) { + const roadId = feature.get('road_id'); + const selected = Boolean(roadId && selectedRoad && roadId === selectedRoad.id); + const composite = feature.get('cluster_preview'); + return new Style({ + stroke: new Stroke({ + color: selected ? '#006e91' : '#f5f6ee', + width: selected ? 3 : composite ? 1.6 : 1.3, + lineDash: composite ? [7, 5] : [5, 4], + }), + }); +} +function markingStyle(feature) { + const yellow = feature?.get('color') === 'yellow'; + return new Style({ + fill: new Fill({ color: yellow ? '#f5be2a' : '#f5f6ee' }), + stroke: new Stroke({ color: yellow ? '#d29d16' : '#d9dacf', width: 1 }), + }); +} +function signalAssemblyStyle(feature) { + const component = feature.get('signal_component'); + if (component === 'mast') + return [ + new Style({ stroke: new Stroke({ color: '#fff', width: 9 }) }), + new Style({ stroke: new Stroke({ color: '#007f99', width: 5 }) }), + ]; + if (component === 'face') + return [ + new Style({ stroke: new Stroke({ color: '#fff', width: 7 }) }), + new Style({ stroke: new Stroke({ color: '#df2435', width: 3 }) }), + ]; + if (component === 'head') { + const heading = Number(feature.get('face_heading_deg')) || 0; + return new Style({ + image: new RegularShape({ + points: 3, + radius: 8, + rotation: (heading * Math.PI) / 180, + fill: new Fill({ color: '#df2435' }), + stroke: new Stroke({ color: '#fff', width: 2 }), + }), + }); + } + return new Style({ + image: new RegularShape({ + points: 4, + radius: 6, + angle: Math.PI / 4, + fill: new Fill({ color: '#263630' }), + stroke: new Stroke({ color: '#fff', width: 2 }), + }), + }); +} +function centerLineStyle(feature) { + const white = feature.get('color') === 'white'; + const color = white ? '#faf9ee' : '#f5be2a'; + return new Style({ + fill: new Fill({ color }), + stroke: new Stroke({ + color: feature.get('pattern') === 'solid' ? color : white ? '#aeb0aa' : '#d29d16', + width: feature.get('pattern') === 'solid' ? 0.25 : 0.8, + }), + }); +} function nativeSurfaceStyle(feature) { // Split road features meet at OSM junction nodes. Their per-feature outlines // are editing aids, not physical seams, so scene mode must render fills only. - if (scenePreview) return new Style({ fill: new Fill({ color: "#3f4b50" }) }); - if (feature.get("cluster_id") && feature.get("complex_part")) return new Style({ fill: new Fill({ color: "#6f948a" }) }); - if (feature.get("kind") === "cluster") return new Style({ fill: new Fill({ color: "rgba(20, 132, 112, .5)" }), stroke: new Stroke({ color: "#075e4f", width: 4, lineDash: [10, 5] }) }); - if (feature.get("template")) return new Style({ fill: new Fill({ color: "rgba(20, 132, 112, .46)" }), stroke: new Stroke({ color: "#087c67", width: 3, lineDash: [7, 4] }) }); - return feature.get("native_id")?.startsWith("junction:") - ? new Style({ fill: new Fill({ color: "rgba(12, 116, 91, .38)" }), stroke: new Stroke({ color: "#0e785f", width: 1.5 }) }) - : new Style({ fill: new Fill({ color: "rgba(40, 105, 86, .35)" }), stroke: new Stroke({ color: "#296956", width: 1 }) }); + if (scenePreview) return new Style({ fill: new Fill({ color: '#3f4b50' }) }); + if (feature.get('cluster_id') && feature.get('complex_part')) + return new Style({ fill: new Fill({ color: '#6f948a' }) }); + if (feature.get('kind') === 'cluster') + return new Style({ + fill: new Fill({ color: 'rgba(20, 132, 112, .5)' }), + stroke: new Stroke({ color: '#075e4f', width: 4, lineDash: [10, 5] }), + }); + if (feature.get('template')) + return new Style({ + fill: new Fill({ color: 'rgba(20, 132, 112, .46)' }), + stroke: new Stroke({ color: '#087c67', width: 3, lineDash: [7, 4] }), + }); + return feature.get('native_id')?.startsWith('junction:') + ? new Style({ + fill: new Fill({ color: 'rgba(12, 116, 91, .38)' }), + stroke: new Stroke({ color: '#0e785f', width: 1.5 }), + }) + : new Style({ + fill: new Fill({ color: 'rgba(40, 105, 86, .35)' }), + stroke: new Stroke({ color: '#296956', width: 1 }), + }); } function sidewalkSurfaceStyle() { return scenePreview - ? new Style({ fill: new Fill({ color: "#b7b9ad" }) }) - : new Style({ fill: new Fill({ color: "rgba(218, 191, 137, .6)" }), stroke: new Stroke({ color: "#9b7c40", width: 1 }) }); + ? new Style({ fill: new Fill({ color: '#b7b9ad' }) }) + : new Style({ + fill: new Fill({ color: 'rgba(218, 191, 137, .6)' }), + stroke: new Stroke({ color: '#9b7c40', width: 1 }), + }); +} +function directionArrowFeature(geometry) { + const middle = geometry.getCoordinateAt(0.5); + const before = geometry.getCoordinateAt(0.48); + const after = geometry.getCoordinateAt(0.52); + const length = Math.hypot(after[0] - before[0], after[1] - before[1]); + if (length < 0.01) return null; + return new Feature({ geometry: new Point(middle), rotation: Math.atan2(after[1] - before[1], after[0] - before[0]) }); +} +function refreshOsmDirection() { + const directionSource = layers.osmDirection.getSource(); + directionSource.clear(); + if (!selectedRoad) return; + const centerline = new LineString(selectedRoad.centerline).transform('EPSG:4326', 'EPSG:3857'); + const arrow = directionArrowFeature(centerline); + if (arrow) directionSource.addFeature(arrow); +} +function refreshSelectedMovement() { + const movementSource = layers.selectedMovement.getSource(); + movementSource.clear(); + if ( + !selectedMovement?.geometryPublished || + !effectiveConnectorEnabled({ + connection_id: selectedMovement.connectionId, + fromLaneId: selectedMovement.fromLaneId, + toLaneId: selectedMovement.toLaneId, + }) + ) + return; + const feature = layers.connectors + .getSource() + .getFeatures() + .find((candidate) => candidate.get('movement_id') === selectedMovement.id); + if (feature) movementSource.addFeature(new Feature({ geometry: feature.getGeometry().clone() })); } -function directionArrowFeature(geometry) { const middle = geometry.getCoordinateAt(.5); const before = geometry.getCoordinateAt(.48); const after = geometry.getCoordinateAt(.52); const length = Math.hypot(after[0] - before[0], after[1] - before[1]); if (length < .01) return null; return new Feature({ geometry: new Point(middle), rotation: Math.atan2(after[1] - before[1], after[0] - before[0]) }); } -function refreshOsmDirection() { const directionSource = layers.osmDirection.getSource(); directionSource.clear(); if (!selectedRoad) return; const centerline = new LineString(selectedRoad.centerline).transform("EPSG:4326", "EPSG:3857"); const arrow = directionArrowFeature(centerline); if (arrow) directionSource.addFeature(arrow); } -function refreshSelectedMovement() { const movementSource = layers.selectedMovement.getSource(); movementSource.clear(); if (!selectedMovement?.geometryPublished || !effectiveConnectorEnabled({ connection_id: selectedMovement.connectionId, fromLaneId: selectedMovement.fromLaneId, toLaneId: selectedMovement.toLaneId })) return; const feature = layers.connectors.getSource().getFeatures().find((candidate) => candidate.get("movement_id") === selectedMovement.id); if (feature) movementSource.addFeature(new Feature({ geometry: feature.getGeometry().clone() })); } function roadForFeature(feature) { const properties = feature.getProperties(); - const roadId = properties.road_id || properties.subjectId || roadIdFromLane(properties.from_lane_id) || properties.directional_road_ids?.split(",")[0]; + const roadId = + properties.road_id || + properties.subjectId || + roadIdFromLane(properties.from_lane_id) || + properties.directional_road_ids?.split(',')[0]; return state.compiled.model.roads.find((road) => road.id === roadId) || null; } -function junctionForFeature(feature) { const id = feature.get("native_id"); return id?.startsWith("junction:") || id?.startsWith("junction-cluster:") ? layers.native.getSource().getFeatures().find((item) => item.get("native_id") === id) : null; } -function endpointFor(road, side) { return state.compiled.model.endpoints.find((endpoint) => endpoint.roadId === road?.id && endpoint.side === side) || null; } -function endpointsCompatible(from, to) { if (!from || !to || from.roadId === to.roadId || from.side !== "end" || to.side !== "start") return false; if (from.nodeId === to.nodeId) return true; const dx = (from.coordinate[0] - to.coordinate[0]) * 111320 * Math.cos(from.coordinate[1] * Math.PI / 180); const dy = (from.coordinate[1] - to.coordinate[1]) * 111320; return Math.hypot(dx, dy) <= 35; } -function readFeatures(collection, predicate = null) { const source = collection || { type: "FeatureCollection", features: [] }; const filtered = predicate ? { ...source, features: (source.features || []).filter(predicate) } : source; return geojson.readFeatures(filtered, { dataProjection: "EPSG:4326", featureProjection: "EPSG:3857" }); } -function rawRoadFeatures() { return state.compiled.model.roads.map((road) => new Feature({ geometry: new LineString(road.centerline).transform("EPSG:4326", "EPSG:3857"), road_id: road.id })); } +function junctionForFeature(feature) { + const id = feature.get('native_id'); + return id?.startsWith('junction:') || id?.startsWith('junction-cluster:') + ? layers.native + .getSource() + .getFeatures() + .find((item) => item.get('native_id') === id) + : null; +} +function endpointFor(road, side) { + return ( + state.compiled.model.endpoints.find((endpoint) => endpoint.roadId === road?.id && endpoint.side === side) || null + ); +} +function endpointsCompatible(from, to) { + if (!from || !to || from.roadId === to.roadId || from.side !== 'end' || to.side !== 'start') return false; + if (from.nodeId === to.nodeId) return true; + const dx = (from.coordinate[0] - to.coordinate[0]) * 111320 * Math.cos((from.coordinate[1] * Math.PI) / 180); + const dy = (from.coordinate[1] - to.coordinate[1]) * 111320; + return Math.hypot(dx, dy) <= 35; +} +function readFeatures(collection, predicate = null) { + const source = collection || { type: 'FeatureCollection', features: [] }; + const filtered = predicate ? { ...source, features: (source.features || []).filter(predicate) } : source; + return geojson.readFeatures(filtered, { dataProjection: 'EPSG:4326', featureProjection: 'EPSG:3857' }); +} +function rawRoadFeatures() { + return state.compiled.model.roads.map( + (road) => + new Feature({ geometry: new LineString(road.centerline).transform('EPSG:4326', 'EPSG:3857'), road_id: road.id }), + ); +} function updateSources() { - layers.reference.getSource().clear(); layers.reference.getSource().addFeatures(readFeatures(state.layers.osm2streetsRoadSurface)); - layers.gaodeReference.getSource().clear(); layers.gaodeReference.getSource().addFeatures(readFeatures(state.junctionReference?.converted)); - layers.native.getSource().clear(); layers.native.getSource().addFeatures([...readFeatures(state.layers.nativeRoadSurface), ...readFeatures(state.layers.nativeIntersectionSurface)]); - layers.sidewalks.getSource().clear(); layers.sidewalks.getSource().addFeatures(readFeatures(state.layers.nativeSidewalkSurface)); - layers.osm.getSource().clear(); layers.osm.getSource().addFeatures(rawRoadFeatures()); - layers.lanes.getSource().clear(); layers.lanes.getSource().addFeatures(readFeatures(state.layers.laneCenterlines, (feature) => !feature.properties?.cluster_internal && !feature.properties?.cluster_preview_hidden)); - layers.edgeLines.getSource().clear(); layers.edgeLines.getSource().addFeatures(readFeatures(state.layers.edgeLines)); - layers.directionArrows.getSource().clear(); layers.directionArrows.getSource().addFeatures(readFeatures(state.layers.directionArrows, (feature) => !feature.properties?.cluster_preview_hidden)); - layers.markings.getSource().clear(); layers.markings.getSource().addFeatures([...readFeatures(state.layers.laneSeparators, (feature) => !feature.properties?.cluster_preview_hidden), ...readFeatures(state.layers.turnArrows, (feature) => !feature.properties?.cluster_preview_hidden)]); - layers.centerLines.getSource().clear(); layers.centerLines.getSource().addFeatures(readFeatures(state.layers.centerLines, (feature) => !feature.properties?.cluster_preview_hidden)); - layers.controls.getSource().clear(); layers.controls.getSource().addFeatures([...readFeatures(state.layers.crosswalks), ...readFeatures(state.layers.vehicleStopLines)]); - const signalFeatures = readFeatures(state.trafficSignals?.assemblies || { type: "FeatureCollection", features: [] }); const armFeatures = []; const faceFeatures = []; const headFeatures = []; for (const signal of state.trafficRuntime?.signals || []) { const arm = signal.pose?.arm; const head = signal.pose?.head; if (!arm || !head) continue; const properties = { signal_uid: signal.id }; const headPoint = fromLonLat([head.longitude, head.latitude]); const radians = Number(head.faceHeadingDegrees) * Math.PI / 180; const faceEnd = [headPoint[0] + Math.sin(radians) * 2.5, headPoint[1] + Math.cos(radians) * 2.5]; armFeatures.push(new Feature({ geometry: new LineString([fromLonLat([arm.from.longitude, arm.from.latitude]), fromLonLat([arm.to.longitude, arm.to.latitude])]), signal_component: "mast", ...properties })); faceFeatures.push(new Feature({ geometry: new LineString([headPoint, faceEnd]), signal_component: "face", ...properties })); headFeatures.push(new Feature({ geometry: new Point(faceEnd), signal_component: "head", face_heading_deg: head.faceHeadingDegrees, ...properties })); } layers.signals.getSource().clear(); layers.signals.getSource().addFeatures([...armFeatures, ...faceFeatures, ...signalFeatures, ...headFeatures]); const pickerValue = signalPicker.value; signalPicker.replaceChildren(new Option("选择设施", "")); signalFeatures.forEach((feature) => signalPicker.add(new Option(feature.get("display_id") || feature.get("signal_uid"), feature.get("signal_uid")))); signalPicker.value = pickerValue; - layers.connectors.getSource().clear(); layers.connectors.getSource().addFeatures(readFeatures(state.layers.connectors, (feature) => !feature.properties?.cluster_internal)); + layers.reference.getSource().clear(); + layers.reference.getSource().addFeatures(readFeatures(state.layers.osm2streetsRoadSurface)); + layers.gaodeReference.getSource().clear(); + layers.gaodeReference.getSource().addFeatures(readFeatures(state.junctionReference?.converted)); + layers.native.getSource().clear(); + layers.native + .getSource() + .addFeatures([ + ...readFeatures(state.layers.nativeRoadSurface), + ...readFeatures(state.layers.nativeIntersectionSurface), + ]); + layers.sidewalks.getSource().clear(); + layers.sidewalks.getSource().addFeatures(readFeatures(state.layers.nativeSidewalkSurface)); + layers.osm.getSource().clear(); + layers.osm.getSource().addFeatures(rawRoadFeatures()); + layers.lanes.getSource().clear(); + layers.lanes + .getSource() + .addFeatures( + readFeatures( + state.layers.laneCenterlines, + (feature) => !feature.properties?.cluster_internal && !feature.properties?.cluster_preview_hidden, + ), + ); + layers.edgeLines.getSource().clear(); + layers.edgeLines.getSource().addFeatures(readFeatures(state.layers.edgeLines)); + layers.directionArrows.getSource().clear(); + layers.directionArrows + .getSource() + .addFeatures(readFeatures(state.layers.directionArrows, (feature) => !feature.properties?.cluster_preview_hidden)); + layers.markings.getSource().clear(); + layers.markings + .getSource() + .addFeatures([ + ...readFeatures(state.layers.laneSeparators, (feature) => !feature.properties?.cluster_preview_hidden), + ...readFeatures(state.layers.turnArrows, (feature) => !feature.properties?.cluster_preview_hidden), + ]); + layers.centerLines.getSource().clear(); + layers.centerLines + .getSource() + .addFeatures(readFeatures(state.layers.centerLines, (feature) => !feature.properties?.cluster_preview_hidden)); + layers.controls.getSource().clear(); + layers.controls + .getSource() + .addFeatures([...readFeatures(state.layers.crosswalks), ...readFeatures(state.layers.vehicleStopLines)]); + const signalFeatures = readFeatures(state.trafficSignals?.assemblies || { type: 'FeatureCollection', features: [] }); + const armFeatures = []; + const faceFeatures = []; + const headFeatures = []; + for (const signal of state.trafficRuntime?.signals || []) { + const arm = signal.pose?.arm; + const head = signal.pose?.head; + if (!arm || !head) continue; + const properties = { signal_uid: signal.id }; + const headPoint = fromLonLat([head.longitude, head.latitude]); + const radians = (Number(head.faceHeadingDegrees) * Math.PI) / 180; + const faceEnd = [headPoint[0] + Math.sin(radians) * 2.5, headPoint[1] + Math.cos(radians) * 2.5]; + armFeatures.push( + new Feature({ + geometry: new LineString([ + fromLonLat([arm.from.longitude, arm.from.latitude]), + fromLonLat([arm.to.longitude, arm.to.latitude]), + ]), + signal_component: 'mast', + ...properties, + }), + ); + faceFeatures.push( + new Feature({ geometry: new LineString([headPoint, faceEnd]), signal_component: 'face', ...properties }), + ); + headFeatures.push( + new Feature({ + geometry: new Point(faceEnd), + signal_component: 'head', + face_heading_deg: head.faceHeadingDegrees, + ...properties, + }), + ); + } + layers.signals.getSource().clear(); + layers.signals.getSource().addFeatures([...armFeatures, ...faceFeatures, ...signalFeatures, ...headFeatures]); + const pickerValue = signalPicker.value; + signalPicker.replaceChildren(new Option('选择设施', '')); + signalFeatures.forEach((feature) => + signalPicker.add(new Option(feature.get('display_id') || feature.get('signal_uid'), feature.get('signal_uid'))), + ); + signalPicker.value = pickerValue; + layers.connectors.getSource().clear(); + layers.connectors + .getSource() + .addFeatures(readFeatures(state.layers.connectors, (feature) => !feature.properties?.cluster_internal)); candidatesToggle.hidden = !(state.debug?.junctionCandidates || []).length; renderJunctionCandidates(); - layers.diagnostics.getSource().clear(); layers.diagnostics.getSource().addFeatures(readFeatures({ type: "FeatureCollection", features: state.compiled.diagnostics.filter((item) => item.geometry).map(({ geometry, ...properties }) => ({ type: "Feature", properties, geometry })) })); - const extent = layers.osm.getSource().getExtent(); if (Number.isFinite(extent[0])) map.getView().fit(extent, { padding: [48, 48, 48, 48], maxZoom: 19 }); + layers.diagnostics.getSource().clear(); + layers.diagnostics.getSource().addFeatures( + readFeatures({ + type: 'FeatureCollection', + features: state.compiled.diagnostics + .filter((item) => item.geometry) + .map(({ geometry, ...properties }) => ({ type: 'Feature', properties, geometry })), + }), + ); + const extent = layers.osm.getSource().getExtent(); + if (Number.isFinite(extent[0])) map.getView().fit(extent, { padding: [48, 48, 48, 48], maxZoom: 19 }); } // Draw one convex-ish hull per candidate cluster so the whole intersection is // outlined, not just its centre, and label it with the same index the inspector @@ -235,8 +634,16 @@ function renderJunctionCandidates() { for (const candidate of list) { const nodes = candidate.nodeIds.map((nodeId) => nodeCoordinate(nodeId)).filter(Boolean); if (!nodes.length) continue; - const ring = candidateRing(nodes, Math.max(12, candidate.coreRadiusMeters * .6)); - source.addFeature(new Feature({ geometry: new Polygon([ring]), candidate_index: candidate.index, index: candidate.index, nodeCount: candidate.nodeCount, candidate_id: candidate.id })); + const ring = candidateRing(nodes, Math.max(12, candidate.coreRadiusMeters * 0.6)); + source.addFeature( + new Feature({ + geometry: new Polygon([ring]), + candidate_index: candidate.index, + index: candidate.index, + nodeCount: candidate.nodeCount, + candidate_id: candidate.id, + }), + ); } } function nodeCoordinate(nodeId) { @@ -250,15 +657,22 @@ function nodeCoordinate(nodeId) { // A rounded envelope around the member nodes: sample a circle of `padMeters` // around each node and take the outer boundary by angle from the centroid. function candidateRing(nodes, padMeters) { - const centre = nodes.reduce((sum, point) => [sum[0] + point[0] / nodes.length, sum[1] + point[1] / nodes.length], [0, 0]); - const metresPerLon = 111320 * Math.cos(centre[1] * Math.PI / 180); + const centre = nodes.reduce( + (sum, point) => [sum[0] + point[0] / nodes.length, sum[1] + point[1] / nodes.length], + [0, 0], + ); + const metresPerLon = 111320 * Math.cos((centre[1] * Math.PI) / 180); const points = []; for (let degrees = 0; degrees < 360; degrees += 12) { - const radians = degrees * Math.PI / 180; + const radians = (degrees * Math.PI) / 180; let best = null; for (const node of nodes) { - const point = [node[0] + Math.sin(radians) * padMeters / metresPerLon, node[1] + Math.cos(radians) * padMeters / 111320]; - const reach = (point[0] - centre[0]) * metresPerLon * Math.sin(radians) + (point[1] - centre[1]) * 111320 * Math.cos(radians); + const point = [ + node[0] + (Math.sin(radians) * padMeters) / metresPerLon, + node[1] + (Math.cos(radians) * padMeters) / 111320, + ]; + const reach = + (point[0] - centre[0]) * metresPerLon * Math.sin(radians) + (point[1] - centre[1]) * 111320 * Math.cos(radians); if (!best || reach > best.reach) best = { point, reach }; } points.push(fromLonLat(best.point)); @@ -266,24 +680,39 @@ function candidateRing(nodes, padMeters) { return [...points, points[0]]; } function selectJunctionCandidate(feature) { - const candidate = (state.debug?.junctionCandidates || []).find((item) => item.index === feature.get("candidate_index")); + const candidate = (state.debug?.junctionCandidates || []).find( + (item) => item.index === feature.get('candidate_index'), + ); if (!candidate) return; - selectedRoad = null; selectedMovement = null; selectedJunction = null; - form.hidden = true; hint.hidden = false; selectedJunctionPanel.hidden = true; + selectedRoad = null; + selectedMovement = null; + selectedJunction = null; + form.hidden = true; + hint.hidden = false; + selectedJunctionPanel.hidden = true; hint.textContent = `复杂路口候选 #${candidate.index}:${candidate.nodeCount} 个节点,直径 ${candidate.diameterMeters} 米。下方是可直接粘贴到 config 的片段。`; - evidence.textContent = JSON.stringify({ - 说明: "复制到区域配置文件的 nativeRoad.junctionTemplates.clusters 数组,然后重新编译", - 片段: { - id: `cluster-${candidate.nodeIds[0]}`, - template: candidate.template, - nodeIds: candidate.nodeIds, - coreRadiusMeters: candidate.coreRadiusMeters, - cornerRadiusMeters: 12, - outerRadiusExtraMeters: 18, + evidence.textContent = JSON.stringify( + { + 说明: '复制到区域配置文件的 nativeRoad.junctionTemplates.clusters 数组,然后重新编译', + 片段: { + id: `cluster-${candidate.nodeIds[0]}`, + template: candidate.template, + nodeIds: candidate.nodeIds, + coreRadiusMeters: candidate.coreRadiusMeters, + cornerRadiusMeters: 12, + outerRadiusExtraMeters: 18, + }, + 实测: { + 节点数: candidate.nodeCount, + 直径米: candidate.diameterMeters, + 最长内部连接米: candidate.longestLinkMeters, + 最宽进口米: candidate.widestApproachMeters, + }, + 提示: 'coreRadiusMeters 是按节点跨度估的起点,配好后按实际效果调整;有高德参考几何时它会被校准值覆盖。', }, - 实测: { 节点数: candidate.nodeCount, 直径米: candidate.diameterMeters, 最长内部连接米: candidate.longestLinkMeters, 最宽进口米: candidate.widestApproachMeters }, - 提示: "coreRadiusMeters 是按节点跨度估的起点,配好后按实际效果调整;有高德参考几何时它会被校准值覆盖。", - }, null, 2); + null, + 2, + ); renderCandidateAction(candidate); message(`已选中复杂路口候选 #${candidate.index}`); } @@ -291,19 +720,27 @@ function selectJunctionCandidate(feature) { // if the write is refused; both describe the same cluster. function renderCandidateAction(candidate) { candidateAction.replaceChildren(); - const button = document.createElement("button"); - button.type = "button"; + const button = document.createElement('button'); + button.type = 'button'; button.textContent = `把候选 #${candidate.index} 加入配置并重新编译`; button.onclick = async () => { button.disabled = true; message(`正在把候选 #${candidate.index} 写入区域配置...`); try { - const response = await fetch("/api/junction-clusters", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ index: candidate.index }), cache: "no-store" }); + const response = await fetch('/api/junction-clusters', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ index: candidate.index }), + cache: 'no-store', + }); const result = await response.json(); if (!response.ok || result.ok === false) throw new Error(result.error || `HTTP ${response.status}`); state = result; staged = []; - updateDirtyState(); updateSources(); renderDiagnostics(); renderSummary(); + updateDirtyState(); + updateSources(); + renderDiagnostics(); + renderSummary(); candidateAction.replaceChildren(); hint.textContent = `已加入复杂路口 ${result.added.id}(${result.added.nodeIds.length} 个节点),配置已更新并重新编译。`; message(`已加入 ${result.added.id};如需撤销可 git checkout 区域配置`); @@ -314,115 +751,608 @@ function renderCandidateAction(candidate) { }; candidateAction.append(button); } -function laneId(connection, side) { return connection[side === "from" ? "fromLaneId" : "toLaneId"] || connection[side === "from" ? "from_lane_id" : "to_lane_id"]; } -function effectiveLaneEnabled(connector) { const id = `车道连接:${laneId(connector, "from")}->${laneId(connector, "to")}`; const override = [...staged, ...state.overrides.overrides].find((item) => item.id === id); return override ? override.enabled : connector.enabled !== false; } -function effectiveConnectionEnabled(connection) { const id = `连接:${connection.id}`; const override = [...staged, ...state.overrides.overrides].find((item) => item.id === id); return override ? override.enabled : connection.enabled; } -function effectiveConnectorEnabled(connector) { const connection = state?.compiled.model.connections.find((item) => item.id === connector.connection_id); return effectiveLaneEnabled(connector) && (!connection || effectiveConnectionEnabled(connection)); } +function laneId(connection, side) { + return ( + connection[side === 'from' ? 'fromLaneId' : 'toLaneId'] || + connection[side === 'from' ? 'from_lane_id' : 'to_lane_id'] + ); +} +function effectiveLaneEnabled(connector) { + const id = `车道连接:${laneId(connector, 'from')}->${laneId(connector, 'to')}`; + const override = [...staged, ...state.overrides.overrides].find((item) => item.id === id); + return override ? override.enabled : connector.enabled !== false; +} +function effectiveConnectionEnabled(connection) { + const id = `连接:${connection.id}`; + const override = [...staged, ...state.overrides.overrides].find((item) => item.id === id); + return override ? override.enabled : connection.enabled; +} +function effectiveConnectorEnabled(connector) { + const connection = state?.compiled.model.connections.find((item) => item.id === connector.connection_id); + return effectiveLaneEnabled(connector) && (!connection || effectiveConnectionEnabled(connection)); +} function selectRoad(road, note, movement = null) { candidateAction.replaceChildren(); - selectedRoad = road; selectedMovement = movement; selectedJunction = null; selectedJunctionPanel.hidden = true; layers.osm.changed(); layers.lanes.changed(); layers.connectors.changed(); refreshOsmDirection(); refreshSelectedMovement(); - layers.selectedRoad.getSource().clear(); if (road) layers.selectedRoad.getSource().addFeature(new Feature({ geometry: new LineString(road.centerline).transform("EPSG:4326", "EPSG:3857") })); - form.hidden = !road; hint.hidden = Boolean(road); if (!road) return; + selectedRoad = road; + selectedMovement = movement; + selectedJunction = null; + selectedJunctionPanel.hidden = true; + layers.osm.changed(); + layers.lanes.changed(); + layers.connectors.changed(); + refreshOsmDirection(); + refreshSelectedMovement(); + layers.selectedRoad.getSource().clear(); + if (road) + layers.selectedRoad + .getSource() + .addFeature(new Feature({ geometry: new LineString(road.centerline).transform('EPSG:4326', 'EPSG:3857') })); + form.hidden = !road; + hint.hidden = Boolean(road); + if (!road) return; roadName.textContent = `${roadLabel(road)}(${osmDirectionLabel(road)})`; - widthInput.value = road.widthMeters; lanesInput.value = road.laneCount; leftInput.checked = road.sidewalkLeft; rightInput.checked = road.sidewalkRight; - evidence.textContent = JSON.stringify({ OSM道路: road.osmWayIds, 当前方向节点顺序: road.sourceNodeIds, 参数来源: road.provenance, 已应用修改: road.appliedOverrideIds, 原始标签: road.tags }, null, 2); - laneConvention.textContent = road.laneCount === 1 ? "蓝色箭头在 OSM 原始中心线上,表示当前方向;“沿 OSM 方向”即节点顺序。本方向只有一条车道。" : "蓝色箭头在 OSM 原始中心线上,表示当前方向;“沿 OSM 方向”即节点顺序。车道按行驶方向从左向右编号。"; - renderDirectionSwitch(road); renderMovementSummary(road); renderSelectedMovement(); renderConnections(road); message(note || (selectedMovement ? `已选中行驶动作:${turnLabel(selectedMovement.turn)}` : `已选中:${roadLabel(road)}`)); + widthInput.value = road.widthMeters; + lanesInput.value = road.laneCount; + leftInput.checked = road.sidewalkLeft; + rightInput.checked = road.sidewalkRight; + evidence.textContent = JSON.stringify( + { + OSM道路: road.osmWayIds, + 当前方向节点顺序: road.sourceNodeIds, + 参数来源: road.provenance, + 已应用修改: road.appliedOverrideIds, + 原始标签: road.tags, + }, + null, + 2, + ); + laneConvention.textContent = + road.laneCount === 1 + ? '蓝色箭头在 OSM 原始中心线上,表示当前方向;“沿 OSM 方向”即节点顺序。本方向只有一条车道。' + : '蓝色箭头在 OSM 原始中心线上,表示当前方向;“沿 OSM 方向”即节点顺序。车道按行驶方向从左向右编号。'; + renderDirectionSwitch(road); + renderMovementSummary(road); + renderSelectedMovement(); + renderConnections(road); + message( + note || (selectedMovement ? `已选中行驶动作:${turnLabel(selectedMovement.turn)}` : `已选中:${roadLabel(road)}`), + ); } function selectJunction(feature) { candidateAction.replaceChildren(); - selectedJunction = feature; selectedRoad = null; selectedMovement = null; form.hidden = true; hint.hidden = true; selectedJunctionPanel.hidden = false; - const properties = feature.getProperties(); const roadIds = String(properties.source_road_ids || "").split(",").filter(Boolean); + selectedJunction = feature; + selectedRoad = null; + selectedMovement = null; + form.hidden = true; + hint.hidden = true; + selectedJunctionPanel.hidden = false; + const properties = feature.getProperties(); + const roadIds = String(properties.source_road_ids || '') + .split(',') + .filter(Boolean); const roads = roadIds.map((id) => state.compiled.model.roads.find((road) => road.id === id)).filter(Boolean); - junctionDetail.textContent = JSON.stringify({ OSM节点: properties.osm_node_id || properties.osm_node_ids, 类型: properties.kind === "cluster" ? "复合路口簇" : properties.kind === "t" ? "T字路口" : "十字路口", 参与方向道路: roads.map((road) => ({ 道路: roadLabel(road), OSM道路: road.osmWayIds, 节点顺序: road.sourceNodeIds })), 构面规则: properties.rule, 模板: properties.template, 边界策略: properties.boundary_mode, 基础截面面积平方米: properties.approach_area_m2, 最终路口面积平方米: properties.surface_area_m2, 外缘扩张倍率: properties.expansion_ratio, 路口退让距离米: properties.cutback_m, 行驶动作数: properties.movement_count, 已绘制连接数: properties.connector_count }, null, 2); + junctionDetail.textContent = JSON.stringify( + { + OSM节点: properties.osm_node_id || properties.osm_node_ids, + 类型: properties.kind === 'cluster' ? '复合路口簇' : properties.kind === 't' ? 'T字路口' : '十字路口', + 参与方向道路: roads.map((road) => ({ + 道路: roadLabel(road), + OSM道路: road.osmWayIds, + 节点顺序: road.sourceNodeIds, + })), + 构面规则: properties.rule, + 模板: properties.template, + 边界策略: properties.boundary_mode, + 基础截面面积平方米: properties.approach_area_m2, + 最终路口面积平方米: properties.surface_area_m2, + 外缘扩张倍率: properties.expansion_ratio, + 路口退让距离米: properties.cutback_m, + 行驶动作数: properties.movement_count, + 已绘制连接数: properties.connector_count, + }, + null, + 2, + ); message(`已选中路口:OSM 节点 ${properties.osm_node_id || properties.osm_node_ids}`); } function selectSignal(feature) { - selectedSignal = feature.get("signal_uid"); const p = feature.getProperties(); signalPicker.value = selectedSignal; + selectedSignal = feature.get('signal_uid'); + const p = feature.getProperties(); + signalPicker.value = selectedSignal; const [x, y] = feature.getGeometry().getCoordinates(); map.getView().fit([x - 25, y - 25, x + 25, y + 25], { padding: [80, 80, 80, 360], maxZoom: 22, duration: 250 }); - signalForm.hidden = false; signalOutput.textContent = `${p.display_id || p.signal_uid}(${p.signal_uid})`; - signalForm.lon.value = feature.getGeometry().clone().transform("EPSG:3857", "EPSG:4326").getCoordinates()[0]; - signalForm.lat.value = feature.getGeometry().clone().transform("EPSG:3857", "EPSG:4326").getCoordinates()[1]; - signalForm.mastHeading.value = p.mast_heading_deg; signalForm.mastReach.value = p.mast_reach_m; signalForm.faceHeading.value = p.face_heading_deg; signalForm.phase.value = p.phase_group; signalForm.enabled.checked = p.enabled; - evidence.textContent = JSON.stringify({ 信号灯: p.signal_uid, 控制节点: p.control_id, 路口方向: p.approach_id, 来源: state.trafficSignals.provenance }, null, 2); message("已选中原生红绿灯"); + signalForm.hidden = false; + signalOutput.textContent = `${p.display_id || p.signal_uid}(${p.signal_uid})`; + signalForm.lon.value = feature.getGeometry().clone().transform('EPSG:3857', 'EPSG:4326').getCoordinates()[0]; + signalForm.lat.value = feature.getGeometry().clone().transform('EPSG:3857', 'EPSG:4326').getCoordinates()[1]; + signalForm.mastHeading.value = p.mast_heading_deg; + signalForm.mastReach.value = p.mast_reach_m; + signalForm.faceHeading.value = p.face_heading_deg; + signalForm.phase.value = p.phase_group; + signalForm.enabled.checked = p.enabled; + evidence.textContent = JSON.stringify( + { 信号灯: p.signal_uid, 控制节点: p.control_id, 路口方向: p.approach_id, 来源: state.trafficSignals.provenance }, + null, + 2, + ); + message('已选中原生红绿灯'); +} +function poleFeatureForSignal(signalUid) { + return layers.signals + .getSource() + .getFeatures() + .find((item) => item.get('signal_uid') === signalUid && !item.get('signal_component')); +} +async function saveSignals(document) { + const response = await fetch('/api/traffic-signals', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(document), + }); + const result = await response.json(); + if (!result.ok) throw new Error(result.error); + state.trafficSignals = result.trafficSignals; + state.trafficRuntime = result.runtime; + updateSources(); + renderSummary(); +} +function signalDocumentWithChange(change) { + const document = structuredClone(state.trafficSignals); + document.assemblies.features = change(document.assemblies.features); + return document; +} +signalForm.onsubmit = async (event) => { + event.preventDefault(); + try { + await saveSignals( + signalDocumentWithChange((features) => + features.map((feature) => + feature.properties.signal_uid !== selectedSignal + ? feature + : { + ...feature, + geometry: { type: 'Point', coordinates: [Number(signalForm.lon.value), Number(signalForm.lat.value)] }, + properties: { + ...feature.properties, + mast_heading_deg: Number(signalForm.mastHeading.value), + mast_reach_m: Number(signalForm.mastReach.value), + face_heading_deg: Number(signalForm.faceHeading.value), + phase_group: Number(signalForm.phase.value), + enabled: signalForm.enabled.checked, + }, + }, + ), + ), + ); + message('红绿灯已保存'); + } catch (error) { + message(error.message); + } +}; +signalPanel.querySelector('[data-signal="delete"]').onclick = async () => { + try { + await saveSignals( + signalDocumentWithChange((features) => + features.filter((feature) => feature.properties.signal_uid !== selectedSignal), + ), + ); + signalForm.hidden = true; + selectedSignal = null; + message('红绿灯已删除'); + } catch (error) { + message(error.message); + } +}; +signalPanel.querySelector('[data-signal="generate"]').onclick = async () => { + try { + const response = await fetch('/api/traffic-signals/generate', { method: 'POST' }); + const result = await response.json(); + if (!result.ok) throw new Error(result.error); + state.trafficSignals = result.trafficSignals; + state.trafficRuntime = result.runtime; + updateSources(); + message('已补充 OSM 信号灯'); + } catch (error) { + message(error.message); + } +}; +signalPicker.onchange = () => { + const feature = poleFeatureForSignal(signalPicker.value); + if (feature) selectSignal(feature); +}; +function turnLabel(turn) { + return { left: '左转', through: '直行', right: '右转', uturn: '掉头' }[turn] || turn; +} +function renderSelectedMovement() { + selectedMovementPanel.hidden = !selectedMovement; + if (!selectedMovement) return; + const targetRoad = state.compiled.model.roads.find((road) => road.id === selectedMovement.toRoadId); + const geometry = + selectedMovement.geometryStatus === 'connector' + ? '已绘制路径' + : selectedMovement.geometryStatus === 'continuous' + ? '节点连续' + : '路径过长未绘制'; + movementDetail.textContent = `${turnLabel(selectedMovement.turn)}:${lanePositionLabel(selectedRoad, laneIndex(selectedMovement.fromLaneId))} → ${lanePositionLabel(targetRoad, laneIndex(selectedMovement.toLaneId))}\n目标:${roadLabel(targetRoad)}(${osmDirectionLabel(targetRoad)})\n来源端点:${selectedRoad.sourceNodeIds.at(-1)};目标端点:${targetRoad.sourceNodeIds[0]}\n路口节点:${selectedMovement.nodeId}\n状态:${geometry}\n来源:${selectedMovement.provenance}`; } -function poleFeatureForSignal(signalUid) { return layers.signals.getSource().getFeatures().find((item) => item.get("signal_uid") === signalUid && !item.get("signal_component")); } -async function saveSignals(document) { const response = await fetch("/api/traffic-signals", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(document) }); const result = await response.json(); if (!result.ok) throw new Error(result.error); state.trafficSignals = result.trafficSignals; state.trafficRuntime = result.runtime; updateSources(); renderSummary(); } -function signalDocumentWithChange(change) { const document = structuredClone(state.trafficSignals); document.assemblies.features = change(document.assemblies.features); return document; } -signalForm.onsubmit = async (event) => { event.preventDefault(); try { await saveSignals(signalDocumentWithChange((features) => features.map((feature) => feature.properties.signal_uid !== selectedSignal ? feature : { ...feature, geometry: { type: "Point", coordinates: [Number(signalForm.lon.value), Number(signalForm.lat.value)] }, properties: { ...feature.properties, mast_heading_deg: Number(signalForm.mastHeading.value), mast_reach_m: Number(signalForm.mastReach.value), face_heading_deg: Number(signalForm.faceHeading.value), phase_group: Number(signalForm.phase.value), enabled: signalForm.enabled.checked } }))); message("红绿灯已保存"); } catch (error) { message(error.message); } }; -signalPanel.querySelector('[data-signal="delete"]').onclick = async () => { try { await saveSignals(signalDocumentWithChange((features) => features.filter((feature) => feature.properties.signal_uid !== selectedSignal))); signalForm.hidden = true; selectedSignal = null; message("红绿灯已删除"); } catch (error) { message(error.message); } }; -signalPanel.querySelector('[data-signal="generate"]').onclick = async () => { try { const response = await fetch("/api/traffic-signals/generate", { method: "POST" }); const result = await response.json(); if (!result.ok) throw new Error(result.error); state.trafficSignals = result.trafficSignals; state.trafficRuntime = result.runtime; updateSources(); message("已补充 OSM 信号灯"); } catch (error) { message(error.message); } }; -signalPicker.onchange = () => { const feature = poleFeatureForSignal(signalPicker.value); if (feature) selectSignal(feature); }; -function turnLabel(turn) { return { left: "左转", through: "直行", right: "右转", uturn: "掉头" }[turn] || turn; } -function renderSelectedMovement() { selectedMovementPanel.hidden = !selectedMovement; if (!selectedMovement) return; const targetRoad = state.compiled.model.roads.find((road) => road.id === selectedMovement.toRoadId); const geometry = selectedMovement.geometryStatus === "connector" ? "已绘制路径" : selectedMovement.geometryStatus === "continuous" ? "节点连续" : "路径过长未绘制"; movementDetail.textContent = `${turnLabel(selectedMovement.turn)}:${lanePositionLabel(selectedRoad, laneIndex(selectedMovement.fromLaneId))} → ${lanePositionLabel(targetRoad, laneIndex(selectedMovement.toLaneId))}\n目标:${roadLabel(targetRoad)}(${osmDirectionLabel(targetRoad)})\n来源端点:${selectedRoad.sourceNodeIds.at(-1)};目标端点:${targetRoad.sourceNodeIds[0]}\n路口节点:${selectedMovement.nodeId}\n状态:${geometry}\n来源:${selectedMovement.provenance}`; } function renderDirectionSwitch(road) { - directionSwitch.innerHTML = ""; const alternatives = state.compiled.model.roads.filter((item) => item.osmWayIds.join(",") === road.osmWayIds.join(",")); - if (alternatives.length < 2) { directionSwitch.textContent = "单向道路"; return; } - for (const item of alternatives) { const button = document.createElement("button"); button.type = "button"; button.textContent = osmDirectionLabel(item); button.disabled = item.id === road.id; button.onclick = () => selectRoad(item); directionSwitch.append(button); } + directionSwitch.innerHTML = ''; + const alternatives = state.compiled.model.roads.filter( + (item) => item.osmWayIds.join(',') === road.osmWayIds.join(','), + ); + if (alternatives.length < 2) { + directionSwitch.textContent = '单向道路'; + return; + } + for (const item of alternatives) { + const button = document.createElement('button'); + button.type = 'button'; + button.textContent = osmDirectionLabel(item); + button.disabled = item.id === road.id; + button.onclick = () => selectRoad(item); + directionSwitch.append(button); + } +} +function renderMovementSummary(road) { + const movements = + state.compiled.movements?.filter( + (movement) => + movement.fromRoadId === road.id && + effectiveConnectorEnabled({ + connection_id: movement.connectionId, + fromLaneId: movement.fromLaneId, + toLaneId: movement.toLaneId, + }), + ) || []; + const turns = movements.reduce((result, item) => { + result[item.turn] = (result[item.turn] || 0) + 1; + return result; + }, {}); + const labels = { left: '左转', through: '直行', right: '右转', uturn: '掉头' }; + const published = movements.filter((movement) => movement.geometryPublished).length; + movementSummary.textContent = movements.length + ? `已识别 ${movements.length} 个行驶动作,${published} 条已绘制路径:${Object.entries(turns) + .map(([key, value]) => `${labels[key] || key} ${value}`) + .join(',')}` + : '当前方向没有已识别的行驶动作'; } -function renderMovementSummary(road) { const movements = state.compiled.movements?.filter((movement) => movement.fromRoadId === road.id && effectiveConnectorEnabled({ connection_id: movement.connectionId, fromLaneId: movement.fromLaneId, toLaneId: movement.toLaneId })) || []; const turns = movements.reduce((result, item) => { result[item.turn] = (result[item.turn] || 0) + 1; return result; }, {}); const labels = { left: "左转", through: "直行", right: "右转", uturn: "掉头" }; const published = movements.filter((movement) => movement.geometryPublished).length; movementSummary.textContent = movements.length ? `已识别 ${movements.length} 个行驶动作,${published} 条已绘制路径:${Object.entries(turns).map(([key, value]) => `${labels[key] || key} ${value}`).join(",")}` : "当前方向没有已识别的行驶动作"; } function renderConnections(road) { - connectionsBox.innerHTML = ""; const endpoint = state.compiled.model.endpoints.find((item) => item.roadId === road.id && item.side === "end"); addConnectionButton.hidden = !endpoint; const rows = state.compiled.model.connections.filter((connection) => connection.fromEndpointId === endpoint?.id); - if (!rows.length) connectionsBox.textContent = "当前方向到达终点后没有已识别的驶出道路。"; - for (const connection of rows) { const target = state.compiled.model.roads.find((item) => item.id === state.compiled.model.endpoints.find((endpointItem) => endpointItem.id === connection.toEndpointId)?.roadId); if (!target) continue; const label = document.createElement("label"); const input = document.createElement("input"); input.type = "checkbox"; input.checked = effectiveConnectionEnabled(connection); input.onchange = () => { stageConnection(connection, input.checked); selectRoad(road, "有未保存修改:转向路径已即时更新"); }; label.append(input, ` ${turnName(road, target)}:${roadLabel(target)}(${osmDirectionLabel(target)})`); connectionsBox.append(label); renderLaneControls(connection); } + connectionsBox.innerHTML = ''; + const endpoint = state.compiled.model.endpoints.find((item) => item.roadId === road.id && item.side === 'end'); + addConnectionButton.hidden = !endpoint; + const rows = state.compiled.model.connections.filter((connection) => connection.fromEndpointId === endpoint?.id); + if (!rows.length) connectionsBox.textContent = '当前方向到达终点后没有已识别的驶出道路。'; + for (const connection of rows) { + const target = state.compiled.model.roads.find( + (item) => + item.id === + state.compiled.model.endpoints.find((endpointItem) => endpointItem.id === connection.toEndpointId)?.roadId, + ); + if (!target) continue; + const label = document.createElement('label'); + const input = document.createElement('input'); + input.type = 'checkbox'; + input.checked = effectiveConnectionEnabled(connection); + input.onchange = () => { + stageConnection(connection, input.checked); + selectRoad(road, '有未保存修改:转向路径已即时更新'); + }; + label.append(input, ` ${turnName(road, target)}:${roadLabel(target)}(${osmDirectionLabel(target)})`); + connectionsBox.append(label); + renderLaneControls(connection); + } renderManualCandidates(endpoint, road); } -function renderManualCandidates(endpoint, road) { const candidates = state.compiled.diagnostics.find((item) => item.endpointId === endpoint?.id)?.manualCandidates || []; if (!candidates.length) return; const title = document.createElement("p"); title.textContent = "附近可手工连接的驶出方向"; connectionsBox.append(title); for (const candidate of candidates) { const target = state.compiled.model.roads.find((item) => item.id === candidate.roadId); if (!target) continue; const button = document.createElement("button"); button.type = "button"; button.textContent = `${roadLabel(target)}(${candidate.distanceMeters} 米)`; button.onclick = () => { stageConnection({ id: `connection:${endpoint.id}:${candidate.toEndpointId}`, fromEndpointId: endpoint.id, toEndpointId: candidate.toEndpointId }, true); selectRoad(road, "有未保存修改:手工连接已暂存;保存并重新生成后会出现转向路径"); }; connectionsBox.append(button); } } -function renderLaneControls(connection) { const rows = state.compiled.movements?.filter((movement) => movement.connectionId === connection.id) || []; for (const row of rows) { const targetRoad = state.compiled.model.roads.find((road) => road.id === row.toRoadId); const label = document.createElement("label"); const input = document.createElement("input"); input.type = "checkbox"; input.checked = effectiveLaneEnabled(row); input.onchange = () => { stageLaneConnection(row, input.checked); selectRoad(selectedRoad, "有未保存修改:转向路径已即时更新"); }; const geometryNote = row.geometryStatus === "continuous" ? ",节点连续" : row.geometryStatus === "deferred-too-long" ? ",路径过长未绘制" : ""; label.append(input, ` ${lanePositionLabel(selectedRoad, laneIndex(row.fromLaneId))} → ${lanePositionLabel(targetRoad, laneIndex(row.toLaneId))}(${osmDirectionLabel(targetRoad)}${geometryNote})`); connectionsBox.append(label); } } -function turnName(from, to) { const heading = (a, b) => Math.atan2(b[0] - a[0], b[1] - a[1]) * 180 / Math.PI; const delta = ((heading(to.centerline[0], to.centerline[1]) - heading(from.centerline.at(-2), from.centerline.at(-1)) + 540) % 360) - 180; return Math.abs(delta) >= 150 ? "掉头" : Math.abs(delta) <= 30 ? "直行" : delta > 0 ? "右转" : "左转"; } -function stageConnection(connection, enabled) { const id = `连接:${connection.id}`; staged = staged.filter((item) => item.id !== id); staged.push({ id, kind: "junction-connection", fromEndpointId: connection.fromEndpointId, toEndpointId: connection.toEndpointId, enabled }); layers.connectors.changed(); updateDirtyState(); } -function stageLaneConnection(connector, enabled) { const fromLaneId = laneId(connector, "from"); const toLaneId = laneId(connector, "to"); const id = `车道连接:${fromLaneId}->${toLaneId}`; staged = staged.filter((item) => item.id !== id); staged.push({ id, kind: "lane-connection", fromLaneId, toLaneId, enabled }); layers.connectors.changed(); updateDirtyState(); } -function chooseManualTarget(targetRoad) { const toEndpoint = endpointFor(targetRoad, "start"); if (!endpointsCompatible(manualFromEndpoint, toEndpoint)) return message("该方向的起点与当前道路终点不兼容:必须是同一路口,或相距不超过 35 米。"); const connection = { id: `connection:${manualFromEndpoint.id}:${toEndpoint.id}`, fromEndpointId: manualFromEndpoint.id, toEndpointId: toEndpoint.id }; manualFromEndpoint = null; stageConnection(connection, true); selectRoad(selectedRoad, "有未保存修改:手工连接已暂存;保存并重新生成后会出现转向路径"); } -addConnectionButton.onclick = () => { const endpoint = endpointFor(selectedRoad, "end"); if (!endpoint) return; manualFromEndpoint = endpoint; select.getFeatures().clear(); message("请在地图上点击目标方向的 OSM 中心线;仅同一路口或 35 米内的驶出方向可连接。"); }; -function focusDiagnostic(item) { const feature = layers.diagnostics.getSource().getFeatures().find((candidate) => candidate.get("id") === item.id); if (feature) map.getView().fit(feature.getGeometry().getExtent(), { padding: [80, 80, 80, 360], maxZoom: 18, duration: 250 }); const junction = layers.native.getSource().getFeatures().find((candidate) => candidate.get("native_id") === item.subjectId); if (junction) return selectJunction(junction); selectRoad(state.compiled.model.roads.find((road) => road.id === item.subjectId), `已定位:${item.message}`); } -function diagnosticLabel(item) { const road = state.compiled.model.roads.find((candidate) => candidate.id === item.subjectId); if (item.rule !== "unconnected-interior-road-end" || !road) return item.message; const candidateCount = item.manualCandidates?.length || 0; return `${roadLabel(road)}(${osmDirectionLabel(road)},节点 ${item.sourceIds[0]}):内部端点未连接${candidateCount ? `,附近有 ${candidateCount} 个可手工连接候选` : ""}`; } -function renderDiagnostics() { const all = state.compiled.diagnostics.filter((diagnostic) => diagnostic.rule !== "ordinary-junction-surface"); const counts = { all: all.length, candidates: all.filter((item) => item.manualCandidates?.length).length, other: all.filter((item) => !item.manualCandidates?.length).length }; for (const button of diagnosticFilters.querySelectorAll("button")) { const filter = button.dataset.diagnosticFilter; button.classList.toggle("active", filter === diagnosticFilter); button.textContent = `${filter === "all" ? "全部" : filter === "candidates" ? "可连接" : "其他"}(${counts[filter]})`; } const visible = all.filter((item) => diagnosticFilter === "all" || diagnosticFilter === "candidates" ? Boolean(item.manualCandidates?.length) : !item.manualCandidates?.length).sort((a, b) => (b.manualCandidates?.length || 0) - (a.manualCandidates?.length || 0)); diagnostics.innerHTML = ""; for (const item of visible) { const button = document.createElement("button"); button.textContent = diagnosticLabel(item); button.onclick = () => focusDiagnostic(item); diagnostics.append(button); } } +function renderManualCandidates(endpoint, road) { + const candidates = + state.compiled.diagnostics.find((item) => item.endpointId === endpoint?.id)?.manualCandidates || []; + if (!candidates.length) return; + const title = document.createElement('p'); + title.textContent = '附近可手工连接的驶出方向'; + connectionsBox.append(title); + for (const candidate of candidates) { + const target = state.compiled.model.roads.find((item) => item.id === candidate.roadId); + if (!target) continue; + const button = document.createElement('button'); + button.type = 'button'; + button.textContent = `${roadLabel(target)}(${candidate.distanceMeters} 米)`; + button.onclick = () => { + stageConnection( + { + id: `connection:${endpoint.id}:${candidate.toEndpointId}`, + fromEndpointId: endpoint.id, + toEndpointId: candidate.toEndpointId, + }, + true, + ); + selectRoad(road, '有未保存修改:手工连接已暂存;保存并重新生成后会出现转向路径'); + }; + connectionsBox.append(button); + } +} +function renderLaneControls(connection) { + const rows = state.compiled.movements?.filter((movement) => movement.connectionId === connection.id) || []; + for (const row of rows) { + const targetRoad = state.compiled.model.roads.find((road) => road.id === row.toRoadId); + const label = document.createElement('label'); + const input = document.createElement('input'); + input.type = 'checkbox'; + input.checked = effectiveLaneEnabled(row); + input.onchange = () => { + stageLaneConnection(row, input.checked); + selectRoad(selectedRoad, '有未保存修改:转向路径已即时更新'); + }; + const geometryNote = + row.geometryStatus === 'continuous' + ? ',节点连续' + : row.geometryStatus === 'deferred-too-long' + ? ',路径过长未绘制' + : ''; + label.append( + input, + ` ${lanePositionLabel(selectedRoad, laneIndex(row.fromLaneId))} → ${lanePositionLabel(targetRoad, laneIndex(row.toLaneId))}(${osmDirectionLabel(targetRoad)}${geometryNote})`, + ); + connectionsBox.append(label); + } +} +function turnName(from, to) { + const heading = (a, b) => (Math.atan2(b[0] - a[0], b[1] - a[1]) * 180) / Math.PI; + const delta = + ((heading(to.centerline[0], to.centerline[1]) - heading(from.centerline.at(-2), from.centerline.at(-1)) + 540) % + 360) - + 180; + return Math.abs(delta) >= 150 ? '掉头' : Math.abs(delta) <= 30 ? '直行' : delta > 0 ? '右转' : '左转'; +} +function stageConnection(connection, enabled) { + const id = `连接:${connection.id}`; + staged = staged.filter((item) => item.id !== id); + staged.push({ + id, + kind: 'junction-connection', + fromEndpointId: connection.fromEndpointId, + toEndpointId: connection.toEndpointId, + enabled, + }); + layers.connectors.changed(); + updateDirtyState(); +} +function stageLaneConnection(connector, enabled) { + const fromLaneId = laneId(connector, 'from'); + const toLaneId = laneId(connector, 'to'); + const id = `车道连接:${fromLaneId}->${toLaneId}`; + staged = staged.filter((item) => item.id !== id); + staged.push({ id, kind: 'lane-connection', fromLaneId, toLaneId, enabled }); + layers.connectors.changed(); + updateDirtyState(); +} +function chooseManualTarget(targetRoad) { + const toEndpoint = endpointFor(targetRoad, 'start'); + if (!endpointsCompatible(manualFromEndpoint, toEndpoint)) + return message('该方向的起点与当前道路终点不兼容:必须是同一路口,或相距不超过 35 米。'); + const connection = { + id: `connection:${manualFromEndpoint.id}:${toEndpoint.id}`, + fromEndpointId: manualFromEndpoint.id, + toEndpointId: toEndpoint.id, + }; + manualFromEndpoint = null; + stageConnection(connection, true); + selectRoad(selectedRoad, '有未保存修改:手工连接已暂存;保存并重新生成后会出现转向路径'); +} +addConnectionButton.onclick = () => { + const endpoint = endpointFor(selectedRoad, 'end'); + if (!endpoint) return; + manualFromEndpoint = endpoint; + select.getFeatures().clear(); + message('请在地图上点击目标方向的 OSM 中心线;仅同一路口或 35 米内的驶出方向可连接。'); +}; +function focusDiagnostic(item) { + const feature = layers.diagnostics + .getSource() + .getFeatures() + .find((candidate) => candidate.get('id') === item.id); + if (feature) + map.getView().fit(feature.getGeometry().getExtent(), { padding: [80, 80, 80, 360], maxZoom: 18, duration: 250 }); + const junction = layers.native + .getSource() + .getFeatures() + .find((candidate) => candidate.get('native_id') === item.subjectId); + if (junction) return selectJunction(junction); + selectRoad( + state.compiled.model.roads.find((road) => road.id === item.subjectId), + `已定位:${item.message}`, + ); +} +function diagnosticLabel(item) { + const road = state.compiled.model.roads.find((candidate) => candidate.id === item.subjectId); + if (item.rule !== 'unconnected-interior-road-end' || !road) return item.message; + const candidateCount = item.manualCandidates?.length || 0; + return `${roadLabel(road)}(${osmDirectionLabel(road)},节点 ${item.sourceIds[0]}):内部端点未连接${candidateCount ? `,附近有 ${candidateCount} 个可手工连接候选` : ''}`; +} +function renderDiagnostics() { + const all = state.compiled.diagnostics.filter((diagnostic) => diagnostic.rule !== 'ordinary-junction-surface'); + const counts = { + all: all.length, + candidates: all.filter((item) => item.manualCandidates?.length).length, + other: all.filter((item) => !item.manualCandidates?.length).length, + }; + for (const button of diagnosticFilters.querySelectorAll('button')) { + const filter = button.dataset.diagnosticFilter; + button.classList.toggle('active', filter === diagnosticFilter); + button.textContent = `${filter === 'all' ? '全部' : filter === 'candidates' ? '可连接' : '其他'}(${counts[filter]})`; + } + const visible = all + .filter((item) => + diagnosticFilter === 'all' || diagnosticFilter === 'candidates' + ? Boolean(item.manualCandidates?.length) + : !item.manualCandidates?.length, + ) + .sort((a, b) => (b.manualCandidates?.length || 0) - (a.manualCandidates?.length || 0)); + diagnostics.innerHTML = ''; + for (const item of visible) { + const button = document.createElement('button'); + button.textContent = diagnosticLabel(item); + button.onclick = () => focusDiagnostic(item); + diagnostics.append(button); + } +} function renderSummary() { const comparison = state.comparison; const rows = [ - ["方向道路", comparison.nativeRoadCount], - ["路缘与步行带", comparison.nativeSidewalkSurfaceFeatures], - ["路口面", comparison.nativeJunctionSurfaceFeatures], - ["普通构面路口", comparison.nativeApproachEnvelopeJunctions], - ["兜底构面路口", comparison.nativeFallbackJunctions], - ["最大外缘扩张", comparison.nativeMaxJunctionExpansionRatio], - ["道路中心虚线", comparison.nativeCenterLineFeatures], - ["道路方向箭头", comparison.nativeDirectionArrowFeatures], - ["路口转向箭头", comparison.nativeTurnArrowFeatures], - ["斑马线条带", comparison.nativeCrosswalkFeatures], - ["停止线", comparison.nativeVehicleStopLineFeatures], - ["红绿灯设施", state.trafficSignals?.assemblies?.features?.length || 0], - ["行驶动作", comparison.nativeMovementCount], - ["已绘制路径", comparison.nativePublishedMovementCount], - ["可手工复核", comparison.unconnectedEndsWithManualCandidates], - ["内部断头", comparison.unconnectedInteriorRoadEnds], - ["osm2streets 参考", comparison.osm2streetsAvailable ? comparison.osm2streetsRoadSurfaceFeatures : "无"], + ['方向道路', comparison.nativeRoadCount], + ['路缘与步行带', comparison.nativeSidewalkSurfaceFeatures], + ['路口面', comparison.nativeJunctionSurfaceFeatures], + ['普通构面路口', comparison.nativeApproachEnvelopeJunctions], + ['兜底构面路口', comparison.nativeFallbackJunctions], + ['最大外缘扩张', comparison.nativeMaxJunctionExpansionRatio], + ['道路中心虚线', comparison.nativeCenterLineFeatures], + ['道路方向箭头', comparison.nativeDirectionArrowFeatures], + ['路口转向箭头', comparison.nativeTurnArrowFeatures], + ['斑马线条带', comparison.nativeCrosswalkFeatures], + ['停止线', comparison.nativeVehicleStopLineFeatures], + ['红绿灯设施', state.trafficSignals?.assemblies?.features?.length || 0], + ['行驶动作', comparison.nativeMovementCount], + ['已绘制路径', comparison.nativePublishedMovementCount], + ['可手工复核', comparison.unconnectedEndsWithManualCandidates], + ['内部断头', comparison.unconnectedInteriorRoadEnds], + ['osm2streets 参考', comparison.osm2streetsAvailable ? comparison.osm2streetsRoadSurfaceFeatures : '无'], ]; - summary.innerHTML = ""; + summary.innerHTML = ''; for (const [label, value] of rows) { - const term = document.createElement("dt"); const detail = document.createElement("dd"); - term.textContent = label; detail.textContent = value; summary.append(term, detail); + const term = document.createElement('dt'); + const detail = document.createElement('dd'); + term.textContent = label; + detail.textContent = value; + summary.append(term, detail); } } -function stageRoadOverride(road, changes) { const id = `道路:${road.id}`; const existing = staged.find((item) => item.id === id) || state.overrides.overrides.find((item) => item.id === id); staged = staged.filter((item) => item.id !== id); staged.push({ ...existing, id, kind: "road", roadId: road.id, ...changes }); } -function setMarkingStyleForm(title, target, style, allowsDouble) { centerLineForm.hidden = false; markingStyleHeading.textContent = title; centerLineSegment.textContent = target; doubleYellowOption.hidden = !allowsDouble; centerLineStyleInput.value = style || "yellow-dashed"; } -function selectCenterLine(feature) { selectedCenterLineSegment = feature.get("segment_id"); selectedLaneSeparator = null; setMarkingStyleForm("道路中心线样式", `道路段:${selectedCenterLineSegment}`, feature.get("effective_style"), true); } -function selectLaneSeparator(feature) { selectedCenterLineSegment = null; selectedLaneSeparator = feature.getProperties(); setMarkingStyleForm("车道分隔线样式", `第 ${selectedLaneSeparator.left_lane_index} 与第 ${selectedLaneSeparator.right_lane_index} 车道之间`, selectedLaneSeparator.effective_style || "white-dashed", false); } -function selectEdgeLine(feature) { selectedCenterLineSegment = null; selectedLaneSeparator = null; selectedEdgeLine = feature.getProperties(); setMarkingStyleForm("道路外缘线样式", `${selectedEdgeLine.side === "left" ? "左" : "右"}侧外缘`, selectedEdgeLine.effective_style || "white-solid", false); } -function clearCenterLineSelection() { selectedCenterLineSegment = null; selectedLaneSeparator = null; selectedEdgeLine = null; centerLineForm.hidden = true; } -function stageCenterLineStyle(segmentId, style) { const parts = style.split("-"); const double = parts[0] === "double"; const [color, pattern] = double ? parts.slice(1) : parts; const id = `道路中心线:${segmentId}`; const existing = staged.find((item) => item.id === id) || state.overrides.overrides.find((item) => item.id === id); staged = staged.filter((item) => item.id !== id); staged.push({ ...existing, id, kind: "center-line-style", segmentId, color, pattern, double }); } -form.onsubmit = (event) => { event.preventDefault(); const roadChanges = { widthMeters: Number(widthInput.value), laneCount: Number(lanesInput.value), sidewalkLeft: leftInput.checked, sidewalkRight: rightInput.checked }; stageRoadOverride(selectedRoad, roadChanges); const opposite = state.compiled.model.roads.find((road) => road.id !== selectedRoad.id && road.segmentId === selectedRoad.segmentId); if (opposite) stageRoadOverride(opposite, { sidewalkLeft: rightInput.checked, sidewalkRight: leftInput.checked }); updateDirtyState(); message(opposite ? "有未保存修改:双向道路的路缘与步行带已按实际侧边同步" : "有未保存修改"); }; -function stageSelectedCenterLineStyle() { if (!selectedCenterLineSegment && !selectedLaneSeparator && !selectedEdgeLine) return; if (selectedCenterLineSegment) stageCenterLineStyle(selectedCenterLineSegment, centerLineStyleInput.value); else { const [color, pattern] = centerLineStyleInput.value.split("-"); const item = selectedLaneSeparator || selectedEdgeLine; const id = selectedLaneSeparator ? `车道分隔线:${item.road_id}:${item.left_lane_index}-${item.right_lane_index}` : `道路外缘线:${item.road_id}:${item.side}`; staged = staged.filter((change) => change.id !== id); staged.push(selectedLaneSeparator ? { id, kind: "lane-separator-style", roadId: item.road_id, leftLaneIndex: item.left_lane_index, rightLaneIndex: item.right_lane_index, color, pattern } : { id, kind: "edge-line-style", roadId: item.road_id, side: item.side, color, pattern }); } updateDirtyState(); message("有未保存修改:线样式"); } -centerLineForm.onsubmit = (event) => { event.preventDefault(); stageSelectedCenterLineStyle(); }; +function stageRoadOverride(road, changes) { + const id = `道路:${road.id}`; + const existing = staged.find((item) => item.id === id) || state.overrides.overrides.find((item) => item.id === id); + staged = staged.filter((item) => item.id !== id); + staged.push({ ...existing, id, kind: 'road', roadId: road.id, ...changes }); +} +function setMarkingStyleForm(title, target, style, allowsDouble) { + centerLineForm.hidden = false; + markingStyleHeading.textContent = title; + centerLineSegment.textContent = target; + doubleYellowOption.hidden = !allowsDouble; + centerLineStyleInput.value = style || 'yellow-dashed'; +} +function selectCenterLine(feature) { + selectedCenterLineSegment = feature.get('segment_id'); + selectedLaneSeparator = null; + setMarkingStyleForm('道路中心线样式', `道路段:${selectedCenterLineSegment}`, feature.get('effective_style'), true); +} +function selectLaneSeparator(feature) { + selectedCenterLineSegment = null; + selectedLaneSeparator = feature.getProperties(); + setMarkingStyleForm( + '车道分隔线样式', + `第 ${selectedLaneSeparator.left_lane_index} 与第 ${selectedLaneSeparator.right_lane_index} 车道之间`, + selectedLaneSeparator.effective_style || 'white-dashed', + false, + ); +} +function selectEdgeLine(feature) { + selectedCenterLineSegment = null; + selectedLaneSeparator = null; + selectedEdgeLine = feature.getProperties(); + setMarkingStyleForm( + '道路外缘线样式', + `${selectedEdgeLine.side === 'left' ? '左' : '右'}侧外缘`, + selectedEdgeLine.effective_style || 'white-solid', + false, + ); +} +function clearCenterLineSelection() { + selectedCenterLineSegment = null; + selectedLaneSeparator = null; + selectedEdgeLine = null; + centerLineForm.hidden = true; +} +function stageCenterLineStyle(segmentId, style) { + const parts = style.split('-'); + const double = parts[0] === 'double'; + const [color, pattern] = double ? parts.slice(1) : parts; + const id = `道路中心线:${segmentId}`; + const existing = staged.find((item) => item.id === id) || state.overrides.overrides.find((item) => item.id === id); + staged = staged.filter((item) => item.id !== id); + staged.push({ ...existing, id, kind: 'center-line-style', segmentId, color, pattern, double }); +} +form.onsubmit = (event) => { + event.preventDefault(); + const roadChanges = { + widthMeters: Number(widthInput.value), + laneCount: Number(lanesInput.value), + sidewalkLeft: leftInput.checked, + sidewalkRight: rightInput.checked, + }; + stageRoadOverride(selectedRoad, roadChanges); + const opposite = state.compiled.model.roads.find( + (road) => road.id !== selectedRoad.id && road.segmentId === selectedRoad.segmentId, + ); + if (opposite) stageRoadOverride(opposite, { sidewalkLeft: rightInput.checked, sidewalkRight: leftInput.checked }); + updateDirtyState(); + message(opposite ? '有未保存修改:双向道路的路缘与步行带已按实际侧边同步' : '有未保存修改'); +}; +function stageSelectedCenterLineStyle() { + if (!selectedCenterLineSegment && !selectedLaneSeparator && !selectedEdgeLine) return; + if (selectedCenterLineSegment) stageCenterLineStyle(selectedCenterLineSegment, centerLineStyleInput.value); + else { + const [color, pattern] = centerLineStyleInput.value.split('-'); + const item = selectedLaneSeparator || selectedEdgeLine; + const id = selectedLaneSeparator + ? `车道分隔线:${item.road_id}:${item.left_lane_index}-${item.right_lane_index}` + : `道路外缘线:${item.road_id}:${item.side}`; + staged = staged.filter((change) => change.id !== id); + staged.push( + selectedLaneSeparator + ? { + id, + kind: 'lane-separator-style', + roadId: item.road_id, + leftLaneIndex: item.left_lane_index, + rightLaneIndex: item.right_lane_index, + color, + pattern, + } + : { id, kind: 'edge-line-style', roadId: item.road_id, side: item.side, color, pattern }, + ); + } + updateDirtyState(); + message('有未保存修改:线样式'); +} +centerLineForm.onsubmit = (event) => { + event.preventDefault(); + stageSelectedCenterLineStyle(); +}; centerLineStyleInput.onchange = stageSelectedCenterLineStyle; -async function saveStagedChanges() { if (!staged.length) return true; const existing = state.overrides.overrides.filter((item) => !staged.some((change) => change.id === item.id)); const response = await fetch("/api/overrides", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ schema: "native-road-overrides/v1", overrides: [...existing, ...staged] }) }); const result = await response.json(); if (!result.ok) { message(result.error); return false; } state.overrides = result.overrides; staged = []; updateDirtyState(); return true; } -saveButton.onclick = async () => { if (await saveStagedChanges()) message("已保存,点击“保存并重新生成”写入几何"); }; +async function saveStagedChanges() { + if (!staged.length) return true; + const existing = state.overrides.overrides.filter((item) => !staged.some((change) => change.id === item.id)); + const response = await fetch('/api/overrides', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ schema: 'native-road-overrides/v1', overrides: [...existing, ...staged] }), + }); + const result = await response.json(); + if (!result.ok) { + message(result.error); + return false; + } + state.overrides = result.overrides; + staged = []; + updateDirtyState(); + return true; +} +saveButton.onclick = async () => { + if (await saveStagedChanges()) message('已保存,点击“保存并重新生成”写入几何'); +}; compileButton.onclick = async () => { - if (!await saveStagedChanges()) return; - message("正在保存修改并重新生成..."); - const response = await fetch("/api/compile", { method: "POST", cache: "no-store" }); + if (!(await saveStagedChanges())) return; + message('正在保存修改并重新生成...'); + const response = await fetch('/api/compile', { method: 'POST', cache: 'no-store' }); const nextState = await response.json(); if (!response.ok || nextState.ok === false || !nextState.compiled?.model || !nextState.layers) { message(`重新生成失败:${nextState.error || `HTTP ${response.status}`}`); @@ -435,14 +1365,20 @@ compileButton.onclick = async () => { renderDiagnostics(); renderSummary(); selectRoad(selectedRoad ? state.compiled.model.roads.find((road) => road.id === selectedRoad.id) : null); - message("已保存并重新生成"); + message('已保存并重新生成'); }; -for (const input of document.querySelectorAll("[data-layer]")) input.onchange = () => { const visible = input.checked; layers[input.dataset.layer].setVisible(visible); if (input.dataset.layer === "osm") layers.osmDirection.setVisible(visible); }; +for (const input of document.querySelectorAll('[data-layer]')) + input.onchange = () => { + const visible = input.checked; + layers[input.dataset.layer].setVisible(visible); + if (input.dataset.layer === 'osm') layers.osmDirection.setVisible(visible); + }; scenePreviewToggle.onchange = () => { scenePreview = scenePreviewToggle.checked; - for (const input of document.querySelectorAll("[data-layer]")) { + for (const input of document.querySelectorAll('[data-layer]')) { const layer = input.dataset.layer; - if (["osm", "lanes", "reference", "gaodeReference"].includes(layer)) layers[layer].setVisible(!scenePreview && input.checked); + if (['osm', 'lanes', 'reference', 'gaodeReference'].includes(layer)) + layers[layer].setVisible(!scenePreview && input.checked); } layers.osmDirection.setVisible(!scenePreview && document.querySelector('[data-layer="osm"]').checked); layers.connectors.setVisible(!scenePreview && document.querySelector('[data-layer="lanes"]').checked); @@ -452,14 +1388,53 @@ scenePreviewToggle.onchange = () => { const signalsVisible = document.querySelector('[data-layer="signals"]').checked; layers.signals.setVisible(signalsVisible); layers.diagnostics.setVisible(!scenePreview); - layers.native.changed(); layers.sidewalks.changed(); - message(scenePreview ? "场景效果预览:当前编译面" : "编辑图层预览"); + layers.native.changed(); + layers.sidewalks.changed(); + message(scenePreview ? '场景效果预览:当前编译面' : '编辑图层预览'); }; -for (const button of diagnosticFilters.querySelectorAll("button")) button.onclick = () => { diagnosticFilter = button.dataset.diagnosticFilter; renderDiagnostics(); }; +for (const button of diagnosticFilters.querySelectorAll('button')) + button.onclick = () => { + diagnosticFilter = button.dataset.diagnosticFilter; + renderDiagnostics(); + }; function showImportScreen() { - const panel = document.createElement("form"); panel.style.cssText = "position:fixed;inset:90px 25%;z-index:20;background:#fff;padding:32px;box-shadow:0 8px 30px #0003"; - panel.innerHTML = "

导入 OSM 地图

选择一个 .osm 文件开始创建工作区。

"; - panel.onsubmit = async (event) => { event.preventDefault(); const file = panel.querySelector("input").files[0]; const output = panel.querySelector("output"); if (!file) return; output.textContent = "正在导入并编译..."; const body = new FormData(); body.append("file", file, file.name); try { const response = await fetch("/api/import", { method: "POST", body }); const result = await response.json(); if (!response.ok || !result.ok) throw new Error(result.error || `HTTP ${response.status}`); location.reload(); } catch (error) { output.textContent = error.message; } }; + const panel = document.createElement('form'); + panel.style.cssText = + 'position:fixed;inset:90px 25%;z-index:20;background:#fff;padding:32px;box-shadow:0 8px 30px #0003'; + panel.innerHTML = + '

导入 OSM 地图

选择一个 .osm 文件开始创建工作区。

'; + panel.onsubmit = async (event) => { + event.preventDefault(); + const file = panel.querySelector('input').files[0]; + const output = panel.querySelector('output'); + if (!file) return; + output.textContent = '正在导入并编译...'; + const body = new FormData(); + body.append('file', file, file.name); + try { + const response = await fetch('/api/import', { method: 'POST', body }); + const result = await response.json(); + if (!response.ok || !result.ok) throw new Error(result.error || `HTTP ${response.status}`); + location.reload(); + } catch (error) { + output.textContent = error.message; + } + }; document.body.append(panel); } -fetch("/api/state").then((response) => response.json()).then((value) => { if (!value.active && !value.compiled) return showImportScreen(); state = value; updateDirtyState(); updateSources(); renderDiagnostics(); renderSummary(); areaLabel.textContent = state.areaId; const signalUid = new URLSearchParams(location.search).get("signal"); const signal = signalUid && poleFeatureForSignal(signalUid); if (signal) selectSignal(signal); message(`已加载 ${state.compiled.model.roads.length} 条方向道路`); }).catch((error) => message(error.message)); +fetch('/api/state') + .then((response) => response.json()) + .then((value) => { + if (!value.active && !value.compiled) return showImportScreen(); + state = value; + updateDirtyState(); + updateSources(); + renderDiagnostics(); + renderSummary(); + areaLabel.textContent = state.areaId; + const signalUid = new URLSearchParams(location.search).get('signal'); + const signal = signalUid && poleFeatureForSignal(signalUid); + if (signal) selectSignal(signal); + message(`已加载 ${state.compiled.model.roads.length} 条方向道路`); + }) + .catch((error) => message(error.message)); diff --git a/workbench/client/index.html b/workbench/client/index.html index cfcc79c..6d1e469 100644 --- a/workbench/client/index.html +++ b/workbench/client/index.html @@ -1,4 +1,12 @@ -道路编译工作台 -
道路编译工作台导出道路包
-
+ + + + + 道路编译工作台 + + +
+ + + diff --git a/workbench/client/src/App.tsx b/workbench/client/src/App.tsx new file mode 100644 index 0000000..8ae1e0d --- /dev/null +++ b/workbench/client/src/App.tsx @@ -0,0 +1,638 @@ +import { useEffect, useState } from 'react'; +import { Download, RefreshCw, Save, Upload } from 'lucide-react'; +import { api } from './lib/api'; +import { MapCanvas } from './components/MapCanvas'; +import { Button } from './ui/button'; +import type { GeoFeature, Override, Road, WorkbenchState } from './types/state'; +import type { LayerName } from './map/layers'; + +const layerLabels: Array<[LayerName, string]> = [ + ['osm', 'OSM 道路中心线'], + ['native', '自研道路与路口面'], + ['sidewalks', '路缘与步行带'], + ['lanes', '车道与转向路径'], + ['directionArrows', '道路方向箭头'], + ['markings', '车道分隔线与路口转向箭头'], + ['centerLines', '道路中心线'], + ['edgeLines', '道路外缘线'], + ['controls', '斑马线与停止线'], + ['signals', '红绿灯设施'], + ['gaodeReference', '高德规整路口参考'], + ['reference', 'osm2streets 参考面'], +]; +const defaultLayers = Object.fromEntries( + layerLabels.map(([key]) => [key, key !== 'edgeLines' && key !== 'reference']), +) as Record; +const direction = (road: Road) => (road.direction === 'forward' ? '沿 OSM 方向' : '逆 OSM 方向'); +const label = (road: Road) => road.tags.name || `${road.highway}(OSM ${road.osmWayIds.join(', ')})`; +const laneIndex = (id: string) => Number(id.split(':').at(-1)); +function App() { + const [state, setState] = useState(); + const [selected, setSelected] = useState(null); + const [staged, setStaged] = useState([]); + const [layers, setLayers] = useState(defaultLayers); + const [scene, setScene] = useState(false); + const [status, setStatus] = useState('正在加载工作台...'); + const [filter, setFilter] = useState<'all' | 'candidates' | 'other'>('all'); + const [marking, setMarking] = useState | null>(null); + useEffect(() => { + api + .state() + .then((value) => { + if ('compiled' in value) { + setState(value); + setStatus(`已加载 ${value.compiled.model.roads.length} 条方向道路`); + } else setStatus('请导入 OSM 文件以开始'); + }) + .catch((error: Error) => setStatus(error.message)); + }, []); + const stage = (change: Override) => + setStaged((current) => [...current.filter((item) => item.id !== change.id), change]); + const save = async () => { + if (!state || !staged.length) return true; + try { + const overrides = [ + ...state.overrides.overrides.filter((item) => !staged.some((change) => change.id === item.id)), + ...staged, + ]; + const result = await api.overrides(overrides); + setState({ ...state, overrides: result.overrides }); + setStaged([]); + setStatus('已保存,点击“保存并重新生成”写入几何'); + return true; + } catch (error) { + setStatus((error as Error).message); + return false; + } + }; + const compile = async () => { + if (!(await save())) return; + try { + setStatus('正在重新生成...'); + const next = await api.compile(); + setState(next); + setSelected((current) => next.compiled.model.roads.find((road) => road.id === current?.id) || null); + setStatus('已保存并重新生成'); + } catch (error) { + setStatus((error as Error).message); + } + }; + if (!state) + return ( + { + setState(next); + setStatus(`已导入 ${next.areaId}`); + }} + /> + ); + const diagnostics = state.compiled.diagnostics.filter( + (item) => + item.rule !== 'ordinary-junction-surface' && + (filter === 'all' || filter === 'candidates' + ? Boolean(item.manualCandidates?.length) + : !item.manualCandidates?.length), + ); + const handleFeature = (properties: Record) => { + setMarking(properties); + const uid = properties.signal_uid; + if (typeof uid === 'string') setStatus(`已选中原生红绿灯 ${uid}`); + else if (properties.effective_style) setStatus('已选中标线,可在右侧暂存样式'); + }; + return ( + <> +
+ 道路编译工作台 + {state.areaId} + {status} + + {staged.length ? `未保存修改 ${staged.length} 项` : '所有修改已保存'} + + + + 导出道路包 + + + +
+
+ + + +
+ + ); +} +function ImportScreen({ status, onImported }: { status: string; onImported: (state: WorkbenchState) => void }) { + const [file, setFile] = useState(); + const [busy, setBusy] = useState(false); + return ( +
+
{ + event.preventDefault(); + if (!file) return; + setBusy(true); + try { + onImported(await api.import(file)); + } catch (error) { + console.error(error); + } finally { + setBusy(false); + } + }} + > +

导入 OSM 地图

+

{status}

+ setFile(event.target.files?.[0])} + required + /> + +
+
+ ); +} +function Inspector({ + road, + state, + staged, + marking, + stage, + status, + setState, +}: { + road: Road | null; + state: WorkbenchState; + staged: Override[]; + marking: Record | null; + stage: (item: Override) => void; + status: (value: string) => void; + setState: (next: WorkbenchState) => void; +}) { + const [width, setWidth] = useState(0); + const [lanes, setLanes] = useState(1); + const [left, setLeft] = useState(false); + const [right, setRight] = useState(false); + useEffect(() => { + if (road) { + setWidth(road.widthMeters); + setLanes(road.laneCount); + setLeft(road.sidewalkLeft); + setRight(road.sidewalkRight); + } + }, [road]); + const endpoint = state.compiled.model.endpoints.find((item) => item.roadId === road?.id && item.side === 'end'); + const movements = state.compiled.movements?.filter((item) => item.fromRoadId === road?.id) || []; + const effective = (id: string, fallback: boolean) => + ((staged.find((item) => item.id === id) || state.overrides.overrides.find((item) => item.id === id))?.enabled as + boolean | undefined) ?? fallback; + const updateSignal = async (features: GeoFeature[]) => { + try { + const result = await api.signals({ + ...state.trafficSignals, + assemblies: { ...state.trafficSignals.assemblies, features }, + }); + setState({ ...state, trafficSignals: result.trafficSignals, trafficRuntime: result.trafficRuntime }); + status('红绿灯已保存'); + } catch (error) { + status((error as Error).message); + } + }; + const signalUid = typeof marking?.signal_uid === 'string' ? marking.signal_uid : ''; + const signal = state.trafficSignals.assemblies.features.find((item) => item.properties?.signal_uid === signalUid); + return ( + + ); +} +function MarkingEditor({ + marking, + stage, +}: { + marking: Record | null; + stage: (item: Override) => void; +}) { + const style = typeof marking?.effective_style === 'string' ? marking.effective_style : ''; + const [value, setValue] = useState(style || 'yellow-dashed'); + useEffect(() => setValue(style || 'yellow-dashed'), [style]); + if (!style || !marking) return null; + const roadId = String(marking.road_id); + const segment = marking.segment_id; + const kind = String(marking.provenance || ''); + const save = () => { + const [color, pattern] = value.replace('double-', '').split('-'); + if (segment) + stage({ + id: `道路中心线:${segment}`, + kind: 'center-line-style', + segmentId: segment, + color, + pattern, + double: value.startsWith('double-'), + }); + else if (kind.includes('edge-line')) + stage({ + id: `道路外缘线:${roadId}:${marking.side}`, + kind: 'edge-line-style', + roadId, + side: marking.side, + color, + pattern, + }); + else + stage({ + id: `车道分隔线:${roadId}:${marking.left_lane_index}-${marking.right_lane_index}`, + kind: 'lane-separator-style', + roadId, + leftLaneIndex: marking.left_lane_index, + rightLaneIndex: marking.right_lane_index, + color, + pattern, + }); + }; + return ( +
{ + event.preventDefault(); + save(); + }} + > +
+

标线样式

+ + +
+ ); +} +function SignalEditor({ + state, + signal, + status, + update, +}: { + state: WorkbenchState; + signal?: GeoFeature; + status: (text: string) => void; + update: (features: GeoFeature[]) => Promise; +}) { + const [selected, setSelected] = useState(''); + useEffect(() => setSelected(String(signal?.properties?.signal_uid || '')), [signal]); + const feature = state.trafficSignals.assemblies.features.find((item) => item.properties?.signal_uid === selected); + const saveField = (key: string, value: unknown) => { + if (!feature) return; + void update( + state.trafficSignals.assemblies.features.map((item) => + item === feature ? { ...item, properties: { ...item.properties, [key]: value } } : item, + ), + ); + }; + return ( +
+
+

原生红绿灯

+ + + {feature ? ( + <> + + + + + + + ) : null} +
+ ); +} +function Candidates({ + state, + setState, + status, +}: { + state: WorkbenchState; + setState: (state: WorkbenchState) => void; + status: (text: string) => void; +}) { + const candidates = state.debug?.junctionCandidates || []; + if (!candidates.length) return null; + return ( +
+
+

复杂路口候选(debug)

+ {candidates.map((candidate) => ( + + ))} +
+ ); +} +export default App; diff --git a/workbench/client/src/components/MapCanvas.tsx b/workbench/client/src/components/MapCanvas.tsx new file mode 100644 index 0000000..5741b7b --- /dev/null +++ b/workbench/client/src/components/MapCanvas.tsx @@ -0,0 +1,88 @@ +import { useEffect, useRef } from 'react'; +import Map from 'ol/Map'; +import View from 'ol/View'; +import Select from 'ol/interaction/Select'; +import { click } from 'ol/events/condition'; +import type { Road, WorkbenchState } from '../types/state'; +import { createLayers, updateLayers, type LayerName } from '../map/layers'; + +interface Props { + state: WorkbenchState; + selected: Road | null; + visible: Partial>; + scene: boolean; + onSelectRoad: (road: Road) => void; + onFeature: (properties: Record) => void; +} +export function MapCanvas({ state, selected, visible, scene, onSelectRoad, onFeature }: Props) { + const target = useRef(null); + const mapRef = useRef(null); + const layersRef = useRef | null>(null); + const selectedRef = useRef(selected); + const stateRef = useRef(state); + selectedRef.current = selected; + stateRef.current = state; + useEffect(() => { + if (!target.current) return; + const layers = createLayers( + () => selectedRef.current, + () => scene, + ); + layersRef.current = layers; + const map = new Map({ + target: target.current, + layers: Object.values(layers), + view: new View({ center: [0, 0], zoom: 2 }), + }); + mapRef.current = map; + const select = new Select({ + condition: click, + layers: (layer) => layer !== layers.selectedRoad, + hitTolerance: 12, + style: null, + }); + map.addInteraction(select); + const listener = ({ selected: values }: { selected: import('ol/Feature').default[] }) => { + const properties = values[0]?.getProperties(); + if (!properties) return; + const roadId = + properties.road_id || + properties.from_road_id || + (typeof properties.from_lane_id === 'string' + ? properties.from_lane_id.slice(5, properties.from_lane_id.lastIndexOf(':')) + : undefined); + const road = stateRef.current.compiled.model.roads.find((item) => item.id === roadId); + if (road) onSelectRoad(road); + onFeature(properties); + }; + select.on('select', listener); + return () => { + select.un('select', listener); + map.setTarget(undefined); + }; + // Map construction must be once per mounted canvas; state changes update sources below. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + useEffect(() => { + const layers = layersRef.current; + const map = mapRef.current; + if (!layers || !map) return; + updateLayers(layers, state, selected); + const extent = layers.osm.getSource()!.getExtent(); + if (extent && Number.isFinite(extent[0])) map.getView().fit(extent, { padding: [48, 48, 48, 48], maxZoom: 19 }); + }, [state, selected]); + useEffect(() => { + const layers = layersRef.current; + if (!layers) return; + Object.entries(visible).forEach(([name, enabled]) => + layers[name as LayerName]?.setVisible( + !scene || !['osm', 'lanes', 'reference', 'gaodeReference', 'connectors'].includes(name) + ? enabled !== false + : false, + ), + ); + layers.native.changed(); + layers.sidewalks.changed(); + }, [visible, scene]); + return
; +} diff --git a/workbench/client/src/lib/api.ts b/workbench/client/src/lib/api.ts new file mode 100644 index 0000000..b4ad3aa --- /dev/null +++ b/workbench/client/src/lib/api.ts @@ -0,0 +1,40 @@ +import type { WorkbenchState } from '../types/state'; + +async function request(path: string, init?: RequestInit): Promise { + const response = await fetch(path, { cache: 'no-store', ...init }); + const value = (await response.json()) as T & { ok?: boolean; error?: string }; + if (!response.ok || value.ok === false) throw new Error(value.error || `HTTP ${response.status}`); + return value; +} + +export const api = { + state: () => request('/api/state'), + import: (file: File) => { + const body = new FormData(); + body.append('file', file, file.name); + return request('/api/import', { method: 'POST', body }); + }, + overrides: (overrides: unknown[]) => + request<{ overrides: WorkbenchState['overrides'] }>('/api/overrides', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ schema: 'native-road-overrides/v1', overrides }), + }), + compile: () => request('/api/compile', { method: 'POST' }), + signals: (document: unknown) => + request>('/api/traffic-signals', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(document), + }), + generateSignals: () => + request>('/api/traffic-signals/generate', { + method: 'POST', + }), + acceptJunctionCandidate: (index: number) => + request('/api/junction-clusters', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ index }), + }), +}; diff --git a/workbench/client/src/main.tsx b/workbench/client/src/main.tsx new file mode 100644 index 0000000..cced5c6 --- /dev/null +++ b/workbench/client/src/main.tsx @@ -0,0 +1,11 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import 'ol/ol.css'; +import './styles.css'; +import App from './App'; + +createRoot(document.getElementById('root')!).render( + + + , +); diff --git a/workbench/client/src/map/layers.ts b/workbench/client/src/map/layers.ts new file mode 100644 index 0000000..5deea3a --- /dev/null +++ b/workbench/client/src/map/layers.ts @@ -0,0 +1,160 @@ +import Feature from 'ol/Feature'; +import GeoJSON from 'ol/format/GeoJSON'; +import LineString from 'ol/geom/LineString'; +import Point from 'ol/geom/Point'; +import VectorLayer from 'ol/layer/Vector'; +import VectorSource from 'ol/source/Vector'; +import CircleStyle from 'ol/style/Circle'; +import Fill from 'ol/style/Fill'; +import RegularShape from 'ol/style/RegularShape'; +import Stroke from 'ol/style/Stroke'; +import Style from 'ol/style/Style'; +import type { Road, WorkbenchState } from '../types/state'; + +const geojson = new GeoJSON(); +const source = () => new VectorSource(); +export type LayerName = + | 'reference' + | 'gaodeReference' + | 'native' + | 'sidewalks' + | 'osm' + | 'lanes' + | 'edgeLines' + | 'directionArrows' + | 'markings' + | 'centerLines' + | 'controls' + | 'signals' + | 'connectors' + | 'diagnostics' + | 'selectedRoad'; +export function createLayers(onRoad: () => Road | null, scene: () => boolean) { + const simple = (color: string, width = 1) => + new Style({ fill: new Fill({ color: `${color}55` }), stroke: new Stroke({ color, width }) }); + const layers: Record> = { + reference: new VectorLayer({ source: source(), visible: false, style: simple('#8999a0') }), + gaodeReference: new VectorLayer({ source: source(), style: simple('#2563eb', 1.5) }), + native: new VectorLayer({ + source: source(), + style: () => (scene() ? new Style({ fill: new Fill({ color: '#3f4b50' }) }) : simple('#296956')), + }), + sidewalks: new VectorLayer({ + source: source(), + style: () => (scene() ? new Style({ fill: new Fill({ color: '#b7b9ad' }) }) : simple('#9b7c40')), + }), + osm: new VectorLayer({ + source: source(), + style: (f) => + new Style({ + stroke: new Stroke({ + color: f.get('road_id') === onRoad()?.id ? '#006e91' : '#263630', + width: f.get('road_id') === onRoad()?.id ? 5 : 2, + }), + }), + }), + lanes: new VectorLayer({ + source: source(), + style: new Style({ stroke: new Stroke({ color: '#f5f6ee', width: 1.5, lineDash: [5, 4] }) }), + }), + edgeLines: new VectorLayer({ + source: source(), + visible: false, + style: new Style({ stroke: new Stroke({ color: '#f5f6ee', width: 1 }) }), + }), + directionArrows: new VectorLayer({ + source: source(), + style: new Style({ fill: new Fill({ color: '#f5f6ee' }), stroke: new Stroke({ color: '#d9dacf', width: 1 }) }), + }), + markings: new VectorLayer({ + source: source(), + style: new Style({ fill: new Fill({ color: '#f5f6ee' }), stroke: new Stroke({ color: '#d9dacf', width: 1 }) }), + }), + centerLines: new VectorLayer({ + source: source(), + style: new Style({ fill: new Fill({ color: '#f5be2a' }), stroke: new Stroke({ color: '#d29d16', width: 1 }) }), + }), + controls: new VectorLayer({ + source: source(), + style: new Style({ fill: new Fill({ color: '#f5f6ee' }), stroke: new Stroke({ color: '#d9dacf', width: 1 }) }), + }), + signals: new VectorLayer({ + source: source(), + style: new Style({ + image: new RegularShape({ + points: 4, + radius: 6, + angle: Math.PI / 4, + fill: new Fill({ color: '#263630' }), + stroke: new Stroke({ color: '#fff', width: 2 }), + }), + }), + }), + connectors: new VectorLayer({ + source: source(), + style: new Style({ stroke: new Stroke({ color: '#ad3a76', width: 2, lineDash: [7, 5] }) }), + }), + diagnostics: new VectorLayer({ + source: source(), + style: (f) => + new Style({ + image: new CircleStyle({ + radius: 6, + fill: new Fill({ color: f.get('severity') === 'error' ? '#bf3b2e' : '#d49318' }), + stroke: new Stroke({ color: '#fff', width: 1 }), + }), + }), + }), + selectedRoad: new VectorLayer({ + source: source(), + style: new Style({ stroke: new Stroke({ color: '#00a5cf', width: 8 }) }), + }), + }; + return layers; +} +function features(value: unknown) { + return geojson.readFeatures((value || { type: 'FeatureCollection', features: [] }) as object, { + dataProjection: 'EPSG:4326', + featureProjection: 'EPSG:3857', + }); +} +export function updateLayers(layers: ReturnType, state: WorkbenchState, selected: Road | null) { + const get = (name: LayerName) => layers[name].getSource()!; + const put = (name: LayerName, value: unknown) => { + const target = get(name); + target.clear(); + target.addFeatures(features(value)); + }; + put('reference', state.layers.osm2streetsRoadSurface); + put('gaodeReference', state.junctionReference?.converted); + put('native', state.layers.nativeRoadSurface); + get('native').addFeatures(features(state.layers.nativeIntersectionSurface)); + put('sidewalks', state.layers.nativeSidewalkSurface); + const roads = state.compiled.model.roads.map( + (road) => + new Feature({ geometry: new LineString(road.centerline).transform('EPSG:4326', 'EPSG:3857'), road_id: road.id }), + ); + put('osm', { type: 'FeatureCollection', features: [] }); + get('osm').addFeatures(roads); + put('lanes', state.layers.laneCenterlines); + put('edgeLines', state.layers.edgeLines); + put('directionArrows', state.layers.directionArrows); + put('markings', state.layers.laneSeparators); + get('markings').addFeatures(features(state.layers.turnArrows)); + put('centerLines', state.layers.centerLines); + put('controls', state.layers.crosswalks); + get('controls').addFeatures(features(state.layers.vehicleStopLines)); + put('signals', state.trafficSignals.assemblies); + put('connectors', state.layers.connectors); + put('diagnostics', { + type: 'FeatureCollection', + features: state.compiled.diagnostics + .filter((item) => item.geometry) + .map(({ geometry, ...properties }) => ({ type: 'Feature', properties, geometry: geometry! })), + }); + put('selectedRoad', { type: 'FeatureCollection', features: [] }); + if (selected) + get('selectedRoad').addFeature( + new Feature({ geometry: new LineString(selected.centerline).transform('EPSG:4326', 'EPSG:3857') }), + ); +} diff --git a/workbench/client/src/styles.css b/workbench/client/src/styles.css new file mode 100644 index 0000000..7715232 --- /dev/null +++ b/workbench/client/src/styles.css @@ -0,0 +1,223 @@ +:root { + font-family: system-ui, sans-serif; + color: #202523; + background: #eef1ef; +} +* { + box-sizing: border-box; +} +body { + margin: 0; +} +header { + height: 50px; + display: flex; + gap: 12px; + align-items: center; + padding: 0 16px; + background: #183a32; + color: white; +} +header span { + color: #c9d8d2; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.dirty { + color: #ffe08a; + font-weight: 700; +} +.scene { + margin-left: auto; + white-space: nowrap; +} +main { + display: grid; + grid-template-columns: 260px minmax(0, 1fr) 320px; + height: calc(100vh - 50px); +} +aside { + overflow: auto; + background: white; + padding: 16px; +} +.issues { + border-right: 1px solid #d5dfda; +} +.inspector { + border-left: 1px solid #d5dfda; +} +.map { + min-height: 400px; + background: #d7e2de; +} +.button { + display: inline-flex; + align-items: center; + gap: 5px; + border: 1px solid #82988f; + background: white; + color: #1d392f; + padding: 7px 10px; + border-radius: 3px; + cursor: pointer; + text-decoration: none; + font: inherit; +} +.button:disabled { + opacity: 0.55; + cursor: default; +} +header .button { + white-space: nowrap; +} +h1 { + font-size: 16px; + margin: 0 0 8px; +} +h2 { + font-size: 14px; + margin: 12px 0 8px; +} +label, +output { + display: block; + margin: 10px 0; +} +input[type='number'] { + display: block; + width: 100%; + padding: 7px; + border: 1px solid #aab8b2; +} +dl { + display: grid; + grid-template-columns: 1fr auto; + gap: 5px 10px; + margin: 0; +} +dt { + color: #52615b; +} +dd { + margin: 0; +} +hr { + border: 0; + border-top: 1px solid #dde4e1; + margin: 16px 0; +} +ul { + list-style: none; + padding: 0; + margin: 0; +} +.issues li .button { + width: 100%; + text-align: left; + margin: 4px 0; + background: #fff7e5; + border-color: #e7c67b; +} +.segmented { + display: flex; +} +.segmented .button { + flex: 1; + border-radius: 0; + padding: 6px 4px; +} +.segmented .active { + background: #286956; + border-color: #286956; + color: white; +} +pre { + white-space: pre-wrap; + overflow-wrap: anywhere; + color: #52615b; +} +.import-screen { + min-height: 100vh; + display: grid; + place-items: center; + padding: 24px; +} +.import-screen form { + width: min(460px, 100%); + background: white; + padding: 28px; + box-shadow: 0 8px 30px #0003; +} +.import-screen input { + margin: 18px 0; +} +@media (max-width: 900px) { + header { + height: auto; + min-height: 50px; + flex-wrap: wrap; + padding-block: 8px; + } + main { + grid-template-columns: minmax(0, 1fr); + height: calc(100vh - 66px); + } + .issues { + display: none; + } + .inspector { + position: absolute; + right: 0; + bottom: 0; + width: min(360px, 100%); + max-height: 55vh; + border-top: 1px solid #d5dfda; + } +} +.overview-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} +.overview-grid div, +.overview-status, +.overview-diagnostics { + border: 1px solid #dce5e1; + padding: 7px; + background: #f8fbfa; +} +.overview-grid span, +.overview-status span, +.overview-diagnostics > span { + display: block; + color: #52615b; + font-size: 12px; +} +.overview-grid strong { + display: block; + margin-top: 2px; + font-size: 16px; + font-variant-numeric: tabular-nums; +} +.overview-status { + display: flex; + justify-content: space-between; + margin-top: 8px; +} +.overview-diagnostics { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin-top: 8px; +} +.overview-diagnostics > span { + width: 100%; +} +.overview-diagnostics strong { + background: #fff4dc; + color: #7c5200; + padding: 2px 5px; + font-size: 12px; +} diff --git a/workbench/client/src/types/state.ts b/workbench/client/src/types/state.ts new file mode 100644 index 0000000..f03c006 --- /dev/null +++ b/workbench/client/src/types/state.ts @@ -0,0 +1,92 @@ +export type Position = [number, number]; +export interface GeoFeature { + type: 'Feature'; + properties?: Record; + geometry: { type: string; coordinates: unknown } | null; +} +export interface GeoJson { + type: 'FeatureCollection'; + features: GeoFeature[]; +} +export interface Road { + id: string; + segmentId: string; + centerline: Position[]; + sourceNodeIds: string[]; + osmWayIds: string[]; + tags: Record; + highway: string; + direction: string; + widthMeters: number; + laneCount: number; + sidewalkLeft: boolean; + sidewalkRight: boolean; + provenance?: unknown; + appliedOverrideIds?: string[]; +} +export interface Endpoint { + id: string; + roadId: string; + side: 'start' | 'end'; + nodeId: string; + coordinate: Position; +} +export interface Connection { + id: string; + fromEndpointId: string; + toEndpointId: string; + enabled: boolean; +} +export interface Movement { + id: string; + connectionId: string; + fromRoadId: string; + toRoadId: string; + fromLaneId: string; + toLaneId: string; + turn: string; + geometryPublished?: boolean; + geometryStatus?: string; + nodeId?: string; + provenance?: string; +} +export interface Override { + id: string; + kind: string; + [key: string]: unknown; +} +export interface JunctionCandidate { + index: number; + id: string; + nodeIds: string[]; + nodeCount: number; + diameterMeters: number; + template: string; + coreRadiusMeters: number; +} +export interface Diagnostic { + id: string; + message: string; + rule: string; + subjectId?: string; + endpointId?: string; + severity?: string; + geometry?: { type: string; coordinates: unknown }; + manualCandidates?: Array<{ roadId: string; distanceMeters: number }>; +} +export interface WorkbenchState { + active?: boolean; + areaId: string; + compiled: { + model: { roads: Road[]; endpoints: Endpoint[]; connections: Connection[] }; + movements?: Movement[]; + diagnostics: Diagnostic[]; + }; + overrides: { schema: string; overrides: Override[] }; + trafficSignals: { provenance: string; assemblies: GeoJson }; + trafficRuntime: { signals: unknown[] }; + comparison: Record; + layers: Record; + junctionReference?: { converted: GeoJson }; + debug?: { junctionCandidates: JunctionCandidate[] } | null; +} diff --git a/workbench/client/src/ui/button.tsx b/workbench/client/src/ui/button.tsx new file mode 100644 index 0000000..4145e72 --- /dev/null +++ b/workbench/client/src/ui/button.tsx @@ -0,0 +1,12 @@ +import type { ButtonHTMLAttributes, PropsWithChildren } from 'react'; +export function Button({ + children, + className = '', + ...props +}: PropsWithChildren>) { + return ( + + ); +} diff --git a/workbench/client/tsconfig.json b/workbench/client/tsconfig.json new file mode 100644 index 0000000..34109ba --- /dev/null +++ b/workbench/client/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["DOM", "DOM.Iterable", "ES2022"], + "skipLibCheck": true, + "esModuleInterop": true, + "module": "ESNext", + "moduleResolution": "Bundler", + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true + }, + "include": ["src"] +} diff --git a/workbench/client/vite.config.ts b/workbench/client/vite.config.ts new file mode 100644 index 0000000..4dbc412 --- /dev/null +++ b/workbench/client/vite.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vite'; +import { resolve } from 'node:path'; + +export default defineConfig({ + root: resolve(__dirname), + base: '/', + build: { outDir: resolve(__dirname, 'dist'), emptyOutDir: true }, + server: { port: 5173, proxy: { '/api': 'http://127.0.0.1:8787' } }, +}); diff --git a/workbench/server.js b/workbench/server.js index d1741ab..d8a143b 100644 --- a/workbench/server.js +++ b/workbench/server.js @@ -1,32 +1,57 @@ #!/usr/bin/env node -"use strict"; +'use strict'; -const fs = require("fs"); -const http = require("http"); -const path = require("path"); -const { loadOverrides, validateOverrides, writeJsonAtomic } = require("../src/compile/native-road"); -const { generate, validateDocument, runtime } = require("../src/native-traffic-signals"); -const { convertGeoJson } = require("../src/reference/gaode"); -const { exportNativeRoadPackage } = require("../src/export/native-road-package"); -const { compileInput } = require("../src/compile/compiler"); +const fs = require('fs'); +const http = require('http'); +const path = require('path'); +const { loadOverrides, validateOverrides, writeJsonAtomic } = require('../src/compile/native-road'); +const { generate, validateDocument, runtime } = require('../src/native-traffic-signals'); +const { convertGeoJson } = require('../src/reference/gaode'); +const { exportNativeRoadPackage } = require('../src/export/native-road-package'); +const { compileInput } = require('../src/compile/compiler'); -function startWorkbench({ area = null, input = null, inputFile = null, repoRoot = process.cwd(), dataRoot = path.join(repoRoot, "workbench-data"), configPath = null, compileFresh = null, readAreaConfig = null, junctionReference = null, debug = false, port = 8787 }) { - if (typeof junctionReference === "string") junctionReference = readJunctionReference(junctionReference); +function startWorkbench({ + area = null, + input = null, + inputFile = null, + repoRoot = process.cwd(), + dataRoot = path.join(repoRoot, 'workbench-data'), + configPath = null, + compileFresh = null, + readAreaConfig = null, + junctionReference = null, + debug = false, + port = 8787, +}) { + if (typeof junctionReference === 'string') junctionReference = readJunctionReference(junctionReference); // `--debug` surfaces advisory compiler findings that have no geometry layer of // their own — currently the complex-junction candidates. Off by default so the // normal editing view stays uncluttered. - if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error("--port must be an integer in [1024, 65535]."); - const session = { area, context: { repoRoot, configPath, compileFresh, readAreaConfig }, junctionReference, debug, dataRoot }; + if (!Number.isInteger(port) || port < 1024 || port > 65535) + throw new Error('--port must be an integer in [1024, 65535].'); + const session = { + area, + context: { repoRoot, configPath, compileFresh, readAreaConfig }, + junctionReference, + debug, + dataRoot, + }; if (input) { - session.context.compileFresh = () => { const compiled = compileInput(input); session.area = compiled.area; return compiled; }; + session.context.compileFresh = () => { + const compiled = compileInput(input); + session.area = compiled.area; + return compiled; + }; session.context.compileFresh(); } const server = http.createServer((request, response) => handle(request, response, session)); - server.on("error", (error) => { + server.on('error', (error) => { console.error(`Road Workbench failed to listen: ${error.message}`); process.exitCode = 1; }); - server.listen(port, "127.0.0.1", () => console.log(`Road Workbench: http://127.0.0.1:${port}/${debug ? " (debug: 复杂路口候选已开启)" : ""}`)); + server.listen(port, '127.0.0.1', () => + console.log(`Road Workbench: http://127.0.0.1:${port}/${debug ? ' (debug: 复杂路口候选已开启)' : ''}`), + ); return server; } @@ -35,73 +60,152 @@ function handle(request, response, session) { const context = session.context; const junctionReference = session.junctionReference; const debug = session.debug; - const url = new URL(request.url, "http://127.0.0.1"); - if (request.method === "GET" && url.pathname === "/") return sendFile(response, path.join(__dirname, "client", "index.html"), "text/html; charset=utf-8"); - if (request.method === "GET" && url.pathname === "/app.js") return sendFile(response, path.join(__dirname, "client", "app.js"), "text/javascript; charset=utf-8"); - if (request.method === "GET" && url.pathname === "/app.css") return sendFile(response, path.join(__dirname, "client", "app.css"), "text/css; charset=utf-8"); - if (request.method === "GET" && url.pathname.startsWith("/vendor/")) return sendVendorFile(response, url.pathname, context.repoRoot); - if (request.method === "GET" && url.pathname === "/api/state") return area ? sendJson(response, 200, state(area, junctionReference, debug)) : sendJson(response, 200, { active: false }); - if (request.method === "GET" && url.pathname === "/api/session") return sendJson(response, 200, { active: Boolean(session.area), areaId: session.area?.id || null }); - if (request.method === "POST" && url.pathname === "/api/import") return readUpload(request, session).then((result) => sendJson(response, 200, { ok: true, areaId: result.area.id, ...state(result.area, junctionReference, debug) })).catch((error) => sendJson(response, 400, { ok: false, error: error.message })); - if (request.method === "GET" && url.pathname === "/api/export.zip") return Promise.resolve().then(() => { - if (!area) throw new Error("请先导入 OSM 文件。"); - const exported = exportNativeRoadPackage(area.outputs.nativeRoadDir); - response.writeHead(200, { - "Content-Type": "application/zip", - "Content-Disposition": `attachment; filename="${area.id}.native-road.zip"`, - "Content-Length": exported.bytes.length, - "Cache-Control": "no-store", - }); - response.end(Buffer.from(exported.bytes)); - }).catch((error) => sendJson(response, 400, { ok: false, error: error.message })); - if (request.method === "POST" && url.pathname === "/api/traffic-signals") return readBody(request).then((body) => { - if (!area) throw new Error("请先导入 OSM 文件。"); - const document = validateDocument(body, fs.readFileSync(area.input, "utf8")); - writeJsonAtomic(area.outputs.nativeTrafficSignals, document); - sendJson(response, 200, { ok: true, trafficSignals: document, runtime: runtime(document) }); - }).catch((error) => sendJson(response, 400, { ok: false, error: error.message })); - if (request.method === "POST" && url.pathname === "/api/traffic-signals/generate") return Promise.resolve().then(() => { - if (!area) throw new Error("请先导入 OSM 文件。"); - const compiled = readCompiled(area); - const generated = generate(fs.readFileSync(area.input, "utf8"), readLayer(path.join(area.outputs.nativeRoadDir, "layers", "vehicle_stop_lines.geojson")), readLayer(path.join(area.outputs.nativeRoadDir, "layers", "intersection_surface.geojson"))); - const current = validateDocument(readJson(area.outputs.nativeTrafficSignals), fs.readFileSync(area.input, "utf8")); - const present = new Set(current.assemblies.features.map((feature) => feature.properties.signal_uid)); - current.assemblies.features.push(...generated.assemblies.features.filter((feature) => !present.has(feature.properties.signal_uid))); - writeJsonAtomic(area.outputs.nativeTrafficSignals, current); - sendJson(response, 200, { ok: true, trafficSignals: current, runtime: runtime(current), generated: generated.assemblies.features.length, compiled: Boolean(compiled) }); - }).catch((error) => sendJson(response, 400, { ok: false, error: error.message })); - if (request.method === "POST" && url.pathname === "/api/overrides") return readBody(request).then((body) => { - if (!area) throw new Error("请先导入 OSM 文件。"); - const compiled = readCompiled(area); - const overrides = validateOverrides(body, { roads: compiled.model.roads, endpoints: compiled.model.endpoints }); - writeJsonAtomic(area.outputs.nativeRoadOverrides, overrides); - sendJson(response, 200, { ok: true, overrides }); - }).catch((error) => sendJson(response, 400, { ok: false, error: error.message })); - if (request.method === "POST" && url.pathname === "/api/junction-clusters") return readBody(request).then((body) => { - if (!area) throw new Error("请先导入 OSM 文件。"); - if (!debug) throw new Error("该接口仅在 --debug 模式下可用。"); - const added = addJunctionCluster(context.configPath, body, readCompiled(area), context.readAreaConfig, context.repoRoot); - context.compileFresh(); - const refreshed = context.readAreaConfig(context.configPath, { repoRoot: context.repoRoot }); - sendJson(response, 200, { ok: true, added, ...state(refreshed, junctionReference, debug) }); - }).catch((error) => sendJson(response, 400, { ok: false, error: error.message })); - if (request.method === "POST" && url.pathname === "/api/compile") return Promise.resolve().then(() => { - if (!area || typeof context.compileFresh !== "function") throw new Error("请先导入 OSM 文件。"); - context.compileFresh(); - sendJson(response, 200, state(session.area, junctionReference, debug)); - }).catch((error) => sendJson(response, 500, { ok: false, error: error.message })); - sendJson(response, 404, { error: "Not found" }); + const url = new URL(request.url, 'http://127.0.0.1'); + if (request.method === 'GET' && url.pathname === '/') return sendWorkbenchApp(response); + if (request.method === 'GET' && url.pathname.startsWith('/vendor/')) + return sendVendorFile(response, url.pathname, context.repoRoot); + if (request.method === 'GET' && url.pathname === '/api/state') + return area + ? sendJson(response, 200, state(area, junctionReference, debug)) + : sendJson(response, 200, { active: false }); + if (request.method === 'GET' && url.pathname === '/api/session') + return sendJson(response, 200, { active: Boolean(session.area), areaId: session.area?.id || null }); + if (request.method === 'POST' && url.pathname === '/api/import') + return readUpload(request, session) + .then((result) => + sendJson(response, 200, { ok: true, areaId: result.area.id, ...state(result.area, junctionReference, debug) }), + ) + .catch((error) => sendJson(response, 400, { ok: false, error: error.message })); + if (request.method === 'GET' && url.pathname === '/api/export.zip') + return Promise.resolve() + .then(() => { + if (!area) throw new Error('请先导入 OSM 文件。'); + const exported = exportNativeRoadPackage(area.outputs.nativeRoadDir); + response.writeHead(200, { + 'Content-Type': 'application/zip', + 'Content-Disposition': `attachment; filename="${area.id}.native-road.zip"`, + 'Content-Length': exported.bytes.length, + 'Cache-Control': 'no-store', + }); + response.end(Buffer.from(exported.bytes)); + }) + .catch((error) => sendJson(response, 400, { ok: false, error: error.message })); + if (request.method === 'POST' && url.pathname === '/api/traffic-signals') + return readBody(request) + .then((body) => { + if (!area) throw new Error('请先导入 OSM 文件。'); + const document = validateDocument(body, fs.readFileSync(area.input, 'utf8')); + writeJsonAtomic(area.outputs.nativeTrafficSignals, document); + sendJson(response, 200, { ok: true, trafficSignals: document, runtime: runtime(document) }); + }) + .catch((error) => sendJson(response, 400, { ok: false, error: error.message })); + if (request.method === 'POST' && url.pathname === '/api/traffic-signals/generate') + return Promise.resolve() + .then(() => { + if (!area) throw new Error('请先导入 OSM 文件。'); + const compiled = readCompiled(area); + const generated = generate( + fs.readFileSync(area.input, 'utf8'), + readLayer(path.join(area.outputs.nativeRoadDir, 'layers', 'vehicle_stop_lines.geojson')), + readLayer(path.join(area.outputs.nativeRoadDir, 'layers', 'intersection_surface.geojson')), + ); + const current = validateDocument( + readJson(area.outputs.nativeTrafficSignals), + fs.readFileSync(area.input, 'utf8'), + ); + const present = new Set(current.assemblies.features.map((feature) => feature.properties.signal_uid)); + current.assemblies.features.push( + ...generated.assemblies.features.filter((feature) => !present.has(feature.properties.signal_uid)), + ); + writeJsonAtomic(area.outputs.nativeTrafficSignals, current); + sendJson(response, 200, { + ok: true, + trafficSignals: current, + runtime: runtime(current), + generated: generated.assemblies.features.length, + compiled: Boolean(compiled), + }); + }) + .catch((error) => sendJson(response, 400, { ok: false, error: error.message })); + if (request.method === 'POST' && url.pathname === '/api/overrides') + return readBody(request) + .then((body) => { + if (!area) throw new Error('请先导入 OSM 文件。'); + const compiled = readCompiled(area); + const overrides = validateOverrides(body, { roads: compiled.model.roads, endpoints: compiled.model.endpoints }); + writeJsonAtomic(area.outputs.nativeRoadOverrides, overrides); + sendJson(response, 200, { ok: true, overrides }); + }) + .catch((error) => sendJson(response, 400, { ok: false, error: error.message })); + if (request.method === 'POST' && url.pathname === '/api/junction-clusters') + return readBody(request) + .then((body) => { + if (!area) throw new Error('请先导入 OSM 文件。'); + if (!debug) throw new Error('该接口仅在 --debug 模式下可用。'); + const added = addJunctionCluster( + context.configPath, + body, + readCompiled(area), + context.readAreaConfig, + context.repoRoot, + ); + context.compileFresh(); + const refreshed = context.readAreaConfig(context.configPath, { repoRoot: context.repoRoot }); + sendJson(response, 200, { ok: true, added, ...state(refreshed, junctionReference, debug) }); + }) + .catch((error) => sendJson(response, 400, { ok: false, error: error.message })); + if (request.method === 'POST' && url.pathname === '/api/compile') + return Promise.resolve() + .then(() => { + if (!area || typeof context.compileFresh !== 'function') throw new Error('请先导入 OSM 文件。'); + context.compileFresh(); + sendJson(response, 200, state(session.area, junctionReference, debug)); + }) + .catch((error) => sendJson(response, 500, { ok: false, error: error.message })); + if (request.method === 'GET' && !url.pathname.startsWith('/api/')) return sendWorkbenchAsset(response, url.pathname); + sendJson(response, 404, { error: 'Not found' }); } function state(area, junctionReference = null, debug = false) { const nativeDir = area.outputs.nativeRoadDir; - const osm2streetsRoadSurface = area.outputs.geojsonDir ? path.join(area.outputs.geojsonDir, "road_surface.geojson") : null; + const osm2streetsRoadSurface = area.outputs.geojsonDir + ? path.join(area.outputs.geojsonDir, 'road_surface.geojson') + : null; const trafficSignals = fs.existsSync(area.outputs.nativeTrafficSignals) - ? validateDocument(readJson(area.outputs.nativeTrafficSignals), fs.readFileSync(area.input, "utf8")) - : { schema: "native-traffic-signals/v1", provenance: "empty", assemblies: { type: "FeatureCollection", features: [] } }; + ? validateDocument(readJson(area.outputs.nativeTrafficSignals), fs.readFileSync(area.input, 'utf8')) + : { + schema: 'native-traffic-signals/v1', + provenance: 'empty', + assemblies: { type: 'FeatureCollection', features: [] }, + }; const trafficRuntime = runtime(trafficSignals); const compiled = readCompiled(area); - return { areaId: area.id, debug: debug ? { junctionCandidates: junctionCandidates(compiled) } : null, compiled, overrides: loadOverrides(area.outputs.nativeRoadOverrides), trafficSignals, trafficRuntime, comparison: readJson(path.join(nativeDir, "comparison.json")), junctionReference, layers: { nativeRoadSurface: readLayer(path.join(nativeDir, "layers", "road_surface.geojson")), edgeLines: readLayer(path.join(nativeDir, "layers", "edge_lines.geojson")), nativeSidewalkSurface: readLayer(path.join(nativeDir, "layers", "sidewalk_surface.geojson")), nativeIntersectionSurface: readLayer(path.join(nativeDir, "layers", "intersection_surface.geojson")), laneCenterlines: readLayer(path.join(nativeDir, "layers", "lane_centerlines.geojson")), laneSeparators: readLayer(path.join(nativeDir, "layers", "lane_separators.geojson")), centerLines: readLayer(path.join(nativeDir, "layers", "center_lines.geojson")), directionArrows: readLayer(path.join(nativeDir, "layers", "direction_arrows.geojson")), turnArrows: readLayer(path.join(nativeDir, "layers", "turn_arrows.geojson")), crosswalks: readLayer(path.join(nativeDir, "layers", "crosswalks.geojson")), vehicleStopLines: readLayer(path.join(nativeDir, "layers", "vehicle_stop_lines.geojson")), connectors: readLayer(path.join(nativeDir, "layers", "connectors.geojson")), osm2streetsRoadSurface: osm2streetsRoadSurface && fs.existsSync(osm2streetsRoadSurface) ? readLayer(osm2streetsRoadSurface) : null } }; + return { + areaId: area.id, + debug: debug ? { junctionCandidates: junctionCandidates(compiled) } : null, + compiled, + overrides: loadOverrides(area.outputs.nativeRoadOverrides), + trafficSignals, + trafficRuntime, + comparison: readJson(path.join(nativeDir, 'comparison.json')), + junctionReference, + layers: { + nativeRoadSurface: readLayer(path.join(nativeDir, 'layers', 'road_surface.geojson')), + edgeLines: readLayer(path.join(nativeDir, 'layers', 'edge_lines.geojson')), + nativeSidewalkSurface: readLayer(path.join(nativeDir, 'layers', 'sidewalk_surface.geojson')), + nativeIntersectionSurface: readLayer(path.join(nativeDir, 'layers', 'intersection_surface.geojson')), + laneCenterlines: readLayer(path.join(nativeDir, 'layers', 'lane_centerlines.geojson')), + laneSeparators: readLayer(path.join(nativeDir, 'layers', 'lane_separators.geojson')), + centerLines: readLayer(path.join(nativeDir, 'layers', 'center_lines.geojson')), + directionArrows: readLayer(path.join(nativeDir, 'layers', 'direction_arrows.geojson')), + turnArrows: readLayer(path.join(nativeDir, 'layers', 'turn_arrows.geojson')), + crosswalks: readLayer(path.join(nativeDir, 'layers', 'crosswalks.geojson')), + vehicleStopLines: readLayer(path.join(nativeDir, 'layers', 'vehicle_stop_lines.geojson')), + connectors: readLayer(path.join(nativeDir, 'layers', 'connectors.geojson')), + osm2streetsRoadSurface: + osm2streetsRoadSurface && fs.existsSync(osm2streetsRoadSurface) ? readLayer(osm2streetsRoadSurface) : null, + }, + }; } // The compiler reports candidates as advisory diagnostics. Lift them into their // own payload with a stable index so the map can label them "#1, #2, ..." and @@ -113,16 +217,16 @@ function state(area, junctionReference = null, debug = false) { // later command, and the file is git-tracked so a bad accept stays revertible. function addJunctionCluster(configPath, body, compiled, readAreaConfig, repoRoot) { const index = Number(body?.index); - if (!Number.isInteger(index)) throw new Error("请求缺少候选编号 index。"); + if (!Number.isInteger(index)) throw new Error('请求缺少候选编号 index。'); const candidate = junctionCandidates(compiled).find((item) => item.index === index); if (!candidate) throw new Error(`候选 #${index} 不在最新一次编译结果里,请刷新页面后重试。`); const raw = readJson(configPath); const templates = raw.nativeRoad?.junctionTemplates; - if (!templates) throw new Error("区域配置缺少 nativeRoad.junctionTemplates,请先手工建立该节点。"); + if (!templates) throw new Error('区域配置缺少 nativeRoad.junctionTemplates,请先手工建立该节点。'); const clusters = Array.isArray(templates.clusters) ? templates.clusters : []; const taken = new Set(clusters.flatMap((cluster) => (cluster.nodeIds || []).map(String))); const clash = candidate.nodeIds.filter((nodeId) => taken.has(String(nodeId))); - if (clash.length) throw new Error(`节点 ${clash.join("、")} 已属于其他复杂路口配置。`); + if (clash.length) throw new Error(`节点 ${clash.join('、')} 已属于其他复杂路口配置。`); const id = uniqueClusterId(`cluster-${candidate.nodeIds[0]}`, new Set(clusters.map((cluster) => cluster.id))); const cluster = { id, @@ -132,7 +236,13 @@ function addJunctionCluster(configPath, body, compiled, readAreaConfig, repoRoot outerRadiusExtraMeters: 18, nodeIds: candidate.nodeIds.map(String), }; - const next = { ...raw, nativeRoad: { ...raw.nativeRoad, junctionTemplates: { ...templates, enabled: true, clusters: [...clusters, cluster] } } }; + const next = { + ...raw, + nativeRoad: { + ...raw.nativeRoad, + junctionTemplates: { ...templates, enabled: true, clusters: [...clusters, cluster] }, + }, + }; const staging = `${configPath}.candidate-${process.pid}.json`; fs.writeFileSync(staging, `${JSON.stringify(next, null, 2)}\n`); try { @@ -148,55 +258,176 @@ function addJunctionCluster(configPath, body, compiled, readAreaConfig, repoRoot function uniqueClusterId(base, taken) { if (!taken.has(base)) return base; for (let suffix = 2; suffix < 100; suffix += 1) if (!taken.has(`${base}-${suffix}`)) return `${base}-${suffix}`; - throw new Error("无法生成唯一的 cluster id。"); + throw new Error('无法生成唯一的 cluster id。'); } function junctionCandidates(compiled) { return (compiled?.diagnostics || []) - .filter((item) => item.rule === "complex-junction-candidate" && item.suggestedCluster) - .sort((first, second) => second.suggestedCluster.nodeCount - first.suggestedCluster.nodeCount || first.suggestedCluster.diameterMeters - second.suggestedCluster.diameterMeters) - .map((item, index) => ({ index: index + 1, id: item.id, message: item.message, coordinate: item.geometry?.coordinates || null, ...item.suggestedCluster })); + .filter((item) => item.rule === 'complex-junction-candidate' && item.suggestedCluster) + .sort( + (first, second) => + second.suggestedCluster.nodeCount - first.suggestedCluster.nodeCount || + first.suggestedCluster.diameterMeters - second.suggestedCluster.diameterMeters, + ) + .map((item, index) => ({ + index: index + 1, + id: item.id, + message: item.message, + coordinate: item.geometry?.coordinates || null, + ...item.suggestedCluster, + })); } function readJunctionReference(file) { if (!fs.existsSync(file)) throw new Error(`Junction reference not found: ${file}`); - const converted = convertGeoJson(JSON.parse(fs.readFileSync(file, "utf8"))); - return { source: file, coordinateSystem: "GCJ-02", converted }; + const converted = convertGeoJson(JSON.parse(fs.readFileSync(file, 'utf8'))); + return { source: file, coordinateSystem: 'GCJ-02', converted }; } function readUpload(request, session) { return readMultipart(request, 20 * 1024 * 1024).then(({ filename, data }) => { - if (!filename || !/\.osm$/i.test(filename)) throw new Error("请选择 .osm 文件。"); - if (!data.length) throw new Error("OSM 文件不能为空。"); - const base = path.basename(filename, path.extname(filename)).replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "").toLowerCase() || "osm-import"; + if (!filename || !/\.osm$/i.test(filename)) throw new Error('请选择 .osm 文件。'); + if (!data.length) throw new Error('OSM 文件不能为空。'); + const base = + path + .basename(filename, path.extname(filename)) + .replace(/[^a-zA-Z0-9_-]+/g, '-') + .replace(/^-+|-+$/g, '') + .toLowerCase() || 'osm-import'; fs.mkdirSync(session.dataRoot, { recursive: true }); - const root = fs.mkdtempSync(path.join(session.dataRoot, "import-")); + const root = fs.mkdtempSync(path.join(session.dataRoot, 'import-')); const areaId = `${base}-${path.basename(root).slice(-6)}`; - const outRoot = path.join(root, "outputs"); - const input = { areaId, osmFile: path.join(root, "source.osm"), outDir: path.join(outRoot, "native-road"), stagingDir: path.join(outRoot, "_pipeline"), overridesFile: path.join(root, "native-road-overrides.json"), trafficSignalsFile: path.join(root, "native-traffic-signals.json"), options: { edgeLines: false, junctionTemplates: { enabled: false, references: [] } } }; + const outRoot = path.join(root, 'outputs'); + const input = { + areaId, + osmFile: path.join(root, 'source.osm'), + outDir: path.join(outRoot, 'native-road'), + stagingDir: path.join(outRoot, '_pipeline'), + overridesFile: path.join(root, 'native-road-overrides.json'), + trafficSignalsFile: path.join(root, 'native-traffic-signals.json'), + options: { edgeLines: false, junctionTemplates: { enabled: false, references: [] } }, + }; fs.writeFileSync(input.osmFile, data); - fs.writeFileSync(input.overridesFile, JSON.stringify({ schema: "native-road-overrides/v1", overrides: [] }, null, 2)); - fs.writeFileSync(input.trafficSignalsFile, JSON.stringify({ schema: "native-traffic-signals/v1", provenance: "empty", assemblies: { type: "FeatureCollection", features: [] } }, null, 2)); - try { const compiled = compileInput(input); session.area = compiled.area; return compiled; } catch (error) { fs.rmSync(root, { recursive: true, force: true }); throw error; } + fs.writeFileSync( + input.overridesFile, + JSON.stringify({ schema: 'native-road-overrides/v1', overrides: [] }, null, 2), + ); + fs.writeFileSync( + input.trafficSignalsFile, + JSON.stringify( + { + schema: 'native-traffic-signals/v1', + provenance: 'empty', + assemblies: { type: 'FeatureCollection', features: [] }, + }, + null, + 2, + ), + ); + try { + const compiled = compileInput(input); + session.area = compiled.area; + return compiled; + } catch (error) { + fs.rmSync(root, { recursive: true, force: true }); + throw error; + } }); } -function readMultipart(request, limit) { return new Promise((resolve, reject) => { - const type = request.headers["content-type"] || ""; const match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(type); if (!match) return reject(new Error("请使用 multipart/form-data 上传 OSM 文件。")); - const boundary = `--${match[1] || match[2]}`; const chunks = []; let size = 0; - request.on("data", (chunk) => { size += chunk.length; if (size > limit) { reject(new Error("上传文件超过 20 MB 限制。")); request.destroy(); return; } chunks.push(chunk); }); - request.on("error", reject); request.on("end", () => { const body = Buffer.concat(chunks); const start = body.indexOf(Buffer.from("\r\n\r\n")); const end = body.lastIndexOf(Buffer.from(`\r\n${boundary}--`)); if (start < 0 || end < start) return reject(new Error("上传内容格式无效。")); const header = body.slice(0, start).toString(); const name = /filename="([^"]*)"/i.exec(header)?.[1] || "upload.osm"; resolve({ filename: name, data: body.slice(start + 4, end) }); }); -}); } -function readCompiled(area) { return readJson(path.join(area.outputs.nativeRoadDir, "compiled.json")); } -function readJson(file) { return JSON.parse(fs.readFileSync(file, "utf8")); } -function readLayer(file) { return fs.existsSync(file) ? readJson(file) : { type: "FeatureCollection", features: [] }; } -function readBody(request) { return new Promise((resolve, reject) => { let body = ""; request.setEncoding("utf8"); request.on("data", (part) => { body += part; if (body.length > 1024 * 1024) request.destroy(); }); request.on("end", () => { try { resolve(JSON.parse(body)); } catch (_) { reject(new Error("Request body must be JSON.")); } }); request.on("error", reject); }); } -function sendFile(response, file, type) { response.writeHead(200, { "Content-Type": type, "Cache-Control": "no-store" }); fs.createReadStream(file).pipe(response); } +function readMultipart(request, limit) { + return new Promise((resolve, reject) => { + const type = request.headers['content-type'] || ''; + const match = /boundary=(?:"([^"]+)"|([^;]+))/i.exec(type); + if (!match) return reject(new Error('请使用 multipart/form-data 上传 OSM 文件。')); + const boundary = `--${match[1] || match[2]}`; + const chunks = []; + let size = 0; + request.on('data', (chunk) => { + size += chunk.length; + if (size > limit) { + reject(new Error('上传文件超过 20 MB 限制。')); + request.destroy(); + return; + } + chunks.push(chunk); + }); + request.on('error', reject); + request.on('end', () => { + const body = Buffer.concat(chunks); + const start = body.indexOf(Buffer.from('\r\n\r\n')); + const end = body.lastIndexOf(Buffer.from(`\r\n${boundary}--`)); + if (start < 0 || end < start) return reject(new Error('上传内容格式无效。')); + const header = body.slice(0, start).toString(); + const name = /filename="([^"]*)"/i.exec(header)?.[1] || 'upload.osm'; + resolve({ filename: name, data: body.slice(start + 4, end) }); + }); + }); +} +function readCompiled(area) { + return readJson(path.join(area.outputs.nativeRoadDir, 'compiled.json')); +} +function readJson(file) { + return JSON.parse(fs.readFileSync(file, 'utf8')); +} +function readLayer(file) { + return fs.existsSync(file) ? readJson(file) : { type: 'FeatureCollection', features: [] }; +} +function readBody(request) { + return new Promise((resolve, reject) => { + let body = ''; + request.setEncoding('utf8'); + request.on('data', (part) => { + body += part; + if (body.length > 1024 * 1024) request.destroy(); + }); + request.on('end', () => { + try { + resolve(JSON.parse(body)); + } catch (_) { + reject(new Error('Request body must be JSON.')); + } + }); + request.on('error', reject); + }); +} +function sendWorkbenchApp(response) { + const built = path.join(__dirname, 'client', 'dist', 'index.html'); + return sendFile( + response, + fs.existsSync(built) ? built : path.join(__dirname, 'client', 'index.html'), + 'text/html; charset=utf-8', + ); +} +function sendWorkbenchAsset(response, pathname) { + const root = path.join(__dirname, 'client', 'dist'); + const file = path.resolve(root, `.${pathname}`); + if (file.startsWith(`${root}${path.sep}`) && fs.existsSync(file) && fs.statSync(file).isFile()) { + const extension = path.extname(file); + const types = { + '.js': 'text/javascript; charset=utf-8', + '.css': 'text/css; charset=utf-8', + '.svg': 'image/svg+xml', + '.png': 'image/png', + '.woff2': 'font/woff2', + }; + return sendFile(response, file, types[extension] || 'application/octet-stream'); + } + return sendWorkbenchApp(response); +} +function sendFile(response, file, type) { + response.writeHead(200, { 'Content-Type': type, 'Cache-Control': 'no-store' }); + fs.createReadStream(file).pipe(response); +} function sendVendorFile(response, pathname, repoRoot) { const match = /^\/vendor\/(ol|rbush|quickselect)\/(.+)$/.exec(pathname); - if (!match) return sendJson(response, 404, { error: "Not found" }); - const root = path.join(repoRoot, "node_modules", match[1]); + if (!match) return sendJson(response, 404, { error: 'Not found' }); + const root = path.join(repoRoot, 'node_modules', match[1]); const file = path.resolve(root, match[2]); - if (!file.startsWith(`${root}${path.sep}`) || !fs.existsSync(file) || !fs.statSync(file).isFile()) return sendJson(response, 404, { error: "Not found" }); - return sendFile(response, file, file.endsWith(".css") ? "text/css; charset=utf-8" : "text/javascript; charset=utf-8"); + if (!file.startsWith(`${root}${path.sep}`) || !fs.existsSync(file) || !fs.statSync(file).isFile()) + return sendJson(response, 404, { error: 'Not found' }); + return sendFile(response, file, file.endsWith('.css') ? 'text/css; charset=utf-8' : 'text/javascript; charset=utf-8'); +} +function sendJson(response, status, value) { + response.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' }); + response.end(`${JSON.stringify(value)}\n`); } -function sendJson(response, status, value) { response.writeHead(status, { "Content-Type": "application/json; charset=utf-8", "Cache-Control": "no-store" }); response.end(`${JSON.stringify(value)}\n`); } module.exports = { startWorkbench, readMultipart };