├── .browserslistrc ├── public ├── favicon.ico └── index.html ├── src ├── assets │ └── logo.png ├── shims-vue.d.ts ├── utils │ ├── keyBy.ts │ ├── loadSDK.ts │ └── equal.ts ├── composables │ ├── use-clean-up.ts │ └── use-event.ts ├── index.ts ├── components │ ├── marker-cluster.ts │ ├── geometry-editor.ts │ ├── multi-label.ts │ ├── multi-circle.ts │ ├── multi-marker.ts │ ├── multi-polyline.ts │ ├── info-window.ts │ ├── dom-overlay.ts │ ├── multi-polygon.ts │ ├── polygon-editor.ts │ └── map.ts └── examples │ ├── map.vue │ ├── multi-marker.vue │ ├── marker-cluster.vue │ ├── multi-polyline.vue │ ├── multi-circle.vue │ ├── huge-polygon.vue │ ├── info-window.vue │ ├── multi-label.vue │ ├── polygon-editor-compose.vue │ ├── dom-overlay.vue │ ├── multi-polygon.vue │ ├── play-back.vue │ └── polygon-editor.vue ├── docs ├── guide │ ├── fence.png │ ├── index.md │ ├── install.md │ ├── qa.md │ └── getting-started.md ├── index.md ├── api │ ├── vector-events.md │ ├── dom-overlay.md │ ├── multi-polygon.md │ ├── multi-circle.md │ ├── multi-marker.md │ ├── multi-polyline.md │ ├── multi-label.md │ ├── marker-cluster.md │ ├── polygon-editor.md │ ├── info-window.md │ └── map.md └── .vitepress │ ├── theme │ └── index.js │ └── config.js ├── babel.config.js ├── .editorconfig ├── .prettierrc ├── .gitignore ├── tests └── unit │ └── example.spec.ts ├── .eslintrc.js ├── vite.config.js ├── tsconfig.json ├── package.json ├── README.zh_CN.md ├── README.md └── LICENSE /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/vue-tmap/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/vue-tmap/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /docs/guide/fence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/didi/vue-tmap/HEAD/docs/guide/fence.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['@vue/cli-plugin-babel/preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue'; 3 | 4 | const component: DefineComponent<{}, {}, unknown>; 5 | export default component; 6 | } 7 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.{js,jsx,ts,tsx,vue}] 2 | indent_style = space 3 | indent_size = 2 4 | end_of_line = lf 5 | trim_trailing_whitespace = true 6 | insert_final_newline = true 7 | max_line_length = 100 8 | -------------------------------------------------------------------------------- /docs/guide/index.md: -------------------------------------------------------------------------------- 1 | # demo 2 | 3 | > 请注意,本文档中所有示例使用的 mapKey 仅作文档测试使用,不定期修改相关配置,请勿使用在任何项目中 4 | 5 |
6 | 7 | <<< src/examples/multi-polygon.vue 8 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "singleQuote": true, 5 | "semi": true, 6 | "useTabs": false, 7 | "trailingComma": "all", 8 | "arrowParens": "always" 9 | } 10 | -------------------------------------------------------------------------------- /docs/guide/install.md: -------------------------------------------------------------------------------- 1 | # 安装 2 | 3 | --- 4 | 5 | ## npm 安装 6 | 7 | ```bash 8 | npm install @map-component/vue-tmap --save 9 | ``` 10 | 11 | ## yarn 安装 12 | 13 | ```bash 14 | yarn add @map-component/vue-tmap 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | /es 5 | 6 | 7 | # local env files 8 | .env.local 9 | .env.*.local 10 | 11 | # Log files 12 | npm-debug.log* 13 | yarn-debug.log* 14 | yarn-error.log* 15 | pnpm-debug.log* 16 | 17 | # Editor directories and files 18 | .idea 19 | .vscode 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | docs/.vitepress/dist -------------------------------------------------------------------------------- /src/utils/keyBy.ts: -------------------------------------------------------------------------------- 1 | interface Dictionary { 2 | [key: string]: T; 3 | } 4 | 5 | function keyBy(collection: T[], iteratee: keyof T): Dictionary { 6 | return collection.reduce((result, value) => { 7 | const key = (value[iteratee] as unknown) as string; 8 | // eslint-disable-next-line no-param-reassign 9 | result[key] = value; 10 | return result; 11 | }, {} as Dictionary); 12 | } 13 | export default keyBy; 14 | -------------------------------------------------------------------------------- /tests/unit/example.spec.ts: -------------------------------------------------------------------------------- 1 | // import { expect } from 'chai'; 2 | // import { shallowMount } from '@vue/test-utils'; 3 | // import TMap from '@/components/map'; 4 | 5 | // describe('HelloWorld.vue', () => { 6 | // it('renders props.msg when passed', () => { 7 | // const msg = 'new message'; 8 | // const wrapper = shallowMount(TMap, { 9 | // props: { msg }, 10 | // }); 11 | // expect(wrapper.text()).to.include(msg); 12 | // }); 13 | // }); 14 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | home: true 3 | # heroImage: /logo.png 4 | heroAlt: Logo image 5 | heroText: vue-tmap 6 | tagline: 基于 Vue 3.x 的腾讯地图组件 7 | actionText: 起步 8 | actionLink: /guide/ 9 | features: 10 | - title: 组件化 11 | details: 组件化的开发方式,响应式状态, 开发者无需关心地图的具体操作 12 | - title: Vue3.0-Powered 13 | details: 基于vue3.0 组合式 API 开发,尽情体验3.0的新特性 14 | - title: Type-safe 15 | details: 补充了腾讯地图sdk的类型声明,组件也使用typescript开发 16 | footer: Copyright © 2012-2021 DiDiChuxing. All Rights Reserved. 17 | --- -------------------------------------------------------------------------------- /docs/api/vector-events.md: -------------------------------------------------------------------------------- 1 | # 矢量图形事件 2 | 3 | 折线、多边形、圆形可以直接使用vue的事件绑定方式绑定事件`v-on:click="methodName"` 或使用快捷方式 `@click="methodName"` 4 | 5 | 支持的事件有 6 | - click 7 | - dblclick 8 | - mousedown 9 | - mouseup 10 | - mousemove 11 | - hover 12 | - touchstart 13 | - touchmove 14 | - touchend 15 | 16 | 监听函数的参数规范参考官方文档 https://lbs.qq.com/webApi/javascriptGL/glDoc/glMapEvent#3 17 | 18 | 19 | ## 基础示例 20 |
21 | 22 | <<< src/examples/multi-polygon.vue 23 | -------------------------------------------------------------------------------- /src/composables/use-clean-up.ts: -------------------------------------------------------------------------------- 1 | export default function useCleanUp(map: TMap.Map, id: string) { 2 | const overlay = map.getLayer(id); 3 | if (overlay) { 4 | overlay.setMap(null); 5 | } 6 | // 有时候新组件加载会在旧组件卸载之前,防止出现边框不显示的bug 7 | const oldOverlayBorder = map.getLayer(`${id}_border_line`); 8 | if (oldOverlayBorder) { 9 | oldOverlayBorder.setMap(null); 10 | } 11 | const overlayBorder = map.getLayer(`${id}_border`); 12 | if (overlayBorder) { 13 | overlayBorder.setMap(null); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /docs/guide/qa.md: -------------------------------------------------------------------------------- 1 | # 常见问题 2 | 3 | ## 修改组件数据 geometries 后组件没有刷新? 4 | 5 | - 为优化性能,所有组件只有在 prop 的引用地址修改后才重新渲染,可浅拷贝 geometries 解决 6 | 7 | ## 修改地图 center、zoom 属性不生效? 8 | 9 | - 在组件初始化时修改 state 中的 center 等值时会出现,原因是修改 center 值时地图实例还未加载完成,可在 TMap 组件的 onLoad 事件触发后修改 state 避免这类问题。 10 | 11 | - 修改 zoom 值时地图会有默认 500 毫秒延迟动画,在这期间地图有其他渲染逻辑有可能阻塞地图渲染,可通过设置 duration 或延迟渲染其他数据解决 12 | 13 | - 检查设置的 zoom 等值是否有其他代码逻辑修改或是受控数据 14 | 15 | ## 控制台提示腾讯地图 js sdk 文件报错修改了不可变数据如 lat、lng 之类错误? 16 | 17 | - 不能直接把地图事件的回调参数数据中值直接保存到数据流 state 中,必须经过深拷贝后才能保存使用 18 | -------------------------------------------------------------------------------- /docs/api/dom-overlay.md: -------------------------------------------------------------------------------- 1 | # DOM 覆盖物 2 | 3 | ## 基础示例 4 | 5 |
6 | 7 | <<< src/examples/dom-overlay.vue 8 | 9 | ## props 10 | 11 | | 名称 | 类型 | 说明 | 12 | | -------- | ------------------------------------ | -------------- | 13 | | id | String | 图层 id | 14 | | position | `{ [key: string]: TMap.LatLngData }` | DOM 的地理位置 | 15 | | offset | Number[] | DOM 的偏移量 | 16 | 17 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocDomOverlay 18 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { node: true }, 4 | extends: [ 5 | 'plugin:vue/vue3-essential', 6 | '@vue/airbnb', 7 | '@vue/typescript/recommended', 8 | '@vue/prettier', 9 | '@vue/prettier/@typescript-eslint', 10 | ], 11 | parserOptions: { ecmaVersion: 2020 }, 12 | rules: { 13 | 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 14 | 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 15 | }, 16 | overrides: [ 17 | { 18 | files: [ 19 | '**/__tests__/*.{j,t}s?(x)', 20 | '**/tests/unit/**/*.spec.{j,t}s?(x)', 21 | ], 22 | env: { mocha: true }, 23 | }, 24 | ], 25 | }; 26 | -------------------------------------------------------------------------------- /docs/api/multi-polygon.md: -------------------------------------------------------------------------------- 1 | # 多边形 2 | 3 | ## 基础示例 4 | 5 |
6 | 7 | <<< src/examples/multi-polygon.vue 8 | 9 | ## props 10 | 11 | | 名称 | 类型 | 说明 | 12 | | ---------- | --------------------------------------------- | ---------------- | 13 | | id | String | 图层 id | 14 | | zIndex | Number | 图层绘制顺序 | 15 | | styles | `{ [key: string]: TMap.PolygonStyleOptions }` | 多边形的相关样式 | 16 | | geometries | TMap.PolygonGeometry[] | 多边形数据数组 | 17 | 18 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector#7 19 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | import { defineConfig } from 'vite'; 3 | import dts from 'vite-plugin-dts'; 4 | 5 | // 文档: https://vitejs.dev/config/ 6 | export default defineConfig({ 7 | resolve: { 8 | alias: { 9 | '@': resolve(__dirname, './src'), 10 | packages: resolve(__dirname, './packages'), 11 | }, 12 | }, 13 | build: { 14 | outDir: 'dist', 15 | lib: { 16 | entry: resolve(__dirname, 'src/index.ts'), 17 | name: 'vue-tmap', 18 | fileName: (format) => 19 | format === 'es' ? `index.js` : `index.${format}.js`, 20 | }, 21 | rollupOptions: { 22 | // 确保外部化处理那些你不想打包进库的依赖 23 | external: ['vue'], 24 | }, 25 | }, 26 | plugins: [dts()], 27 | }); 28 | -------------------------------------------------------------------------------- /docs/api/multi-circle.md: -------------------------------------------------------------------------------- 1 | # 圆形标记 2 | 3 | ## 基础示例 4 | 5 |
6 | 7 | <<< src/examples/multi-circle.vue 8 | 9 | ## props 10 | 11 | | 名称 | 类型 | 说明 | 12 | | ---------- | ---------------------------------------------- | ------------------ | 13 | | id | String | 图层 id | 14 | | zIndex | Number | 图层绘制顺序 | 15 | | styles | `{ [key: string]: TMap.MultiCircleStyleHash }` | 圆形标记的相关样式 | 16 | | geometries | TMap.CircleGeometry[] | 圆形标记数据数组。 | 17 | 18 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector#13 19 | -------------------------------------------------------------------------------- /docs/api/multi-marker.md: -------------------------------------------------------------------------------- 1 | # 标注点 2 | 3 | ## 基础示例 4 | 5 |
6 | 7 | <<< src/examples/play-back.vue 8 | 9 | ## props 10 | 11 | | 名称 | 类型 | 说明 | 12 | | ---------- | -------------------------------------------- | ---------------- | 13 | | id | String | 图层 id | 14 | | styles | `{ [key: string]: TMap.MarkerStyleOptions }` | 标注点的相关样式 | 15 | | geometries | TMap.PointGeometry[] | 标注点数据数组 | 16 | 17 | ## ref 可用方法 18 | 19 | - moveAlong: 指定 id 的标注点,沿着指定路径移动; 20 | - stopMove: 停止移动,尚未完成的动画会被取消 21 | - pauseMove: 暂停点标记的动画效果, 22 | - resumeMove: 重新开始 23 | 24 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocMarker 25 | -------------------------------------------------------------------------------- /src/composables/use-event.ts: -------------------------------------------------------------------------------- 1 | import { onUnmounted } from 'vue'; 2 | 3 | export default function useEvent( 4 | overlay: TMap.GeometryOverlay, 5 | attrs: Record, 6 | emit: (event: string, ...args: unknown[]) => void, 7 | ) { 8 | const events: string[] = []; 9 | const listeners: Function[] = []; 10 | Object.keys(attrs).forEach((attr) => { 11 | if (attr.indexOf('on') === 0) { 12 | const eventName = attr[2].toLowerCase() + attr.slice(3); 13 | events.push(eventName); 14 | listeners.push(emit.bind(null, eventName)); 15 | } 16 | }); 17 | 18 | events.forEach((eventName, i) => { 19 | overlay.on(eventName, listeners[i]); 20 | }); 21 | onUnmounted(() => { 22 | events.forEach((eventName, i) => { 23 | overlay.off(eventName, listeners[i]); 24 | }); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /docs/api/multi-polyline.md: -------------------------------------------------------------------------------- 1 | # 折线 2 | 3 | ## 基础示例 4 | 5 |
6 | 7 | <<< src/examples/multi-polyline.vue 8 | 9 | ## props 10 | 11 | 官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector 12 | 13 | ## props 14 | 15 | | 名称 | 类型 | 说明 | 16 | | ---------- | ---------------------------------------------- | ----------------- | 17 | | id | String | 图层 id | 18 | | zIndex | Number | 图层绘制顺序 | 19 | | styles | `{ [key: string]: TMap.PolylineStyleOptions }` | 折线 v 的相关样式 | 20 | | geometries | TMap.PolylineGeometry[] | 折线数据数组 | 21 | 22 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocVector 23 | -------------------------------------------------------------------------------- /docs/api/multi-label.md: -------------------------------------------------------------------------------- 1 | # 文本标记 2 | 3 | ## 基础示例 4 | 5 |
6 | 7 | <<< src/examples/multi-label.vue 8 | 9 | ## props 10 | 11 | | 名称 | 类型 | 说明 | 12 | | --------------- | --------------------------------------------- | -------------------------------- | 13 | | id | String | 图层 id | 14 | | styles | `{ [key: string]: TMap.MultiLabelStyleHash }` | 文本标注的相关样式 | 15 | | geometries | TMap.LabelGeometry[] | 文本标注数据数组。 | 16 | | | 17 | | enableCollision | Boolean | 是否开启图层内部的文本标注碰撞。 | 18 | | | 19 | 20 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocLabel 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "esnext", 4 | "module": "esnext", 5 | "declaration": true, 6 | "outDir": "es", 7 | "strict": true, 8 | "jsx": "preserve", 9 | "isolatedModules": false, 10 | "importHelpers": true, 11 | "moduleResolution": "node", 12 | "skipLibCheck": true, 13 | "esModuleInterop": true, 14 | "allowSyntheticDefaultImports": true, 15 | "resolveJsonModule": true, 16 | "sourceMap": true, 17 | "baseUrl": ".", 18 | "types": ["@map-component/tmap-types", "webpack-env", "mocha", "chai"], 19 | "paths": { 20 | "@/*": ["src/*"] 21 | }, 22 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 23 | }, 24 | "include": [ 25 | "src/**/*.ts", 26 | "src/**/*.tsx", 27 | "src/**/*.vue", 28 | "tests/**/*.ts", 29 | "tests/**/*.tsx" 30 | ], 31 | "exclude": ["node_modules"] 32 | } 33 | -------------------------------------------------------------------------------- /src/utils/loadSDK.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | /* eslint-disable no-underscore-dangle */ 3 | export default function loadSDK( 4 | version: string, 5 | key: string, 6 | libraries?: string[], 7 | ) { 8 | const libs = ['visualization', 'tools', 'geometry', ...(libraries || [])]; 9 | return new Promise((resolve) => { 10 | if (window.TMap) { 11 | resolve(window.TMap); 12 | return; 13 | } 14 | window.tmapCallback = function tmapCallback() { 15 | resolve(window.TMap); 16 | }; 17 | 18 | // 在乾坤子应用中使用 19 | if ((window as any).__POWERED_BY_QIANKUN__ && top) { 20 | top.tmapCallback = function tmapCallback() { 21 | resolve(TMap); 22 | }; 23 | } 24 | 25 | const script = document.createElement('script'); 26 | script.type = 'text/javascript'; 27 | script.src = `https://map.qq.com/api/gljs?v=${version}&key=${key}&libraries=${libs.join( 28 | ',', 29 | )}&callback=tmapCallback`; 30 | document.body.appendChild(script); 31 | }); 32 | } 33 | -------------------------------------------------------------------------------- /docs/api/marker-cluster.md: -------------------------------------------------------------------------------- 1 | # 点聚合 2 | 3 | 4 | ## 基础示例 5 |
6 | 7 | <<< src/examples/marker-cluster.vue 8 | 9 | ## props 10 | 11 | | 名称 | 类型 | 说明 | 12 | | ------------------ | -------------------- | ------------------------------------------------------------ | 13 | | id | String | 图层id | 14 | | enableDefaultStyle | Boolean | 是否启用默认的聚合样式 | 15 | | minimumClusterSize | Number | 形成聚合簇的最小个数 | 16 | | zoomOnClick | Boolean | 点击已经聚合的标记点时是否实现聚合分离 | 17 | | gridSize | Number | 聚合算法的可聚合距离 | 18 | | averageCenter | Boolean | 每个聚和簇的中心是否应该是聚类中所有标记的平均值,默认为false | 19 | | maxZoom | Number | 采用聚合策略的最大缩放级别 | 20 | | geometries | TMap.PointGeometry[] | 标注点数据数组 | 21 | 22 | 23 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocCluster -------------------------------------------------------------------------------- /src/utils/equal.ts: -------------------------------------------------------------------------------- 1 | function equalPolygonGeometry( 2 | a: TMap.PolygonGeometry, 3 | b: TMap.PolygonGeometry, 4 | ): boolean { 5 | if (a.id !== b.id) { 6 | return false; 7 | } 8 | if (a.styleId !== b.styleId) { 9 | return false; 10 | } 11 | if (a.rank !== b.rank) { 12 | return false; 13 | } 14 | if (a.paths.length === b.paths.length) { 15 | if (JSON.stringify(a.paths) !== JSON.stringify(b.paths)) { 16 | return false; 17 | } 18 | } else { 19 | return false; 20 | } 21 | if (a.properties !== undefined && b.properties !== undefined) { 22 | type KeysType = keyof T; 23 | const aPropertyKeys = Object.keys(a.properties || {}) as KeysType[]; 24 | const bPropertyKeys = Object.keys(b.properties || {}) as KeysType[]; 25 | 26 | if (aPropertyKeys.length !== bPropertyKeys.length) { 27 | return false; 28 | } 29 | // properties 只做一层深度的比较 30 | for (let i = aPropertyKeys.length - 1; i >= 0; i -= 1) { 31 | const key = aPropertyKeys[i]; 32 | if (a.properties?.[key] !== b.properties?.[key]) { 33 | return false; 34 | } 35 | } 36 | } else if (a.properties !== b.properties) { 37 | return false; 38 | } 39 | return true; 40 | } 41 | 42 | // eslint-disable-next-line import/prefer-default-export 43 | export { equalPolygonGeometry }; 44 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { App, Plugin } from 'vue'; 2 | import TMap from './components/map'; 3 | import MultiMarker from './components/multi-marker'; 4 | import MarkerCluster from './components/marker-cluster'; 5 | import MultiPolyline from './components/multi-polyline'; 6 | import MultiPolygon from './components/multi-polygon'; 7 | import MultiLabel from './components/multi-label'; 8 | import MultiCircle from './components/multi-circle'; 9 | import InfoWindow from './components/info-window'; 10 | import PolygonEditor from './components/polygon-editor'; 11 | // import GeometryEditor from './components/geometry-editor'; 12 | import DOMOverlay from './components/dom-overlay'; 13 | 14 | const components = [ 15 | TMap, 16 | MultiMarker, 17 | MarkerCluster, 18 | MultiPolygon, 19 | MultiPolyline, 20 | MultiLabel, 21 | MultiCircle, 22 | InfoWindow, 23 | PolygonEditor, 24 | // GeometryEditor, 25 | DOMOverlay, 26 | ]; 27 | 28 | const install: Plugin = (Vue: App) => { 29 | components.forEach((Component) => { 30 | Vue.component(Component.name, Component); 31 | }); 32 | }; 33 | 34 | export { 35 | TMap, 36 | MultiMarker, 37 | MarkerCluster, 38 | MultiPolygon, 39 | MultiPolyline, 40 | MultiLabel, 41 | MultiCircle, 42 | InfoWindow, 43 | PolygonEditor, 44 | // GeometryEditor, 45 | DOMOverlay, 46 | }; 47 | export default install; 48 | -------------------------------------------------------------------------------- /docs/api/polygon-editor.md: -------------------------------------------------------------------------------- 1 | # 多边形编辑 2 | 3 | 基于腾讯地图几何图形编辑器封装,开发者不需要关心用户的操作行为,多边形数据会响应式的更新 4 | 5 | ## 基础示例 6 | 7 | 8 | 9 | <<< src/examples/polygon-editor.vue 10 | 11 | ## props 12 | 13 | | 名称 | 类型 | 说明 | 14 | | --------------- | --------------------------------------------- | ------------------------------ | 15 | | id | String | 图层 id | 16 | | zIndex | Number | 图层绘制顺序 | 17 | | snappable | Boolean | 是否开启吸附功能,默认为 false | 18 | | drawingStyleId | String | 编辑态的样式 id | 19 | | selectedStyleId | String | 选中态态的样式 id | 20 | | styles | `{ [key: string]: TMap.PolygonStyleOptions }` | 样式 | 21 | | modelValue | TMap.PolygonGeometry[] | 多边形数据 | 22 | | actionMode | Number | 编辑器的操作状态 | 23 | 24 | ## ref 可用方法 25 | 26 | - select: 选中属于激活状态的图层内的几何图形,若传入空数组则清空; 27 | - stop: 停止绘制或编辑过程 28 | - split: 拆分已选中多边形, 29 | - union: 合并已选中多边形 30 | - delete: 删除已选中图形 31 | 32 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocEditor 33 | -------------------------------------------------------------------------------- /docs/api/info-window.md: -------------------------------------------------------------------------------- 1 | # 信息窗体 2 | 3 | ## 基础示例 4 | 5 |
6 | 7 | <<< src/examples/info-window.vue 8 | 9 | ## props 10 | 11 | | 名称 | 类型 | 说明 | 12 | | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------- | 13 | | id | String | 图层 id | 14 | | visible | Boolean | 是否显示 | 15 | | position | `{ [key: string]: TMap.LatLngData }` | (必需)信息窗的经纬度坐标。 | 16 | | content | String | 信息窗显示内容,默认为空字符串。当 enableCustom 为 true 时,需传入信息窗体的 dom 字符串 | 17 | | zIndex | Number | 显示层级 | 18 | | offset | Object | 偏移量(默认:{ x: 0, y: 0 }) | 19 | | enableCustom | Boolean | 信息窗体样式是否为自定义,默认为 false。 | 20 | 21 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocInfo 22 | -------------------------------------------------------------------------------- /src/components/marker-cluster.ts: -------------------------------------------------------------------------------- 1 | import { defineComponent, inject, Ref, watch, PropType, toRaw } from 'vue'; 2 | import { buildGeometries } from './multi-marker'; 3 | 4 | export default defineComponent({ 5 | name: 'tmap-marker-cluster', 6 | props: { 7 | id: { 8 | type: String, 9 | default: 'default', 10 | }, 11 | enableDefaultStyle: { 12 | type: Boolean, 13 | default: true, 14 | }, 15 | minimumClusterSize: { 16 | type: Number, 17 | default: 2, 18 | }, 19 | geometries: { 20 | type: Array as PropType, 21 | required: true, 22 | }, 23 | zoomOnClick: { 24 | type: Boolean, 25 | default: true, 26 | }, 27 | gridSize: { 28 | type: Number, 29 | default: 60, 30 | }, 31 | averageCenter: { 32 | type: Boolean, 33 | default: false, 34 | }, 35 | maxZoom: { 36 | type: Number, 37 | default: 20, 38 | }, 39 | }, 40 | setup(props) { 41 | const map = inject>('map'); 42 | if (!map) { 43 | return {}; 44 | } 45 | const markers = new TMap.MarkerCluster({ 46 | id: props.id, 47 | map: toRaw(map.value), 48 | enableDefaultStyle: props.enableDefaultStyle, 49 | minimumClusterSize: props.minimumClusterSize, 50 | geometries: buildGeometries(props.geometries), 51 | zoomOnClick: props.zoomOnClick, 52 | gridSize: props.gridSize, 53 | averageCenter: props.averageCenter, 54 | maxZoom: props.maxZoom, 55 | }); 56 | watch( 57 | () => props.geometries, 58 | (geometries) => { 59 | markers.setGeometries(buildGeometries(geometries)); 60 | }, 61 | ); 62 | return {}; 63 | }, 64 | render() { 65 | return null; 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /docs/.vitepress/theme/index.js: -------------------------------------------------------------------------------- 1 | import theme from 'vitepress/theme'; 2 | import TmapSDK from '../../../src/index'; 3 | import TMap from '../../../src/examples/map.vue'; 4 | import MultiPolyline from '../../../src/examples/multi-polyline.vue'; 5 | import MultiPolygon from '../../../src/examples/multi-polygon.vue'; 6 | // import HugePolygon from '../../../src/examples/huge-polygon.vue'; 7 | import MultiMarker from '../../../src/examples/multi-marker.vue'; 8 | import MarkerCluster from '../../../src/examples/marker-cluster.vue'; 9 | import MultiCircle from '../../../src/examples/multi-circle.vue'; 10 | import MultiLabel from '../../../src/examples/multi-label.vue'; 11 | import InfoWindow from '../../../src/examples/info-window.vue'; 12 | import DomOverlay from '../../../src/examples/dom-overlay.vue'; 13 | import PlayBack from '../../../src/examples/play-back.vue'; 14 | import PolygonEditor from '../../../src/examples/polygon-editor.vue'; 15 | 16 | export default { 17 | ...theme, 18 | enhanceApp({ app, router, siteData }) { 19 | // app is the Vue 3 app instance from createApp() 20 | // router is VitePress' custom router (see `lib/app/router.js`) 21 | // siteData is a ref of current site-level metadata. 22 | app.use(TmapSDK); 23 | app.component('DemoTMap', TMap); 24 | app.component('DemoMultiPolyline', MultiPolyline); 25 | app.component('DemoMultiPolygon', MultiPolygon); 26 | // app.component('DemoHugePolygon', HugePolygon); 27 | app.component('DemoMultiMarker', MultiMarker); 28 | app.component('DemoMarkerCluster', MarkerCluster); 29 | app.component('DemoMultiCircle', MultiCircle); 30 | app.component('DemoMultiLabel', MultiLabel); 31 | app.component('DemoInfoWindow', InfoWindow); 32 | app.component('DemoDomOverlay', DomOverlay); 33 | app.component('DemoPlayBack', PlayBack); 34 | app.component('DemoPolygonEditor', PolygonEditor); 35 | }, 36 | }; 37 | -------------------------------------------------------------------------------- /docs/guide/getting-started.md: -------------------------------------------------------------------------------- 1 | # 快速上手 2 | 3 | --- 4 | 5 | 本节将介绍如何在项目中使用 vue-tmap。 6 | 7 | ## 申请腾讯地图密钥 8 | 9 | https://lbs.qq.com/dev/console/key/manage 10 | 11 | ## 项目结构 12 | 13 | 使用`Vue CLI`新建项目 14 | `vue create hello-tmap` 15 | 16 | 项目结构为: 17 | 18 | ```html 19 | |- src/ --------------------- 项目源代码 |- App.vue |- main.js -------------- 20 | 入口文件 21 | ``` 22 | 23 | ## 引入 vue-tmap 24 | 25 | main.js 26 | 27 | ```javascript 28 | import { createApp } from 'vue'; 29 | import App from './App.vue'; 30 | import router from './router'; 31 | import Tmap from '@map-component/vue-tmap'; 32 | 33 | createApp(App) 34 | .use(router) 35 | .use(Tmap) 36 | .mount('#app'); 37 | ``` 38 | 39 | App.vue 40 | 41 | > mapKey 为新申请的密钥 42 | 43 | ```html 44 | 55 | 56 | 85 | ``` 86 | 87 | ## 安装依赖 88 | 89 | ```javascript 90 | npm install 91 | ``` 92 | 93 | ## 构建 94 | 95 | ```javascript 96 | npm run dev 97 | ``` 98 | -------------------------------------------------------------------------------- /src/components/geometry-editor.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | import { defineComponent, inject, Ref, toRaw } from 'vue'; 3 | 4 | export default defineComponent({ 5 | name: 'tmap-geometry-editor', 6 | props: { 7 | selectedStyleId: { 8 | type: String, 9 | default: 'selected', 10 | }, 11 | }, 12 | setup(props) { 13 | const map = inject>('map'); 14 | const geometry = inject('geometry'); 15 | if (!map || !geometry) { 16 | return {}; 17 | } 18 | const editor = new TMap.tools.GeometryEditor({ 19 | map: toRaw(map.value), 20 | overlayList: [ 21 | { 22 | overlay: geometry, 23 | id: geometry.id, 24 | selectedStyleId: props.selectedStyleId, 25 | }, 26 | ], 27 | actionMode: TMap.tools.constants.EDITOR_ACTION.INTERACT, // 编辑器的工作模式 28 | activeOverlayId: geometry.id, // 激活图层 29 | selectable: true, // 开启点选功能 30 | snappable: true, // 开启吸附 31 | }); 32 | editor.on('active_changed', (e: unknown) => { 33 | console.log('active_changed', e); 34 | }); 35 | editor.on('select', (e: unknown) => { 36 | console.log('select', e); 37 | }); 38 | editor.on('draw_complete', (e: unknown) => { 39 | console.log('draw_complete', e); 40 | }); 41 | editor.on('adjust_complete', (e: unknown) => { 42 | console.log('adjust_complete', e); 43 | }); 44 | editor.on('delete_complete', (e: unknown) => { 45 | console.log('delete_complete', e); 46 | }); 47 | editor.on('split_complete', (e: unknown) => { 48 | console.log('split_complete', e); 49 | }); 50 | editor.on('union_complete', (e: unknown) => { 51 | console.log('union_complete', e); 52 | }); 53 | editor.on('split_fail', (e: unknown) => { 54 | console.log('split_fail', e); 55 | }); 56 | editor.on('union_fail', (e: unknown) => { 57 | console.log('union_fail', e); 58 | }); 59 | return {}; 60 | }, 61 | render() { 62 | return null; 63 | }, 64 | }); 65 | -------------------------------------------------------------------------------- /src/examples/map.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 70 | 71 | 81 | -------------------------------------------------------------------------------- /src/examples/multi-marker.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 64 | 86 | -------------------------------------------------------------------------------- /docs/.vitepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | lang: 'zh-CN', 3 | title: '腾讯地图 vue 组件', 4 | description: '腾讯地图 vue 组件 tmap', 5 | base: '/vue-tmap/', 6 | themeConfig: { 7 | lastUpdated: 'Last Updated', 8 | nav: [ 9 | { text: '文档', link: '/guide/', activeMatch: '^/$|^/guide|^/api/' }, 10 | { 11 | text: '仓库地址', 12 | link: 'https://github.com/didi/vue-tmap', 13 | }, 14 | ], 15 | 16 | sidebar: { 17 | '/guide/': getGuideSidebar(), 18 | '/api/': getGuideSidebar(), 19 | '/config/': getConfigSidebar(), 20 | }, 21 | }, 22 | }; 23 | 24 | function getGuideSidebar() { 25 | return [ 26 | { 27 | text: '基础', 28 | children: [ 29 | { text: 'demo', link: '/guide/' }, 30 | { text: '安装', link: '/guide/install' }, 31 | { text: '快速上手', link: '/guide/getting-started' }, 32 | { text: '常见问题', link: '/guide/qa' }, 33 | ], 34 | }, 35 | { 36 | text: '组件', 37 | children: [ 38 | { text: '地图', link: '/api/map' }, 39 | { text: '矢量图形事件', link: '/api/vector-events' }, 40 | { text: '折线', link: '/api/multi-polyline' }, 41 | { text: '多边形', link: '/api/multi-polygon' }, 42 | { text: '多边形编辑', link: '/api/polygon-editor' }, 43 | { text: '圆形标记', link: '/api/multi-circle' }, 44 | { text: '点标记', link: '/api/multi-marker' }, 45 | { text: '点聚合', link: '/api/marker-cluster' }, 46 | { text: '文本标注', link: '/api/multi-label' }, 47 | { text: 'DOM覆盖物', link: '/api/dom-overlay' }, 48 | { text: '信息提示窗', link: '/api/info-window' }, 49 | ], 50 | }, 51 | ]; 52 | } 53 | 54 | function getConfigSidebar() { 55 | return [ 56 | { 57 | text: 'App Config', 58 | children: [{ text: 'Basics', link: '/config/basics' }], 59 | }, 60 | { 61 | text: 'Theme Config', 62 | children: [ 63 | { text: 'Homepage', link: '/config/homepage' }, 64 | { text: 'Algolia Search', link: '/config/algolia-search' }, 65 | { text: 'Carbon Ads', link: '/config/carbon-ads' }, 66 | ], 67 | }, 68 | ]; 69 | } 70 | -------------------------------------------------------------------------------- /src/examples/marker-cluster.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 61 | 83 | -------------------------------------------------------------------------------- /src/components/multi-label.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | inject, 4 | Ref, 5 | watch, 6 | PropType, 7 | toRaw, 8 | onUnmounted, 9 | } from 'vue'; 10 | import useCleanUp from '../composables/use-clean-up'; 11 | import useEvent from '../composables/use-event'; 12 | 13 | function builtStyle(opt: { [key: string]: TMap.LabelStyle }) { 14 | const styled: TMap.MultiLabelStyleHash = {}; 15 | 16 | Object.keys(opt).forEach((k) => { 17 | styled[k] = new TMap.LabelStyle(opt[k]); 18 | }); 19 | return styled; 20 | } 21 | 22 | export default defineComponent({ 23 | name: 'tmap-multi-label', 24 | props: { 25 | id: { 26 | type: String, 27 | default: 'default', 28 | }, 29 | styles: { 30 | type: Object as PropType, 31 | required: true, 32 | }, 33 | geometries: { 34 | type: Array as PropType, 35 | required: true, 36 | }, 37 | enableCollision: { 38 | type: Boolean, 39 | required: false, 40 | default: false, 41 | }, 42 | }, 43 | setup(props, { attrs, emit }) { 44 | const map = inject>('map'); 45 | if (!map) { 46 | return {}; 47 | } 48 | const originMap = toRaw(map.value); 49 | useCleanUp(originMap, props.id); 50 | 51 | const getResGeo = (geo: TMap.LabelGeometry[]) => 52 | geo.map((item: TMap.LabelGeometry) => ({ 53 | ...item, 54 | position: new TMap.LatLng(item.position.lat, item.position.lng), 55 | })); 56 | 57 | const labelInstance = new TMap.MultiLabel({ 58 | id: props.id, 59 | map: toRaw(map.value), 60 | styles: builtStyle(props.styles), 61 | geometries: getResGeo(props.geometries), 62 | enableCollision: props.enableCollision, 63 | }); 64 | 65 | useEvent(labelInstance, attrs, emit); 66 | 67 | watch( 68 | () => props.styles, 69 | (styles) => { 70 | labelInstance.setStyles(builtStyle(styles)); 71 | }, 72 | ); 73 | 74 | watch( 75 | () => props.geometries, 76 | (r) => { 77 | labelInstance.setGeometries(getResGeo(r)); 78 | }, 79 | ); 80 | onUnmounted(() => { 81 | labelInstance.setMap(null); 82 | }); 83 | return {}; 84 | }, 85 | render() { 86 | return null; 87 | }, 88 | }); 89 | -------------------------------------------------------------------------------- /src/components/multi-circle.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | inject, 4 | Ref, 5 | watch, 6 | PropType, 7 | toRaw, 8 | onUnmounted, 9 | } from 'vue'; 10 | import useEvent from '../composables/use-event'; 11 | import useCleanUp from '../composables/use-clean-up'; 12 | 13 | function builtStyle(opt: { [key: string]: TMap.CircleStyle }) { 14 | const styled: TMap.MultiPolygonStyleHash = {}; 15 | 16 | Object.keys(opt).forEach((k) => { 17 | styled[k] = new TMap.CircleStyle(opt[k]); 18 | }); 19 | return styled; 20 | } 21 | 22 | export default defineComponent({ 23 | name: 'tmap-multi-circle', 24 | props: { 25 | id: { 26 | type: String, 27 | default: 'default', 28 | }, 29 | zIndex: { 30 | type: Number, 31 | default: 1, 32 | }, 33 | styles: { 34 | type: Object as PropType, 35 | required: true, 36 | }, 37 | geometries: { 38 | type: Array as PropType, 39 | required: true, 40 | }, 41 | }, 42 | setup(props, { attrs, emit }) { 43 | const map = inject>('map'); 44 | if (!map) { 45 | return {}; 46 | } 47 | 48 | const originMap = toRaw(map.value); 49 | useCleanUp(originMap, props.id); 50 | const getResGeo = (geo: TMap.CircleGeometry[]) => 51 | geo.map((item: TMap.CircleGeometry) => ({ 52 | ...item, 53 | center: new TMap.LatLng(item.center.lat, item.center.lng), 54 | })); 55 | const circle = new TMap.MultiCircle({ 56 | id: props.id, 57 | map: originMap, 58 | styles: builtStyle(props.styles), 59 | geometries: getResGeo(props.geometries), 60 | zIndex: 1, 61 | }); 62 | 63 | useEvent(circle, attrs, emit); 64 | watch( 65 | () => props.zIndex, 66 | (zIndex) => { 67 | circle.setZIndex(zIndex); 68 | }, 69 | ); 70 | watch( 71 | () => props.styles, 72 | (styles) => { 73 | circle.setStyles(builtStyle(styles)); 74 | }, 75 | ); 76 | watch( 77 | () => props.geometries, 78 | (r) => { 79 | circle.setGeometries(getResGeo(r)); 80 | }, 81 | ); 82 | onUnmounted(() => { 83 | circle.setMap(null); 84 | }); 85 | return {}; 86 | }, 87 | render() { 88 | return null; 89 | }, 90 | }); 91 | -------------------------------------------------------------------------------- /src/examples/multi-polyline.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 67 | 89 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@map-component/vue-tmap", 3 | "version": "1.0.1", 4 | "description": "基于腾讯地图 JavaScript API GL、TypeScript 封装适用于 Vue3 的高性能地图组件库", 5 | "license": "MIT", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/didi/vue-tmap.git" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/didi/vue-tmap/issues" 12 | }, 13 | "homepage": "https://didi.github.io/vue-tmap/", 14 | "main": "dist/index.js", 15 | "module": "dist/index.js", 16 | "keywords": [ 17 | "map", 18 | "tmap", 19 | "qqmap", 20 | "vue map", 21 | "map component" 22 | ], 23 | "scripts": { 24 | "dev": "vitepress dev docs", 25 | "prebuild": "tsc -b buildconfig.json", 26 | "build": "npm run prebuild && vite build --config vite.config.js", 27 | "release": "npm run build && npm publish", 28 | "test:unit": "vue-cli-service test:unit", 29 | "lint": "vue-cli-service lint", 30 | "docs:dev": "vitepress dev docs", 31 | "docs:build": "vitepress build docs", 32 | "docs:serve": "vitepress serve docs --port 8080", 33 | "docs:deploy": "gh-pages -d docs/.vitepress/dist", 34 | "deploy": "npm run docs:build && npm run docs:deploy" 35 | }, 36 | "files": [ 37 | "dist" 38 | ], 39 | "dependencies": { 40 | "core-js": "^3.22.4" 41 | }, 42 | "devDependencies": { 43 | "@map-component/tmap-types": "^0.1.1", 44 | "@types/chai": "^4.2.11", 45 | "@types/mocha": "^5.2.4", 46 | "@typescript-eslint/eslint-plugin": "^2.33.0", 47 | "@typescript-eslint/parser": "^2.33.0", 48 | "@vue/cli-plugin-babel": "~4.5.0", 49 | "@vue/cli-plugin-eslint": "~4.5.0", 50 | "@vue/cli-plugin-typescript": "^5.0.4", 51 | "@vue/cli-plugin-unit-mocha": "~4.5.0", 52 | "@vue/cli-service": "~4.5.0", 53 | "@vue/compiler-sfc": "^3.2.33", 54 | "@vue/eslint-config-airbnb": "^5.0.2", 55 | "@vue/eslint-config-prettier": "^6.0.0", 56 | "@vue/eslint-config-typescript": "^5.0.2", 57 | "@vue/test-utils": "^2.0.0-0", 58 | "chai": "^4.1.2", 59 | "eslint": "^6.7.2", 60 | "eslint-plugin-import": "^2.20.2", 61 | "eslint-plugin-prettier": "^3.1.3", 62 | "eslint-plugin-vue": "^7.0.0-0", 63 | "gh-pages": "^3.2.3", 64 | "prettier": "^1.19.1", 65 | "sass": "^1.26.5", 66 | "sass-loader": "^8.0.2", 67 | "typescript": "^4.6.4", 68 | "vite": "^2.9.8", 69 | "vite-plugin-dts": "^1.5.0", 70 | "vitepress": "^0.22.4", 71 | "vue": "^3.1.0" 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/components/multi-marker.ts: -------------------------------------------------------------------------------- 1 | import useEvent from '../composables/use-event'; 2 | import { 3 | defineComponent, 4 | inject, 5 | onUnmounted, 6 | PropType, 7 | Ref, 8 | toRaw, 9 | watch, 10 | } from 'vue'; 11 | import useCleanUp from '../composables/use-clean-up'; 12 | 13 | function builtStyle(opt: { [key: string]: TMap.MarkerStyleOptions }) { 14 | const styled: TMap.MultiMarkerStyleHash = {}; 15 | Object.keys(opt).forEach((k) => { 16 | styled[k] = new TMap.MarkerStyle(opt[k]); 17 | }); 18 | return styled; 19 | } 20 | export function buildGeometries(geometries: TMap.PointGeometry[]) { 21 | return geometries.map((v) => ({ 22 | ...v, 23 | position: new TMap.LatLng(v.position.lat, v.position.lng), 24 | })); 25 | } 26 | export default defineComponent({ 27 | name: 'tmap-multi-marker', 28 | props: { 29 | id: { 30 | type: String, 31 | default: 'default', 32 | }, 33 | styles: { 34 | type: Object as PropType<{ [key: string]: TMap.MarkerStyleOptions }>, 35 | required: true, 36 | }, 37 | geometries: { 38 | type: Array as PropType, 39 | required: true, 40 | }, 41 | }, 42 | setup(props, { attrs, emit }) { 43 | const map = inject>('map'); 44 | if (!map) { 45 | return {}; 46 | } 47 | const originMap = toRaw(map.value); 48 | useCleanUp(originMap, props.id); 49 | 50 | const markers = new TMap.MultiMarker({ 51 | id: props.id, 52 | map: originMap, 53 | styles: builtStyle(props.styles), 54 | geometries: buildGeometries(props.geometries), 55 | }); 56 | 57 | useEvent(markers, attrs, emit); 58 | 59 | watch( 60 | () => props.styles, 61 | (styles) => { 62 | markers.setStyles(builtStyle(styles)); 63 | }, 64 | ); 65 | watch( 66 | () => props.geometries, 67 | (geometries) => { 68 | markers.setGeometries(buildGeometries(geometries)); 69 | }, 70 | ); 71 | onUnmounted(() => { 72 | markers.setMap(null); 73 | }); 74 | 75 | // 提供给ref实例使用 76 | return { 77 | getStyles: markers.getStyles, 78 | moveAlong: markers.moveAlong.bind(markers), 79 | stopMove: markers.stopMove.bind(markers), 80 | pauseMove: markers.pauseMove.bind(markers), 81 | resumeMove: markers.resumeMove.bind(markers), 82 | on: markers.on.bind(markers), 83 | }; 84 | }, 85 | render() { 86 | return null; 87 | }, 88 | }); 89 | -------------------------------------------------------------------------------- /src/components/multi-polyline.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | inject, 4 | Ref, 5 | watch, 6 | PropType, 7 | toRaw, 8 | onUnmounted, 9 | } from 'vue'; 10 | import useCleanUp from '../composables/use-clean-up'; 11 | import useEvent from '../composables/use-event'; 12 | 13 | function builtStyle(opt: { [key: string]: TMap.PolylineStyleOptions }) { 14 | const styled: TMap.MultiPolylineStyleHash = {}; 15 | Object.keys(opt).forEach((k) => { 16 | styled[k] = new TMap.PolylineStyle(opt[k]); 17 | }); 18 | return styled; 19 | } 20 | 21 | export function buildGeometries( 22 | geometries: TMap.PolylineGeometry[], 23 | ): TMap.PolylineGeometry[] { 24 | return geometries.map((v) => { 25 | return { 26 | ...v, 27 | paths: (v.paths as Array<{ 28 | lat: number; 29 | lng: number; 30 | }>).map((p) => new TMap.LatLng(p.lat, p.lng)), 31 | }; 32 | }); 33 | } 34 | export default defineComponent({ 35 | name: 'tmap-multi-polyline', 36 | props: { 37 | id: { 38 | type: String, 39 | default: 'default', 40 | }, 41 | zIndex: { 42 | type: Number, 43 | default: 1, 44 | }, 45 | styles: { 46 | type: Object as PropType<{ [key: string]: TMap.PolylineStyleOptions }>, 47 | required: true, 48 | }, 49 | geometries: { 50 | type: Array as PropType, 51 | required: true, 52 | }, 53 | }, 54 | setup(props, { attrs, emit }) { 55 | const map = inject>('map'); 56 | if (!map) { 57 | return {}; 58 | } 59 | 60 | const originMap = toRaw(map.value); 61 | useCleanUp(originMap, props.id); 62 | const polyline = new TMap.MultiPolyline({ 63 | id: props.id, 64 | map: originMap, 65 | zIndex: props.zIndex, 66 | styles: builtStyle(props.styles), 67 | geometries: buildGeometries(props.geometries), 68 | }); 69 | useEvent(polyline, attrs, emit); 70 | watch( 71 | () => props.zIndex, 72 | (zIndex) => { 73 | polyline.setZIndex(zIndex); 74 | }, 75 | ); 76 | watch( 77 | () => props.styles, 78 | (styles) => { 79 | polyline.setStyles(builtStyle(styles)); 80 | }, 81 | ); 82 | watch( 83 | () => props.geometries, 84 | (geometries) => { 85 | polyline.setGeometries(buildGeometries(geometries)); 86 | }, 87 | ); 88 | onUnmounted(() => { 89 | polyline.setMap(null); 90 | }); 91 | return {}; 92 | }, 93 | render() { 94 | return null; 95 | }, 96 | }); 97 | -------------------------------------------------------------------------------- /src/components/info-window.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | inject, 4 | Ref, 5 | watch, 6 | PropType, 7 | toRaw, 8 | onUnmounted, 9 | } from 'vue'; 10 | import useCleanUp from '../composables/use-clean-up'; 11 | 12 | const getLatLng = (latlngData: TMap.LatLngData) => { 13 | return new TMap.LatLng(latlngData.lat, latlngData.lng); 14 | }; 15 | 16 | export default defineComponent({ 17 | name: 'tmap-info-window', 18 | props: { 19 | id: { 20 | type: String, 21 | default: 'default', 22 | }, 23 | visible: { 24 | type: Boolean, 25 | required: true, 26 | }, 27 | position: { 28 | type: Object as PropType, 29 | required: true, 30 | }, 31 | content: { 32 | type: String, 33 | required: true, 34 | }, 35 | zIndex: { 36 | type: Number, 37 | required: false, 38 | default: 0, 39 | }, 40 | offset: { 41 | type: Object as PropType, 42 | required: false, 43 | default: () => ({ x: 0, y: 0 }), 44 | }, 45 | enableCustom: { 46 | type: Boolean, 47 | required: false, 48 | default: false, 49 | }, 50 | }, 51 | emits: ['close-click'], 52 | setup(props, { emit }) { 53 | const map = inject>('map'); 54 | if (!map) { 55 | return {}; 56 | } 57 | 58 | const originMap = toRaw(map.value); 59 | useCleanUp(originMap, props.id); 60 | 61 | const center = getLatLng(props.position); 62 | // 初始化infoWindow 63 | const infoWindow = new TMap.InfoWindow({ 64 | map: toRaw(map.value), 65 | position: center, // 设置信息框位置 66 | content: props.content, // 设置信息框内容 67 | zIndex: props.zIndex, 68 | offset: props.offset, 69 | enableCustom: props.enableCustom, 70 | }); 71 | 72 | infoWindow.on('closeclick', () => { 73 | emit('close-click'); 74 | }); 75 | 76 | watch( 77 | () => props.visible, 78 | (v) => { 79 | if (v) { 80 | infoWindow.open(); 81 | } else { 82 | infoWindow.close(); 83 | } 84 | }, 85 | ); 86 | watch( 87 | () => props.content, 88 | (v) => { 89 | infoWindow.setContent(v); 90 | }, 91 | ); 92 | watch( 93 | () => props.position, 94 | (v) => { 95 | infoWindow.setPosition(getLatLng(v)); 96 | }, 97 | ); 98 | 99 | onUnmounted(() => { 100 | infoWindow.destroy(); 101 | }); 102 | 103 | return {}; 104 | }, 105 | render() { 106 | return null; 107 | }, 108 | }); 109 | -------------------------------------------------------------------------------- /src/examples/multi-circle.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 84 | 106 | -------------------------------------------------------------------------------- /src/examples/huge-polygon.vue: -------------------------------------------------------------------------------- 1 | 40 | 41 | 91 | 113 | -------------------------------------------------------------------------------- /src/examples/info-window.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 95 | 117 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # vue-tmap 2 | 3 | - en [English](https://github.com/didi/vue-tmap/blob/main/README.md) 4 | 5 | ### 简介 6 | 7 | vue-tmap,一个基于腾讯地图、TypeScript 封装适用于 Vue3 的高性能地图组件库,拥有以下功能特性: 8 | 9 | - 文档完善:基于官方文档和框架用法的文档可读性高,组件示例完善 10 | - 组件化:封装腾讯地图命令式的 api 为响应式组件,无需关心复杂的地图 api,只需要操作数据即可 11 | - 多框架:包含 [react-tmap](https://github.com/didi/react-tmap) 和 [vue-tmap](https://github.com/didi/vue-tmap),且共享同一套类型定义 12 | - Type-safe:补充了腾讯地图 sdk 的类型声明,组件也使用 TypeScript 开发,更好的开发体验 13 | - 自定义组件:提供开放地图实例,可编写自定义组件或直接调用地图原生 api 14 | - 性能优化:统一地图 api 调用方式和数据监听,防止误用地图 api 引起性能问题 15 | 16 | ### 文档和示例 17 | 18 | 欢迎访问[文档地址](https://didi.github.io/vue-tmap/),查看更多地图组件。 19 | 20 | - [腾讯地图官方文档](https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocIndex) 21 | 22 | ### 主要组件 23 | 24 | | tmap-class | vue component | 简介 | 25 | | ------------- | ------------------- | ---------------- | 26 | | Map | tmap-map | 地图基础组件 | 27 | | MultiMarker | tmap-multi-marker | 多个标注点 | 28 | | MultiPolyline | tmap-multi-polyline | 折线 | 29 | | MultiPolygon | tmap-multi-polygon | 多边形 | 30 | | MultiLabel | tmap-multi-label | 文本标注 | 31 | | MultiCircle | tmap-multi-circle | 圆形 | 32 | | DOMOverlay | tmap-dom-overlay | DOM 覆盖物抽象类 | 33 | | InfoWindow | tmap-info-window | 信息提示窗 | 34 | | MarkerCluster | tmap-marker-cluster | 点聚合 | 35 | 36 | ### 快速开始 37 | 38 | #### 安装 39 | 40 | ```shell 41 | npm install @map-component/vue-tmap 42 | ``` 43 | 44 | #### 申请腾讯地图密钥 45 | 46 | https://lbs.qq.com/dev/console/key/manage 47 | 48 | #### 简单示例 49 | 50 | ```vue 51 | 62 | 63 | 92 | ``` 93 | 94 | > mapKey 为新申请的密钥 95 | 96 | ### 贡献指南 97 | 98 | > 感谢所有参与贡献的技术爱好者,一起共建好用易用的地图组件库 99 | 100 | #### 提交错误 101 | 102 | 请通过 issue 提交错误,详细描述错误复现方式和依赖版本,最好通过在线代码编辑器展示复现代码 103 | 104 | #### 提交代码 105 | 106 | 请通过 pull request 提交您的代码,我们将尽快查看 107 | 108 | #### 开始开发 109 | 110 | ``` 111 | git clone xxx 112 | 113 | cd react-tmap // cd vue-tmap 114 | 115 | npm install 116 | 117 | npm run dev 118 | ``` 119 | -------------------------------------------------------------------------------- /src/examples/multi-label.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 96 | 118 | -------------------------------------------------------------------------------- /src/examples/polygon-editor-compose.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 87 | 88 | 102 | -------------------------------------------------------------------------------- /src/components/dom-overlay.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | inject, 4 | Ref, 5 | ref, 6 | h, 7 | PropType, 8 | onMounted, 9 | watch, 10 | computed, 11 | onUnmounted, 12 | toRaw, 13 | } from 'vue'; 14 | import useCleanUp from '../composables/use-clean-up'; 15 | 16 | const getLatLng = (latlngData: TMap.LatLngData) => { 17 | return new TMap.LatLng(latlngData.lat, latlngData.lng); 18 | }; 19 | 20 | interface Options extends TMap.DOMOverlayOptions { 21 | ele: HTMLSpanElement; 22 | } 23 | 24 | export default defineComponent({ 25 | name: 'tmap-dom-overlay', 26 | props: { 27 | id: { 28 | type: String, 29 | default: 'default', 30 | }, 31 | position: { 32 | type: Object as PropType, 33 | required: true, 34 | }, 35 | offset: { 36 | type: Array as PropType, 37 | required: false, 38 | default: () => [0, 0], 39 | }, 40 | }, 41 | setup(props, { slots }) { 42 | const map = inject>('map'); 43 | if (!map) { 44 | return {}; 45 | } 46 | 47 | const originMap = toRaw(map.value); 48 | useCleanUp(originMap, props.id); 49 | 50 | const domRef = ref(document.createElement('div')); 51 | 52 | const position = computed(() => { 53 | return getLatLng(getLatLng(props.position)); 54 | }); 55 | 56 | class DomClass extends TMap.DOMOverlay { 57 | map: TMap.Map; 58 | // eslint-disable-next-line lines-between-class-members 59 | ele!: HTMLSpanElement; 60 | 61 | constructor(options: Options) { 62 | super(options); 63 | this.map = options.map; 64 | this.onInit(options); 65 | } 66 | 67 | onInit(options: Options) { 68 | this.ele = options.ele; 69 | } 70 | 71 | createDOM() { 72 | return this.ele; 73 | } 74 | 75 | updateDOM() { 76 | // 经纬度坐标转容器像素坐标,直接使用 position 77 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 78 | const pixel: any = this.map.projectToContainer(position.value); 79 | // 使饼图中心点对齐经纬度坐标点 80 | const left = `${pixel.getX() - 81 | this.ele.clientWidth / 2 + 82 | props.offset[0]}px`; 83 | const top = `${pixel.getY() - 84 | this.ele.clientHeight / 2 + 85 | props.offset[1]}px`; 86 | 87 | this.ele.setAttribute( 88 | 'style', 89 | `top: ${top}; left: ${left}; position: absolute;`, 90 | ); 91 | } 92 | 93 | onDestroy() { 94 | this.ele.innerHTML = ''; 95 | } 96 | } 97 | 98 | let domIns: DomClass; 99 | 100 | onMounted(() => { 101 | domIns = new DomClass({ 102 | map: map.value, 103 | ele: domRef.value, 104 | }); 105 | }); 106 | 107 | watch( 108 | () => props.position, 109 | () => { 110 | domIns.updateDOM(); 111 | }, 112 | ); 113 | 114 | onUnmounted(() => { 115 | domIns.onDestroy(); 116 | }); 117 | 118 | return () => 119 | h( 120 | 'span', 121 | { 122 | ref: domRef, 123 | }, 124 | slots.default ? slots.default() : [], 125 | ); 126 | }, 127 | }); 128 | -------------------------------------------------------------------------------- /src/examples/dom-overlay.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 107 | 134 | -------------------------------------------------------------------------------- /docs/api/map.md: -------------------------------------------------------------------------------- 1 | # 地图 2 | 3 | 地图组件 4 | 5 | > 请注意,本文档中所有示例使用的 mapKey 仅作文档测试使用,不定期修改相关配置,请勿使用在任何项目中 6 | 7 | ## 基础示例 8 | 9 |
10 | 11 | <<< src/examples/map.vue 12 | 13 | ## props 14 | 15 | ### 本组件库自定义属性 16 | 17 | | 名称 | 类型 | 说明 | 18 | | --------- | ------------------------------- | ------------------------------------------------------------------------- | 19 | | version | String | sdk 版本 | 20 | | mapKey | String | 开发者 token | 21 | | libraries | String[] | 地图扩展库,默认包含 ['visualization', 'tools', 'geometry'], 可增加其他库 | 22 | | class | String | 地图容器 classname | 23 | | style | Object | 地图容器 style | 24 | | control | ControlType(参考下面的类型定义) | 地图控件的配置 | 25 | | events | `{ [key: string]: Function }` | 地图事件 | 26 | 27 | ```ts 28 | interface ControlType { 29 | scale: { position: string; className: string }; 30 | zoom: { position: string; className: string }; 31 | rotation: { position: string; className: string }; 32 | } 33 | ``` 34 | 35 | ### 腾讯地图原有属性 36 | 37 | | 名称 | 类型 | 说明 | 38 | | --------------- | ---------------------------- | ---------------------------------------------------- | 39 | | center | `{ lat:number; lng:number }` | 地图中心点经纬度。 | 40 | | zoom | Number | 地图缩放级别,支持 3 ~ 20。 | 41 | | minZoom | Number | 地图最小缩放级别,默认为 3。 | 42 | | maxZoom | Number | 地图最大缩放级别,默认为 20。 | 43 | | rotation | Number | 地图在水平面上的旋转角度,顺时针方向为正,默认为 0。 | 44 | | pitch | Number | 地图俯仰角度,取值范围为 0~80,默认为 0。 | 45 | | scale | Number | 地图显示比例,默认为 1。 | 46 | | offset | `{ x:number; y:number }` | 地图中心与容器的偏移量 | 47 | | draggable | Boolean | 是否支持拖拽移动地图,默认为 true。 | 48 | | scrollable | Boolean | 是否支持鼠标滚轮缩放地图,默认为 true。 | 49 | | doubleClickZoom | Boolean | 是否支持双击缩放地图,默认为 true。 | 50 | | boundary | LatLngBounds | 地图边界 | 51 | | mapStyleId | String | 地图样式 ID | 52 | | baseMap | TMap.BaseMap | 地图底图 | 53 | | viewMode | String | 地图视图模式,支持 2D 和 3D | 54 | 55 | 详细文档见官网 https://lbs.qq.com/webApi/javascriptGL/glDoc/docIndexMap 56 | 57 | ## 事件 58 | 59 | 以事件名为 key,监听函数为 value 的配置对象传入 props.events 60 | 61 | ## 地图实例 62 | 63 | 地图组件的子组件可以通过 64 | 65 | ```js 66 | const map = inject < Ref < TMap.Map >> 'map'; 67 | ``` 68 | 69 | 获取组件的实例 ref 70 | -------------------------------------------------------------------------------- /src/components/multi-polygon.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | inject, 4 | Ref, 5 | watch, 6 | PropType, 7 | toRaw, 8 | provide, 9 | onUnmounted, 10 | } from 'vue'; 11 | import useEvent from '../composables/use-event'; 12 | import useCleanUp from '../composables/use-clean-up'; 13 | import keyBy from '../utils/keyBy'; 14 | import { equalPolygonGeometry } from '../utils/equal'; 15 | 16 | export function builtStyle(opt: { [key: string]: TMap.PolygonStyleOptions }) { 17 | const styled: TMap.MultiPolygonStyleHash = {}; 18 | Object.keys(opt).forEach((k) => { 19 | styled[k] = new TMap.PolygonStyle(opt[k]); 20 | }); 21 | return styled; 22 | } 23 | export function buildGeometries( 24 | geometries: TMap.PolygonGeometry[], 25 | ): TMap.PolygonGeometry[] { 26 | return geometries.map((v) => { 27 | return { 28 | ...v, 29 | paths: (v.paths as Array<{ 30 | lat: number; 31 | lng: number; 32 | }>).map((p) => new TMap.LatLng(p.lat, p.lng)), 33 | }; 34 | }); 35 | } 36 | export default defineComponent({ 37 | name: 'tmap-multi-polygon', 38 | props: { 39 | id: { 40 | type: String, 41 | default: 'default', 42 | }, 43 | zIndex: { 44 | type: Number, 45 | default: 1, 46 | }, 47 | styles: { 48 | type: Object as PropType<{ [key: string]: TMap.PolygonStyleOptions }>, 49 | required: true, 50 | }, 51 | geometries: { 52 | type: Array as PropType, 53 | required: true, 54 | }, 55 | }, 56 | setup(props, { attrs, emit }) { 57 | const map = inject>('map'); 58 | if (!map) { 59 | return {}; 60 | } 61 | const originMap = toRaw(map.value); 62 | // eslint-disable-next-line vue/no-setup-props-destructure 63 | let currentGeometries = props.geometries; 64 | useCleanUp(originMap, props.id); 65 | const polygon = new TMap.MultiPolygon({ 66 | id: props.id, 67 | map: originMap, 68 | zIndex: props.zIndex, 69 | styles: builtStyle(props.styles), 70 | geometries: buildGeometries(currentGeometries), 71 | }); 72 | useEvent(polygon, attrs, emit); 73 | watch( 74 | () => props.zIndex, 75 | (zIndex) => { 76 | polygon.setZIndex(zIndex); 77 | }, 78 | ); 79 | watch( 80 | () => props.styles, 81 | (styles) => { 82 | polygon.setStyles(builtStyle(styles)); 83 | }, 84 | ); 85 | watch( 86 | () => props.geometries, 87 | (geometries) => { 88 | const currentGeometriesMap = keyBy(currentGeometries, 'id'); 89 | const toDelete = new Set(Object.keys(currentGeometriesMap)); 90 | const toAddOrModify: TMap.PolygonGeometry[] = []; 91 | 92 | geometries.forEach((v) => { 93 | if (currentGeometriesMap[v.id]) { 94 | toDelete.delete(v.id); 95 | if (!equalPolygonGeometry(currentGeometriesMap[v.id], v)) { 96 | toAddOrModify.push(v); 97 | } 98 | } else { 99 | toAddOrModify.push(v); 100 | } 101 | }); 102 | currentGeometries = geometries; 103 | if (toDelete.size > 0) { 104 | polygon.remove([...toDelete]); 105 | } 106 | if (toAddOrModify.length > 0) { 107 | polygon.updateGeometries(buildGeometries(geometries)); 108 | } 109 | }, 110 | ); 111 | provide('geometry', polygon); 112 | onUnmounted(() => { 113 | polygon.setMap(null); 114 | }); 115 | return {}; 116 | }, 117 | render() { 118 | return this.$slots.default ? this.$slots.default() : null; 119 | }, 120 | }); 121 | -------------------------------------------------------------------------------- /src/examples/multi-polygon.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 116 | 138 | -------------------------------------------------------------------------------- /src/examples/play-back.vue: -------------------------------------------------------------------------------- 1 | 23 | 24 | 132 | 142 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-tmap 2 | 3 | ![](https://img.shields.io/npm/v/@map-component/vue-tmap.svg) 4 | ![](https://img.shields.io/npm/dt/@map-component/vue-tmap.svg) 5 | ![](https://img.shields.io/npm/l/express.svg) 6 | 7 | - zh_CN [简体中文](https://github.com/didi/vue-tmap/blob/main/README.zh_CN.md) 8 | 9 | ### Introduction 10 | 11 | vue-tmap, a high-performance map component library for Vue3 based on Tencent Maps and TypeScript encapsulation, has the following features: 12 | 13 | - Improve documentation: improve the readability of documentation based on official documentation and framework usage, and improve component examples 14 | - Componentization: Encapsulate the Tencent Maps imperative api as a responsive component, no need to care about the complex map api, only need to operate the data 15 | - Multi-framework: including [react-tmap](https://github.com/didi/react-tmap) and [vue-tmap](https://github.com/didi/vue-tmap), and share the same set of type definitions 16 | - Type-safe: supplemented the type declaration of Tencent Maps sdk, components are also developed using TypeScript, a better development experience 17 | - Custom components: provide an open map instance, you can write custom components or directly call the map's native api 18 | - Performance optimization: unify the map api calling method and data monitoring to prevent performance problems caused by misuse of the map api 19 | 20 | ### Documentation and Examples 21 | 22 | Welcome to [Official document address](https://didi.github.io/vue-tmap/) to view more map components. 23 | 24 | - [Tencent Maps Official Documentation](https://lbs.qq.com/webApi/javascriptGL/glDoc/glDocIndex) 25 | 26 | ### Main Components 27 | 28 | | tmap-class | vue component | Introduction | 29 | | ------------- | ------------------- | -------------------------- | 30 | | Map | tmap-map | Map base components | 31 | | MultiMarker | tmap-multi-marker | Multiple Marker Points | 32 | | MultiPolyline | tmap-multi-polyline | Polyline | 33 | | MultiPolygon | tmap-multi-polygon | Polygon | 34 | | MultiLabel | tmap-multi-label | Text Labeling | 35 | | MultiCircle | tmap-multi-circle | Circle | 36 | | DOMOverlay | tmap-dom-overlay | DOM overlay abstract class | 37 | | InfoWindow | tmap-info-window | Information prompt window | 38 | | MarkerCluster | tmap-marker-cluster | Point Aggregation | 39 | 40 | ### Quick start 41 | 42 | #### Install 43 | 44 | ```shell 45 | npm install @map-component/vue-tmap 46 | ``` 47 | 48 | #### Apply for Tencent map key 49 | 50 | https://lbs.qq.com/dev/console/key/manage 51 | 52 | #### Simple example 53 | 54 | ```vue 55 | 66 | 67 | 96 | ``` 97 | 98 | > mapKey is the newly applied key 99 | 100 | ### Contribution Guidelines 101 | 102 | > Thanks to all the technical enthusiasts who participated in the contribution, let's build an easy-to-use map component library together 103 | 104 | #### Commit bug 105 | 106 | Please submit a bug through issue, and describe in detail how to reproduce the error and the version of dependencies. It is best to display the reproduced code through an online code editor. 107 | 108 | #### Submit code 109 | 110 | Please submit your code via pull request and we'll take a look soon 111 | 112 | #### Start development 113 | 114 | ``` 115 | git clone xxx 116 | 117 | cd react-tmap // cd vue-tmap 118 | 119 | npm install 120 | 121 | npm run dev 122 | ``` 123 | 124 | ### communicate with 125 | 126 | Add WeChat group after open source 127 | -------------------------------------------------------------------------------- /src/examples/polygon-editor.vue: -------------------------------------------------------------------------------- 1 | 41 | 42 | 153 | 154 | 164 | -------------------------------------------------------------------------------- /src/components/polygon-editor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | inject, 4 | onUnmounted, 5 | PropType, 6 | Ref, 7 | toRaw, 8 | watch, 9 | } from 'vue'; 10 | import useCleanUp from '../composables/use-clean-up'; 11 | import { builtStyle, buildGeometries } from './multi-polygon'; 12 | 13 | export default defineComponent({ 14 | name: 'tmap-polygon-editor', 15 | props: { 16 | id: { 17 | type: String, 18 | default: 'default', 19 | }, 20 | zIndex: { 21 | type: Number, 22 | default: 2, 23 | }, 24 | snappable: { 25 | type: Boolean, 26 | default: true, 27 | }, 28 | drawingStyleId: { 29 | type: String, 30 | default: 'drawing', 31 | }, 32 | selectedStyleId: { 33 | type: String, 34 | default: 'selected', 35 | }, 36 | styles: { 37 | type: Object as PropType<{ [key: string]: TMap.PolygonStyleOptions }>, 38 | required: true, 39 | }, 40 | modelValue: { 41 | type: Array as PropType, 42 | required: true, 43 | }, 44 | actionMode: { 45 | type: Number, 46 | }, 47 | }, 48 | emits: ['update:modelValue', 'select', 'error'], 49 | setup(props, { emit }) { 50 | const map = inject>('map'); 51 | if (!map) { 52 | return {}; 53 | } 54 | const originMap = toRaw(map.value); 55 | useCleanUp(originMap, props.id); 56 | const geometries = buildGeometries(props.modelValue); 57 | const polygon = new TMap.MultiPolygon({ 58 | id: props.id, 59 | map: originMap, 60 | zIndex: props.zIndex, 61 | styles: builtStyle(props.styles), 62 | geometries, 63 | }); 64 | const editor = new TMap.tools.GeometryEditor({ 65 | map: originMap, 66 | overlayList: [ 67 | { 68 | overlay: polygon, 69 | id: props.id, 70 | drawingStyleId: props.drawingStyleId, 71 | selectedStyleId: props.selectedStyleId, 72 | }, 73 | ], 74 | actionMode: 75 | props.actionMode === 1 76 | ? TMap.tools.constants.EDITOR_ACTION.INTERACT 77 | : TMap.tools.constants.EDITOR_ACTION.DRAW, 78 | activeOverlayId: props.id, // 激活图层 79 | selectable: true, // 开启点选功能 80 | snappable: props.snappable, // 开启吸附 81 | }); 82 | editor.on('select', () => { 83 | emit('select', editor.getSelectedList()); 84 | }); 85 | editor.on('draw_complete', (e: TMap.PolygonGeometry) => { 86 | emit('update:modelValue', [...props.modelValue, e]); 87 | }); 88 | editor.on('adjust_complete', (e: TMap.PolygonGeometry) => { 89 | for (let i = props.modelValue.length - 1; i >= 0; i -= 1) { 90 | if (props.modelValue[i].id === e.id) { 91 | Object.assign(props.modelValue[i], e); 92 | emit('update:modelValue', [...props.modelValue]); 93 | break; 94 | } 95 | } 96 | }); 97 | editor.on('delete_complete', (e: TMap.PolygonGeometry[]) => { 98 | const removedIds = e.map((v) => v.id); 99 | emit( 100 | 'update:modelValue', 101 | props.modelValue.filter((v) => removedIds.indexOf(v.id) === -1), 102 | ); 103 | emit('select', editor.getSelectedList()); 104 | }); 105 | editor.on('split_complete', (e: TMap.PolygonGeometry[]) => { 106 | const activeOverlay = editor.getActiveOverlay(); 107 | emit('update:modelValue', [ 108 | ...activeOverlay.overlay.getGeometries(), 109 | ...e, 110 | ]); 111 | emit('select', editor.getSelectedList()); 112 | }); 113 | editor.on('union_complete', (e: TMap.PolygonGeometry) => { 114 | const activeOverlay = editor.getActiveOverlay(); 115 | emit('update:modelValue', [...activeOverlay.overlay.getGeometries(), e]); 116 | emit('select', editor.getSelectedList()); 117 | }); 118 | editor.on('split_fail', (e: object) => { 119 | emit('error', e); 120 | }); 121 | editor.on('union_fail', (e: object) => { 122 | emit('error', e); 123 | }); 124 | watch( 125 | () => props.actionMode, 126 | (actionMode) => { 127 | const x: TMap.tools.constants.EDITOR_ACTION = 128 | actionMode === 1 129 | ? TMap.tools.constants.EDITOR_ACTION.INTERACT 130 | : TMap.tools.constants.EDITOR_ACTION.DRAW; 131 | editor.setActionMode(x); 132 | }, 133 | ); 134 | onUnmounted(() => { 135 | polygon.setMap(null); 136 | try { 137 | editor.destroy(); 138 | } catch (e) { 139 | // 直接销毁地图时会报错,兼容一下 140 | } 141 | }); 142 | return { 143 | select: editor.select.bind(editor), 144 | stop: editor.stop.bind(editor), 145 | split: editor.split.bind(editor), 146 | union: editor.union.bind(editor), 147 | delete: editor.delete.bind(editor), 148 | destroy: editor.destroy.bind(editor), 149 | }; 150 | }, 151 | render() { 152 | return null; 153 | }, 154 | }); 155 | -------------------------------------------------------------------------------- /src/components/map.ts: -------------------------------------------------------------------------------- 1 | import { 2 | defineComponent, 3 | ref, 4 | provide, 5 | onMounted, 6 | onUnmounted, 7 | h, 8 | PropType, 9 | watch, 10 | } from 'vue'; 11 | import loadSDK from '../utils/loadSDK'; 12 | 13 | type ControlConfig = { position: string; className: string }; 14 | type PositionMap = { 15 | [key: string]: TMap.constants.CONTROL_POSITION; 16 | }; 17 | 18 | function setMapCtrl( 19 | mapIns: TMap.Map, 20 | ctrlId: TMap.constants.DEFAULT_CONTROL_ID, 21 | config: ControlConfig, 22 | positionMap: PositionMap, 23 | ) { 24 | if (!config) { 25 | mapIns.removeControl(ctrlId); 26 | return; 27 | } 28 | const ctrl = mapIns.getControl(ctrlId); 29 | const { position, className } = config; 30 | if (positionMap[position]) { 31 | ctrl.setPosition(positionMap[position]); 32 | } 33 | ctrl.setClassName(className); 34 | } 35 | 36 | export default defineComponent({ 37 | name: 'tmap-map', 38 | props: { 39 | version: { 40 | type: String, 41 | default: '1.exp', 42 | }, 43 | mapKey: { 44 | type: String, 45 | default: '', 46 | }, 47 | libraries: { 48 | type: Array as PropType, 49 | default: () => [], 50 | }, 51 | class: { 52 | type: String, 53 | default: '', 54 | }, 55 | style: { 56 | type: Object as PropType<{}>, 57 | default: () => ({}), 58 | }, 59 | center: { 60 | type: Object as PropType<{ lat: number; lng: number }>, 61 | default: () => ({ lat: 40.040452, lng: 116.273486 }), 62 | }, 63 | zoom: { 64 | type: Number, 65 | default: 17, 66 | }, 67 | minZoom: { 68 | type: Number, 69 | default: 3, 70 | }, 71 | maxZoom: { 72 | type: Number, 73 | default: 20, 74 | }, 75 | rotation: { 76 | type: Number, 77 | default: 0, 78 | }, 79 | pitch: { 80 | type: Number, 81 | default: 0, 82 | }, 83 | scale: { 84 | type: Number, 85 | default: 1, 86 | }, 87 | offset: { 88 | type: Object as PropType<{ x: number; y: number }>, 89 | default: () => ({ x: 0, y: 0 }), 90 | }, 91 | draggable: { 92 | type: Boolean, 93 | default: true, 94 | }, 95 | scrollable: { 96 | type: Boolean, 97 | default: true, 98 | }, 99 | doubleClickZoom: { 100 | type: Boolean, 101 | default: true, 102 | }, 103 | boundary: { 104 | type: Object as PropType, 105 | default: null, 106 | }, 107 | mapStyleId: { 108 | type: String, 109 | }, 110 | baseMap: { 111 | type: Object as PropType, 112 | }, 113 | viewMode: { 114 | type: String as PropType<'2D' | '3D'>, 115 | default: '3D', 116 | }, 117 | control: { 118 | type: Object as PropType<{ 119 | scale: { position: string; className: string }; 120 | zoom: { position: string; className: string }; 121 | rotation: { position: string; className: string }; 122 | }>, 123 | default: () => ({ scale: {}, zoom: {}, rotation: {} }), 124 | }, 125 | events: { 126 | type: Object as PropType<{ [key: string]: Function }>, 127 | default: () => ({}), 128 | }, 129 | }, 130 | setup(props) { 131 | const el = ref(null); 132 | const map = ref(null); 133 | let mapIns: TMap.Map; 134 | let positionMap: PositionMap; 135 | const events: string[] = []; 136 | Object.keys(props.events).forEach((eventName) => { 137 | events.push(eventName); 138 | }); 139 | onMounted(async () => { 140 | await loadSDK(props.version, props.mapKey, props.libraries); 141 | positionMap = { 142 | topLeft: TMap.constants.CONTROL_POSITION.TOP_LEFT, 143 | topCenter: TMap.constants.CONTROL_POSITION.TOP_CENTER, 144 | topRight: TMap.constants.CONTROL_POSITION.TOP_RIGHT, 145 | centerLeft: TMap.constants.CONTROL_POSITION.CENTER_LEFT, 146 | center: TMap.constants.CONTROL_POSITION.CENTER, 147 | centerRight: TMap.constants.CONTROL_POSITION.CENTER_RIGHT, 148 | bottomLeft: TMap.constants.CONTROL_POSITION.BOTTOM_LEFT, 149 | bottomCenter: TMap.constants.CONTROL_POSITION.BOTTOM_CENTER, 150 | bottomRight: TMap.constants.CONTROL_POSITION.BOTTOM_RIGHT, 151 | }; 152 | const center = new TMap.LatLng(props.center.lat, props.center.lng); 153 | if (el.value) { 154 | mapIns = new TMap.Map(el.value, { 155 | center, 156 | zoom: props.zoom, 157 | minZoom: props.minZoom, 158 | maxZoom: props.maxZoom, 159 | rotation: props.rotation, 160 | pitch: props.pitch, 161 | scale: props.scale, 162 | offset: props.offset, 163 | draggable: props.draggable, 164 | scrollable: props.scrollable, 165 | doubleClickZoom: props.doubleClickZoom, 166 | boundary: props.boundary, 167 | mapStyleId: props.mapStyleId, 168 | baseMap: props.baseMap, 169 | viewMode: props.viewMode, 170 | showControl: true, 171 | }); 172 | 173 | setMapCtrl( 174 | mapIns, 175 | TMap.constants.DEFAULT_CONTROL_ID.SCALE, 176 | props.control.scale, 177 | positionMap, 178 | ); 179 | setMapCtrl( 180 | mapIns, 181 | TMap.constants.DEFAULT_CONTROL_ID.ZOOM, 182 | props.control.zoom, 183 | positionMap, 184 | ); 185 | setMapCtrl( 186 | mapIns, 187 | TMap.constants.DEFAULT_CONTROL_ID.ROTATION, 188 | props.control.rotation, 189 | positionMap, 190 | ); 191 | 192 | events.forEach((eventName) => { 193 | mapIns.on(eventName, props.events[eventName]); 194 | }); 195 | 196 | map.value = mapIns; 197 | } 198 | }); 199 | onUnmounted(() => { 200 | if (mapIns) { 201 | events.forEach((eventName) => { 202 | mapIns.off(eventName, props.events[eventName]); 203 | }); 204 | mapIns.destroy(); 205 | } 206 | }); 207 | watch( 208 | () => [props.center, props.zoom, props.rotation, props.pitch], 209 | ([center, zoom, rotation, pitch]) => { 210 | if (mapIns) { 211 | mapIns.easeTo( 212 | { 213 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore 214 | // @ts-ignore 215 | center: new TMap.LatLng(center.lat, center.lng), 216 | zoom: zoom as number, 217 | rotation: rotation as number, 218 | pitch: pitch as number, 219 | }, 220 | { 221 | duration: 500, 222 | }, 223 | ); 224 | } 225 | }, 226 | ); 227 | watch( 228 | () => props.scale, 229 | (value) => mapIns && mapIns.setScale(value), 230 | ); 231 | watch( 232 | () => props.offset, 233 | (value) => mapIns && mapIns.setOffset(value), 234 | ); 235 | watch( 236 | () => props.draggable, 237 | (value) => mapIns && mapIns.setDraggable(value), 238 | ); 239 | watch( 240 | () => props.scrollable, 241 | (value) => mapIns && mapIns.setScrollable(value), 242 | ); 243 | watch( 244 | () => props.doubleClickZoom, 245 | (value) => mapIns && mapIns.setDoubleClickZoom(value), 246 | ); 247 | watch( 248 | () => props.boundary, 249 | (value) => mapIns && mapIns.setBoundary(value), 250 | ); 251 | watch( 252 | () => props.control, 253 | (value) => { 254 | setMapCtrl( 255 | mapIns, 256 | TMap.constants.DEFAULT_CONTROL_ID.SCALE, 257 | value.scale, 258 | positionMap, 259 | ); 260 | setMapCtrl( 261 | mapIns, 262 | TMap.constants.DEFAULT_CONTROL_ID.ZOOM, 263 | value.zoom, 264 | positionMap, 265 | ); 266 | setMapCtrl( 267 | mapIns, 268 | TMap.constants.DEFAULT_CONTROL_ID.ROTATION, 269 | value.rotation, 270 | positionMap, 271 | ); 272 | }, 273 | ); 274 | provide('map', map); 275 | return { 276 | map, 277 | el, 278 | getCenter: () => mapIns?.getCenter(), 279 | getZoom: () => mapIns?.getZoom(), 280 | }; 281 | }, 282 | render() { 283 | return h( 284 | 'div', 285 | { 286 | class: this.class, 287 | style: { ...this.style, height: '100%', width: '100%' }, 288 | ref: 'el', 289 | }, 290 | this.$slots.default && this.map ? this.$slots.default() : [], 291 | ); 292 | }, 293 | }); 294 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | 3 | Version 2.0, January 2004 4 | 5 | http://www.apache.org/licenses/ 6 | 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 8 | 9 | 1. Definitions. 10 | 11 | "License" shall mean the terms and conditions for use, reproduction, 12 | 13 | and distribution as defined by Sections 1 through 9 of this document. 14 | 15 | "Licensor" shall mean the copyright owner or entity authorized by 16 | 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | 21 | other entities that control, are controlled by, or are under common 22 | 23 | control with that entity. For the purposes of this definition, 24 | 25 | "control" means (i) the power, direct or indirect, to cause the 26 | 27 | direction or management of such entity, whether by contract or 28 | 29 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 30 | 31 | outstanding shares, or (iii) beneficial ownership of such entity. 32 | 33 | "You" (or "Your") shall mean an individual or Legal Entity 34 | 35 | exercising permissions granted by this License. 36 | 37 | "Source" form shall mean the preferred form for making modifications, 38 | 39 | including but not limited to software source code, documentation 40 | 41 | source, and configuration files. 42 | 43 | "Object" form shall mean any form resulting from mechanical 44 | 45 | transformation or translation of a Source form, including but 46 | 47 | not limited to compiled object code, generated documentation, 48 | 49 | and conversions to other media types. 50 | 51 | "Work" shall mean the work of authorship, whether in Source or 52 | 53 | Object form, made available under the License, as indicated by a 54 | 55 | copyright notice that is included in or attached to the work 56 | 57 | (an example is provided in the Appendix below). 58 | 59 | "Derivative Works" shall mean any work, whether in Source or Object 60 | 61 | form, that is based on (or derived from) the Work and for which the 62 | 63 | editorial revisions, annotations, elaborations, or other modifications 64 | 65 | represent, as a whole, an original work of authorship. For the purposes 66 | 67 | of this License, Derivative Works shall not include works that remain 68 | 69 | separable from, or merely link (or bind by name) to the interfaces of, 70 | 71 | the Work and Derivative Works thereof. 72 | 73 | "Contribution" shall mean any work of authorship, including 74 | 75 | the original version of the Work and any modifications or additions 76 | 77 | to that Work or Derivative Works thereof, that is intentionally 78 | 79 | submitted to Licensor for inclusion in the Work by the copyright owner 80 | 81 | or by an individual or Legal Entity authorized to submit on behalf of 82 | 83 | the copyright owner. For the purposes of this definition, "submitted" 84 | 85 | means any form of electronic, verbal, or written communication sent 86 | 87 | to the Licensor or its representatives, including but not limited to 88 | 89 | communication on electronic mailing lists, source code control systems, 90 | 91 | and issue tracking systems that are managed by, or on behalf of, the 92 | 93 | Licensor for the purpose of discussing and improving the Work, but 94 | 95 | excluding communication that is conspicuously marked or otherwise 96 | 97 | designated in writing by the copyright owner as "Not a Contribution." 98 | 99 | "Contributor" shall mean Licensor and any individual or Legal Entity 100 | 101 | on behalf of whom a Contribution has been received by Licensor and 102 | 103 | subsequently incorporated within the Work. 104 | 105 | 2. Grant of Copyright License. Subject to the terms and conditions of 106 | 107 | this License, each Contributor hereby grants to You a perpetual, 108 | 109 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 110 | 111 | copyright license to reproduce, prepare Derivative Works of, 112 | 113 | publicly display, publicly perform, sublicense, and distribute the 114 | 115 | Work and such Derivative Works in Source or Object form. 116 | 117 | 3) Grant of Patent License. Subject to the terms and conditions of 118 | 119 | this License, each Contributor hereby grants to You a perpetual, 120 | 121 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 122 | 123 | (except as stated in this section) patent license to make, have made, 124 | 125 | use, offer to sell, sell, import, and otherwise transfer the Work, 126 | 127 | where such license applies only to those patent claims licensable 128 | 129 | by such Contributor that are necessarily infringed by their 130 | 131 | Contribution(s) alone or by combination of their Contribution(s) 132 | 133 | with the Work to which such Contribution(s) was submitted. If You 134 | 135 | institute patent litigation against any entity (including a 136 | 137 | cross-claim or counterclaim in a lawsuit) alleging that the Work 138 | 139 | or a Contribution incorporated within the Work constitutes direct 140 | 141 | or contributory patent infringement, then any patent licenses 142 | 143 | granted to You under this License for that Work shall terminate 144 | 145 | as of the date such litigation is filed. 146 | 147 | 4. Redistribution. You may reproduce and distribute copies of the 148 | 149 | Work or Derivative Works thereof in any medium, with or without 150 | 151 | modifications, and in Source or Object form, provided that You 152 | 153 | meet the following conditions: 154 | 155 | (a) You must give any other recipients of the Work or 156 | 157 | Derivative Works a copy of this License; and 158 | 159 | (b) You must cause any modified files to carry prominent notices 160 | 161 | stating that You changed the files; and 162 | 163 | (c) You must retain, in the Source form of any Derivative Works 164 | 165 | that You distribute, all copyright, patent, trademark, and 166 | 167 | attribution notices from the Source form of the Work, 168 | 169 | excluding those notices that do not pertain to any part of 170 | 171 | the Derivative Works; and 172 | 173 | (d) If the Work includes a "NOTICE" text file as part of its 174 | 175 | distribution, then any Derivative Works that You distribute must 176 | 177 | include a readable copy of the attribution notices contained 178 | 179 | within such NOTICE file, excluding those notices that do not 180 | 181 | pertain to any part of the Derivative Works, in at least one 182 | 183 | of the following places: within a NOTICE text file distributed 184 | 185 | as part of the Derivative Works; within the Source form or 186 | 187 | documentation, if provided along with the Derivative Works; or, 188 | 189 | within a display generated by the Derivative Works, if and 190 | 191 | wherever such third-party notices normally appear. The contents 192 | 193 | of the NOTICE file are for informational purposes only and 194 | 195 | do not modify the License. You may add Your own attribution 196 | 197 | notices within Derivative Works that You distribute, alongside 198 | 199 | or as an addendum to the NOTICE text from the Work, provided 200 | 201 | that such additional attribution notices cannot be construed 202 | 203 | as modifying the License. 204 | 205 | You may add Your own copyright statement to Your modifications and 206 | 207 | may provide additional or different license terms and conditions 208 | 209 | for use, reproduction, or distribution of Your modifications, or 210 | 211 | for any such Derivative Works as a whole, provided Your use, 212 | 213 | reproduction, and distribution of the Work otherwise complies with 214 | 215 | the conditions stated in this License. 216 | 217 | 5. Submission of Contributions. Unless You explicitly state otherwise, 218 | 219 | any Contribution intentionally submitted for inclusion in the Work 220 | 221 | by You to the Licensor shall be under the terms and conditions of 222 | 223 | this License, without any additional terms or conditions. 224 | 225 | Notwithstanding the above, nothing herein shall supersede or modify 226 | 227 | the terms of any separate license agreement you may have executed 228 | 229 | with Licensor regarding such Contributions. 230 | 231 | 6) Trademarks. This License does not grant permission to use the trade 232 | 233 | names, trademarks, service marks, or product names of the Licensor, 234 | 235 | except as required for reasonable and customary use in describing the 236 | 237 | origin of the Work and reproducing the content of the NOTICE file. 238 | 239 | 7. Disclaimer of Warranty. Unless required by applicable law or 240 | 241 | agreed to in writing, Licensor provides the Work (and each 242 | 243 | Contributor provides its Contributions) on an "AS IS" BASIS, 244 | 245 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 246 | 247 | implied, including, without limitation, any warranties or conditions 248 | 249 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 250 | 251 | PARTICULAR PURPOSE. You are solely responsible for determining the 252 | 253 | appropriateness of using or redistributing the Work and assume any 254 | 255 | risks associated with Your exercise of permissions under this License. 256 | 257 | 8) Limitation of Liability. In no event and under no legal theory, 258 | 259 | whether in tort (including negligence), contract, or otherwise, 260 | 261 | unless required by applicable law (such as deliberate and grossly 262 | 263 | negligent acts) or agreed to in writing, shall any Contributor be 264 | 265 | liable to You for damages, including any direct, indirect, special, 266 | 267 | incidental, or consequential damages of any character arising as a 268 | 269 | result of this License or out of the use or inability to use the 270 | 271 | Work (including but not limited to damages for loss of goodwill, 272 | 273 | work stoppage, computer failure or malfunction, or any and all 274 | 275 | other commercial damages or losses), even if such Contributor 276 | 277 | has been advised of the possibility of such damages. 278 | 279 | 9. Accepting Warranty or Additional Liability. While redistributing 280 | 281 | the Work or Derivative Works thereof, You may choose to offer, 282 | 283 | and charge a fee for, acceptance of support, warranty, indemnity, 284 | 285 | or other liability obligations and/or rights consistent with this 286 | 287 | License. However, in accepting such obligations, You may act only 288 | 289 | on Your own behalf and on Your sole responsibility, not on behalf 290 | 291 | of any other Contributor, and only if You agree to indemnify, 292 | 293 | defend, and hold each Contributor harmless for any liability 294 | 295 | incurred by, or claims asserted against, such Contributor by reason 296 | 297 | of your accepting any such warranty or additional liability. 298 | 299 | END OF TERMS AND CONDITIONS 300 | 301 | APPENDIX: How to apply the Apache License to your work. 302 | 303 | To apply the Apache License to your work, attach the following 304 | 305 | boilerplate notice, with the fields enclosed by brackets "{}" 306 | 307 | replaced with your own identifying information. (Don't include 308 | 309 | the brackets!) The text should be enclosed in the appropriate 310 | 311 | comment syntax for the file format. We also recommend that a 312 | 313 | file or class name and description of purpose be included on the 314 | 315 | same "printed page" as the copyright notice for easier 316 | 317 | identification within third-party archives. 318 | 319 | Copyright (C) 2017 Beijing Didi Infinity Technology and Development Co.,Ltd. All rights reserved. 320 | 321 | Licensed under the Apache License, Version 2.0 (the "License"); 322 | 323 | you may not use this file except in compliance with the License. 324 | 325 | You may obtain a copy of the License at 326 | 327 | http://www.apache.org/licenses/LICENSE-2.0 328 | 329 | Unless required by applicable law or agreed to in writing, software 330 | 331 | distributed under the License is distributed on an "AS IS" BASIS, 332 | 333 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 334 | 335 | See the License for the specific language governing permissions and 336 | 337 | limitations under the License. 338 | --------------------------------------------------------------------------------